From bb0a012b13c47b5fbd1228c1fd4df75979c3033f Mon Sep 17 00:00:00 2001 From: "propr-dev[bot]" <1316198+propr-dev[bot]@users.noreply.github.com> Date: Tue, 1 Sep 2026 22:21:41 +0000 Subject: [PATCH 01/33] fix(ai): Resolve issue #2053 - Make Windows cleanup-controller protocol complete Implemented by ProPR AI using gpt-5.6-sol model. Implementation completed successfully. --- ...lled-windows-app-workflow-cleanup-body.ps1 | 18 +- ...installed-windows-app-workflow-cleanup.ps1 | 81 +- .../test-installed-windows-app-supervisor.ps1 | 852 +++++++++++++++--- apps/desktop/src/release-workflow.test.ts | 45 +- 4 files changed, 847 insertions(+), 149 deletions(-) diff --git a/apps/desktop/scripts/run-installed-windows-app-workflow-cleanup-body.ps1 b/apps/desktop/scripts/run-installed-windows-app-workflow-cleanup-body.ps1 index 76e6eeeea..f8f245b9a 100644 --- a/apps/desktop/scripts/run-installed-windows-app-workflow-cleanup-body.ps1 +++ b/apps/desktop/scripts/run-installed-windows-app-workflow-cleanup-body.ps1 @@ -47,11 +47,17 @@ $validatedManifestPath = $null [WorkflowCleanupControllerLine]$controllerLine = 'TYPE_LOAD' $cleanupTreeZeroVerified = $false +function Write-StartupRecord { + [Console]::Out.WriteLine( + 'PROPR_WINDOWS_INSTALLED_SMOKE:WORKFLOW_CLEANUP:STARTUP:READY') + [Console]::Out.Flush() +} + function Write-FixedResult([ValidateSet('COMPLETE','FAILED','TIMED_OUT')][string]$Result) { - [Console]::Out.WriteLine("PROPR_WINDOWS_INSTALLED_SMOKE:WORKFLOW_CLEANUP:$Result") [Console]::Out.WriteLine( - 'PROPR_WINDOWS_INSTALLED_SMOKE:WORKFLOW_CLEANUP:STATUS:{0}:EXIT_CODE:{1}' -f ` - $script:fixedStatus, $script:fixedExitCode) + ('PROPR_WINDOWS_INSTALLED_SMOKE:WORKFLOW_CLEANUP:TERMINAL:RESULT:{0}:' + + 'STATUS:{1}:EXIT_CODE:{2}') -f ` + $Result, $script:fixedStatus, $script:fixedExitCode) [Console]::Out.Flush() } @@ -318,6 +324,12 @@ public sealed class ProPRWorkflowCleanupOutputDrain : IDisposable } '@ +# Reaching this boundary proves that the whole body parsed and its fixed native +# types loaded. Publish and flush the startup record before parameter or path +# validation can begin; the terminal record is emitted only after all process, +# stream, resource, and authority finalization has completed. +Write-StartupRecord + try { $controllerPhase = 'PARAMETER_VALIDATION' $controllerLine = 'PARAMETERS' diff --git a/apps/desktop/scripts/run-installed-windows-app-workflow-cleanup.ps1 b/apps/desktop/scripts/run-installed-windows-app-workflow-cleanup.ps1 index e96daa0e8..ce003665a 100644 --- a/apps/desktop/scripts/run-installed-windows-app-workflow-cleanup.ps1 +++ b/apps/desktop/scripts/run-installed-windows-app-workflow-cleanup.ps1 @@ -6,7 +6,8 @@ param( [object]$TerminationTimeoutMilliseconds = 30 * 1000, [object]$FixtureRoot, [object]$FixtureEarlyInitializationChild, - [object]$StartupFailureClass + [object]$StartupFailureClass, + [object]$ProtocolFixture ) $ErrorActionPreference = 'Stop' @@ -36,16 +37,88 @@ function Write-StartupFailure($ErrorRecord) { $candidateLine = [int64]$ErrorRecord.InvocationInfo.ScriptLineNumber if ($candidateLine -ge 0 -and $candidateLine -le 999999) { $line = $candidateLine } } catch {} - [Console]::Out.WriteLine('PROPR_WINDOWS_INSTALLED_SMOKE:WORKFLOW_CLEANUP:FAILED') [Console]::Out.WriteLine(( - ('PROPR_WINDOWS_INSTALLED_SMOKE:WORKFLOW_CLEANUP:STATUS:STARTUP_FAILURE:' + - 'EXIT_CODE:125:STARTUP_CLASS:{0}:PROCESS_EXIT:125:LINE:{1}') -f ` + ('PROPR_WINDOWS_INSTALLED_SMOKE:WORKFLOW_CLEANUP:STARTUP:FAILED:' + + 'CLASS:{0}:PROCESS_EXIT:125:LINE:{1}') -f ` $failureClass, $line )) [Console]::Out.Flush() + [Console]::Out.WriteLine( + ('PROPR_WINDOWS_INSTALLED_SMOKE:WORKFLOW_CLEANUP:TERMINAL:' + + 'RESULT:FAILED:STATUS:STARTUP_FAILURE:EXIT_CODE:125')) + [Console]::Out.Flush() +} + +function Invoke-ProtocolFixture([string]$Name) { + $startup = 'PROPR_WINDOWS_INSTALLED_SMOKE:WORKFLOW_CLEANUP:STARTUP:READY' + $terminal = ('PROPR_WINDOWS_INSTALLED_SMOKE:WORKFLOW_CLEANUP:TERMINAL:' + + 'RESULT:FAILED:STATUS:CONTROLLER_FAILURE:EXIT_CODE:125') + switch ($Name) { + 'ONE_LINE_STARTUP' { + [Console]::Out.Write("$startup`r`n"); [Console]::Out.Flush(); exit 125 + } + 'MISSING_TERMINAL' { + [Console]::Out.Write("$startup`r`n"); [Console]::Out.Flush(); exit 125 + } + 'DUPLICATE_STARTUP' { + [Console]::Out.Write("$startup`r`n$startup`r`n$terminal`r`n") + [Console]::Out.Flush(); exit 125 + } + 'EXTRA_RECORD' { + [Console]::Out.Write("$startup`r`n$terminal`r`nEXTRA`r`n") + [Console]::Out.Flush(); exit 125 + } + 'REORDERED_RECORDS' { + [Console]::Out.Write("$terminal`r`n$startup`r`n") + [Console]::Out.Flush(); exit 125 + } + 'OVERSIZED_RECORD' { + [Console]::Out.Write(('A' * 385) + "`r`n") + [Console]::Out.Flush(); exit 125 + } + 'MALFORMED_RECORD' { + [Console]::Out.Write("MALFORMED`r`n$terminal`r`n") + [Console]::Out.Flush(); exit 125 + } + 'PARTIAL_RECORD' { + [Console]::Out.Write($startup); [Console]::Out.Flush(); exit 125 + } + 'STDERR_RECORD' { + [Console]::Out.Write("$startup`r`n$terminal`r`n"); [Console]::Out.Flush() + [Console]::Error.Write('E'); [Console]::Error.Flush(); exit 125 + } + 'TIMEOUT_BEFORE_STARTUP' { [Threading.Thread]::Sleep(60000); exit 125 } + 'TIMEOUT_AFTER_STARTUP' { + [Console]::Out.Write("$startup`r`n"); [Console]::Out.Flush() + [Threading.Thread]::Sleep(60000); exit 125 + } + 'STREAM_DRAIN_RACE' { + [Console]::Out.Write("$startup`r`n$terminal`r`n"); [Console]::Out.Flush() + $child = [Diagnostics.ProcessStartInfo]::new() + $child.FileName = (Get-Process -Id $PID -ErrorAction Stop).Path + $child.UseShellExecute = $false + $child.ArgumentList.Add('-NoLogo') + $child.ArgumentList.Add('-NoProfile') + $child.ArgumentList.Add('-NonInteractive') + $child.ArgumentList.Add('-Command') + $child.ArgumentList.Add('[Threading.Thread]::Sleep(60000)') + [void][Diagnostics.Process]::Start($child) + exit 125 + } + 'INVALID_STARTUP_METADATA' { + [Console]::Out.Write( + ('PROPR_WINDOWS_INSTALLED_SMOKE:WORKFLOW_CLEANUP:STARTUP:FAILED:' + + "CLASS:INVALID:PROCESS_EXIT:125:LINE:0`r`n$terminal`r`n")) + [Console]::Out.Flush(); exit 125 + } + default { throw [InvalidOperationException]::new('protocol fixture is invalid') } + } } try { + if ($null -ne $ProtocolFixture) { + Invoke-ProtocolFixture ([string]$ProtocolFixture) + } if ($null -ne $StartupFailureClass) { switch ([string]$StartupFailureClass) { 'PARSER' { [void][scriptblock]::Create('{') } diff --git a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 index e76a10ecb..aa4985c9c 100644 --- a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 +++ b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 @@ -36,6 +36,371 @@ function Assert-NotContains([string]$Text, [string]$Forbidden, [string]$Message) Assert-True (!$Text.Contains($Forbidden, [StringComparison]::OrdinalIgnoreCase)) $Message } +Add-Type -TypeDefinition @' +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Win32.SafeHandles; + +public sealed class ProPRWorkflowCleanupInvocationJob : IDisposable +{ + [StructLayout(LayoutKind.Sequential)] + private struct JOBOBJECT_BASIC_LIMIT_INFORMATION + { + public long PerProcessUserTimeLimit; + public long PerJobUserTimeLimit; + public uint LimitFlags; + public UIntPtr MinimumWorkingSetSize; + public UIntPtr MaximumWorkingSetSize; + public uint ActiveProcessLimit; + public UIntPtr Affinity; + public uint PriorityClass; + public uint SchedulingClass; + } + + [StructLayout(LayoutKind.Sequential)] + private struct IO_COUNTERS + { + public ulong ReadOperationCount; + public ulong WriteOperationCount; + public ulong OtherOperationCount; + public ulong ReadTransferCount; + public ulong WriteTransferCount; + public ulong OtherTransferCount; + } + + [StructLayout(LayoutKind.Sequential)] + private struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION + { + public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation; + public IO_COUNTERS IoInfo; + public UIntPtr ProcessMemoryLimit; + public UIntPtr JobMemoryLimit; + public UIntPtr PeakProcessMemoryUsed; + public UIntPtr PeakJobMemoryUsed; + } + + [StructLayout(LayoutKind.Sequential)] + private struct JOBOBJECT_BASIC_ACCOUNTING_INFORMATION + { + public long TotalUserTime; + public long TotalKernelTime; + public long ThisPeriodTotalUserTime; + public long ThisPeriodTotalKernelTime; + public uint TotalPageFaultCount; + public uint TotalProcesses; + public uint ActiveProcesses; + public uint TotalTerminatedProcesses; + } + + private const int JobObjectExtendedLimitInformation = 9; + private const uint JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x00002000; + private SafeFileHandle handle; + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + private static extern SafeFileHandle CreateJobObject(IntPtr attributes, string name); + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool SetInformationJobObject( + SafeFileHandle job, int informationClass, IntPtr information, uint length); + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool AssignProcessToJobObject(SafeFileHandle job, IntPtr process); + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool TerminateJobObject(SafeFileHandle job, uint exitCode); + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool QueryInformationJobObject( + SafeFileHandle job, int informationClass, + out JOBOBJECT_BASIC_ACCOUNTING_INFORMATION information, + uint length, IntPtr returnLength); + + public ProPRWorkflowCleanupInvocationJob() + { + handle = CreateJobObject(IntPtr.Zero, null); + if (handle == null || handle.IsInvalid) + throw new Win32Exception(Marshal.GetLastWin32Error(), "invocation job creation failed"); + var limits = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION(); + limits.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; + int size = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION)); + IntPtr buffer = Marshal.AllocHGlobal(size); + try + { + Marshal.StructureToPtr(limits, buffer, false); + if (!SetInformationJobObject(handle, JobObjectExtendedLimitInformation, + buffer, (uint)size)) + throw new Win32Exception(Marshal.GetLastWin32Error(), + "invocation job configuration failed"); + } + finally { Marshal.FreeHGlobal(buffer); } + } + + public void AddProcess(IntPtr processHandle) + { + if (!AssignProcessToJobObject(handle, processHandle)) + throw new Win32Exception(Marshal.GetLastWin32Error(), + "invocation process ownership failed"); + } + + private uint ReadActiveProcessCount() + { + JOBOBJECT_BASIC_ACCOUNTING_INFORMATION information; + uint size = (uint)Marshal.SizeOf(typeof(JOBOBJECT_BASIC_ACCOUNTING_INFORMATION)); + if (!QueryInformationJobObject(handle, 1, out information, size, IntPtr.Zero)) + throw new Win32Exception(Marshal.GetLastWin32Error(), + "invocation accounting failed"); + return information.ActiveProcesses; + } + + public bool HasNoActiveProcesses() { return ReadActiveProcessCount() == 0; } + + public bool TerminateAndWait(uint exitCode, int timeoutMilliseconds) + { + if (!TerminateJobObject(handle, exitCode)) + throw new Win32Exception(Marshal.GetLastWin32Error(), + "invocation termination failed"); + var watch = Stopwatch.StartNew(); + do + { + if (ReadActiveProcessCount() == 0) return true; + Thread.Sleep(25); + } + while (watch.ElapsedMilliseconds < timeoutMilliseconds); + return ReadActiveProcessCount() == 0; + } + + public void Dispose() { if (handle != null) handle.Dispose(); } +} + +public sealed class ProPRWorkflowCleanupProtocolCapture : IDisposable +{ + private const int LineCharacterLimit = 384; + private const int CountLimit = 4096; + private const string Prefix = "PROPR_WINDOWS_INSTALLED_SMOKE:WORKFLOW_CLEANUP:"; + private static readonly Regex ReadyStartup = new Regex( + "^" + Prefix + "STARTUP:READY$", RegexOptions.CultureInvariant); + private static readonly Regex FailedStartup = new Regex( + "^" + Prefix + "STARTUP:FAILED:CLASS:(PARSER|PARAMETER_BINDING|TYPE_LOAD|OTHER):" + + "PROCESS_EXIT:(0|-?[1-9][0-9]*):LINE:(0|[1-9][0-9]{0,5})$", + RegexOptions.CultureInvariant); + private static readonly Regex Terminal = new Regex( + "^" + Prefix + "TERMINAL:RESULT:(COMPLETE|FAILED|TIMED_OUT):" + + "STATUS:([A-Z_]+):EXIT_CODE:(0|20|21|122|123|124|125)$", + RegexOptions.CultureInvariant); + private static readonly Regex ControllerFailure = new Regex( + "^CONTROLLER_(INITIALIZATION|PARAMETER_VALIDATION|PATH_VALIDATION|PROCESS_START|" + + "PROCESS_WAIT|PROCESS_FINALIZATION|STREAM_FINALIZATION|RESOURCE_FINALIZATION|" + + "AUTHORITY_FINALIZATION|RESULT_EMISSION)_(TYPE_LOAD|PARAMETERS|PATHS|START|WAIT|" + + "TERMINATE|DRAIN|DISPOSE|AUTHORITY|EMIT)_(AUTHENTICATION|CLOSE|INVALID_ARGUMENT|" + + "INVALID_DATA|INVALID_OPERATION|LIMIT|NOT_ENABLED|NOT_FOUND|OPEN|STOPPED|" + + "PERMISSION|READ|BUSY|UNAVAILABLE|SECURITY|WRITE|UNCLASSIFIED)$", + RegexOptions.CultureInvariant); + private static readonly string[] FixedStatuses = new string[] { + "CONTROLLER_FAILURE", "TIMEOUT", "TERMINATION_FAILURE", + "ACTIVE_PROCESS_AFTER_ROOT_EXIT", "EMPTY_OR_CLEANED", + "MANIFEST_VALIDATION_FAILURE", "OWNED_RESOURCE_CLEANUP_FAILURE", + "PROCESS_FINALIZATION_TIMEOUT", "PROCESS_FINALIZATION_FAILURE", + "STREAM_DRAIN_TIMEOUT", "CHILD_STDERR_LIMIT", "CHILD_STDERR", + "CHILD_STDOUT_LIMIT", "CHILD_STDOUT", "STREAM_DRAIN_FAILURE", + "RESOURCE_FINALIZATION_FAILURE", "AUTHORITY_FINALIZATION_FAILURE", + "STARTUP_FAILURE" + }; + + private StreamReader outputReader; + private StreamReader errorReader; + private Task outputTask; + private Task errorTask; + private bool startupSeen; + private bool terminalSeen; + private bool defect; + + public int LineCount { get; private set; } + public int StandardErrorCount { get; private set; } + public string ObservedLineCategory { get; private set; } + public int ObservedLineNumber { get; private set; } + public string StartupClass { get; private set; } + public int StartupProcessExit { get; private set; } + public int StartupLine { get; private set; } + public string Result { get; private set; } + public string ControllerStatus { get; private set; } + public int ReportedExitCode { get; private set; } + public bool DrainFailed { get; private set; } + + public ProPRWorkflowCleanupProtocolCapture() + { + ObservedLineCategory = "NONE"; + StartupClass = "NONE"; + Result = "INVALID"; + ControllerStatus = "INVALID"; + ReportedExitCode = -1; + } + + private static bool IsFixedStatus(string value) + { + for (int i = 0; i < FixedStatuses.Length; i++) + if (String.Equals(FixedStatuses[i], value, StringComparison.Ordinal)) return true; + return ControllerFailure.IsMatch(value); + } + + private void SetDefect(string category) + { + if (!defect) + { + defect = true; + ObservedLineCategory = category; + ObservedLineNumber = Math.Min(3, Math.Max(1, LineCount)); + } + } + + private void CompleteLine(string line, bool oversized) + { + LineCount = Math.Min(3, LineCount + 1); + if (defect) return; + if (oversized) { SetDefect("OVERSIZED"); return; } + + Match ready = ReadyStartup.Match(line); + Match failed = FailedStartup.Match(line); + Match terminal = Terminal.Match(line); + if (!startupSeen) + { + if (terminal.Success) { SetDefect("REORDERED"); return; } + if (!ready.Success && !failed.Success) { SetDefect("MALFORMED"); return; } + startupSeen = true; + ObservedLineCategory = "STARTUP"; + ObservedLineNumber = LineCount; + if (ready.Success) StartupClass = "READY"; + else + { + StartupClass = failed.Groups[1].Value; + int processExit; + int startupLine; + if (!Int32.TryParse(failed.Groups[2].Value, out processExit) || + !Int32.TryParse(failed.Groups[3].Value, out startupLine)) + { SetDefect("MALFORMED"); return; } + StartupProcessExit = processExit; + StartupLine = startupLine; + } + return; + } + + if (!terminalSeen) + { + if (ready.Success || failed.Success) { SetDefect("DUPLICATE"); return; } + if (!terminal.Success || !IsFixedStatus(terminal.Groups[2].Value)) + { SetDefect("MALFORMED"); return; } + terminalSeen = true; + ObservedLineCategory = "TERMINAL"; + ObservedLineNumber = LineCount; + Result = terminal.Groups[1].Value; + ControllerStatus = terminal.Groups[2].Value; + ReportedExitCode = Int32.Parse(terminal.Groups[3].Value); + bool startupFailure = !String.Equals(StartupClass, "READY", StringComparison.Ordinal); + if ((startupFailure && (!String.Equals(Result, "FAILED", StringComparison.Ordinal) || + !String.Equals(ControllerStatus, "STARTUP_FAILURE", StringComparison.Ordinal) || + ReportedExitCode != 125)) || + (!startupFailure && String.Equals(ControllerStatus, "STARTUP_FAILURE", + StringComparison.Ordinal)) || + (String.Equals(Result, "COMPLETE", StringComparison.Ordinal) && + (!String.Equals(ControllerStatus, "EMPTY_OR_CLEANED", StringComparison.Ordinal) || + ReportedExitCode != 0)) || + (String.Equals(Result, "TIMED_OUT", StringComparison.Ordinal) && + (!String.Equals(ControllerStatus, "TIMEOUT", StringComparison.Ordinal) || + ReportedExitCode != 124)) || + (String.Equals(Result, "FAILED", StringComparison.Ordinal) && + (String.Equals(ControllerStatus, "EMPTY_OR_CLEANED", StringComparison.Ordinal) || + String.Equals(ControllerStatus, "TIMEOUT", StringComparison.Ordinal) || + ReportedExitCode == 0))) + SetDefect("MALFORMED"); + return; + } + + SetDefect((ready.Success || failed.Success || terminal.Success) ? "DUPLICATE" : "EXTRA"); + } + + private void PumpOutput() + { + var line = new StringBuilder(); + var buffer = new char[256]; + bool oversized = false; + while (true) + { + int count = outputReader.Read(buffer, 0, buffer.Length); + if (count == 0) break; + for (int i = 0; i < count; i++) + { + char value = buffer[i]; + if (value == '\n') + { + if (line.Length > 0 && line[line.Length - 1] == '\r') + line.Length = line.Length - 1; + CompleteLine(line.ToString(), oversized); + line.Clear(); + oversized = false; + } + else if (value > 0x7f) oversized = true; + else if (line.Length < LineCharacterLimit) line.Append(value); + else oversized = true; + } + } + if (line.Length != 0 || oversized) + { + LineCount = Math.Min(3, LineCount + 1); + SetDefect(oversized ? "OVERSIZED" : "PARTIAL"); + } + } + + private void PumpError() + { + var buffer = new char[256]; + while (true) + { + int count = errorReader.Read(buffer, 0, buffer.Length); + if (count == 0) return; + StandardErrorCount = Math.Min(CountLimit + 1, StandardErrorCount + count); + } + } + + public void Start(Process process) + { + outputReader = process.StandardOutput; + errorReader = process.StandardError; + outputTask = Task.Factory.StartNew(PumpOutput, CancellationToken.None, + TaskCreationOptions.LongRunning, TaskScheduler.Default); + errorTask = Task.Factory.StartNew(PumpError, CancellationToken.None, + TaskCreationOptions.LongRunning, TaskScheduler.Default); + } + + public bool Finish(int timeoutMilliseconds) + { + if (outputTask == null || errorTask == null) return false; + try + { + if (!Task.WaitAll(new Task[] { outputTask, errorTask }, timeoutMilliseconds)) + return false; + } + catch { DrainFailed = true; return false; } + return true; + } + + public bool IsProtocolValid(int processExitCode) + { + return !defect && startupSeen && terminalSeen && LineCount == 2 && + StandardErrorCount == 0 && processExitCode == ReportedExitCode && + (String.Equals(StartupClass, "READY", StringComparison.Ordinal) || + StartupProcessExit == processExitCode); + } + + public void Dispose() + { + try { if (outputReader != null) outputReader.Dispose(); } catch { } + try { if (errorReader != null) errorReader.Dispose(); } catch { } + } +} +'@ + function Test-WorkflowCleanupBodyParserRegression { $cleanupBodyPath = Join-Path $PSScriptRoot ` 'run-installed-windows-app-workflow-cleanup-body.ps1' @@ -561,13 +926,12 @@ function Get-SanitizedWorkflowCleanupResultDiagnostic($Result) { return $diagnostic } -function Get-WorkflowCleanupControllerStatusMatch([string]$StatusLine) { +function Get-WorkflowCleanupControllerStatusMatch([string]$TerminalLine) { return [regex]::Match( - $StatusLine, - ('^PROPR_WINDOWS_INSTALLED_SMOKE:WORKFLOW_CLEANUP:STATUS:([A-Z_]+):' + - 'EXIT_CODE:([0-9]+)(?::STARTUP_CLASS:' + - '(PARSER|PARAMETER_BINDING|TYPE_LOAD|OTHER):PROCESS_EXIT:(-?[0-9]+):' + - 'LINE:([0-9]+))?$') + $TerminalLine, + ('^PROPR_WINDOWS_INSTALLED_SMOKE:WORKFLOW_CLEANUP:TERMINAL:' + + 'RESULT:(COMPLETE|FAILED|TIMED_OUT):STATUS:([A-Z_]+):' + + 'EXIT_CODE:(0|20|21|122|123|124|125)$') ) } @@ -656,51 +1020,87 @@ function Assert-MsiPreflightPreservedResources($Owned) { 'MSI file-system preflight failure removed the run-owned user' } -function Get-SanitizedControllerStartupDiagnostic( - [string]$ErrorText, - [int]$ProcessExitCode +function Get-WorkflowCleanupProtocolMismatchDiagnostic( + [string]$InvocationIdentifier, + [string]$ObservedLineCategory, + [int]$LineCount, + [int]$StandardErrorCount, + [string]$ValidatedProcessExit, + [string]$LifecycleCategory, + [string]$TreeTerminationCategory, + [string]$StartupClass, + [int]$LineNumber ) { - $classification = if ($ErrorText -match - '(?im)\bParserError\b|\bMissingEndCurlyBrace\b|\bUnexpectedToken\b|\bParseException\b') { - 'PARSER' - } elseif ($ErrorText -match - '(?im)\bParameterBinding(?:Exception|ValidationException)?\b|cannot bind (?:argument|parameter)|parameter cannot be processed') { - 'PARAMETER_BINDING' - } elseif ($ErrorText -match - '(?im)\bAdd-Type\b|\bTypeNotFound\b|unable to find type|error CS[0-9]{4}') { - 'TYPE_LOAD' - } else { - 'OTHER' - } - $lineNumber = 0 - $lineMatch = [regex]::Match( - $ErrorText, - '(?im)^\s*at .+?:(\d+)\s+char:\d+\s*$' + $invocations = @( + 'STARTUP_PROTOCOL','REPLACEMENT_RETRY','REPLACED_ENTRY_RETRY', + 'PROFILE_ALTERNATE_LEAF','PROFILE_RETRY','EXECUTABLE_IDENTITY_RETRY', + 'FOREIGN_CHILD_RETRY','TERMINATION_RETRY','PARAMETER_VALIDATION', + 'EARLY_INITIALIZATION_TIMEOUT','CLEANUP_TIMEOUT','INSTALLER_REPLACEMENT', + 'RESOURCE_COLLISION','WORKFLOW_RETRY','NORMAL_CLEANUP','MANIFEST_VALIDATION', + 'SMOKE_PROMOTION_RETRY','SMOKE_TOKEN_MISSING','SMOKE_TOKEN_RETRY', + 'APP_PATH_MISMATCH','HKCU_BASELINE_RESTORE','HKCU_PENDING_RECEIPT', + 'HKCU_NONEMPTY','HKCU_EMPTY','HKCU_CONFLICT','HKCU_PROVISIONAL', + 'USER_MARKER_OWNED','USER_MARKER_REPLACEMENT','PROTOCOL_REGRESSION' ) - if (!$lineMatch.Success) { - $lineMatch = [regex]::Match($ErrorText, '(?im)\bline\s+(\d+)\b') + if ($InvocationIdentifier -cnotin $invocations) { $InvocationIdentifier = 'INVALID' } + if ($ObservedLineCategory -cnotin @( + 'NONE','STARTUP','TERMINAL','MALFORMED','PARTIAL','DUPLICATE', + 'REORDERED','EXTRA','OVERSIZED' + )) { $ObservedLineCategory = 'MALFORMED' } + if ($LifecycleCategory -cnotin @( + 'EXITED','PROCESS_CREATION_FAILURE','OWNERSHIP_FAILURE', + 'TIMEOUT_BEFORE_STARTUP','TIMEOUT_AFTER_STARTUP', + 'CANCELLED_BEFORE_STARTUP','CANCELLED_AFTER_STARTUP', + 'ACTIVE_TREE_AFTER_EXIT','DRAIN_TIMEOUT','DRAIN_FAILURE' + )) { $LifecycleCategory = 'DRAIN_FAILURE' } + if ($TreeTerminationCategory -cnotin @('NOT_REQUIRED','COMPLETE','FAILED')) { + $TreeTerminationCategory = 'FAILED' } - if ($lineMatch.Success) { - [void]([int]::TryParse( - $lineMatch.Groups[1].Value, - [Globalization.NumberStyles]::None, - [Globalization.CultureInfo]::InvariantCulture, - [ref]$lineNumber - )) + if ($StartupClass -cnotin @( + 'NONE','READY','PARSER','PARAMETER_BINDING','TYPE_LOAD','OTHER' + )) { $StartupClass = 'NONE' } + if ($ValidatedProcessExit -cnotmatch '^(?:0|20|21|122|123|124|125)$') { + $ValidatedProcessExit = 'INVALID' } - $signedExit = $ProcessExitCode.ToString([Globalization.CultureInfo]::InvariantCulture) - $numericLine = $lineNumber.ToString([Globalization.CultureInfo]::InvariantCulture) - return 'STARTUP_CLASS:{0}:PROCESS_EXIT:{1}:LINE:{2}' -f ` - $classification, $signedExit, $numericLine + $boundedLineCount = if ($LineCount -ge 3) { '3+' } else { + [Math]::Max(0, $LineCount).ToString([Globalization.CultureInfo]::InvariantCulture) + } + $boundedStderrCount = [Math]::Min(4096, [Math]::Max(0, $StandardErrorCount)) + $boundedLineNumber = [Math]::Min(3, [Math]::Max(0, $LineNumber)) + return (('PROPR_WORKFLOW_CLEANUP_FIXTURE:PROTOCOL_MISMATCH:' + + 'INVOCATION:{0}:OBSERVED:{1}:LINE_COUNT:{2}:STDERR_COUNT:{3}:' + + 'PROCESS_EXIT:{4}:LIFECYCLE:{5}:TREE_TERMINATION:{6}:' + + 'STARTUP_CLASS:{7}:LINE_NUMBER:{8}') -f ` + $InvocationIdentifier, $ObservedLineCategory, $boundedLineCount, + $boundedStderrCount.ToString([Globalization.CultureInfo]::InvariantCulture), + $ValidatedProcessExit, $LifecycleCategory, $TreeTerminationCategory, + $StartupClass, + $boundedLineNumber.ToString([Globalization.CultureInfo]::InvariantCulture)) } function Invoke-WorkflowCleanupController( + [Parameter(Mandatory=$true)] + [ValidateSet( + 'STARTUP_PROTOCOL','REPLACEMENT_RETRY','REPLACED_ENTRY_RETRY', + 'PROFILE_ALTERNATE_LEAF','PROFILE_RETRY','EXECUTABLE_IDENTITY_RETRY', + 'FOREIGN_CHILD_RETRY','TERMINATION_RETRY','PARAMETER_VALIDATION', + 'EARLY_INITIALIZATION_TIMEOUT','CLEANUP_TIMEOUT','INSTALLER_REPLACEMENT', + 'RESOURCE_COLLISION','WORKFLOW_RETRY','NORMAL_CLEANUP','MANIFEST_VALIDATION', + 'SMOKE_PROMOTION_RETRY','SMOKE_TOKEN_MISSING','SMOKE_TOKEN_RETRY', + 'APP_PATH_MISMATCH','HKCU_BASELINE_RESTORE','HKCU_PENDING_RECEIPT', + 'HKCU_NONEMPTY','HKCU_EMPTY','HKCU_CONFLICT','HKCU_PROVISIONAL', + 'USER_MARKER_OWNED','USER_MARKER_REPLACEMENT','PROTOCOL_REGRESSION' + )][string]$InvocationIdentifier, [string]$ManifestPath, [string]$RunId, [string]$FixtureRoot, [object]$CleanupTimeoutMilliseconds = 30000, [bool]$FixtureEarlyInitializationChild = $false, - [string]$StartupFailureClass = '' + [string]$StartupFailureClass = '', + [object]$InvocationTimeoutMilliseconds = 40000, + [Threading.WaitHandle]$CancellationWaitHandle = $null, + [bool]$InjectTreeTerminationFailure = $false, + [string]$ProtocolFixture = '' ) { $startInfo = [Diagnostics.ProcessStartInfo]::new() $startInfo.FileName = $hostPath @@ -728,62 +1128,134 @@ function Invoke-WorkflowCleanupController( $startInfo.ArgumentList.Add('-StartupFailureClass') $startInfo.ArgumentList.Add($StartupFailureClass) } + if ($ProtocolFixture) { + $startInfo.ArgumentList.Add('-ProtocolFixture') + $startInfo.ArgumentList.Add($ProtocolFixture) + } + $invocationTimeout = 0 + if (![int]::TryParse( + [string]$InvocationTimeoutMilliseconds, + [Globalization.NumberStyles]::None, + [Globalization.CultureInfo]::InvariantCulture, + [ref]$invocationTimeout + ) -or $invocationTimeout -lt 1 -or $invocationTimeout -gt 40000) { + throw 'workflow cleanup invocation timeout is invalid' + } $process = [Diagnostics.Process]::new() $process.StartInfo = $startInfo + $job = $null + $capture = $null + $processStarted = $false + $lifecycleCategory = 'PROCESS_CREATION_FAILURE' + $treeTerminationCategory = 'NOT_REQUIRED' + $validatedProcessExit = 'INVALID' try { + $job = [ProPRWorkflowCleanupInvocationJob]::new() if (!$process.Start()) { throw 'workflow cleanup fixture did not start' } - Assert-True ($process.WaitForExit(40000)) 'workflow cleanup fixture exceeded its bound' - $output = $process.StandardOutput.ReadToEnd() - $errorOutput = $process.StandardError.ReadToEnd() - $outputLines = @($output -split '\r?\n' | Where-Object { $_ }) - $lineCount = if ($outputLines.Count -ge 3) { '3+' } else { [string]$outputLines.Count } - $stderrCount = [Math]::Min(4096, $errorOutput.Length) - if ($output.Length -gt 512 -or $outputLines.Count -ne 2) { - $startupDiagnostic = Get-SanitizedControllerStartupDiagnostic ` - $errorOutput ([int]$process.ExitCode) - throw ('PROPR_WORKFLOW_CLEANUP_FIXTURE:PROTOCOL_MISMATCH:LINE_COUNT:{0}:STDERR_COUNT:{1}:{2}' -f ` - $lineCount, $stderrCount, $startupDiagnostic) - } - $resultMatch = [regex]::Match( - $outputLines[0], - '^PROPR_WINDOWS_INSTALLED_SMOKE:WORKFLOW_CLEANUP:(COMPLETE|FAILED|TIMED_OUT)$' - ) - if (!$resultMatch.Success) { - $startupDiagnostic = Get-SanitizedControllerStartupDiagnostic ` - $errorOutput ([int]$process.ExitCode) - throw ('PROPR_WORKFLOW_CLEANUP_FIXTURE:PROTOCOL_MISMATCH:LINE_COUNT:{0}:STDERR_COUNT:{1}:{2}' -f ` - $lineCount, $stderrCount, $startupDiagnostic) - } - $resultName = $resultMatch.Groups[1].Value - $statusMatch = Get-WorkflowCleanupControllerStatusMatch $outputLines[1] - if (!$statusMatch.Success) { - $startupDiagnostic = Get-SanitizedControllerStartupDiagnostic ` - $errorOutput ([int]$process.ExitCode) - throw ('PROPR_WORKFLOW_CLEANUP_FIXTURE:PROTOCOL_MISMATCH:LINE_COUNT:{0}:STDERR_COUNT:{1}:{2}' -f ` - $lineCount, $stderrCount, $startupDiagnostic) - } - $controllerStatus = $statusMatch.Groups[1].Value - $reportedExitCode = [int]$statusMatch.Groups[2].Value - if ($errorOutput.Length -ne 0) { - $stderrCode = if ($errorOutput.Length -gt 4096) { - 'CONTROLLER_STDERR_LIMIT' - } else { 'CONTROLLER_STDERR_PRESENT' } - throw ('PROPR_WORKFLOW_CLEANUP_FIXTURE:{0}:STATUS:{1}:EXIT_CODE:{2}:' + - 'LINE_COUNT:{3}:STDERR_COUNT:{4}' -f ` - $stderrCode, $controllerStatus, $reportedExitCode, $lineCount, $stderrCount) + $processStarted = $true + try { + $job.AddProcess($process.Handle) + } catch { + $lifecycleCategory = 'OWNERSHIP_FAILURE' + throw + } + $capture = [ProPRWorkflowCleanupProtocolCapture]::new() + $capture.Start($process) + $watch = [Diagnostics.Stopwatch]::StartNew() + $cancelled = $false + while (!$process.HasExited -and $watch.ElapsedMilliseconds -lt $invocationTimeout) { + if ($null -ne $CancellationWaitHandle -and $CancellationWaitHandle.WaitOne(0)) { + $cancelled = $true + break + } + [Threading.Thread]::Sleep(25) + } + if (!$process.HasExited) { + $lifecycleCategory = if ($cancelled) { + 'CANCELLED_BEFORE_STARTUP' + } else { 'TIMEOUT_BEFORE_STARTUP' } + $treeTerminationCategory = 'FAILED' + if (!$InjectTreeTerminationFailure) { + try { + if ($job.TerminateAndWait(125, 3000)) { + $treeTerminationCategory = 'COMPLETE' + } + } catch {} + } + [void]$process.WaitForExit(3000) + } else { + $lifecycleCategory = 'EXITED' + if (!$job.HasNoActiveProcesses()) { + $lifecycleCategory = 'ACTIVE_TREE_AFTER_EXIT' + $treeTerminationCategory = 'FAILED' + if (!$InjectTreeTerminationFailure) { + try { + if ($job.TerminateAndWait(125, 3000)) { + $treeTerminationCategory = 'COMPLETE' + } + } catch {} + } + } + } + $drainComplete = $capture.Finish(3000) + if (!$drainComplete -and $lifecycleCategory -ceq 'EXITED') { + $lifecycleCategory = if ($capture.DrainFailed) { 'DRAIN_FAILURE' } else { 'DRAIN_TIMEOUT' } + } + if ($lifecycleCategory -like '*BEFORE_STARTUP' -and + $capture.StartupClass -cne 'NONE') { + $lifecycleCategory = $lifecycleCategory.Replace('BEFORE_STARTUP', 'AFTER_STARTUP') + } + if ($process.HasExited -and $process.ExitCode -in @(0,20,21,122,123,124,125)) { + $validatedProcessExit = + $process.ExitCode.ToString([Globalization.CultureInfo]::InvariantCulture) + } + if ($lifecycleCategory -cne 'EXITED' -or + $treeTerminationCategory -cne 'NOT_REQUIRED' -or + !$drainComplete -or + !$capture.IsProtocolValid([int]$process.ExitCode)) { + throw (Get-WorkflowCleanupProtocolMismatchDiagnostic ` + $InvocationIdentifier $capture.ObservedLineCategory $capture.LineCount ` + $capture.StandardErrorCount $validatedProcessExit $lifecycleCategory ` + $treeTerminationCategory $capture.StartupClass $capture.ObservedLineNumber) } return [PSCustomObject]@{ + InvocationIdentifier = $InvocationIdentifier ExitCode = $process.ExitCode - Result = $resultName - ControllerStatus = $controllerStatus - ReportedExitCode = $reportedExitCode - StartupClass = [string]$statusMatch.Groups[3].Value - StartupProcessExit = [string]$statusMatch.Groups[4].Value - StartupLine = [string]$statusMatch.Groups[5].Value - Output = $output + Result = $capture.Result + ControllerStatus = $capture.ControllerStatus + ReportedExitCode = $capture.ReportedExitCode + StartupClass = if ($capture.StartupClass -ceq 'READY') { '' } else { + $capture.StartupClass + } + StartupProcessExit = if ($capture.StartupClass -ceq 'READY') { '' } else { + [string]$capture.StartupProcessExit + } + StartupLine = if ($capture.StartupClass -ceq 'READY') { '' } else { + [string]$capture.StartupLine + } + } + } catch { + if ($_.Exception.Message -like 'PROPR_WORKFLOW_CLEANUP_FIXTURE:PROTOCOL_MISMATCH:*') { + throw + } + $lineCategory = if ($null -eq $capture) { 'NONE' } else { + $capture.ObservedLineCategory } + $lineCount = if ($null -eq $capture) { 0 } else { $capture.LineCount } + $stderrCount = if ($null -eq $capture) { 0 } else { $capture.StandardErrorCount } + $startupClass = if ($null -eq $capture) { 'NONE' } else { $capture.StartupClass } + $lineNumber = if ($null -eq $capture) { 0 } else { $capture.ObservedLineNumber } + throw (Get-WorkflowCleanupProtocolMismatchDiagnostic ` + $InvocationIdentifier $lineCategory $lineCount $stderrCount ` + $validatedProcessExit $lifecycleCategory $treeTerminationCategory ` + $startupClass $lineNumber) } finally { - if (!$process.HasExited) { try { $process.Kill($true) } catch {} } + if ($processStarted -and !$process.HasExited) { + try { if ($null -ne $job) { [void]$job.TerminateAndWait(125, 3000) } } catch {} + try { if (!$process.HasExited) { $process.Kill($true) } } catch {} + } + if ($null -ne $capture) { $capture.Dispose() } + if ($null -ne $job) { $job.Dispose() } $process.Dispose() } } @@ -791,7 +1263,8 @@ function Invoke-WorkflowCleanupController( function Test-WorkflowCleanupStartupProtocol { foreach ($failureClass in @('PARSER','PARAMETER_BINDING','TYPE_LOAD','OTHER')) { $result = Invoke-WorkflowCleanupController ` - $dummyInstaller $([Guid]::NewGuid().ToString('N')) $testRoot 30000 $false ` + 'STARTUP_PROTOCOL' $dummyInstaller $([Guid]::NewGuid().ToString('N')) ` + $testRoot 30000 $false ` $failureClass Assert-True ($result.ExitCode -eq 125 -and $result.ReportedExitCode -eq 125 -and @@ -812,12 +1285,12 @@ function Test-WorkflowCleanupStartupProtocol { } foreach ($invalidStatusLine in @( - 'PROPR_WINDOWS_INSTALLED_SMOKE:WORKFLOW_CLEANUP:STATUS:STARTUP_FAILURE:EXIT_CODE:125:STARTUP_CLASS:INVALID:PROCESS_EXIT:125:LINE:12', - 'PROPR_WINDOWS_INSTALLED_SMOKE:WORKFLOW_CLEANUP:STATUS:STARTUP_FAILURE:EXIT_CODE:125:STARTUP_CLASS:PARSER:PROCESS_EXIT:+125:LINE:12', - 'PROPR_WINDOWS_INSTALLED_SMOKE:WORKFLOW_CLEANUP:STATUS:STARTUP_FAILURE:EXIT_CODE:125:STARTUP_CLASS:PARSER:PROCESS_EXIT:125:LINE:-1' + 'PROPR_WINDOWS_INSTALLED_SMOKE:WORKFLOW_CLEANUP:TERMINAL:RESULT:INVALID:STATUS:STARTUP_FAILURE:EXIT_CODE:125', + 'PROPR_WINDOWS_INSTALLED_SMOKE:WORKFLOW_CLEANUP:TERMINAL:RESULT:FAILED:STATUS:STARTUP_FAILURE:EXIT_CODE:+125', + 'PROPR_WINDOWS_INSTALLED_SMOKE:WORKFLOW_CLEANUP:TERMINAL:RESULT:FAILED:STATUS:STARTUP_FAILURE:EXIT_CODE:126' )) { Assert-True (!(Get-WorkflowCleanupControllerStatusMatch $invalidStatusLine).Success) ` - 'workflow cleanup parser accepted malformed startup metadata' + 'workflow cleanup parser accepted a malformed terminal record' } $validStartupMetadata = [PSCustomObject]@{ @@ -877,6 +1350,114 @@ function Test-WorkflowCleanupStartupProtocol { [Console]::Out.Flush() } +function Test-WorkflowCleanupProtocolStateMachine { + $scriptText = Get-Content -LiteralPath $PSCommandPath -Raw -Encoding UTF8 + $expectedInvocations = @( + 'STARTUP_PROTOCOL','REPLACEMENT_RETRY','REPLACED_ENTRY_RETRY', + 'PROFILE_ALTERNATE_LEAF','PROFILE_RETRY','EXECUTABLE_IDENTITY_RETRY', + 'FOREIGN_CHILD_RETRY','TERMINATION_RETRY','PARAMETER_VALIDATION', + 'EARLY_INITIALIZATION_TIMEOUT','CLEANUP_TIMEOUT','INSTALLER_REPLACEMENT', + 'RESOURCE_COLLISION','WORKFLOW_RETRY','NORMAL_CLEANUP','MANIFEST_VALIDATION', + 'SMOKE_PROMOTION_RETRY','SMOKE_TOKEN_MISSING','SMOKE_TOKEN_RETRY', + 'APP_PATH_MISMATCH','HKCU_BASELINE_RESTORE','HKCU_PENDING_RECEIPT', + 'HKCU_NONEMPTY','HKCU_EMPTY','HKCU_CONFLICT','HKCU_PROVISIONAL', + 'USER_MARKER_OWNED','USER_MARKER_REPLACEMENT','PROTOCOL_REGRESSION' + ) + foreach ($identifier in $expectedInvocations) { + Assert-True ($scriptText -cmatch (( + "Invoke-WorkflowCleanupController\s+``\r?\n\s+(?:" + + "'|\-InvocationIdentifier\s+')") + + [regex]::Escape($identifier) + "'" + )) "workflow cleanup invocation identifier $identifier has no fixed callsite" + } + + $cases = @( + [PSCustomObject]@{ Fixture='ONE_LINE_STARTUP'; Observed='STARTUP'; Lifecycle='EXITED' }, + [PSCustomObject]@{ Fixture='MISSING_TERMINAL'; Observed='STARTUP'; Lifecycle='EXITED' }, + [PSCustomObject]@{ Fixture='DUPLICATE_STARTUP'; Observed='DUPLICATE'; Lifecycle='EXITED' }, + [PSCustomObject]@{ Fixture='EXTRA_RECORD'; Observed='EXTRA'; Lifecycle='EXITED' }, + [PSCustomObject]@{ Fixture='REORDERED_RECORDS'; Observed='REORDERED'; Lifecycle='EXITED' }, + [PSCustomObject]@{ Fixture='OVERSIZED_RECORD'; Observed='OVERSIZED'; Lifecycle='EXITED' }, + [PSCustomObject]@{ Fixture='MALFORMED_RECORD'; Observed='MALFORMED'; Lifecycle='EXITED' }, + [PSCustomObject]@{ Fixture='PARTIAL_RECORD'; Observed='PARTIAL'; Lifecycle='EXITED' }, + [PSCustomObject]@{ Fixture='STDERR_RECORD'; Observed='TERMINAL'; Lifecycle='EXITED'; Stderr=1 }, + [PSCustomObject]@{ Fixture='INVALID_STARTUP_METADATA'; Observed='MALFORMED'; Lifecycle='EXITED' }, + [PSCustomObject]@{ Fixture='TIMEOUT_BEFORE_STARTUP'; Observed='NONE'; Lifecycle='TIMEOUT_BEFORE_STARTUP' }, + [PSCustomObject]@{ Fixture='TIMEOUT_AFTER_STARTUP'; Observed='STARTUP'; Lifecycle='TIMEOUT_AFTER_STARTUP' }, + [PSCustomObject]@{ Fixture='STREAM_DRAIN_RACE'; Observed='TERMINAL'; Lifecycle='ACTIVE_TREE_AFTER_EXIT' } + ) + foreach ($case in $cases) { + $diagnostic = '' + try { + [void](Invoke-WorkflowCleanupController ` + -InvocationIdentifier 'PROTOCOL_REGRESSION' ` + -ManifestPath $dummyInstaller ` + -RunId ([Guid]::NewGuid().ToString('N')) ` + -FixtureRoot $testRoot ` + -InvocationTimeoutMilliseconds 250 ` + -ProtocolFixture $case.Fixture) + } catch { $diagnostic = $_.Exception.Message } + Assert-Contains $diagnostic ` + 'PROPR_WORKFLOW_CLEANUP_FIXTURE:PROTOCOL_MISMATCH:INVOCATION:PROTOCOL_REGRESSION:' ` + "$($case.Fixture) did not emit an invocation-attributed fixed diagnostic" + Assert-Contains $diagnostic ":OBSERVED:$($case.Observed):" ` + "$($case.Fixture) did not retain its bounded observed-line category" + Assert-Contains $diagnostic ":LIFECYCLE:$($case.Lifecycle):" ` + "$($case.Fixture) did not retain its primary lifecycle category" + if ($case.PSObject.Properties['Stderr']) { + Assert-Contains $diagnostic ":STDERR_COUNT:$($case.Stderr):" ` + "$($case.Fixture) did not retain its bounded stderr count" + } + Assert-NotContains $diagnostic $dummyInstaller ` + "$($case.Fixture) diagnostic disclosed a path" + } + + foreach ($cancellationAfterStartup in @($false)) { + $cancel = [Threading.EventWaitHandle]::new( + $true, [Threading.EventResetMode]::ManualReset) + try { + $diagnostic = '' + $fixture = if ($cancellationAfterStartup) { + 'TIMEOUT_AFTER_STARTUP' + } else { 'TIMEOUT_BEFORE_STARTUP' } + try { + [void](Invoke-WorkflowCleanupController ` + -InvocationIdentifier 'PROTOCOL_REGRESSION' ` + -ManifestPath $dummyInstaller ` + -RunId ([Guid]::NewGuid().ToString('N')) ` + -FixtureRoot $testRoot ` + -InvocationTimeoutMilliseconds 1000 ` + -CancellationWaitHandle $cancel ` + -ProtocolFixture $fixture) + } catch { $diagnostic = $_.Exception.Message } + Assert-True ($diagnostic -cmatch + ':LIFECYCLE:CANCELLED_(?:BEFORE|AFTER)_STARTUP:') ` + 'workflow cleanup cancellation lost its bounded lifecycle category' + Assert-Contains $diagnostic ':TREE_TERMINATION:COMPLETE:' ` + 'workflow cleanup cancellation did not terminate its complete owned tree' + } finally { $cancel.Dispose() } + } + + $treeFailureDiagnostic = '' + try { + [void](Invoke-WorkflowCleanupController ` + -InvocationIdentifier 'PROTOCOL_REGRESSION' ` + -ManifestPath $dummyInstaller ` + -RunId ([Guid]::NewGuid().ToString('N')) ` + -FixtureRoot $testRoot ` + -InvocationTimeoutMilliseconds 250 ` + -InjectTreeTerminationFailure $true ` + -ProtocolFixture 'TIMEOUT_AFTER_STARTUP') + } catch { $treeFailureDiagnostic = $_.Exception.Message } + Assert-Contains $treeFailureDiagnostic ':LIFECYCLE:TIMEOUT_AFTER_STARTUP:' ` + 'tree-termination failure replaced the primary timeout outcome' + Assert-Contains $treeFailureDiagnostic ':TREE_TERMINATION:FAILED:' ` + 'tree-termination failure was not represented by its fixed category' + + Write-Host 'PROPR_WINDOWS_SUPERVISOR_CONTROLLER_STATE_MACHINE:BOUNDED:PASSED' + [Console]::Out.Flush() +} + function Start-ExternallyInterruptibleSupervisor([string]$StateDirectory) { $scriptText = @' param($SupervisorPath, $Installer, $Architecture, $FixtureWorker, $Scenario, @@ -1405,7 +1986,8 @@ function Test-PreExistingCleanupOwnership { 'false standalone cleanup result discarded authenticated recovery authority' Restore-ReplacedFixtureAuthority $replacementOwned $replacementRetry = Invoke-WorkflowCleanupController ` - $replacementOwned.ManifestPath $replacementOwned.RunId $replacementStateDirectory + 'REPLACEMENT_RETRY' $replacementOwned.ManifestPath $replacementOwned.RunId ` + $replacementStateDirectory $replacementRetryDiagnostic = Get-SanitizedWorkflowCleanupResultDiagnostic $replacementRetry Assert-True ($replacementRetry.ExitCode -eq 0 -and @@ -1448,7 +2030,8 @@ function Test-PreExistingCleanupOwnership { "replacement $($replacementCase.Label) discarded ACTIVE recovery authority" Restore-ReplacedFixtureAuthority $replacedOwned $replacedRetry = Invoke-WorkflowCleanupController ` - $replacedOwned.ManifestPath $replacedOwned.RunId $replacedStateDirectory + 'REPLACED_ENTRY_RETRY' $replacedOwned.ManifestPath $replacedOwned.RunId ` + $replacedStateDirectory Assert-True ($replacedRetry.ExitCode -eq 0 -and $replacedRetry.Result -ceq 'COMPLETE') ` "replacement $($replacementCase.Label) authority did not retry to success" @@ -1504,7 +2087,8 @@ function Test-PreExistingCleanupOwnership { $ownedProfileRecords[0].LocalPath = $runnerProfileBefore.CanonicalLocalPath Write-TestOwnershipManifest $profileMismatchOwned.ManifestPath $profileMismatchManifest $alternateLeafCleanup = Invoke-WorkflowCleanupController ` - $profileMismatchOwned.ManifestPath $profileMismatchOwned.RunId $profileMismatchDirectory + 'PROFILE_ALTERNATE_LEAF' $profileMismatchOwned.ManifestPath ` + $profileMismatchOwned.RunId $profileMismatchDirectory Assert-True ($alternateLeafCleanup.ExitCode -eq 21 -and $alternateLeafCleanup.Result -ceq 'FAILED') ` 'alternate ProfilesDirectory leaf did not fail closed' @@ -1520,7 +2104,8 @@ function Test-PreExistingCleanupOwnership { $ownedProfileRecords[0].LocalPath = [string]$profileMismatchOwned.ProfilePath Write-TestOwnershipManifest $profileMismatchOwned.ManifestPath $profileMismatchManifest $profileMismatchRetry = Invoke-WorkflowCleanupController ` - $profileMismatchOwned.ManifestPath $profileMismatchOwned.RunId $profileMismatchDirectory + 'PROFILE_RETRY' $profileMismatchOwned.ManifestPath ` + $profileMismatchOwned.RunId $profileMismatchDirectory Assert-True ($profileMismatchRetry.ExitCode -eq 0 -and $profileMismatchRetry.Result -ceq 'COMPLETE') ` 'profile cleanup did not succeed after exact durable path restoration' @@ -1541,7 +2126,8 @@ function Test-PreExistingCleanupOwnership { Move-Item -LiteralPath $byteIdenticalOwned.ExecutableBackup ` -Destination $byteIdenticalOwned.Executable -ErrorAction Stop $byteIdenticalRetry = Invoke-WorkflowCleanupController ` - $byteIdenticalOwned.ManifestPath $byteIdenticalOwned.RunId $byteIdenticalDirectory + 'EXECUTABLE_IDENTITY_RETRY' $byteIdenticalOwned.ManifestPath ` + $byteIdenticalOwned.RunId $byteIdenticalDirectory Assert-True ($byteIdenticalRetry.ExitCode -eq 0 -and $byteIdenticalRetry.Result -ceq 'COMPLETE') ` 'byte-identical file cleanup did not succeed after exact entry identity restoration' @@ -1569,7 +2155,8 @@ function Test-PreExistingCleanupOwnership { 'in-place foreign-child failure did not preserve the ACTIVE manifest' Remove-Item -LiteralPath $foreignChildPath -Force -ErrorAction Stop $foreignChildRetry = Invoke-WorkflowCleanupController ` - $foreignChildOwned.ManifestPath $foreignChildOwned.RunId $foreignChildStateDirectory + 'FOREIGN_CHILD_RETRY' $foreignChildOwned.ManifestPath ` + $foreignChildOwned.RunId $foreignChildStateDirectory Assert-True ($foreignChildRetry.ExitCode -eq 0 -and $foreignChildRetry.Result -ceq 'COMPLETE') ` 'in-place foreign-child cleanup did not retry to exact success' @@ -1595,7 +2182,8 @@ function Test-PreExistingCleanupOwnership { Assert-True (Test-Path -LiteralPath $terminationFailureOwned.InstallRoot -PathType Container) ` 'cleanup mutated resources before worker-tree termination was verified' $terminationRetry = Invoke-WorkflowCleanupController ` - $terminationFailureOwned.ManifestPath $terminationFailureOwned.RunId ` + 'TERMINATION_RETRY' $terminationFailureOwned.ManifestPath ` + $terminationFailureOwned.RunId ` $terminationFailureStateDirectory Assert-True ($terminationRetry.ExitCode -eq 0 -and $terminationRetry.Result -ceq 'COMPLETE') ` @@ -1649,7 +2237,8 @@ function Test-PreExistingCleanupOwnership { Assert-True (Test-Path -LiteralPath $workflowManifest -PathType Leaf) ` 'killed supervisor did not preserve the durable ownership manifest' $parameterFailure = Invoke-WorkflowCleanupController ` - $workflowManifest $workflowRunId $workflowStateDirectory -1 + 'PARAMETER_VALIDATION' $workflowManifest $workflowRunId ` + $workflowStateDirectory -1 Assert-True ($parameterFailure.ExitCode -eq 125 -and $parameterFailure.Result -ceq 'FAILED' -and $parameterFailure.ControllerStatus.StartsWith( @@ -1659,7 +2248,8 @@ function Test-PreExistingCleanupOwnership { Assert-True (Test-Path -LiteralPath $workflowManifest -PathType Leaf) ` 'controller parameter failure discarded authenticated recovery authority' $earlyInitializationTimeout = Invoke-WorkflowCleanupController ` - $workflowManifest $workflowRunId $workflowStateDirectory 5000 $true + 'EARLY_INITIALIZATION_TIMEOUT' $workflowManifest $workflowRunId ` + $workflowStateDirectory 5000 $true Assert-True ($earlyInitializationTimeout.ExitCode -eq 124 -and $earlyInitializationTimeout.ReportedExitCode -eq 124 -and $earlyInitializationTimeout.Result -ceq 'TIMED_OUT') ` @@ -1671,7 +2261,7 @@ function Test-PreExistingCleanupOwnership { Assert-True (Test-Path -LiteralPath $workflowManifest -PathType Leaf) ` 'early-initialization timeout discarded authenticated recovery authority' $timedOutCleanup = Invoke-WorkflowCleanupController ` - $workflowManifest $workflowRunId $workflowStateDirectory 1 + 'CLEANUP_TIMEOUT' $workflowManifest $workflowRunId $workflowStateDirectory 1 Assert-True ($timedOutCleanup.ExitCode -eq 124 -and $timedOutCleanup.ReportedExitCode -eq 124 -and $timedOutCleanup.Result -ceq 'TIMED_OUT') ` @@ -1687,7 +2277,8 @@ function Test-PreExistingCleanupOwnership { (Get-FileHash -LiteralPath $dummyInstaller -Algorithm SHA256).Hash try { $replacedInstallerCleanup = Invoke-WorkflowCleanupController ` - $workflowManifest $workflowRunId $workflowStateDirectory + 'INSTALLER_REPLACEMENT' $workflowManifest $workflowRunId ` + $workflowStateDirectory Assert-True ($replacedInstallerCleanup.ExitCode -eq 21 -and $replacedInstallerCleanup.ReportedExitCode -eq 21 -and $replacedInstallerCleanup.Result -ceq 'FAILED' -and @@ -1718,7 +2309,7 @@ function Test-PreExistingCleanupOwnership { Set-ItemProperty -LiteralPath $workflowOwned.RegistryPath ` -Name 'ProPRInstalledAppOwner' -Value 'foreign-owner' $failedWorkflowCleanup = Invoke-WorkflowCleanupController ` - $workflowManifest $workflowRunId $workflowStateDirectory + 'RESOURCE_COLLISION' $workflowManifest $workflowRunId $workflowStateDirectory Assert-True ($failedWorkflowCleanup.ExitCode -eq 21 -and $failedWorkflowCleanup.ReportedExitCode -eq 21 -and $failedWorkflowCleanup.Result -ceq 'FAILED' -and @@ -1733,14 +2324,12 @@ function Test-PreExistingCleanupOwnership { Set-ItemProperty -LiteralPath $workflowOwned.RegistryPath ` -Name 'ProPRInstalledAppOwner' -Value ([string]$workflowOwned.Token) $workflowCleanup = Invoke-WorkflowCleanupController ` - $workflowManifest $workflowRunId $workflowStateDirectory + 'WORKFLOW_RETRY' $workflowManifest $workflowRunId $workflowStateDirectory Assert-True ($workflowCleanup.ExitCode -eq 0 -and $workflowCleanup.ReportedExitCode -eq 0 -and - $workflowCleanup.ControllerStatus -ceq 'EMPTY_OR_CLEANED') ` + $workflowCleanup.ControllerStatus -ceq 'EMPTY_OR_CLEANED' -and + $workflowCleanup.InvocationIdentifier -ceq 'WORKFLOW_RETRY') ` 'workflow cleanup controller did not retry to fixed cleanup success' - Assert-Contains $workflowCleanup.Output ` - 'PROPR_WINDOWS_INSTALLED_SMOKE:WORKFLOW_CLEANUP:COMPLETE' ` - 'workflow cleanup controller did not emit fixed completion evidence' Assert-OwnedResourcesGone $workflowOwned Assert-True (!(Test-Path -LiteralPath $workflowManifest)) ` 'workflow cleanup did not consume the ownership manifest' @@ -1783,7 +2372,7 @@ function Test-PreExistingCleanupOwnership { @($normalReceipt.Profiles).Count -eq 0) ` 'normal supervisor did not produce a typed authenticated empty-state receipt' $normalCleanup = Invoke-WorkflowCleanupController ` - $normalManifest $normalRunId $normalStateDirectory + 'NORMAL_CLEANUP' $normalManifest $normalRunId $normalStateDirectory Assert-True ($normalCleanup.ExitCode -eq 0 -and $normalCleanup.ReportedExitCode -eq 0 -and $normalCleanup.ControllerStatus -ceq 'EMPTY_OR_CLEANED') ` @@ -1826,16 +2415,14 @@ function Test-PreExistingCleanupOwnership { ) } $failedCleanup = Invoke-WorkflowCleanupController ` - $badManifest $badRunId $workflowStateDirectory + 'MANIFEST_VALIDATION' $badManifest $badRunId $workflowStateDirectory Assert-True ($failedCleanup.ExitCode -ne 0) ` "$manifestCase workflow manifest did not fail closed" Assert-True ($failedCleanup.ExitCode -eq 20 -and $failedCleanup.ReportedExitCode -eq 20 -and - $failedCleanup.ControllerStatus -ceq 'MANIFEST_VALIDATION_FAILURE') ` + $failedCleanup.ControllerStatus -ceq 'MANIFEST_VALIDATION_FAILURE' -and + $failedCleanup.InvocationIdentifier -ceq 'MANIFEST_VALIDATION') ` "$manifestCase workflow manifest did not report fixed validation status" - Assert-Contains $failedCleanup.Output ` - 'PROPR_WINDOWS_INSTALLED_SMOKE:WORKFLOW_CLEANUP:FAILED' ` - "$manifestCase workflow manifest did not emit fixed failure evidence" if ($manifestCase -ne 'MISSING') { Assert-True (Test-Path -LiteralPath $badManifest -PathType Leaf) ` "$manifestCase workflow failure discarded authenticated recovery authority" @@ -1929,7 +2516,8 @@ function Test-SmokePromotionInterruptionAuthority { 'smoke foreign descendant did not preserve ACTIVE recovery authority' Remove-Item -LiteralPath $foreignOwned.ForeignSmokePath -Force -ErrorAction Stop $retry = Invoke-WorkflowCleanupController ` - $foreignOwned.ManifestPath $foreignOwned.RunId $foreignStateDirectory + 'SMOKE_PROMOTION_RETRY' $foreignOwned.ManifestPath $foreignOwned.RunId ` + $foreignStateDirectory Assert-True ($retry.ExitCode -eq 0 -and $retry.Result -ceq 'COMPLETE') ` 'smoke foreign-descendant recovery did not retry to exact success' Assert-OwnedResourcesGone $foreignOwned @@ -1947,14 +2535,15 @@ function Test-SmokePromotionInterruptionAuthority { 'mismatched smoke ownership token discarded recovery authority' Remove-Item -LiteralPath $tokenPath -Force -ErrorAction Stop $missingToken = Invoke-WorkflowCleanupController ` - $tokenOwned.ManifestPath $tokenOwned.RunId $tokenStateDirectory + 'SMOKE_TOKEN_MISSING' $tokenOwned.ManifestPath $tokenOwned.RunId ` + $tokenStateDirectory Assert-True ($missingToken.ExitCode -eq 20 -and $missingToken.Result -ceq 'FAILED') ` 'missing smoke ownership token did not fail manifest validation closed' Assert-True (Test-Path -LiteralPath $tokenOwned.ManifestPath -PathType Leaf) ` 'missing smoke ownership token discarded recovery authority' [IO.File]::WriteAllText($tokenPath, [string]$tokenOwned.Token, [Text.Encoding]::ASCII) $tokenRetry = Invoke-WorkflowCleanupController ` - $tokenOwned.ManifestPath $tokenOwned.RunId $tokenStateDirectory + 'SMOKE_TOKEN_RETRY' $tokenOwned.ManifestPath $tokenOwned.RunId $tokenStateDirectory Assert-True ($tokenRetry.ExitCode -eq 0 -and $tokenRetry.Result -ceq 'COMPLETE') ` 'restored exact smoke ownership token did not retry to cleanup success' Assert-OwnedResourcesGone $tokenOwned @@ -2064,16 +2653,14 @@ function Test-PreExistingAppPathsAuthority { [Text.Encoding]::UTF8 ) $mismatchCleanup = Invoke-WorkflowCleanupController ` - $mismatchManifest $mismatchRunId '' + 'APP_PATH_MISMATCH' $mismatchManifest $mismatchRunId '' Assert-True ($mismatchCleanup.ExitCode -ne 0) ` 'mismatched App Paths ownership identity did not fail closed' Assert-True ($mismatchCleanup.ExitCode -eq 20 -and $mismatchCleanup.ReportedExitCode -eq 20 -and - $mismatchCleanup.ControllerStatus -ceq 'MANIFEST_VALIDATION_FAILURE') ` + $mismatchCleanup.ControllerStatus -ceq 'MANIFEST_VALIDATION_FAILURE' -and + $mismatchCleanup.InvocationIdentifier -ceq 'APP_PATH_MISMATCH') ` 'mismatched App Paths ownership did not report fixed validation status' - Assert-Contains $mismatchCleanup.Output ` - 'PROPR_WINDOWS_INSTALLED_SMOKE:WORKFLOW_CLEANUP:FAILED' ` - 'mismatched App Paths ownership did not emit fixed failure evidence' Assert-True ((Get-Item -LiteralPath $appPaths).GetValue('') -ceq $sentinelApplication) ` 'mismatched App Paths ownership removed the pre-existing executable value' Assert-True ((Get-Item -LiteralPath $appPaths).GetValue('Path') -ceq 'C:\pre-existing') ` @@ -2171,7 +2758,8 @@ function Test-HkcuInstalledValueOwnership { (Get-Item -LiteralPath $desktopKey).SetValue( $installedName, [int]1, [Microsoft.Win32.RegistryValueKind]::DWord) $restoreManifest = New-HkcuManifest $true $true 'String' $baselineData $false - $restore = Invoke-WorkflowCleanupController $restoreManifest.Path $restoreManifest.RunId '' + $restore = Invoke-WorkflowCleanupController ` + 'HKCU_BASELINE_RESTORE' $restoreManifest.Path $restoreManifest.RunId '' Assert-True ($restore.ExitCode -eq 0 -and $restore.ControllerStatus -ceq 'EMPTY_OR_CLEANED') ` 'pre-existing HKCU installed value restoration did not complete' @@ -2185,7 +2773,7 @@ function Test-HkcuInstalledValueOwnership { $unchangedManifest = New-HkcuManifest ` $true $true 'String' $baselineData $false $false $true $unchanged = Invoke-WorkflowCleanupController ` - $unchangedManifest.Path $unchangedManifest.RunId '' + 'HKCU_PENDING_RECEIPT' $unchangedManifest.Path $unchangedManifest.RunId '' Assert-True ($unchanged.ExitCode -eq 21 -and $unchanged.ControllerStatus -ceq 'OWNED_RESOURCE_CLEANUP_FAILURE') ` 'path-only pending MSI receipt was not rejected before uninstall' @@ -2204,7 +2792,8 @@ function Test-HkcuInstalledValueOwnership { (Get-Item -LiteralPath $desktopKey).SetValue( 'Unrelated', $sentinelUnrelated, [Microsoft.Win32.RegistryValueKind]::String) $nonemptyManifest = New-HkcuManifest $false $false $null $null $true - $nonempty = Invoke-WorkflowCleanupController $nonemptyManifest.Path $nonemptyManifest.RunId '' + $nonempty = Invoke-WorkflowCleanupController ` + 'HKCU_NONEMPTY' $nonemptyManifest.Path $nonemptyManifest.RunId '' Assert-True ($nonempty.ExitCode -eq 0) ` 'run-owned HKCU value cleanup with unrelated values failed' $nonemptyKey = Get-Item -LiteralPath $desktopKey -ErrorAction Stop @@ -2217,7 +2806,8 @@ function Test-HkcuInstalledValueOwnership { (Get-Item -LiteralPath $desktopKey).SetValue( $installedName, [int]1, [Microsoft.Win32.RegistryValueKind]::DWord) $emptyManifest = New-HkcuManifest $false $false $null $null $true - $empty = Invoke-WorkflowCleanupController $emptyManifest.Path $emptyManifest.RunId '' + $empty = Invoke-WorkflowCleanupController ` + 'HKCU_EMPTY' $emptyManifest.Path $emptyManifest.RunId '' Assert-True ($empty.ExitCode -eq 0 -and !(Test-Path -LiteralPath $desktopKey)) ` 'run-created empty HKCU key was not removed' @@ -2226,7 +2816,7 @@ function Test-HkcuInstalledValueOwnership { $installedName, 'foreign-conflict', [Microsoft.Win32.RegistryValueKind]::String) $conflictManifest = New-HkcuManifest $false $false $null $null $true $conflict = Invoke-WorkflowCleanupController ` - $conflictManifest.Path $conflictManifest.RunId '' + 'HKCU_CONFLICT' $conflictManifest.Path $conflictManifest.RunId '' Assert-True ($conflict.ExitCode -eq 21 -and $conflict.ReportedExitCode -eq 21 -and $conflict.ControllerStatus -ceq 'OWNED_RESOURCE_CLEANUP_FAILURE') ` @@ -2244,7 +2834,7 @@ function Test-HkcuInstalledValueOwnership { $installedName, [int]1, [Microsoft.Win32.RegistryValueKind]::DWord) $provisionalManifest = New-HkcuManifest $false $false $null $null $true $true $provisional = Invoke-WorkflowCleanupController ` - $provisionalManifest.Path $provisionalManifest.RunId '' + 'HKCU_PROVISIONAL' $provisionalManifest.Path $provisionalManifest.RunId '' Assert-True ($provisional.ExitCode -eq 21 -and $provisional.ControllerStatus -ceq 'OWNED_RESOURCE_CLEANUP_FAILURE') ` 'provisional HKCU evidence authorized manual registry deletion' @@ -2320,7 +2910,7 @@ function Test-ProvisionalUserMarkerOwnership { New-LocalUser -Name $positiveName -Password $password ` -Description $positiveMarker -AccountNeverExpires -PasswordNeverExpires | Out-Null $positive = Invoke-WorkflowCleanupController ` - $positiveManifest.Path $positiveManifest.RunId $testRoot + 'USER_MARKER_OWNED' $positiveManifest.Path $positiveManifest.RunId $testRoot Assert-True ($positive.ExitCode -eq 0 -and $positive.Result -ceq 'COMPLETE') ` 'marker-bound provisional local-user recovery did not complete' @@ -2333,7 +2923,8 @@ function Test-ProvisionalUserMarkerOwnership { -AccountNeverExpires -PasswordNeverExpires | Out-Null $replacementSid = (Get-LocalUser -Name $replacementName -ErrorAction Stop).SID.Value $replacement = Invoke-WorkflowCleanupController ` - $replacementManifest.Path $replacementManifest.RunId $testRoot + 'USER_MARKER_REPLACEMENT' $replacementManifest.Path ` + $replacementManifest.RunId $testRoot Assert-True ($replacement.ExitCode -eq 21 -and $replacement.ControllerStatus -ceq 'OWNED_RESOURCE_CLEANUP_FAILURE') ` 'provisional username authorized replacement-account deletion' @@ -2371,6 +2962,7 @@ Test-WorkflowCleanupBodyParserRegression Initialize-TestInstaller try { Test-WorkflowCleanupStartupProtocol + Test-WorkflowCleanupProtocolStateMachine Test-BootstrapTimeout Test-WindowsPowerShellCleanupCompatibility Test-OperationDeadlineAndTreeTermination diff --git a/apps/desktop/src/release-workflow.test.ts b/apps/desktop/src/release-workflow.test.ts index bf0a28a03..a71068b33 100644 --- a/apps/desktop/src/release-workflow.test.ts +++ b/apps/desktop/src/release-workflow.test.ts @@ -912,7 +912,7 @@ describe('desktop trusted release workflow', () => { assert.doesNotMatch(installedWindowsAppWorkflowCleanup, /Write-Host/); assert.match( installedWindowsAppWorkflowCleanup, - /Add-Type -TypeDefinition @'[\s\S]*'@\n\ntry \{\n\$controllerPhase = 'PARAMETER_VALIDATION'[\s\S]*\$controllerPhase = 'PROCESS_WAIT'[\s\S]*\n\} catch \{\n\s+Set-CaughtControllerFailure \$_\n\}/, + /Add-Type -TypeDefinition @'[\s\S]*'@[\s\S]*Write-StartupRecord\n\ntry \{\n\$controllerPhase = 'PARAMETER_VALIDATION'[\s\S]*\$controllerPhase = 'PROCESS_WAIT'[\s\S]*\n\} catch \{\n\s+Set-CaughtControllerFailure \$_\n\}/, ); assert.doesNotMatch(installedWindowsAppWorkflowCleanup, /\$invokeController|StartupFailureClass/); assert.match( @@ -928,6 +928,12 @@ describe('desktop trusted release workflow', () => { /'PARSER'[\s\S]*'PARAMETER_BINDING'[\s\S]*'TYPE_LOAD'[\s\S]*'OTHER'/, ); assert.match(installedWindowsAppWorkflowCleanupWrapper, /Write-StartupFailure \$_/); + assert.match( + installedWindowsAppWorkflowCleanupWrapper, + /WORKFLOW_CLEANUP:STARTUP:FAILED:[\s\S]*WORKFLOW_CLEANUP:TERMINAL:/, + ); + assert.match(installedWindowsAppWorkflowCleanup, /WORKFLOW_CLEANUP:STARTUP:READY/); + assert.match(installedWindowsAppWorkflowCleanup, /WORKFLOW_CLEANUP:TERMINAL:RESULT:\{0\}/); assert.equal( installedWindowsAppWorkflowCleanupWrapper.match(/\[Console\]::Out\.WriteLine/g)?.length, 2, @@ -975,24 +981,39 @@ describe('desktop trusted release workflow', () => { installedWindowsAppSupervisorFixture, /'OWNED_RESOURCES_THEN_DEADLINE' \{[\s\S]*Write-FixtureMarker[\s\S]*New-OwnedFixtureResources/, ); - const controllerStatusParser = installedWindowsAppSupervisorBehaviorTest.indexOf( - '$statusMatch = Get-WorkflowCleanupControllerStatusMatch', + assert.match(installedWindowsAppSupervisorBehaviorTest, /ProPRWorkflowCleanupProtocolCapture/); + const controllerJobAssignment = installedWindowsAppSupervisorBehaviorTest.indexOf( + '$job.AddProcess($process.Handle)', + ); + const controllerCaptureStart = installedWindowsAppSupervisorBehaviorTest.indexOf( + '$capture.Start($process)', ); - assert.notEqual(controllerStatusParser, -1); assert.ok( - controllerStatusParser - < installedWindowsAppSupervisorBehaviorTest.indexOf('if ($errorOutput.Length -ne 0)'), - 'controller fixed stdout must be parsed before bounded stderr classification', + controllerJobAssignment !== -1 && controllerJobAssignment < controllerCaptureStart, + 'controller root must enter the outer Job Object before bounded stream capture starts', + ); + const workflowCleanupInvocation = installedWindowsAppSupervisorBehaviorTest.slice( + installedWindowsAppSupervisorBehaviorTest.indexOf( + 'function Invoke-WorkflowCleanupController', + ), + installedWindowsAppSupervisorBehaviorTest.indexOf( + 'function Test-WorkflowCleanupStartupProtocol', + ), ); + assert.doesNotMatch(workflowCleanupInvocation, /\.ReadToEnd\(\)|\bOutput = \$output/); assert.match( installedWindowsAppSupervisorBehaviorTest, - /PROPR_WORKFLOW_CLEANUP_FIXTURE:\{0\}:STATUS:\{1\}:EXIT_CODE:\{2\}/, + /PROPR_WORKFLOW_CLEANUP_FIXTURE:PROTOCOL_MISMATCH:' \+\s*'INVOCATION:\{0\}:OBSERVED:\{1\}:LINE_COUNT:\{2\}:STDERR_COUNT:\{3\}:/, ); - assert.match(installedWindowsAppSupervisorBehaviorTest, /Get-SanitizedControllerStartupDiagnostic/); assert.match( installedWindowsAppSupervisorBehaviorTest, - /STARTUP_CLASS:\{0\}:PROCESS_EXIT:\{1\}:LINE:\{2\}/, + /PROCESS_EXIT:\{4\}:LIFECYCLE:\{5\}:TREE_TERMINATION:\{6\}:' \+\s*'STARTUP_CLASS:\{7\}:LINE_NUMBER:\{8\}/, ); + assert.match(installedWindowsAppSupervisorBehaviorTest, /\[ValidateSet\([\s\S]*'STARTUP_PROTOCOL'[\s\S]*'PROTOCOL_REGRESSION'/); + assert.match(installedWindowsAppSupervisorBehaviorTest, /ONE_LINE_STARTUP[\s\S]*DUPLICATE_STARTUP[\s\S]*REORDERED_RECORDS/); + assert.match(installedWindowsAppSupervisorBehaviorTest, /TIMEOUT_BEFORE_STARTUP[\s\S]*TIMEOUT_AFTER_STARTUP[\s\S]*STREAM_DRAIN_RACE/); + assert.match(installedWindowsAppSupervisorBehaviorTest, /CANCELLED_(?:BEFORE|AFTER)_STARTUP/); + assert.match(installedWindowsAppSupervisorBehaviorTest, /TREE_TERMINATION:FAILED/); assert.match( installedWindowsAppSupervisorBehaviorTest, /'PARSER'[\s\S]*'PARAMETER_BINDING'[\s\S]*'TYPE_LOAD'[\s\S]*'OTHER'/, @@ -1064,7 +1085,7 @@ describe('desktop trusted release workflow', () => { ); assert.match( installedWindowsAppSupervisorBehaviorTest, - /Get-WorkflowCleanupControllerStatusMatch[\s\S]*workflow cleanup parser accepted malformed startup metadata/, + /Get-WorkflowCleanupControllerStatusMatch[\s\S]*workflow cleanup parser accepted a malformed terminal record/, ); assert.match( installedWindowsAppSupervisorBehaviorTest, @@ -1131,7 +1152,7 @@ describe('desktop trusted release workflow', () => { installedWindowsAppSupervisorBehaviorTest, /OWNED_EXECUTABLE_BYTE_IDENTICAL_REPLACED_THEN_DEADLINE/, ); - assert.match(installedWindowsAppSupervisorBehaviorTest, /LINE_COUNT:\{0\}:STDERR_COUNT:\{1\}/); + assert.match(installedWindowsAppSupervisorBehaviorTest, /LINE_COUNT:\{2\}:STDERR_COUNT:\{3\}/); assert.match(installedWindowsAppCleanup, /smoke user-data object owner is not authorized/); assert.match(installedWindowsAppCleanup, /smoke user-data object ACL is not authorized/); assert.match(installedWindowsAppCleanup, /entries\.Count -ge 50000/); From a72c0814a1cf4fe6db1ffd59a7d9d309c7b8fdf7 Mon Sep 17 00:00:00 2001 From: "propr-dev[bot]" <1316198+propr-dev[bot]@users.noreply.github.com> Date: Tue, 1 Sep 2026 22:33:49 +0000 Subject: [PATCH 02/33] feat(ai): Implemented the three follow-up fixes: Implemented the three follow-up fixes: - Bounded Job Object accounting quiescence before classifying/terminating an active tree. - Deterministic before-startup and after-startup cancellation coverage with exact invocation attribution. - Exact protocol status/exit mapping with positive and negative regression fixtures. Validation: - Desktop tests: 177 passed, 6 platform skips. - Release workflow tests: 23 passed. - `git diff --check`: passed. - Native Windows x64/ARM64 could not run from this Linux-only worktree. Per instruction, no commit was created. Current exact HEAD remains `bb0a012b13c47b5fbd1228c1fd4df75979c3033f`; the automation must report the new head after committing these changes. PR: #2057 Comment by: @integry (ID: 5501268411) Model: gpt-5.6-sol --- ...lled-windows-app-workflow-cleanup-body.ps1 | 3 +- ...installed-windows-app-workflow-cleanup.ps1 | 60 ++++++++++ .../test-installed-windows-app-supervisor.ps1 | 106 +++++++++++++++--- apps/desktop/src/release-workflow.test.ts | 16 ++- 4 files changed, 170 insertions(+), 15 deletions(-) diff --git a/apps/desktop/scripts/run-installed-windows-app-workflow-cleanup-body.ps1 b/apps/desktop/scripts/run-installed-windows-app-workflow-cleanup-body.ps1 index f8f245b9a..64a4d09b0 100644 --- a/apps/desktop/scripts/run-installed-windows-app-workflow-cleanup-body.ps1 +++ b/apps/desktop/scripts/run-installed-windows-app-workflow-cleanup-body.ps1 @@ -454,7 +454,8 @@ $TerminationTimeoutMilliseconds = $terminationTimeout $fixedExitCode = 125 } } else { - $cleanupTreeZeroVerified = $cleanupJob.HasNoActiveProcesses() + $cleanupTreeZeroVerified = $cleanupJob.WaitForNoActiveProcesses( + $TerminationTimeoutMilliseconds) if (!$cleanupTreeZeroVerified) { try { $cleanupTreeZeroVerified = $cleanupJob.TerminateAndWait( diff --git a/apps/desktop/scripts/run-installed-windows-app-workflow-cleanup.ps1 b/apps/desktop/scripts/run-installed-windows-app-workflow-cleanup.ps1 index ce003665a..bb05cda38 100644 --- a/apps/desktop/scripts/run-installed-windows-app-workflow-cleanup.ps1 +++ b/apps/desktop/scripts/run-installed-windows-app-workflow-cleanup.ps1 @@ -92,6 +92,66 @@ function Invoke-ProtocolFixture([string]$Name) { [Console]::Out.Write("$startup`r`n"); [Console]::Out.Flush() [Threading.Thread]::Sleep(60000); exit 125 } + 'MISMATCHED_MANIFEST_EXIT_125' { + [Console]::Out.Write( + "$startup`r`n" + + ('PROPR_WINDOWS_INSTALLED_SMOKE:WORKFLOW_CLEANUP:TERMINAL:' + + "RESULT:FAILED:STATUS:MANIFEST_VALIDATION_FAILURE:EXIT_CODE:125`r`n")) + [Console]::Out.Flush(); exit 125 + } + 'MISMATCHED_CHILD_STDOUT_EXIT_21' { + [Console]::Out.Write( + "$startup`r`n" + + ('PROPR_WINDOWS_INSTALLED_SMOKE:WORKFLOW_CLEANUP:TERMINAL:' + + "RESULT:FAILED:STATUS:CHILD_STDOUT:EXIT_CODE:21`r`n")) + [Console]::Out.Flush(); exit 21 + } + 'EXACT_MANIFEST_20' { + [Console]::Out.Write( + "$startup`r`n" + + ('PROPR_WINDOWS_INSTALLED_SMOKE:WORKFLOW_CLEANUP:TERMINAL:' + + "RESULT:FAILED:STATUS:MANIFEST_VALIDATION_FAILURE:EXIT_CODE:20`r`n")) + [Console]::Out.Flush(); exit 20 + } + 'EXACT_OWNED_RESOURCE_21' { + [Console]::Out.Write( + "$startup`r`n" + + ('PROPR_WINDOWS_INSTALLED_SMOKE:WORKFLOW_CLEANUP:TERMINAL:' + + "RESULT:FAILED:STATUS:OWNED_RESOURCE_CLEANUP_FAILURE:EXIT_CODE:21`r`n")) + [Console]::Out.Flush(); exit 21 + } + 'EXACT_CHILD_STDOUT_122' { + [Console]::Out.Write( + "$startup`r`n" + + ('PROPR_WINDOWS_INSTALLED_SMOKE:WORKFLOW_CLEANUP:TERMINAL:' + + "RESULT:FAILED:STATUS:CHILD_STDOUT:EXIT_CODE:122`r`n")) + [Console]::Out.Flush(); exit 122 + } + 'EXACT_CHILD_STDERR_123' { + [Console]::Out.Write( + "$startup`r`n" + + ('PROPR_WINDOWS_INSTALLED_SMOKE:WORKFLOW_CLEANUP:TERMINAL:' + + "RESULT:FAILED:STATUS:CHILD_STDERR:EXIT_CODE:123`r`n")) + [Console]::Out.Flush(); exit 123 + } + 'EXACT_TIMEOUT_124' { + [Console]::Out.Write( + "$startup`r`n" + + ('PROPR_WINDOWS_INSTALLED_SMOKE:WORKFLOW_CLEANUP:TERMINAL:' + + "RESULT:TIMED_OUT:STATUS:TIMEOUT:EXIT_CODE:124`r`n")) + [Console]::Out.Flush(); exit 124 + } + 'EXACT_CONTROLLER_FAILURE_125' { + [Console]::Out.Write("$startup`r`n$terminal`r`n") + [Console]::Out.Flush(); exit 125 + } + 'EXACT_FINALIZATION_FAILURE_125' { + [Console]::Out.Write( + "$startup`r`n" + + ('PROPR_WINDOWS_INSTALLED_SMOKE:WORKFLOW_CLEANUP:TERMINAL:' + + "RESULT:FAILED:STATUS:PROCESS_FINALIZATION_FAILURE:EXIT_CODE:125`r`n")) + [Console]::Out.Flush(); exit 125 + } 'STREAM_DRAIN_RACE' { [Console]::Out.Write("$startup`r`n$terminal`r`n"); [Console]::Out.Flush() $child = [Diagnostics.ProcessStartInfo]::new() diff --git a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 index aa4985c9c..7cb84b962 100644 --- a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 +++ b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 @@ -155,13 +155,8 @@ public sealed class ProPRWorkflowCleanupInvocationJob : IDisposable return information.ActiveProcesses; } - public bool HasNoActiveProcesses() { return ReadActiveProcessCount() == 0; } - - public bool TerminateAndWait(uint exitCode, int timeoutMilliseconds) + public bool WaitForNoActiveProcesses(int timeoutMilliseconds) { - if (!TerminateJobObject(handle, exitCode)) - throw new Win32Exception(Marshal.GetLastWin32Error(), - "invocation termination failed"); var watch = Stopwatch.StartNew(); do { @@ -172,6 +167,14 @@ public sealed class ProPRWorkflowCleanupInvocationJob : IDisposable return ReadActiveProcessCount() == 0; } + public bool TerminateAndWait(uint exitCode, int timeoutMilliseconds) + { + if (!TerminateJobObject(handle, exitCode)) + throw new Win32Exception(Marshal.GetLastWin32Error(), + "invocation termination failed"); + return WaitForNoActiveProcesses(timeoutMilliseconds); + } + public void Dispose() { if (handle != null) handle.Dispose(); } } @@ -213,6 +216,7 @@ public sealed class ProPRWorkflowCleanupProtocolCapture : IDisposable private StreamReader errorReader; private Task outputTask; private Task errorTask; + private readonly ManualResetEventSlim startupObserved = new ManualResetEventSlim(false); private bool startupSeen; private bool terminalSeen; private bool defect; @@ -245,6 +249,23 @@ public sealed class ProPRWorkflowCleanupProtocolCapture : IDisposable return ControllerFailure.IsMatch(value); } + private static bool IsStatusExitPairValid(string status, int exitCode) + { + switch (status) + { + case "EMPTY_OR_CLEANED": return exitCode == 0; + case "MANIFEST_VALIDATION_FAILURE": return exitCode == 20; + case "OWNED_RESOURCE_CLEANUP_FAILURE": return exitCode == 21; + case "CHILD_STDOUT": + case "CHILD_STDOUT_LIMIT": return exitCode == 122; + case "CHILD_STDERR": + case "CHILD_STDERR_LIMIT": return exitCode == 123; + case "TIMEOUT": return exitCode == 124; + default: + return exitCode == 125 && IsFixedStatus(status); + } + } + private void SetDefect(string category) { if (!defect) @@ -283,6 +304,7 @@ public sealed class ProPRWorkflowCleanupProtocolCapture : IDisposable StartupProcessExit = processExit; StartupLine = startupLine; } + startupObserved.Set(); return; } @@ -312,7 +334,8 @@ public sealed class ProPRWorkflowCleanupProtocolCapture : IDisposable (String.Equals(Result, "FAILED", StringComparison.Ordinal) && (String.Equals(ControllerStatus, "EMPTY_OR_CLEANED", StringComparison.Ordinal) || String.Equals(ControllerStatus, "TIMEOUT", StringComparison.Ordinal) || - ReportedExitCode == 0))) + ReportedExitCode == 0)) || + !IsStatusExitPairValid(ControllerStatus, ReportedExitCode)) SetDefect("MALFORMED"); return; } @@ -385,10 +408,22 @@ public sealed class ProPRWorkflowCleanupProtocolCapture : IDisposable return true; } + public Task SignalCancellationAfterStartup( + EventWaitHandle cancellation, int timeoutMilliseconds) + { + if (cancellation == null) throw new ArgumentNullException("cancellation"); + return Task.Factory.StartNew(() => + { + if (!startupObserved.Wait(timeoutMilliseconds)) return false; + return cancellation.Set(); + }, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default); + } + public bool IsProtocolValid(int processExitCode) { return !defect && startupSeen && terminalSeen && LineCount == 2 && StandardErrorCount == 0 && processExitCode == ReportedExitCode && + IsStatusExitPairValid(ControllerStatus, ReportedExitCode) && (String.Equals(StartupClass, "READY", StringComparison.Ordinal) || StartupProcessExit == processExitCode); } @@ -397,6 +432,7 @@ public sealed class ProPRWorkflowCleanupProtocolCapture : IDisposable { try { if (outputReader != null) outputReader.Dispose(); } catch { } try { if (errorReader != null) errorReader.Dispose(); } catch { } + startupObserved.Dispose(); } } '@ @@ -1099,6 +1135,7 @@ function Invoke-WorkflowCleanupController( [string]$StartupFailureClass = '', [object]$InvocationTimeoutMilliseconds = 40000, [Threading.WaitHandle]$CancellationWaitHandle = $null, + [bool]$SignalCancellationAfterStartup = $false, [bool]$InjectTreeTerminationFailure = $false, [string]$ProtocolFixture = '' ) { @@ -1145,6 +1182,7 @@ function Invoke-WorkflowCleanupController( $process.StartInfo = $startInfo $job = $null $capture = $null + $cancellationSignalTask = $null $processStarted = $false $lifecycleCategory = 'PROCESS_CREATION_FAILURE' $treeTerminationCategory = 'NOT_REQUIRED' @@ -1161,6 +1199,13 @@ function Invoke-WorkflowCleanupController( } $capture = [ProPRWorkflowCleanupProtocolCapture]::new() $capture.Start($process) + if ($SignalCancellationAfterStartup) { + if ($CancellationWaitHandle -isnot [Threading.EventWaitHandle]) { + throw 'workflow cleanup after-startup cancellation event is invalid' + } + $cancellationSignalTask = $capture.SignalCancellationAfterStartup( + [Threading.EventWaitHandle]$CancellationWaitHandle, $invocationTimeout) + } $watch = [Diagnostics.Stopwatch]::StartNew() $cancelled = $false while (!$process.HasExited -and $watch.ElapsedMilliseconds -lt $invocationTimeout) { @@ -1185,7 +1230,7 @@ function Invoke-WorkflowCleanupController( [void]$process.WaitForExit(3000) } else { $lifecycleCategory = 'EXITED' - if (!$job.HasNoActiveProcesses()) { + if (!$job.WaitForNoActiveProcesses(3000)) { $lifecycleCategory = 'ACTIVE_TREE_AFTER_EXIT' $treeTerminationCategory = 'FAILED' if (!$InjectTreeTerminationFailure) { @@ -1254,6 +1299,9 @@ function Invoke-WorkflowCleanupController( try { if ($null -ne $job) { [void]$job.TerminateAndWait(125, 3000) } } catch {} try { if (!$process.HasExited) { $process.Kill($true) } } catch {} } + if ($null -ne $cancellationSignalTask) { + try { [void]$cancellationSignalTask.Wait(3000) } catch {} + } if ($null -ne $capture) { $capture.Dispose() } if ($null -ne $job) { $job.Dispose() } $process.Dispose() @@ -1382,6 +1430,8 @@ function Test-WorkflowCleanupProtocolStateMachine { [PSCustomObject]@{ Fixture='PARTIAL_RECORD'; Observed='PARTIAL'; Lifecycle='EXITED' }, [PSCustomObject]@{ Fixture='STDERR_RECORD'; Observed='TERMINAL'; Lifecycle='EXITED'; Stderr=1 }, [PSCustomObject]@{ Fixture='INVALID_STARTUP_METADATA'; Observed='MALFORMED'; Lifecycle='EXITED' }, + [PSCustomObject]@{ Fixture='MISMATCHED_MANIFEST_EXIT_125'; Observed='MALFORMED'; Lifecycle='EXITED' }, + [PSCustomObject]@{ Fixture='MISMATCHED_CHILD_STDOUT_EXIT_21'; Observed='MALFORMED'; Lifecycle='EXITED' }, [PSCustomObject]@{ Fixture='TIMEOUT_BEFORE_STARTUP'; Observed='NONE'; Lifecycle='TIMEOUT_BEFORE_STARTUP' }, [PSCustomObject]@{ Fixture='TIMEOUT_AFTER_STARTUP'; Observed='STARTUP'; Lifecycle='TIMEOUT_AFTER_STARTUP' }, [PSCustomObject]@{ Fixture='STREAM_DRAIN_RACE'; Observed='TERMINAL'; Lifecycle='ACTIVE_TREE_AFTER_EXIT' } @@ -1412,9 +1462,32 @@ function Test-WorkflowCleanupProtocolStateMachine { "$($case.Fixture) diagnostic disclosed a path" } - foreach ($cancellationAfterStartup in @($false)) { + foreach ($exactPair in @( + [PSCustomObject]@{ Fixture='EXACT_MANIFEST_20'; Status='MANIFEST_VALIDATION_FAILURE'; ExitCode=20; Result='FAILED' }, + [PSCustomObject]@{ Fixture='EXACT_OWNED_RESOURCE_21'; Status='OWNED_RESOURCE_CLEANUP_FAILURE'; ExitCode=21; Result='FAILED' }, + [PSCustomObject]@{ Fixture='EXACT_CHILD_STDOUT_122'; Status='CHILD_STDOUT'; ExitCode=122; Result='FAILED' }, + [PSCustomObject]@{ Fixture='EXACT_CHILD_STDERR_123'; Status='CHILD_STDERR'; ExitCode=123; Result='FAILED' }, + [PSCustomObject]@{ Fixture='EXACT_TIMEOUT_124'; Status='TIMEOUT'; ExitCode=124; Result='TIMED_OUT' }, + [PSCustomObject]@{ Fixture='EXACT_CONTROLLER_FAILURE_125'; Status='CONTROLLER_FAILURE'; ExitCode=125; Result='FAILED' }, + [PSCustomObject]@{ Fixture='EXACT_FINALIZATION_FAILURE_125'; Status='PROCESS_FINALIZATION_FAILURE'; ExitCode=125; Result='FAILED' } + )) { + $result = Invoke-WorkflowCleanupController ` + -InvocationIdentifier 'PROTOCOL_REGRESSION' ` + -ManifestPath $dummyInstaller ` + -RunId ([Guid]::NewGuid().ToString('N')) ` + -FixtureRoot $testRoot ` + -InvocationTimeoutMilliseconds 1000 ` + -ProtocolFixture $exactPair.Fixture + Assert-True ($result.ExitCode -eq $exactPair.ExitCode -and + $result.ReportedExitCode -eq $exactPair.ExitCode -and + $result.Result -ceq $exactPair.Result -and + $result.ControllerStatus -ceq $exactPair.Status) ` + "$($exactPair.Fixture) did not preserve its exact status/exit pair" + } + + foreach ($cancellationAfterStartup in @($false, $true)) { $cancel = [Threading.EventWaitHandle]::new( - $true, [Threading.EventResetMode]::ManualReset) + !$cancellationAfterStartup, [Threading.EventResetMode]::ManualReset) try { $diagnostic = '' $fixture = if ($cancellationAfterStartup) { @@ -1426,12 +1499,19 @@ function Test-WorkflowCleanupProtocolStateMachine { -ManifestPath $dummyInstaller ` -RunId ([Guid]::NewGuid().ToString('N')) ` -FixtureRoot $testRoot ` - -InvocationTimeoutMilliseconds 1000 ` + -InvocationTimeoutMilliseconds 5000 ` -CancellationWaitHandle $cancel ` + -SignalCancellationAfterStartup $cancellationAfterStartup ` -ProtocolFixture $fixture) } catch { $diagnostic = $_.Exception.Message } - Assert-True ($diagnostic -cmatch - ':LIFECYCLE:CANCELLED_(?:BEFORE|AFTER)_STARTUP:') ` + $expectedLifecycle = if ($cancellationAfterStartup) { + 'CANCELLED_AFTER_STARTUP' + } else { 'CANCELLED_BEFORE_STARTUP' } + Assert-Contains $diagnostic ` + ('PROPR_WORKFLOW_CLEANUP_FIXTURE:PROTOCOL_MISMATCH:' + + 'INVOCATION:PROTOCOL_REGRESSION:') ` + 'workflow cleanup cancellation lost its exact invocation attribution' + Assert-Contains $diagnostic ":LIFECYCLE:$expectedLifecycle:" ` 'workflow cleanup cancellation lost its bounded lifecycle category' Assert-Contains $diagnostic ':TREE_TERMINATION:COMPLETE:' ` 'workflow cleanup cancellation did not terminate its complete owned tree' diff --git a/apps/desktop/src/release-workflow.test.ts b/apps/desktop/src/release-workflow.test.ts index a71068b33..ff7b2b863 100644 --- a/apps/desktop/src/release-workflow.test.ts +++ b/apps/desktop/src/release-workflow.test.ts @@ -1012,7 +1012,21 @@ describe('desktop trusted release workflow', () => { assert.match(installedWindowsAppSupervisorBehaviorTest, /\[ValidateSet\([\s\S]*'STARTUP_PROTOCOL'[\s\S]*'PROTOCOL_REGRESSION'/); assert.match(installedWindowsAppSupervisorBehaviorTest, /ONE_LINE_STARTUP[\s\S]*DUPLICATE_STARTUP[\s\S]*REORDERED_RECORDS/); assert.match(installedWindowsAppSupervisorBehaviorTest, /TIMEOUT_BEFORE_STARTUP[\s\S]*TIMEOUT_AFTER_STARTUP[\s\S]*STREAM_DRAIN_RACE/); - assert.match(installedWindowsAppSupervisorBehaviorTest, /CANCELLED_(?:BEFORE|AFTER)_STARTUP/); + assert.match(installedWindowsAppSupervisorBehaviorTest, /foreach \(\$cancellationAfterStartup in @\(\$false, \$true\)\)/); + assert.match(installedWindowsAppSupervisorBehaviorTest, /CANCELLED_BEFORE_STARTUP[\s\S]*CANCELLED_AFTER_STARTUP/); + assert.match(installedWindowsAppSupervisorBehaviorTest, /SignalCancellationAfterStartup/); + assert.match(installedWindowsAppSupervisorBehaviorTest, /WaitForNoActiveProcesses\(3000\)/); + assert.match( + installedWindowsAppWorkflowCleanup, + /WaitForNoActiveProcesses\(\s*\$TerminationTimeoutMilliseconds\)/, + ); + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /MANIFEST_VALIDATION_FAILURE[\s\S]*return exitCode == 20[\s\S]*CHILD_STDOUT[\s\S]*return exitCode == 122/, + ); + assert.match(installedWindowsAppSupervisorBehaviorTest, /MISMATCHED_MANIFEST_EXIT_125/); + assert.match(installedWindowsAppSupervisorBehaviorTest, /MISMATCHED_CHILD_STDOUT_EXIT_21/); + assert.match(installedWindowsAppSupervisorBehaviorTest, /EXACT_MANIFEST_20[\s\S]*EXACT_FINALIZATION_FAILURE_125/); assert.match(installedWindowsAppSupervisorBehaviorTest, /TREE_TERMINATION:FAILED/); assert.match( installedWindowsAppSupervisorBehaviorTest, From 331085db6baf3ffca345eaaa6261bb92e85f5f09 Mon Sep 17 00:00:00 2001 From: "propr-dev[bot]" <1316198+propr-dev[bot]@users.noreply.github.com> Date: Tue, 1 Sep 2026 22:39:17 +0000 Subject: [PATCH 03/33] feat(ai): Corrected only the parser failure in [test-installed-windows-app-supervisor.ps1](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-01T22-35-57/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1:1514): MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Corrected only the parser failure in [test-installed-windows-app-supervisor.ps1](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-01T22-35-57/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1:1514): ```powershell ":LIFECYCLE:${expectedLifecycle}:" ``` Validation: - Official PowerShell parser: passed. - Newly added block scan for similar unbraced interpolation: passed. - `git diff --check`: passed. - Diff: one insertion, one deletion; no behavior or assertions changed. - Native Windows x64/ARM64 focused tests could not run on this Linux x64 host; they require the post-commit CI runners. Per instruction, I did not commit. Current HEAD remains `a72c0814a1cf4fe6db1ffd59a7d9d309c7b8fdf7`; the exact new head will be assigned by the system’s automatic commit. PR: #2057 Comment by: @integry (ID: 5501350657) Model: gpt-5.6-sol --- apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 index 7cb84b962..2ef747a61 100644 --- a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 +++ b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 @@ -1511,7 +1511,7 @@ function Test-WorkflowCleanupProtocolStateMachine { ('PROPR_WORKFLOW_CLEANUP_FIXTURE:PROTOCOL_MISMATCH:' + 'INVOCATION:PROTOCOL_REGRESSION:') ` 'workflow cleanup cancellation lost its exact invocation attribution' - Assert-Contains $diagnostic ":LIFECYCLE:$expectedLifecycle:" ` + Assert-Contains $diagnostic ":LIFECYCLE:${expectedLifecycle}:" ` 'workflow cleanup cancellation lost its bounded lifecycle category' Assert-Contains $diagnostic ':TREE_TERMINATION:COMPLETE:' ` 'workflow cleanup cancellation did not terminate its complete owned tree' From 2d8532d415b64c9cee4cd0822b9815ddc4f1f9db Mon Sep 17 00:00:00 2001 From: "propr-dev[bot]" <1316198+propr-dev[bot]@users.noreply.github.com> Date: Tue, 1 Sep 2026 22:56:00 +0000 Subject: [PATCH 04/33] feat(ai): Implemented the ultrafix in [test-installed-windows-app-supervisor.ps1](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-01T22-44-15/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1:1187). MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implemented the ultrafix in [test-installed-windows-app-supervisor.ps1](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-01T22-44-15/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1:1187). The pre-fix failing path resolves to: ```text PROPR_WORKFLOW_CLEANUP_FIXTURE:PROTOCOL_MISMATCH:INVOCATION:PROTOCOL_REGRESSION:OBSERVED:NONE:LINE_COUNT:0:STDERR_COUNT:0:PROCESS_EXIT:125:LIFECYCLE:TIMEOUT_BEFORE_STARTUP:TREE_TERMINATION:COMPLETE:STARTUP_CLASS:NONE:LINE_NUMBER:0 ``` Root cause: immediate fixtures inherited a 250 ms deadline, racing native `pwsh` startup. Additionally, clean-exit classification could proceed to Job Object accounting before bounded stream drain had stabilized capture state. Changes: - Immediate protocol fixtures now use the existing bounded 1,000 ms window; deliberate timeout/tree-race fixtures remain at 250 ms. - Clean exits drain streams before post-exit Job Object quiescence and snapshot the fixed exit first. - `ONE_LINE_STARTUP` asserts the complete required tuple. - State-machine mismatch assertions append the diagnostic only after proving it is path-free and exactly reproducible through `Get-WorkflowCleanupProtocolMismatchDiagnostic`. Validation: - PowerShell parser: passed. - One-line capture: `STARTUP|1|125|READY|1`. - Desktop suite: 177 passed, 6 skipped. - Release workflow: 23 passed. - `git diff --check`: passed. Native Windows x64/ARM64 execution cannot be launched from this Linux worktree without committing/pushing, which the instructions prohibit. The uncommitted base remains `331085db6baf3ffca345eaaa6261bb92e85f5f09`; the exact new head will be produced by the system’s automatic commit. PR: #2057 Comment by: @integry (ID: 5501424376) Model: gpt-5.6-sol --- .../test-installed-windows-app-supervisor.ps1 | 126 ++++++++++++++++-- 1 file changed, 112 insertions(+), 14 deletions(-) diff --git a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 index 2ef747a61..f41447aed 100644 --- a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 +++ b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 @@ -1187,6 +1187,8 @@ function Invoke-WorkflowCleanupController( $lifecycleCategory = 'PROCESS_CREATION_FAILURE' $treeTerminationCategory = 'NOT_REQUIRED' $validatedProcessExit = 'INVALID' + $drainComplete = $false + $drainAttempted = $false try { $job = [ProPRWorkflowCleanupInvocationJob]::new() if (!$process.Start()) { throw 'workflow cleanup fixture did not start' } @@ -1230,6 +1232,17 @@ function Invoke-WorkflowCleanupController( [void]$process.WaitForExit(3000) } else { $lifecycleCategory = 'EXITED' + $drainAttempted = $true + $drainComplete = $capture.Finish(3000) + if (!$drainComplete) { + $lifecycleCategory = if ($capture.DrainFailed) { + 'DRAIN_FAILURE' + } else { 'DRAIN_TIMEOUT' } + } + if ($process.ExitCode -in @(0,20,21,122,123,124,125)) { + $validatedProcessExit = + $process.ExitCode.ToString([Globalization.CultureInfo]::InvariantCulture) + } if (!$job.WaitForNoActiveProcesses(3000)) { $lifecycleCategory = 'ACTIVE_TREE_AFTER_EXIT' $treeTerminationCategory = 'FAILED' @@ -1240,9 +1253,15 @@ function Invoke-WorkflowCleanupController( } } catch {} } + if (!$drainComplete) { + $drainComplete = $capture.Finish(0) + } } } - $drainComplete = $capture.Finish(3000) + if (!$drainAttempted) { + $drainAttempted = $true + $drainComplete = $capture.Finish(3000) + } if (!$drainComplete -and $lifecycleCategory -ceq 'EXITED') { $lifecycleCategory = if ($capture.DrainFailed) { 'DRAIN_FAILURE' } else { 'DRAIN_TIMEOUT' } } @@ -1398,6 +1417,45 @@ function Test-WorkflowCleanupStartupProtocol { [Console]::Out.Flush() } +function Get-WorkflowCleanupStateMachineAssertionMessage( + [string]$Message, + [string]$Diagnostic, + [string]$Fixture +) { + Assert-NotContains $Diagnostic $dummyInstaller ` + "$Fixture diagnostic disclosed the dummy installer" + Assert-NotContains $Diagnostic $testRoot ` + "$Fixture diagnostic disclosed a path" + $fixedMatch = [regex]::Match($Diagnostic, ( + '^PROPR_WORKFLOW_CLEANUP_FIXTURE:PROTOCOL_MISMATCH:' + + 'INVOCATION:PROTOCOL_REGRESSION:OBSERVED:' + + '(?NONE|STARTUP|TERMINAL|MALFORMED|PARTIAL|DUPLICATE|REORDERED|EXTRA|OVERSIZED):' + + 'LINE_COUNT:(?0|1|2|3\+):STDERR_COUNT:(?[0-9]+):' + + 'PROCESS_EXIT:(?0|20|21|122|123|124|125|INVALID):' + + 'LIFECYCLE:(?EXITED|PROCESS_CREATION_FAILURE|OWNERSHIP_FAILURE|' + + 'TIMEOUT_BEFORE_STARTUP|TIMEOUT_AFTER_STARTUP|' + + 'CANCELLED_BEFORE_STARTUP|CANCELLED_AFTER_STARTUP|' + + 'ACTIVE_TREE_AFTER_EXIT|DRAIN_TIMEOUT|DRAIN_FAILURE):' + + 'TREE_TERMINATION:(?NOT_REQUIRED|COMPLETE|FAILED):' + + 'STARTUP_CLASS:(?NONE|READY|PARSER|PARAMETER_BINDING|TYPE_LOAD|OTHER):' + + 'LINE_NUMBER:(?[0-3])$' + ), [Text.RegularExpressions.RegexOptions]::CultureInvariant) + Assert-True $fixedMatch.Success ` + "$Fixture did not emit the fixed bounded diagnostic" + $lineCount = if ($fixedMatch.Groups['LineCount'].Value -ceq '3+') { + 3 + } else { [int]$fixedMatch.Groups['LineCount'].Value } + $expectedDiagnostic = Get-WorkflowCleanupProtocolMismatchDiagnostic ` + 'PROTOCOL_REGRESSION' $fixedMatch.Groups['Observed'].Value $lineCount ` + ([int]$fixedMatch.Groups['StderrCount'].Value) ` + $fixedMatch.Groups['ProcessExit'].Value $fixedMatch.Groups['Lifecycle'].Value ` + $fixedMatch.Groups['TreeTermination'].Value $fixedMatch.Groups['StartupClass'].Value ` + ([int]$fixedMatch.Groups['LineNumber'].Value) + Assert-True ($Diagnostic -ceq $expectedDiagnostic) ` + "$Fixture diagnostic did not equal the fixed bounded value" + return "$Message`: $Diagnostic" +} + function Test-WorkflowCleanupProtocolStateMachine { $scriptText = Get-Content -LiteralPath $PSCommandPath -Raw -Encoding UTF8 $expectedInvocations = @( @@ -1420,7 +1478,10 @@ function Test-WorkflowCleanupProtocolStateMachine { } $cases = @( - [PSCustomObject]@{ Fixture='ONE_LINE_STARTUP'; Observed='STARTUP'; Lifecycle='EXITED' }, + [PSCustomObject]@{ + Fixture='ONE_LINE_STARTUP'; Observed='STARTUP'; LineCount=1; ProcessExit=125 + Lifecycle='EXITED'; TreeTermination='NOT_REQUIRED'; StartupClass='READY'; LineNumber=1 + }, [PSCustomObject]@{ Fixture='MISSING_TERMINAL'; Observed='STARTUP'; Lifecycle='EXITED' }, [PSCustomObject]@{ Fixture='DUPLICATE_STARTUP'; Observed='DUPLICATE'; Lifecycle='EXITED' }, [PSCustomObject]@{ Fixture='EXTRA_RECORD'; Observed='EXTRA'; Lifecycle='EXITED' }, @@ -1438,28 +1499,54 @@ function Test-WorkflowCleanupProtocolStateMachine { ) foreach ($case in $cases) { $diagnostic = '' + $caseInvocationTimeout = if ($case.Fixture -in @( + 'TIMEOUT_BEFORE_STARTUP','TIMEOUT_AFTER_STARTUP','STREAM_DRAIN_RACE' + )) { 250 } else { 1000 } try { [void](Invoke-WorkflowCleanupController ` -InvocationIdentifier 'PROTOCOL_REGRESSION' ` -ManifestPath $dummyInstaller ` -RunId ([Guid]::NewGuid().ToString('N')) ` -FixtureRoot $testRoot ` - -InvocationTimeoutMilliseconds 250 ` + -InvocationTimeoutMilliseconds $caseInvocationTimeout ` -ProtocolFixture $case.Fixture) } catch { $diagnostic = $_.Exception.Message } + $fixture = [string]$case.Fixture Assert-Contains $diagnostic ` 'PROPR_WORKFLOW_CLEANUP_FIXTURE:PROTOCOL_MISMATCH:INVOCATION:PROTOCOL_REGRESSION:' ` - "$($case.Fixture) did not emit an invocation-attributed fixed diagnostic" + (Get-WorkflowCleanupStateMachineAssertionMessage ` + "$fixture did not emit an invocation-attributed fixed diagnostic" ` + $diagnostic $fixture) Assert-Contains $diagnostic ":OBSERVED:$($case.Observed):" ` - "$($case.Fixture) did not retain its bounded observed-line category" + (Get-WorkflowCleanupStateMachineAssertionMessage ` + "$fixture did not retain its bounded observed-line category" ` + $diagnostic $fixture) Assert-Contains $diagnostic ":LIFECYCLE:$($case.Lifecycle):" ` - "$($case.Fixture) did not retain its primary lifecycle category" + (Get-WorkflowCleanupStateMachineAssertionMessage ` + "$fixture did not retain its primary lifecycle category" ` + $diagnostic $fixture) if ($case.PSObject.Properties['Stderr']) { Assert-Contains $diagnostic ":STDERR_COUNT:$($case.Stderr):" ` - "$($case.Fixture) did not retain its bounded stderr count" + (Get-WorkflowCleanupStateMachineAssertionMessage ` + "$fixture did not retain its bounded stderr count" $diagnostic $fixture) + } + if ($case.PSObject.Properties['LineCount']) { + Assert-Contains $diagnostic ":LINE_COUNT:$($case.LineCount):" ` + (Get-WorkflowCleanupStateMachineAssertionMessage ` + "$fixture did not retain its bounded line count" $diagnostic $fixture) + Assert-Contains $diagnostic ":PROCESS_EXIT:$($case.ProcessExit):" ` + (Get-WorkflowCleanupStateMachineAssertionMessage ` + "$fixture did not retain its fixed process exit" $diagnostic $fixture) + Assert-Contains $diagnostic ":TREE_TERMINATION:$($case.TreeTermination):" ` + (Get-WorkflowCleanupStateMachineAssertionMessage ` + "$fixture did not retain its tree outcome" $diagnostic $fixture) + Assert-Contains $diagnostic ":STARTUP_CLASS:$($case.StartupClass):" ` + (Get-WorkflowCleanupStateMachineAssertionMessage ` + "$fixture did not retain its startup class" $diagnostic $fixture) + Assert-Contains $diagnostic ":LINE_NUMBER:$($case.LineNumber)" ` + (Get-WorkflowCleanupStateMachineAssertionMessage ` + "$fixture did not retain its startup line number" $diagnostic $fixture) } - Assert-NotContains $diagnostic $dummyInstaller ` - "$($case.Fixture) diagnostic disclosed a path" } foreach ($exactPair in @( @@ -1507,14 +1594,21 @@ function Test-WorkflowCleanupProtocolStateMachine { $expectedLifecycle = if ($cancellationAfterStartup) { 'CANCELLED_AFTER_STARTUP' } else { 'CANCELLED_BEFORE_STARTUP' } + $fixture = "CANCELLATION_$expectedLifecycle" Assert-Contains $diagnostic ` ('PROPR_WORKFLOW_CLEANUP_FIXTURE:PROTOCOL_MISMATCH:' + 'INVOCATION:PROTOCOL_REGRESSION:') ` - 'workflow cleanup cancellation lost its exact invocation attribution' + (Get-WorkflowCleanupStateMachineAssertionMessage ` + 'workflow cleanup cancellation lost its exact invocation attribution' ` + $diagnostic $fixture) Assert-Contains $diagnostic ":LIFECYCLE:${expectedLifecycle}:" ` - 'workflow cleanup cancellation lost its bounded lifecycle category' + (Get-WorkflowCleanupStateMachineAssertionMessage ` + 'workflow cleanup cancellation lost its bounded lifecycle category' ` + $diagnostic $fixture) Assert-Contains $diagnostic ':TREE_TERMINATION:COMPLETE:' ` - 'workflow cleanup cancellation did not terminate its complete owned tree' + (Get-WorkflowCleanupStateMachineAssertionMessage ` + 'workflow cleanup cancellation did not terminate its complete owned tree' ` + $diagnostic $fixture) } finally { $cancel.Dispose() } } @@ -1530,9 +1624,13 @@ function Test-WorkflowCleanupProtocolStateMachine { -ProtocolFixture 'TIMEOUT_AFTER_STARTUP') } catch { $treeFailureDiagnostic = $_.Exception.Message } Assert-Contains $treeFailureDiagnostic ':LIFECYCLE:TIMEOUT_AFTER_STARTUP:' ` - 'tree-termination failure replaced the primary timeout outcome' + (Get-WorkflowCleanupStateMachineAssertionMessage ` + 'tree-termination failure replaced the primary timeout outcome' ` + $treeFailureDiagnostic 'TREE_TERMINATION_FAILURE') Assert-Contains $treeFailureDiagnostic ':TREE_TERMINATION:FAILED:' ` - 'tree-termination failure was not represented by its fixed category' + (Get-WorkflowCleanupStateMachineAssertionMessage ` + 'tree-termination failure was not represented by its fixed category' ` + $treeFailureDiagnostic 'TREE_TERMINATION_FAILURE') Write-Host 'PROPR_WINDOWS_SUPERVISOR_CONTROLLER_STATE_MACHINE:BOUNDED:PASSED' [Console]::Out.Flush() From 25e00ef82b4a052d60f08e5e2958c223cad4abca Mon Sep 17 00:00:00 2001 From: "propr-dev[bot]" <1316198+propr-dev[bot]@users.noreply.github.com> Date: Tue, 1 Sep 2026 23:03:37 +0000 Subject: [PATCH 05/33] feat(ai): Implemented the deterministic timeout-fixture correction in [test-installed-windows-app-supervisor.ps1](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-01T22-58-22/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1:422): MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implemented the deterministic timeout-fixture correction in [test-installed-windows-app-supervisor.ps1](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-01T22-58-22/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1:422): - Waits up to five seconds for the capture’s complete startup signal before starting the 250 ms after-startup countdown. - Explicitly asserts `OBSERVED:STARTUP`, `LINE_COUNT:1`, `STARTUP_CLASS:READY`, and `LINE_NUMBER:1`. - Applies the same startup synchronization to the tree-termination-failure case. - Leaves production semantics, cancellation matrix, Job Object handling, and other 250 ms fixtures unchanged. Validation: - Desktop suite: 177 passed, 6 platform skips. - Release workflow: 23 passed. - `git diff --check`: passed. Native x64/ARM64 final markers could not be run because this environment is Linux x64 without PowerShell. Per instruction, no commit was created; current HEAD remains `2d8532d415b64c9cee4cd0822b9815ddc4f1f9db`. The automated commit/Windows CI must provide the new head and native final markers. PR: #2057 Comment by: @integry (ID: 5501554980) Model: gpt-5.6-sol --- .../test-installed-windows-app-supervisor.ps1 | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 index f41447aed..acfac7ae4 100644 --- a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 +++ b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 @@ -419,6 +419,11 @@ public sealed class ProPRWorkflowCleanupProtocolCapture : IDisposable }, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default); } + public bool WaitForStartup(int timeoutMilliseconds) + { + return startupObserved.Wait(timeoutMilliseconds); + } + public bool IsProtocolValid(int processExitCode) { return !defect && startupSeen && terminalSeen && LineCount == 2 && @@ -1136,6 +1141,7 @@ function Invoke-WorkflowCleanupController( [object]$InvocationTimeoutMilliseconds = 40000, [Threading.WaitHandle]$CancellationWaitHandle = $null, [bool]$SignalCancellationAfterStartup = $false, + [bool]$BeginTimeoutAfterStartup = $false, [bool]$InjectTreeTerminationFailure = $false, [string]$ProtocolFixture = '' ) { @@ -1208,9 +1214,15 @@ function Invoke-WorkflowCleanupController( $cancellationSignalTask = $capture.SignalCancellationAfterStartup( [Threading.EventWaitHandle]$CancellationWaitHandle, $invocationTimeout) } + $startupWindowTimedOut = $false + if ($BeginTimeoutAfterStartup) { + # The capture signals only after parsing one complete startup record. + $startupWindowTimedOut = !$capture.WaitForStartup(5000) + } $watch = [Diagnostics.Stopwatch]::StartNew() $cancelled = $false - while (!$process.HasExited -and $watch.ElapsedMilliseconds -lt $invocationTimeout) { + while (!$startupWindowTimedOut -and !$process.HasExited -and + $watch.ElapsedMilliseconds -lt $invocationTimeout) { if ($null -ne $CancellationWaitHandle -and $CancellationWaitHandle.WaitOne(0)) { $cancelled = $true break @@ -1494,11 +1506,17 @@ function Test-WorkflowCleanupProtocolStateMachine { [PSCustomObject]@{ Fixture='MISMATCHED_MANIFEST_EXIT_125'; Observed='MALFORMED'; Lifecycle='EXITED' }, [PSCustomObject]@{ Fixture='MISMATCHED_CHILD_STDOUT_EXIT_21'; Observed='MALFORMED'; Lifecycle='EXITED' }, [PSCustomObject]@{ Fixture='TIMEOUT_BEFORE_STARTUP'; Observed='NONE'; Lifecycle='TIMEOUT_BEFORE_STARTUP' }, - [PSCustomObject]@{ Fixture='TIMEOUT_AFTER_STARTUP'; Observed='STARTUP'; Lifecycle='TIMEOUT_AFTER_STARTUP' }, + [PSCustomObject]@{ + Fixture='TIMEOUT_AFTER_STARTUP'; Observed='STARTUP'; LineCount=1; ProcessExit=125 + Lifecycle='TIMEOUT_AFTER_STARTUP'; TreeTermination='COMPLETE' + StartupClass='READY'; LineNumber=1; BeginTimeoutAfterStartup=$true + }, [PSCustomObject]@{ Fixture='STREAM_DRAIN_RACE'; Observed='TERMINAL'; Lifecycle='ACTIVE_TREE_AFTER_EXIT' } ) foreach ($case in $cases) { $diagnostic = '' + # TIMEOUT_AFTER_STARTUP gets its separate five-second startup phase above; + # its 250 ms countdown begins only after the complete record is captured. $caseInvocationTimeout = if ($case.Fixture -in @( 'TIMEOUT_BEFORE_STARTUP','TIMEOUT_AFTER_STARTUP','STREAM_DRAIN_RACE' )) { 250 } else { 1000 } @@ -1509,6 +1527,9 @@ function Test-WorkflowCleanupProtocolStateMachine { -RunId ([Guid]::NewGuid().ToString('N')) ` -FixtureRoot $testRoot ` -InvocationTimeoutMilliseconds $caseInvocationTimeout ` + -BeginTimeoutAfterStartup ( + $case.PSObject.Properties['BeginTimeoutAfterStartup'] -and + $case.BeginTimeoutAfterStartup) ` -ProtocolFixture $case.Fixture) } catch { $diagnostic = $_.Exception.Message } $fixture = [string]$case.Fixture @@ -1620,6 +1641,7 @@ function Test-WorkflowCleanupProtocolStateMachine { -RunId ([Guid]::NewGuid().ToString('N')) ` -FixtureRoot $testRoot ` -InvocationTimeoutMilliseconds 250 ` + -BeginTimeoutAfterStartup $true ` -InjectTreeTerminationFailure $true ` -ProtocolFixture 'TIMEOUT_AFTER_STARTUP') } catch { $treeFailureDiagnostic = $_.Exception.Message } @@ -1631,6 +1653,14 @@ function Test-WorkflowCleanupProtocolStateMachine { (Get-WorkflowCleanupStateMachineAssertionMessage ` 'tree-termination failure was not represented by its fixed category' ` $treeFailureDiagnostic 'TREE_TERMINATION_FAILURE') + Assert-Contains $treeFailureDiagnostic ':OBSERVED:STARTUP:LINE_COUNT:1:' ` + (Get-WorkflowCleanupStateMachineAssertionMessage ` + 'tree-termination failure timeout began before complete startup capture' ` + $treeFailureDiagnostic 'TREE_TERMINATION_FAILURE') + Assert-Contains $treeFailureDiagnostic ':STARTUP_CLASS:READY:LINE_NUMBER:1' ` + (Get-WorkflowCleanupStateMachineAssertionMessage ` + 'tree-termination failure lost its exact startup proof' ` + $treeFailureDiagnostic 'TREE_TERMINATION_FAILURE') Write-Host 'PROPR_WINDOWS_SUPERVISOR_CONTROLLER_STATE_MACHINE:BOUNDED:PASSED' [Console]::Out.Flush() From 16a980f36ecf8dd80226167318ee355be031fc08 Mon Sep 17 00:00:00 2001 From: "propr-dev[bot]" <1316198+propr-dev[bot]@users.noreply.github.com> Date: Tue, 1 Sep 2026 23:10:31 +0000 Subject: [PATCH 06/33] feat(ai): Implemented the narrow fixture correction in [test-installed-windows-app-supervisor.ps1](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-01T23-06-48/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1:1514): Implemented the narrow fixture correction in [test-installed-windows-app-supervisor.ps1](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-01T23-06-48/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1:1514): - `STREAM_DRAIN_RACE` retains its 250 ms window but now uses `BeginTimeoutAfterStartup`. - Added exact assertions for terminal observation, 2 lines, zero stderr, exit 125, `READY` startup, terminal line 2, `ACTIVE_TREE_AFTER_EXIT`, and complete tree termination. - `TIMEOUT_BEFORE_STARTUP` remains launch-relative at 250 ms. - No production files were changed. Validation: - Full desktop suite: 177 passed, 6 platform skips. - Release-workflow contract: 23 passed. - `git diff --check`: passed. Native Windows execution is unavailable locally. CI must show `PROPR_WINDOWS_SUPERVISOR_CONTROLLER_STATE_MACHINE:BOUNDED:PASSED` on both x64 and ARM64 before acceptance. No commit was created. PR: #2057 Comment by: @integry (ID: 5501660217) Model: gpt-5.6-sol --- .../scripts/test-installed-windows-app-supervisor.ps1 | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 index acfac7ae4..bc478b6d4 100644 --- a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 +++ b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 @@ -1511,12 +1511,16 @@ function Test-WorkflowCleanupProtocolStateMachine { Lifecycle='TIMEOUT_AFTER_STARTUP'; TreeTermination='COMPLETE' StartupClass='READY'; LineNumber=1; BeginTimeoutAfterStartup=$true }, - [PSCustomObject]@{ Fixture='STREAM_DRAIN_RACE'; Observed='TERMINAL'; Lifecycle='ACTIVE_TREE_AFTER_EXIT' } + [PSCustomObject]@{ + Fixture='STREAM_DRAIN_RACE'; Observed='TERMINAL'; LineCount=2; Stderr=0; ProcessExit=125 + Lifecycle='ACTIVE_TREE_AFTER_EXIT'; TreeTermination='COMPLETE' + StartupClass='READY'; LineNumber=2; BeginTimeoutAfterStartup=$true + } ) foreach ($case in $cases) { $diagnostic = '' - # TIMEOUT_AFTER_STARTUP gets its separate five-second startup phase above; - # its 250 ms countdown begins only after the complete record is captured. + # Startup-gated cases get a separate five-second startup phase above; their + # 250 ms countdown begins only after the complete startup record is captured. $caseInvocationTimeout = if ($case.Fixture -in @( 'TIMEOUT_BEFORE_STARTUP','TIMEOUT_AFTER_STARTUP','STREAM_DRAIN_RACE' )) { 250 } else { 1000 } From db7f411449338f5b0b7de2e48393fbbd6bf95b3c Mon Sep 17 00:00:00 2001 From: "propr-dev[bot]" <1316198+propr-dev[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 05:53:10 +0000 Subject: [PATCH 07/33] feat(ai): Implemented the narrow post-startup protocol correction on head `16a980f36ecf8dd80226167318ee355be031fc08` without committing. Implemented the narrow post-startup protocol correction on head `16a980f36ecf8dd80226167318ee355be031fc08` without committing. Key changes: - [cleanup body](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T05-39-55/apps/desktop/scripts/run-installed-windows-app-workflow-cleanup-body.ps1:58) - Validates every fixed result/status/exit tuple before emission. - Emits a single allowlisted fallback terminal on primary result-emission failure. - Prevents duplicate terminals when `WriteLine` completed but flushing fails. - Fails closed instead of claiming `COMPLETE` when cleanup-tree or authority finalization is unproven. - [native supervisor tests](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T05-39-55/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1:1613) - Added deterministic post-startup RESULT_EMISSION failure coverage proving READY/1 + terminal/2 + zero stderr + exit 125. - Strengthened real `REPLACEMENT_RETRY` to require COMPLETE/0, READY line 1, terminal line 2, and zero stderr before the final architecture-specific success marker. Validation passed: - PowerShell parsing for all three scripts. - Direct wrapper emission fixture: 2 lines, zero stderr, exit 125. - Fixed status-to-exit mapping checks. - Desktop suite: 177 passed, 6 platform skips. - Release workflow contract: 23 passed. - `git diff --check`. Native Windows x64 and ARM64 execution remains pending CI after the system commits these changes; I did not claim those jobs passed locally. PR: #2057 Comment by: @integry (ID: 5504990403) Model: gpt-5.6-sol --- ...lled-windows-app-workflow-cleanup-body.ps1 | 130 +++++++++++++++--- ...installed-windows-app-workflow-cleanup.ps1 | 4 + .../test-installed-windows-app-supervisor.ps1 | 41 +++++- apps/desktop/src/release-workflow.test.ts | 28 +++- 4 files changed, 180 insertions(+), 23 deletions(-) diff --git a/apps/desktop/scripts/run-installed-windows-app-workflow-cleanup-body.ps1 b/apps/desktop/scripts/run-installed-windows-app-workflow-cleanup-body.ps1 index 64a4d09b0..d20b8e15e 100644 --- a/apps/desktop/scripts/run-installed-windows-app-workflow-cleanup-body.ps1 +++ b/apps/desktop/scripts/run-installed-windows-app-workflow-cleanup-body.ps1 @@ -5,7 +5,8 @@ param( [object]$CleanupTimeoutMilliseconds = 4 * 60 * 1000, [object]$TerminationTimeoutMilliseconds = 30 * 1000, [object]$FixtureRoot, - [switch]$FixtureEarlyInitializationChild + [switch]$FixtureEarlyInitializationChild, + [switch]$FixtureResultEmissionFailure ) enum WorkflowCleanupControllerPhase { @@ -46,6 +47,7 @@ $validatedManifestPath = $null [WorkflowCleanupControllerPhase]$controllerPhase = 'INITIALIZATION' [WorkflowCleanupControllerLine]$controllerLine = 'TYPE_LOAD' $cleanupTreeZeroVerified = $false +$protocolState = @{ TerminalRecordWritten = $false } function Write-StartupRecord { [Console]::Out.WriteLine( @@ -53,11 +55,82 @@ function Write-StartupRecord { [Console]::Out.Flush() } -function Write-FixedResult([ValidateSet('COMPLETE','FAILED','TIMED_OUT')][string]$Result) { - [Console]::Out.WriteLine( - ('PROPR_WINDOWS_INSTALLED_SMOKE:WORKFLOW_CLEANUP:TERMINAL:RESULT:{0}:' + - 'STATUS:{1}:EXIT_CODE:{2}') -f ` - $Result, $script:fixedStatus, $script:fixedExitCode) +function Get-FixedResultRecord( + [ValidateSet('COMPLETE','FAILED','TIMED_OUT')][string]$Result, + [string]$Status, + [int]$ExitCode +) { + $fixedFailureStatuses = @( + 'CONTROLLER_FAILURE','TERMINATION_FAILURE', + 'ACTIVE_PROCESS_AFTER_ROOT_EXIT','PROCESS_FINALIZATION_TIMEOUT', + 'PROCESS_FINALIZATION_FAILURE','STREAM_DRAIN_TIMEOUT', + 'STREAM_DRAIN_FAILURE','RESOURCE_FINALIZATION_FAILURE', + 'AUTHORITY_FINALIZATION_FAILURE','STARTUP_FAILURE' + ) + $controllerFailureStatus = $Status -cmatch ( + '^CONTROLLER_(INITIALIZATION|PARAMETER_VALIDATION|PATH_VALIDATION|' + + 'PROCESS_START|PROCESS_WAIT|PROCESS_FINALIZATION|STREAM_FINALIZATION|' + + 'RESOURCE_FINALIZATION|AUTHORITY_FINALIZATION|RESULT_EMISSION)_' + + '(TYPE_LOAD|PARAMETERS|PATHS|START|WAIT|TERMINATE|DRAIN|DISPOSE|' + + 'AUTHORITY|EMIT)_(AUTHENTICATION|CLOSE|INVALID_ARGUMENT|INVALID_DATA|' + + 'INVALID_OPERATION|LIMIT|NOT_ENABLED|NOT_FOUND|OPEN|STOPPED|' + + 'PERMISSION|READ|BUSY|UNAVAILABLE|SECURITY|WRITE|UNCLASSIFIED)$') + $valid = switch -CaseSensitive ($Status) { + 'EMPTY_OR_CLEANED' { + $Result -ceq 'COMPLETE' -and $ExitCode -eq 0 + break + } + 'MANIFEST_VALIDATION_FAILURE' { + $Result -ceq 'FAILED' -and $ExitCode -eq 20 + break + } + 'OWNED_RESOURCE_CLEANUP_FAILURE' { + $Result -ceq 'FAILED' -and $ExitCode -eq 21 + break + } + { $_ -cin @('CHILD_STDOUT','CHILD_STDOUT_LIMIT') } { + $Result -ceq 'FAILED' -and $ExitCode -eq 122 + break + } + { $_ -cin @('CHILD_STDERR','CHILD_STDERR_LIMIT') } { + $Result -ceq 'FAILED' -and $ExitCode -eq 123 + break + } + 'TIMEOUT' { + $Result -ceq 'TIMED_OUT' -and $ExitCode -eq 124 + break + } + default { + $Result -ceq 'FAILED' -and $ExitCode -eq 125 -and + ($Status -cin $fixedFailureStatuses -or $controllerFailureStatus) + } + } + if (!$valid) { + throw [InvalidOperationException]::new('fixed controller result is inconsistent') + } + return ('PROPR_WINDOWS_INSTALLED_SMOKE:WORKFLOW_CLEANUP:TERMINAL:RESULT:' + + $Result + ':STATUS:' + $Status + ':EXIT_CODE:' + + $ExitCode.ToString([Globalization.CultureInfo]::InvariantCulture)) +} + +function Write-FixedResult( + [ValidateSet('COMPLETE','FAILED','TIMED_OUT')][string]$Result, + [string]$Status, + [int]$ExitCode, + [hashtable]$ProtocolState, + [bool]$InjectEmissionFailure = $false +) { + if ($ProtocolState.TerminalRecordWritten) { + throw [InvalidOperationException]::new('terminal record was already emitted') + } + $record = Get-FixedResultRecord $Result $Status $ExitCode + if ($InjectEmissionFailure) { + throw [InvalidOperationException]::new('fixed result-emission fixture') + } + [Console]::Out.WriteLine($record) + # WriteLine completed one newline-terminated record. Mark it before Flush so + # a later flush exception cannot cause a duplicate fallback record. + $ProtocolState.TerminalRecordWritten = $true [Console]::Out.Flush() } @@ -358,6 +431,9 @@ $ExpectedRunId = [string]$ExpectedRunId $FixtureRoot = if ($null -eq $FixtureRoot) { $null } else { [string]$FixtureRoot } $CleanupTimeoutMilliseconds = $cleanupTimeout $TerminationTimeoutMilliseconds = $terminationTimeout +if ($FixtureResultEmissionFailure -and !$FixtureRoot) { + throw 'result-emission fixture requires a fixture scope' +} $controllerPhase = 'PATH_VALIDATION' $controllerLine = 'PATHS' @@ -539,28 +615,46 @@ foreach ($resource in @($outputDrain, $cleanupJob, $cleanupProcess, $cleanupRead } } -if ($fixedResult -ceq 'COMPLETE' -and $cleanupTreeZeroVerified -and - $validatedManifestPath) { - try { - $controllerPhase = 'AUTHORITY_FINALIZATION' - $controllerLine = 'AUTHORITY' - foreach ($path in @("$validatedManifestPath.new", $validatedManifestPath)) { - if ([IO.File]::Exists($path)) { [IO.File]::Delete($path) } - } - } catch { +if ($fixedResult -ceq 'COMPLETE') { + $controllerPhase = 'AUTHORITY_FINALIZATION' + $controllerLine = 'AUTHORITY' + if (!$cleanupTreeZeroVerified -or !$validatedManifestPath) { $fixedResult = 'FAILED' $fixedStatus = 'AUTHORITY_FINALIZATION_FAILURE' $fixedExitCode = 125 + } else { + try { + foreach ($path in @("$validatedManifestPath.new", $validatedManifestPath)) { + if ([IO.File]::Exists($path)) { [IO.File]::Delete($path) } + } + } catch { + $fixedResult = 'FAILED' + $fixedStatus = 'AUTHORITY_FINALIZATION_FAILURE' + $fixedExitCode = 125 + } } } try { $controllerPhase = 'RESULT_EMISSION' $controllerLine = 'EMIT' - Write-FixedResult $fixedResult + Write-FixedResult $fixedResult $fixedStatus $fixedExitCode $protocolState ` + ([bool]$FixtureResultEmissionFailure) } catch { - Set-CaughtControllerFailure $_ - exit 125 + if (!$protocolState.TerminalRecordWritten) { + $fixedResult = 'FAILED' + $fixedStatus = 'CONTROLLER_RESULT_EMISSION_EMIT_UNCLASSIFIED' + $fixedExitCode = 125 + try { + [Console]::Out.WriteLine( + ('PROPR_WINDOWS_INSTALLED_SMOKE:WORKFLOW_CLEANUP:TERMINAL:' + + 'RESULT:FAILED:' + + 'STATUS:CONTROLLER_RESULT_EMISSION_EMIT_UNCLASSIFIED:' + + 'EXIT_CODE:125')) + $protocolState.TerminalRecordWritten = $true + [Console]::Out.Flush() + } catch {} + } } exit $fixedExitCode diff --git a/apps/desktop/scripts/run-installed-windows-app-workflow-cleanup.ps1 b/apps/desktop/scripts/run-installed-windows-app-workflow-cleanup.ps1 index bb05cda38..82f9abc02 100644 --- a/apps/desktop/scripts/run-installed-windows-app-workflow-cleanup.ps1 +++ b/apps/desktop/scripts/run-installed-windows-app-workflow-cleanup.ps1 @@ -6,6 +6,7 @@ param( [object]$TerminationTimeoutMilliseconds = 30 * 1000, [object]$FixtureRoot, [object]$FixtureEarlyInitializationChild, + [switch]$FixtureResultEmissionFailure, [object]$StartupFailureClass, [object]$ProtocolFixture ) @@ -204,6 +205,9 @@ try { if ([bool]$FixtureEarlyInitializationChild) { $bodyParameters.FixtureEarlyInitializationChild = $true } + if ([bool]$FixtureResultEmissionFailure) { + $bodyParameters.FixtureResultEmissionFailure = $true + } $LASTEXITCODE = $null & $bodyPath @bodyParameters $bodyExitCode = 0 diff --git a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 index bc478b6d4..9b9497f4c 100644 --- a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 +++ b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 @@ -225,6 +225,8 @@ public sealed class ProPRWorkflowCleanupProtocolCapture : IDisposable public int StandardErrorCount { get; private set; } public string ObservedLineCategory { get; private set; } public int ObservedLineNumber { get; private set; } + public int StartupRecordLineNumber { get; private set; } + public int TerminalRecordLineNumber { get; private set; } public string StartupClass { get; private set; } public int StartupProcessExit { get; private set; } public int StartupLine { get; private set; } @@ -292,6 +294,7 @@ public sealed class ProPRWorkflowCleanupProtocolCapture : IDisposable startupSeen = true; ObservedLineCategory = "STARTUP"; ObservedLineNumber = LineCount; + StartupRecordLineNumber = LineCount; if (ready.Success) StartupClass = "READY"; else { @@ -316,6 +319,7 @@ public sealed class ProPRWorkflowCleanupProtocolCapture : IDisposable terminalSeen = true; ObservedLineCategory = "TERMINAL"; ObservedLineNumber = LineCount; + TerminalRecordLineNumber = LineCount; Result = terminal.Groups[1].Value; ControllerStatus = terminal.Groups[2].Value; ReportedExitCode = Int32.Parse(terminal.Groups[3].Value); @@ -1143,6 +1147,7 @@ function Invoke-WorkflowCleanupController( [bool]$SignalCancellationAfterStartup = $false, [bool]$BeginTimeoutAfterStartup = $false, [bool]$InjectTreeTerminationFailure = $false, + [bool]$FixtureResultEmissionFailure = $false, [string]$ProtocolFixture = '' ) { $startInfo = [Diagnostics.ProcessStartInfo]::new() @@ -1167,6 +1172,9 @@ function Invoke-WorkflowCleanupController( if ($FixtureEarlyInitializationChild) { $startInfo.ArgumentList.Add('-FixtureEarlyInitializationChild') } + if ($FixtureResultEmissionFailure) { + $startInfo.ArgumentList.Add('-FixtureResultEmissionFailure') + } if ($StartupFailureClass) { $startInfo.ArgumentList.Add('-StartupFailureClass') $startInfo.ArgumentList.Add($StartupFailureClass) @@ -1309,6 +1317,11 @@ function Invoke-WorkflowCleanupController( StartupLine = if ($capture.StartupClass -ceq 'READY') { '' } else { [string]$capture.StartupLine } + ProtocolLineCount = $capture.LineCount + ProtocolStandardErrorCount = $capture.StandardErrorCount + ProtocolStartupClass = $capture.StartupClass + ProtocolStartupLineNumber = $capture.StartupRecordLineNumber + ProtocolTerminalLineNumber = $capture.TerminalRecordLineNumber } } catch { if ($_.Exception.Message -like 'PROPR_WORKFLOW_CLEANUP_FIXTURE:PROTOCOL_MISMATCH:*') { @@ -1597,6 +1610,25 @@ function Test-WorkflowCleanupProtocolStateMachine { "$($exactPair.Fixture) did not preserve its exact status/exit pair" } + $resultEmissionFailure = Invoke-WorkflowCleanupController ` + -InvocationIdentifier 'PROTOCOL_REGRESSION' ` + -ManifestPath $dummyInstaller ` + -RunId ([Guid]::NewGuid().ToString('N')) ` + -FixtureRoot $testRoot ` + -InvocationTimeoutMilliseconds 5000 ` + -FixtureResultEmissionFailure $true + Assert-True ($resultEmissionFailure.ExitCode -eq 125 -and + $resultEmissionFailure.ReportedExitCode -eq 125 -and + $resultEmissionFailure.Result -ceq 'FAILED' -and + $resultEmissionFailure.ControllerStatus -ceq + 'CONTROLLER_RESULT_EMISSION_EMIT_UNCLASSIFIED' -and + $resultEmissionFailure.ProtocolLineCount -eq 2 -and + $resultEmissionFailure.ProtocolStandardErrorCount -eq 0 -and + $resultEmissionFailure.ProtocolStartupClass -ceq 'READY' -and + $resultEmissionFailure.ProtocolStartupLineNumber -eq 1 -and + $resultEmissionFailure.ProtocolTerminalLineNumber -eq 2) ` + 'post-startup result-emission failure did not preserve the exact fixed protocol' + foreach ($cancellationAfterStartup in @($false, $true)) { $cancel = [Threading.EventWaitHandle]::new( !$cancellationAfterStartup, [Threading.EventResetMode]::ManualReset) @@ -2203,7 +2235,14 @@ function Test-PreExistingCleanupOwnership { $replacementRetryDiagnostic = Get-SanitizedWorkflowCleanupResultDiagnostic $replacementRetry Assert-True ($replacementRetry.ExitCode -eq 0 -and - $replacementRetry.Result -ceq 'COMPLETE') ` + $replacementRetry.ReportedExitCode -eq 0 -and + $replacementRetry.Result -ceq 'COMPLETE' -and + $replacementRetry.ControllerStatus -ceq 'EMPTY_OR_CLEANED' -and + $replacementRetry.ProtocolLineCount -eq 2 -and + $replacementRetry.ProtocolStandardErrorCount -eq 0 -and + $replacementRetry.ProtocolStartupClass -ceq 'READY' -and + $replacementRetry.ProtocolStartupLineNumber -eq 1 -and + $replacementRetry.ProtocolTerminalLineNumber -eq 2) ` "standalone cleanup did not retry to exact success after authority restoration:$replacementRetryDiagnostic" Assert-OwnedResourcesGone $replacementOwned Assert-True (!(Test-Path -LiteralPath $replacementOwned.ManifestPath)) ` diff --git a/apps/desktop/src/release-workflow.test.ts b/apps/desktop/src/release-workflow.test.ts index ff7b2b863..4bfaf0bf5 100644 --- a/apps/desktop/src/release-workflow.test.ts +++ b/apps/desktop/src/release-workflow.test.ts @@ -908,7 +908,7 @@ describe('desktop trusted release workflow', () => { assert.match(installedWindowsAppWorkflowCleanup, /Set-CaughtControllerFailure/); assert.doesNotMatch(installedWindowsAppWorkflowCleanup, /Console\]::SetError|\btrap\b|controllerBody/); assert.match(installedWindowsAppWorkflowCleanup, /\[Console\]::Out\.WriteLine/); - assert.equal(installedWindowsAppWorkflowCleanup.match(/\[Console\]::Out\.WriteLine/g)?.length, 2); + assert.equal(installedWindowsAppWorkflowCleanup.match(/\[Console\]::Out\.WriteLine/g)?.length, 3); assert.doesNotMatch(installedWindowsAppWorkflowCleanup, /Write-Host/); assert.match( installedWindowsAppWorkflowCleanup, @@ -933,7 +933,15 @@ describe('desktop trusted release workflow', () => { /WORKFLOW_CLEANUP:STARTUP:FAILED:[\s\S]*WORKFLOW_CLEANUP:TERMINAL:/, ); assert.match(installedWindowsAppWorkflowCleanup, /WORKFLOW_CLEANUP:STARTUP:READY/); - assert.match(installedWindowsAppWorkflowCleanup, /WORKFLOW_CLEANUP:TERMINAL:RESULT:\{0\}/); + assert.match(installedWindowsAppWorkflowCleanup, /Get-FixedResultRecord/); + assert.match( + installedWindowsAppWorkflowCleanup, + /CONTROLLER_RESULT_EMISSION_EMIT_UNCLASSIFIED[\s\S]*EXIT_CODE:125/, + ); + assert.match( + installedWindowsAppWorkflowCleanup, + /\$ProtocolState\.TerminalRecordWritten = \$true\n\s+\[Console\]::Out\.Flush\(\)/, + ); assert.equal( installedWindowsAppWorkflowCleanupWrapper.match(/\[Console\]::Out\.WriteLine/g)?.length, 2, @@ -949,7 +957,7 @@ describe('desktop trusted release workflow', () => { ); assert.match( installedWindowsAppWorkflowCleanup, - /if \(\$fixedResult -ceq 'COMPLETE' -and \$cleanupTreeZeroVerified -and/, + /if \(\$fixedResult -ceq 'COMPLETE'\)[\s\S]*!\$cleanupTreeZeroVerified -or !\$validatedManifestPath[\s\S]*AUTHORITY_FINALIZATION_FAILURE/, ); assert.match( installedWindowsAppSupervisor, @@ -1027,6 +1035,10 @@ describe('desktop trusted release workflow', () => { assert.match(installedWindowsAppSupervisorBehaviorTest, /MISMATCHED_MANIFEST_EXIT_125/); assert.match(installedWindowsAppSupervisorBehaviorTest, /MISMATCHED_CHILD_STDOUT_EXIT_21/); assert.match(installedWindowsAppSupervisorBehaviorTest, /EXACT_MANIFEST_20[\s\S]*EXACT_FINALIZATION_FAILURE_125/); + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /FixtureResultEmissionFailure[\s\S]*CONTROLLER_RESULT_EMISSION_EMIT_UNCLASSIFIED/, + ); assert.match(installedWindowsAppSupervisorBehaviorTest, /TREE_TERMINATION:FAILED/); assert.match( installedWindowsAppSupervisorBehaviorTest, @@ -1113,6 +1125,14 @@ describe('desktop trusted release workflow', () => { installedWindowsAppSupervisorBehaviorTest, /standalone cleanup did not retry to exact success after authority restoration:\$replacementRetryDiagnostic/, ); + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /\$replacementRetry\.ProtocolLineCount -eq 2[\s\S]*\$replacementRetry\.ProtocolStandardErrorCount -eq 0[\s\S]*\$replacementRetry\.ProtocolStartupLineNumber -eq 1[\s\S]*\$replacementRetry\.ProtocolTerminalLineNumber -eq 2/, + ); + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /PROPR_WINDOWS_SUPERVISOR_TESTS:\$\{Architecture\}:PASSED/, + ); assert.match( installedWindowsAppSupervisor, /FIXTURE_FINALIZATION:' \+\s*'WORKER_TREE_TERMINATION:\{0\}'\) -f/, @@ -1198,7 +1218,7 @@ describe('desktop trusted release workflow', () => { assert.ok( fixedResultWrite > installedWindowsAppWorkflowCleanup.indexOf('$resource.Dispose()') && fixedResultWrite > installedWindowsAppWorkflowCleanup.indexOf( - 'if ($fixedResult -ceq \'COMPLETE\' -and $validatedManifestPath)', + 'if ($fixedResult -ceq \'COMPLETE\')', ), 'fixed controller evidence must be emitted after bounded finalization', ); From 0c200e4d52591ef867bd5c670ddf4b8f180bf19d Mon Sep 17 00:00:00 2001 From: "propr-dev[bot]" <1316198+propr-dev[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 06:15:44 +0000 Subject: [PATCH 08/33] feat(ai): Implemented the follow-up on head `db7f411449338f5b0b7de2e48393fbbd6bf95b3c`. Implemented the follow-up on head `db7f411449338f5b0b7de2e48393fbbd6bf95b3c`. The exact post-gate failure path was the real `OWNED_RESOURCES_THEN_DEADLINE` supervisor scenario. The null `LiteralPath` consumer was the cleanup assertion for `$owned.RegistryRoot` in [test-installed-windows-app-supervisor.ps1](/home/node/workspace/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1:2242). Its producer was `New-OwnedFixtureResources`, which used `Split-Path -Parent $registryPath` for a provider-qualified registry path; that could publish null. I changed it to publish the explicit authenticated registry root derived from the manifest run id in [test-installed-windows-app-supervisor-fixture.ps1](/home/node/workspace/apps/desktop/scripts/test-installed-windows-app-supervisor-fixture.ps1:283). I also added a deterministic regression before the `LiteralPath` cleanup checks that proves the exact scenario published complete non-null authority, with bounded path-free diagnostics: `SCENARIO:OWNED_RESOURCES_THEN_DEADLINE:PHASE:RESOURCE_STATE:FIELD:`. Finally, both Windows workflow supervisor invocations now run through a nested native PowerShell host and require `PROPR_WINDOWS_SUPERVISOR_TESTS:${{ matrix.arch }}:PASSED` before the job can pass. Validation: `npm test -w @propr/desktop -- --run apps/desktop/src/release-workflow.test.ts` passed: 177 passed, 6 skipped. `git diff --check` passed. I could not run the native Windows x64/ARM64 supervisor scripts locally because this Linux host has no `pwsh`; CI must provide that final native evidence. PR: #2057 Comment by: @integry (ID: 5505259543) Model: gpt-5.5 --- .github/workflows/desktop-release-guard.yml | 26 ++++++++++++--- ...stalled-windows-app-supervisor-fixture.ps1 | 5 +-- .../test-installed-windows-app-supervisor.ps1 | 32 +++++++++++++++++++ apps/desktop/src/release-workflow.test.ts | 27 ++++++++++++++++ 4 files changed, 84 insertions(+), 6 deletions(-) diff --git a/.github/workflows/desktop-release-guard.yml b/.github/workflows/desktop-release-guard.yml index d121602e4..91a8cc5d3 100644 --- a/.github/workflows/desktop-release-guard.yml +++ b/.github/workflows/desktop-release-guard.yml @@ -102,8 +102,17 @@ jobs: if: matrix.platform == 'win32' shell: pwsh run: | - & apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 ` - -Architecture '${{ matrix.arch }}' + $supervisorHost = (Get-Process -Id $PID -ErrorAction Stop).Path + & $supervisorHost -NoLogo -NoProfile -NonInteractive ` + -File apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 ` + -Architecture '${{ matrix.arch }}' 6>&1 | + Tee-Object -Variable supervisorOutput + $supervisorExitCode = if ($null -eq $LASTEXITCODE) { 0 } else { $LASTEXITCODE } + if ($supervisorExitCode -ne 0) { exit $supervisorExitCode } + $supervisorLines = @($supervisorOutput | ForEach-Object { [string]$_ }) + if ($supervisorLines -notcontains 'PROPR_WINDOWS_SUPERVISOR_TESTS:${{ matrix.arch }}:PASSED') { + throw 'installed-app supervisor final success marker was not observed' + } - name: Audit committed dependency resolution shell: bash @@ -450,8 +459,17 @@ jobs: if: matrix.platform == 'win32' shell: pwsh run: | - & apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 ` - -Architecture '${{ matrix.arch }}' + $supervisorHost = (Get-Process -Id $PID -ErrorAction Stop).Path + & $supervisorHost -NoLogo -NoProfile -NonInteractive ` + -File apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 ` + -Architecture '${{ matrix.arch }}' 6>&1 | + Tee-Object -Variable supervisorOutput + $supervisorExitCode = if ($null -eq $LASTEXITCODE) { 0 } else { $LASTEXITCODE } + if ($supervisorExitCode -ne 0) { exit $supervisorExitCode } + $supervisorLines = @($supervisorOutput | ForEach-Object { [string]$_ }) + if ($supervisorLines -notcontains 'PROPR_WINDOWS_SUPERVISOR_TESTS:${{ matrix.arch }}:PASSED') { + throw 'installed-app supervisor final success marker was not observed' + } - name: Audit committed dependency resolution shell: bash diff --git a/apps/desktop/scripts/test-installed-windows-app-supervisor-fixture.ps1 b/apps/desktop/scripts/test-installed-windows-app-supervisor-fixture.ps1 index ee1de9bb9..bfa2c2587 100644 --- a/apps/desktop/scripts/test-installed-windows-app-supervisor-fixture.ps1 +++ b/apps/desktop/scripts/test-installed-windows-app-supervisor-fixture.ps1 @@ -280,7 +280,8 @@ function New-OwnedFixtureResources( [IO.File]::WriteAllText($executable, 'owned-executable', [Text.Encoding]::ASCII) [IO.File]::WriteAllText($shortcut, 'owned-shortcut', [Text.Encoding]::ASCII) - $registryPath = "Registry::HKEY_LOCAL_MACHINE\Software\ProPRSupervisorFixture\$($manifest.RunId)\owned" + $registryRoot = "Registry::HKEY_LOCAL_MACHINE\Software\ProPRSupervisorFixture\$($manifest.RunId)" + $registryPath = "$registryRoot\owned" [void](New-Item -Path $registryPath -Force -ErrorAction Stop) Set-ItemProperty -LiteralPath $registryPath -Name 'ProPRInstalledAppOwner' -Value $token Set-ItemProperty -LiteralPath $registryPath -Name 'Payload' -Value 'owned' @@ -464,7 +465,7 @@ function New-OwnedFixtureResources( Shortcut = $shortcut SmokeDirectory = $smokeDirectory RegistryPath = $registryPath - RegistryRoot = Split-Path -Parent $registryPath + RegistryRoot = $registryRoot UserName = $userName UserSid = $userSid ProfilePath = $canonicalProfilePath diff --git a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 index 9b9497f4c..56b333fe8 100644 --- a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 +++ b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 @@ -1065,6 +1065,37 @@ function Assert-MsiPreflightPreservedResources($Owned) { 'MSI file-system preflight failure removed the run-owned user' } +function Get-SanitizedFixtureAuthorityDiagnostic($Scenario, $Field) { + $scenarioName = if ([string]$Scenario -cmatch '^[A-Z_]{1,64}$') { + [string]$Scenario + } else { 'INVALID' } + $fieldName = if ([string]$Field -cmatch '^[A-Za-z][A-Za-z0-9]{0,31}$') { + [string]$Field + } else { 'InvalidField' } + return ('PROPR_SUPERVISOR_FIXTURE_AUTHORITY:SCENARIO:{0}:' + + 'PHASE:RESOURCE_STATE:FIELD:{1}:INVALID') -f $scenarioName, $fieldName +} + +function Assert-OwnedFixtureAuthorityComplete($Owned, [string]$Scenario) { + foreach ($field in @( + 'OwnedRoot','InstallRoot','ShortcutFolder','Shortcut','SmokeDirectory', + 'RegistryPath','RegistryRoot','UserName','UserSid','ProfilePath', + 'ManifestPath','RunId','Token' + )) { + $property = $Owned.PSObject.Properties[$field] + Assert-True ($null -ne $property -and + ![string]::IsNullOrWhiteSpace([string]$property.Value)) ` + (Get-SanitizedFixtureAuthorityDiagnostic $Scenario $field) + } + $expectedRegistryRoot = + "Registry::HKEY_LOCAL_MACHINE\Software\ProPRSupervisorFixture\$($Owned.RunId)" + Assert-True ([string]::Equals( + [string]$Owned.RegistryRoot, + $expectedRegistryRoot, + [StringComparison]::OrdinalIgnoreCase + )) (Get-SanitizedFixtureAuthorityDiagnostic $Scenario 'RegistryRoot') +} + function Get-WorkflowCleanupProtocolMismatchDiagnostic( [string]$InvocationIdentifier, [string]$ObservedLineCategory, @@ -2198,6 +2229,7 @@ function Test-PreExistingCleanupOwnership { } $owned = Read-FixtureResourceState $stateDirectory + Assert-OwnedFixtureAuthorityComplete $owned 'OWNED_RESOURCES_THEN_DEADLINE' foreach ($ownedPath in @( $owned.OwnedRoot, $owned.InstallRoot, $owned.ShortcutFolder, $owned.Shortcut, $owned.SmokeDirectory diff --git a/apps/desktop/src/release-workflow.test.ts b/apps/desktop/src/release-workflow.test.ts index 4bfaf0bf5..daaeff7da 100644 --- a/apps/desktop/src/release-workflow.test.ts +++ b/apps/desktop/src/release-workflow.test.ts @@ -575,6 +575,15 @@ describe('desktop trusted release workflow', () => { assert.match(section, /if: always\(\) && matrix\.platform == 'win32'/); assert.match(section, /-OwnershipManifest \$env:PROPR_WINDOWS_INSTALLED_APP_MANIFEST/); assert.match(section, /-ExpectedRunId \$env:PROPR_WINDOWS_INSTALLED_APP_RUN_ID/); + assert.match(section, /\$supervisorHost = \(Get-Process -Id \$PID -ErrorAction Stop\)\.Path/); + assert.match(section, /& \$supervisorHost -NoLogo -NoProfile -NonInteractive `\n\s+-File apps\/desktop\/scripts\/test-installed-windows-app-supervisor\.ps1/); + assert.match(section, /6>&1 \|\n\s+Tee-Object -Variable supervisorOutput/); + assert.match(section, /if \(\$supervisorExitCode -ne 0\) \{ exit \$supervisorExitCode \}/); + assert.match(section, /\$supervisorLines = @\(\$supervisorOutput \| ForEach-Object \{ \[string\]\$_ \}\)/); + assert.match( + section, + /PROPR_WINDOWS_SUPERVISOR_TESTS:\$\{\{ matrix\.arch \}\}:PASSED/, + ); } }); @@ -589,6 +598,24 @@ describe('desktop trusted release workflow', () => { assert.match(installedWindowsAppSupervisorBehaviorTest, /Invoke-WorkflowCleanupController/); assert.match(installedWindowsAppSupervisorBehaviorTest, /Test-PreExistingAppPathsAuthority/); assert.match(installedWindowsAppSupervisorBehaviorTest, /Assert-ProcessTreeGone/); + assert.match(installedWindowsAppSupervisorBehaviorTest, /Assert-OwnedFixtureAuthorityComplete/); + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /PROPR_SUPERVISOR_FIXTURE_AUTHORITY:SCENARIO:\{0\}:' \+\s*'PHASE:RESOURCE_STATE:FIELD:\{1\}:INVALID/, + ); + assert.match( + installedWindowsAppSupervisorFixture, + /\$registryRoot = "Registry::HKEY_LOCAL_MACHINE\\Software\\ProPRSupervisorFixture\\\$\(\$manifest\.RunId\)"[\s\S]*RegistryRoot = \$registryRoot/, + ); + assert.ok( + installedWindowsAppSupervisorBehaviorTest.indexOf( + "Assert-OwnedFixtureAuthorityComplete $owned 'OWNED_RESOURCES_THEN_DEADLINE'", + ) + < installedWindowsAppSupervisorBehaviorTest.indexOf( + 'Test-Path -LiteralPath $owned.RegistryRoot', + ), + 'fixture authority must be checked before LiteralPath cleanup assertions', + ); assert.match(installedWindowsAppSupervisorBehaviorTest, /WindowsIdentity\]::GetCurrent\(\)/); assert.match( installedWindowsAppSupervisorBehaviorTest, From 1c88c0dc599065f3dc4eb52f4c7eb85c15e3c161 Mon Sep 17 00:00:00 2001 From: "propr-dev[bot]" <1316198+propr-dev[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 06:34:37 +0000 Subject: [PATCH 09/33] feat(ai): Implemented the follow-up on exact head `0c200e4d52591ef867bd5c670ddf4b8f180bf19d`. Implemented the follow-up on exact head `0c200e4d52591ef867bd5c670ddf4b8f180bf19d`. Changes made: - Added allowlisted, path-free supervisor invocation attribution in [test-installed-windows-app-supervisor.ps1](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T06-22-20/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1:1134). - Wrapped every post-state-machine top-level native supervisor boundary so failures emit: `PROPR_WINDOWS_SUPERVISOR_INVOCATION:TEST::SCENARIO::PHASE::FAILED` - Added deterministic hostile-failure coverage proving attribution totality and redaction in [test-installed-windows-app-supervisor.ps1](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T06-22-20/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1:1980). - Preserved the explicit RegistryRoot producer correction and did not guess another LiteralPath sink, since the native logs still only prove an unattributed binding failure. - Added release workflow contract coverage in [release-workflow.test.ts](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T06-22-20/apps/desktop/src/release-workflow.test.ts:590). Validation: - `npm run test -w @propr/desktop -- src/release-workflow.test.ts` passed: `183` tests, `177` passed, `6` skipped. - `git diff --check` passed. - Native Windows x64/ARM64 jobs could not be run locally because this environment has no `pwsh`/Windows runtime. PR: #2057 Comment by: @integry (ID: 5505393751) Model: gpt-5.5 --- .../test-installed-windows-app-supervisor.ps1 | 490 +++++++++++++++++- apps/desktop/src/release-workflow.test.ts | 17 + 2 files changed, 494 insertions(+), 13 deletions(-) diff --git a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 index 56b333fe8..8a2f60056 100644 --- a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 +++ b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 @@ -23,6 +23,9 @@ $conflictingFixtureRegistryPath = $null $dummyInstallerProductCode = ('{' + [Guid]::NewGuid().ToString().ToUpperInvariant() + '}') $dummyInstallerEntryIdentity = $null $dummyInstallerSha256 = $null +$script:currentSupervisorInvocationTest = 'UNATTRIBUTED' +$script:currentSupervisorInvocationScenario = 'UNATTRIBUTED' +$script:currentSupervisorInvocationPhase = 'UNATTRIBUTED' function Assert-True([bool]$Condition, [string]$Message) { if (!$Condition) { throw $Message } @@ -648,6 +651,10 @@ function Read-FixtureProcessState([string]$StateDirectory) { } function Read-FixtureResourceState([string]$StateDirectory) { + Set-SupervisorInvocationContext ` + $script:currentSupervisorInvocationTest ` + $script:currentSupervisorInvocationScenario ` + 'RESOURCE_STATE' $statePath = Join-Path $StateDirectory 'resources.json' $stopwatch = [Diagnostics.Stopwatch]::StartNew() while (!(Test-Path -LiteralPath $statePath -PathType Leaf)) { @@ -981,6 +988,10 @@ function Get-WorkflowCleanupControllerStatusMatch([string]$TerminalLine) { } function Assert-OwnedResourcesGone($Owned) { + Set-SupervisorInvocationContext ` + $script:currentSupervisorInvocationTest ` + $script:currentSupervisorInvocationScenario ` + 'RESOURCE_ASSERTION' foreach ($ownedPath in @( $Owned.OwnedRoot, $Owned.InstallRoot, $Owned.ShortcutFolder, $Owned.Shortcut, $Owned.SmokeDirectory @@ -1001,6 +1012,10 @@ function Assert-OwnedResourcesGone($Owned) { } function Restore-ReplacedFixtureAuthority($Owned) { + Set-SupervisorInvocationContext ` + $script:currentSupervisorInvocationTest ` + $script:currentSupervisorInvocationScenario ` + 'AUTHORITY_RESTORE' [IO.File]::WriteAllText( (Join-Path $Owned.OwnedRoot '.propr-installed-app-owner'), [string]$Owned.Token, @@ -1030,6 +1045,10 @@ function Restore-ReplacedFixtureAuthority($Owned) { } function Assert-ReplacedFixtureResourcesSurvive($Owned) { + Set-SupervisorInvocationContext ` + $script:currentSupervisorInvocationTest ` + $script:currentSupervisorInvocationScenario ` + 'RESOURCE_ASSERTION' Assert-True ((Get-Content -LiteralPath (Join-Path $Owned.InstallRoot 'foreign.txt') -Raw).Trim() ` -ceq 'foreign-install-tree') ` 'replacement install tree was removed or changed' @@ -1041,6 +1060,10 @@ function Assert-ReplacedFixtureResourcesSurvive($Owned) { } function Assert-ReplacedExecutableSurvives($Owned) { + Set-SupervisorInvocationContext ` + $script:currentSupervisorInvocationTest ` + $script:currentSupervisorInvocationScenario ` + 'RESOURCE_ASSERTION' $expected = if ($Owned.PSObject.Properties['ByteIdenticalReplacement']) { 'owned-executable' } else { 'foreign-executable' } @@ -1049,11 +1072,19 @@ function Assert-ReplacedExecutableSurvives($Owned) { } function Assert-ReplacedShortcutSurvives($Owned) { + Set-SupervisorInvocationContext ` + $script:currentSupervisorInvocationTest ` + $script:currentSupervisorInvocationScenario ` + 'RESOURCE_ASSERTION' Assert-True ((Get-Content -LiteralPath $Owned.Shortcut -Raw).Trim() -ceq 'foreign-shortcut') 'replacement shortcut was removed or changed' } function Assert-MsiPreflightPreservedResources($Owned) { + Set-SupervisorInvocationContext ` + $script:currentSupervisorInvocationTest ` + $script:currentSupervisorInvocationScenario ` + 'RESOURCE_ASSERTION' foreach ($path in @( $Owned.OwnedRoot, $Owned.InstallRoot, $Owned.ShortcutFolder, $Owned.Shortcut, $Owned.SmokeDirectory, $Owned.RegistryPath @@ -1077,6 +1108,10 @@ function Get-SanitizedFixtureAuthorityDiagnostic($Scenario, $Field) { } function Assert-OwnedFixtureAuthorityComplete($Owned, [string]$Scenario) { + Set-SupervisorInvocationContext ` + $script:currentSupervisorInvocationTest ` + $Scenario ` + 'AUTHORITY_ASSERTION' foreach ($field in @( 'OwnedRoot','InstallRoot','ShortcutFolder','Shortcut','SmokeDirectory', 'RegistryPath','RegistryRoot','UserName','UserSid','ProfilePath', @@ -1096,6 +1131,211 @@ function Assert-OwnedFixtureAuthorityComplete($Owned, [string]$Scenario) { )) (Get-SanitizedFixtureAuthorityDiagnostic $Scenario 'RegistryRoot') } +function Get-SupervisorInvocationTests { + return @( + 'UNATTRIBUTED', + 'BOOTSTRAP_TIMEOUT', + 'WINDOWS_POWERSHELL_CLEANUP_COMPATIBILITY', + 'OPERATION_DEADLINE_AND_TREE_TERMINATION', + 'NEGATIVE_WORKER_EXIT_FINALIZATION', + 'FAIL_CLOSED_MARKERS', + 'LIVE_CANCELLATION_AND_REDACTION', + 'MSI_TRANSACTION_INTERRUPTION_GATES', + 'PRIMARY_WORKER_FALLBACK_FOREIGN_DESCENDANTS', + 'PRE_EXISTING_CLEANUP_OWNERSHIP', + 'SMOKE_PROMOTION_INTERRUPTION_AUTHORITY', + 'PRE_EXISTING_APP_PATHS_AUTHORITY', + 'HKCU_INSTALLED_VALUE_OWNERSHIP', + 'PROVISIONAL_USER_MARKER_OWNERSHIP', + 'ATTRIBUTION_TOTALITY' + ) +} + +function Get-SupervisorInvocationScenarios { + return @( + 'UNATTRIBUTED', + 'TEST', + 'NO_MARKER', + 'NO_MARKER_WINDOWS_POWERSHELL', + 'VALID_THEN_DEADLINE', + 'NEGATIVE_EXIT', + 'MALFORMED_MARKER', + 'TORN_MARKER', + 'STALE_MARKER', + 'INACCESSIBLE_MARKER', + 'CANCELLATION', + 'DURING_MSI', + 'DURING_OWNERSHIP_CAPTURE', + 'PRIMARY_FALLBACK_FOREIGN_DESCENDANTS', + 'PRE_EXISTING_APP_PATHS', + 'OWNED_RESOURCES_THEN_DEADLINE', + 'OWNED_RESOURCES_FOR_INTERRUPTION', + 'OWNED_RESOURCES_NORMAL_SUCCESS', + 'OWNED_RESOURCES_REPLACED_THEN_DEADLINE', + 'OWNED_EXECUTABLE_REPLACED_THEN_DEADLINE', + 'OWNED_EXECUTABLE_BYTE_IDENTICAL_REPLACED_THEN_DEADLINE', + 'OWNED_SHORTCUT_REPLACED_THEN_DEADLINE', + 'OWNED_PROFILE_PATH_MISMATCH_THEN_DEADLINE', + 'OWNED_RESOURCES_FOREIGN_CHILD_THEN_DEADLINE', + 'SMOKE_BEFORE_PROMOTION_THEN_DEADLINE', + 'SMOKE_AFTER_PROMOTION_THEN_DEADLINE', + 'SMOKE_AFTER_ARTIFACTS_THEN_DEADLINE', + 'SMOKE_FOREIGN_DESCENDANT_THEN_DEADLINE', + 'SMOKE_TOKEN_MISMATCH_THEN_DEADLINE', + 'STARTUP_PROTOCOL', + 'REPLACEMENT_RETRY', + 'REPLACED_ENTRY_RETRY', + 'PROFILE_ALTERNATE_LEAF', + 'PROFILE_RETRY', + 'EXECUTABLE_IDENTITY_RETRY', + 'FOREIGN_CHILD_RETRY', + 'TERMINATION_RETRY', + 'PARAMETER_VALIDATION', + 'EARLY_INITIALIZATION_TIMEOUT', + 'CLEANUP_TIMEOUT', + 'INSTALLER_REPLACEMENT', + 'RESOURCE_COLLISION', + 'WORKFLOW_RETRY', + 'NORMAL_CLEANUP', + 'MANIFEST_VALIDATION', + 'SMOKE_PROMOTION_RETRY', + 'SMOKE_TOKEN_MISSING', + 'SMOKE_TOKEN_RETRY', + 'APP_PATH_MISMATCH', + 'HKCU_BASELINE_RESTORE', + 'HKCU_PENDING_RECEIPT', + 'HKCU_NONEMPTY', + 'HKCU_EMPTY', + 'HKCU_CONFLICT', + 'HKCU_PROVISIONAL', + 'USER_MARKER_OWNED', + 'USER_MARKER_REPLACEMENT', + 'PROTOCOL_REGRESSION' + ) +} + +function Get-SupervisorInvocationPhases { + return @( + 'UNATTRIBUTED', + 'TEST', + 'FIXTURE_SETUP', + 'SUPERVISOR_PROCESS', + 'PROCESS_START', + 'PROCESS_WAIT', + 'PROCESS_OUTPUT', + 'PROCESS_STATE', + 'RESOURCE_STATE', + 'RESOURCE_ASSERTION', + 'AUTHORITY_ASSERTION', + 'AUTHORITY_RESTORE', + 'WORKFLOW_CLEANUP_CONTROLLER', + 'PIPELINE_START', + 'PIPELINE_STOP', + 'MANIFEST_ASSERTION', + 'CLEANUP_ASSERTION', + 'HOSTILE_FAILURE', + 'FINALIZER' + ) +} + +function Get-SanitizedSupervisorInvocationToken([string]$Token, [string[]]$AllowList) { + if ($Token -cin $AllowList) { return $Token } + return 'UNATTRIBUTED' +} + +function Get-SanitizedSupervisorInvocationDiagnostic( + [string]$Test, + [string]$Scenario, + [string]$Phase +) { + $testName = Get-SanitizedSupervisorInvocationToken $Test (Get-SupervisorInvocationTests) + $scenarioName = Get-SanitizedSupervisorInvocationToken $Scenario (Get-SupervisorInvocationScenarios) + $phaseName = Get-SanitizedSupervisorInvocationToken $Phase (Get-SupervisorInvocationPhases) + return ('PROPR_WINDOWS_SUPERVISOR_INVOCATION:TEST:{0}:' + + 'SCENARIO:{1}:PHASE:{2}:FAILED') -f $testName, $scenarioName, $phaseName +} + +function Set-SupervisorInvocationContext( + [string]$Test, + [string]$Scenario, + [string]$Phase +) { + $script:currentSupervisorInvocationTest = + Get-SanitizedSupervisorInvocationToken $Test (Get-SupervisorInvocationTests) + $script:currentSupervisorInvocationScenario = + Get-SanitizedSupervisorInvocationToken $Scenario (Get-SupervisorInvocationScenarios) + $script:currentSupervisorInvocationPhase = + Get-SanitizedSupervisorInvocationToken $Phase (Get-SupervisorInvocationPhases) +} + +function Invoke-SupervisorAttributedBoundary( + [string]$Test, + [string]$Scenario, + [string]$Phase, + [scriptblock]$Action +) { + $previousTest = $script:currentSupervisorInvocationTest + $previousScenario = $script:currentSupervisorInvocationScenario + $previousPhase = $script:currentSupervisorInvocationPhase + Set-SupervisorInvocationContext $Test $Scenario $Phase + try { + & $Action + } catch { + if ($_.Exception.Message -clike 'PROPR_WINDOWS_SUPERVISOR_INVOCATION:*') { + throw + } + throw (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + $script:currentSupervisorInvocationScenario ` + $script:currentSupervisorInvocationPhase) + } finally { + $script:currentSupervisorInvocationTest = $previousTest + $script:currentSupervisorInvocationScenario = $previousScenario + $script:currentSupervisorInvocationPhase = $previousPhase + } +} + +function Invoke-SupervisorAttributedTest( + [string]$Test, + [scriptblock]$Action +) { + Invoke-SupervisorAttributedBoundary $Test 'TEST' 'TEST' $Action +} + +function Assert-SupervisorInvocationDiagnosticBounded( + [string]$Diagnostic, + [string]$Test, + [string]$Scenario, + [string]$Phase +) { + $expected = Get-SanitizedSupervisorInvocationDiagnostic $Test $Scenario $Phase + Assert-True ($Diagnostic -ceq $expected) ` + 'supervisor invocation attribution was not the exact fixed diagnostic' + Assert-True ([Text.Encoding]::ASCII.GetByteCount($Diagnostic) -le 224) ` + 'supervisor invocation attribution exceeded its bounded size' + Assert-True ($Diagnostic -cmatch ( + '^PROPR_WINDOWS_SUPERVISOR_INVOCATION:TEST:[A-Z_]+:' + + 'SCENARIO:[A-Z_]+:PHASE:[A-Z_]+:FAILED$' + )) 'supervisor invocation attribution used a non-allowlisted token format' + foreach ($forbidden in @( + $secretNeedle, + $testRoot, + $dummyInstaller, + 'Cannot bind argument', + 'LiteralPath', + 'Registry::', + 'S-1-5-', + 'fixture-user', + 'credential', + 'manifest', + 'stdout', + 'stderr' + )) { + Assert-NotContains $Diagnostic $forbidden ` + 'supervisor invocation attribution disclosed raw failure context' + } +} + function Get-WorkflowCleanupProtocolMismatchDiagnostic( [string]$InvocationIdentifier, [string]$ObservedLineCategory, @@ -1181,6 +1421,10 @@ function Invoke-WorkflowCleanupController( [bool]$FixtureResultEmissionFailure = $false, [string]$ProtocolFixture = '' ) { + Set-SupervisorInvocationContext ` + $script:currentSupervisorInvocationTest ` + $InvocationIdentifier ` + 'WORKFLOW_CLEANUP_CONTROLLER' $startInfo = [Diagnostics.ProcessStartInfo]::new() $startInfo.FileName = $hostPath $startInfo.UseShellExecute = $false @@ -1733,7 +1977,100 @@ function Test-WorkflowCleanupProtocolStateMachine { [Console]::Out.Flush() } +function Test-SupervisorInvocationAttributionTotality { + foreach ($case in @( + [PSCustomObject]@{ + Test='BOOTSTRAP_TIMEOUT'; Scenario='NO_MARKER'; Phase='SUPERVISOR_PROCESS' + }, + [PSCustomObject]@{ + Test='WINDOWS_POWERSHELL_CLEANUP_COMPATIBILITY' + Scenario='NO_MARKER_WINDOWS_POWERSHELL'; Phase='SUPERVISOR_PROCESS' + }, + [PSCustomObject]@{ + Test='OPERATION_DEADLINE_AND_TREE_TERMINATION' + Scenario='VALID_THEN_DEADLINE'; Phase='SUPERVISOR_PROCESS' + }, + [PSCustomObject]@{ + Test='NEGATIVE_WORKER_EXIT_FINALIZATION' + Scenario='NEGATIVE_EXIT'; Phase='SUPERVISOR_PROCESS' + }, + [PSCustomObject]@{ + Test='FAIL_CLOSED_MARKERS'; Scenario='MALFORMED_MARKER' + Phase='SUPERVISOR_PROCESS' + }, + [PSCustomObject]@{ + Test='LIVE_CANCELLATION_AND_REDACTION'; Scenario='CANCELLATION' + Phase='PROCESS_OUTPUT' + }, + [PSCustomObject]@{ + Test='MSI_TRANSACTION_INTERRUPTION_GATES'; Scenario='DURING_MSI' + Phase='PROCESS_WAIT' + }, + [PSCustomObject]@{ + Test='PRIMARY_WORKER_FALLBACK_FOREIGN_DESCENDANTS' + Scenario='PRIMARY_FALLBACK_FOREIGN_DESCENDANTS'; Phase='SUPERVISOR_PROCESS' + }, + [PSCustomObject]@{ + Test='PRE_EXISTING_CLEANUP_OWNERSHIP' + Scenario='OWNED_RESOURCES_THEN_DEADLINE'; Phase='RESOURCE_STATE' + }, + [PSCustomObject]@{ + Test='SMOKE_PROMOTION_INTERRUPTION_AUTHORITY' + Scenario='SMOKE_BEFORE_PROMOTION_THEN_DEADLINE'; Phase='RESOURCE_STATE' + }, + [PSCustomObject]@{ + Test='PRE_EXISTING_APP_PATHS_AUTHORITY'; Scenario='PRE_EXISTING_APP_PATHS' + Phase='PROCESS_OUTPUT' + }, + [PSCustomObject]@{ + Test='HKCU_INSTALLED_VALUE_OWNERSHIP'; Scenario='HKCU_BASELINE_RESTORE' + Phase='WORKFLOW_CLEANUP_CONTROLLER' + }, + [PSCustomObject]@{ + Test='PROVISIONAL_USER_MARKER_OWNERSHIP'; Scenario='USER_MARKER_REPLACEMENT' + Phase='WORKFLOW_CLEANUP_CONTROLLER' + } + )) { + $diagnostic = '' + try { + Invoke-SupervisorAttributedTest $case.Test { + Invoke-SupervisorAttributedBoundary $case.Test $case.Scenario $case.Phase { + throw ( + "Cannot bind argument to parameter 'LiteralPath' because it is null. " + + "$secretNeedle $testRoot Registry::HKEY_LOCAL_MACHINE S-1-5-21 " + + 'manifest stdout stderr' + ) + } + } + } catch { + $diagnostic = $_.Exception.Message + } + Assert-SupervisorInvocationDiagnosticBounded ` + $diagnostic $case.Test $case.Scenario $case.Phase + } + foreach ($testName in Get-SupervisorInvocationTests) { + foreach ($scenarioName in Get-SupervisorInvocationScenarios) { + foreach ($phaseName in Get-SupervisorInvocationPhases) { + $diagnostic = Get-SanitizedSupervisorInvocationDiagnostic ` + $testName $scenarioName $phaseName + Assert-True ([Text.Encoding]::ASCII.GetByteCount($diagnostic) -le 224) ` + 'an allowlisted supervisor invocation diagnostic exceeded its byte bound' + Assert-True ($diagnostic -cmatch ( + '^PROPR_WINDOWS_SUPERVISOR_INVOCATION:TEST:[A-Z_]+:' + + 'SCENARIO:[A-Z_]+:PHASE:[A-Z_]+:FAILED$' + )) 'an allowlisted supervisor invocation diagnostic was not token-only' + } + } + } + Write-Host 'PROPR_WINDOWS_SUPERVISOR_INVOCATION_ATTRIBUTION:TOTAL:PASSED' + [Console]::Out.Flush() +} + function Start-ExternallyInterruptibleSupervisor([string]$StateDirectory) { + Set-SupervisorInvocationContext ` + $script:currentSupervisorInvocationTest ` + 'OWNED_RESOURCES_FOR_INTERRUPTION' ` + 'PIPELINE_START' $scriptText = @' param($SupervisorPath, $Installer, $Architecture, $FixtureWorker, $Scenario, $StateDirectory, $Secret, $OwnedUser, $OwnedPassword, @@ -1788,6 +2125,8 @@ function Invoke-FixtureScenario( [string]$ExistingStateDirectory = '', [bool]$InjectTerminationFailure = $false ) { + Set-SupervisorInvocationContext ` + $script:currentSupervisorInvocationTest $Scenario 'FIXTURE_SETUP' $stateDirectory = if ($ExistingStateDirectory) { $ExistingStateDirectory } else { @@ -1797,6 +2136,8 @@ function Invoke-FixtureScenario( $process.StartInfo = New-SupervisorStartInfo ` $Scenario $stateDirectory '' $false '' '' $InjectTerminationFailure $stopwatch = [Diagnostics.Stopwatch]::StartNew() + Set-SupervisorInvocationContext ` + $script:currentSupervisorInvocationTest $Scenario 'PROCESS_START' if (!$process.Start()) { throw 'supervisor test process did not start' } try { $completionBound = if ($Scenario -in @( @@ -1817,13 +2158,19 @@ function Invoke-FixtureScenario( 'SMOKE_FOREIGN_DESCENDANT_THEN_DEADLINE', 'SMOKE_TOKEN_MISMATCH_THEN_DEADLINE' )) { 90000 } else { 20000 } + Set-SupervisorInvocationContext ` + $script:currentSupervisorInvocationTest $Scenario 'PROCESS_WAIT' if (!$process.WaitForExit($completionBound)) { try { $process.Kill($true) } catch {} throw 'supervisor exceeded the executable test completion bound' } $stopwatch.Stop() + Set-SupervisorInvocationContext ` + $script:currentSupervisorInvocationTest $Scenario 'PROCESS_OUTPUT' $standardOutput = $process.StandardOutput.ReadToEnd() $standardError = $process.StandardError.ReadToEnd() + Set-SupervisorInvocationContext ` + $script:currentSupervisorInvocationTest $Scenario 'PROCESS_STATE' $state = Read-FixtureProcessState $stateDirectory Assert-ProcessTreeGone $state return [PSCustomObject]@{ @@ -1839,6 +2186,8 @@ function Invoke-FixtureScenario( } function Invoke-CriticalCancellationScenario([string]$Scenario) { + Set-SupervisorInvocationContext ` + $script:currentSupervisorInvocationTest $Scenario 'FIXTURE_SETUP' $stateDirectory = New-StateDirectory $Scenario.ToLowerInvariant() $eventName = "Local\ProPRInstalledAppCancellation-$([Guid]::NewGuid().ToString('N'))" $cancellation = [Threading.EventWaitHandle]::new( @@ -1847,22 +2196,34 @@ function Invoke-CriticalCancellationScenario([string]$Scenario) { $process.StartInfo = New-SupervisorStartInfo ` $Scenario $stateDirectory $eventName $false try { + Set-SupervisorInvocationContext ` + $script:currentSupervisorInvocationTest $Scenario 'PROCESS_START' if (!$process.Start()) { throw 'critical-cancellation supervisor did not start' } $gatePath = Join-Path $stateDirectory 'critical-gate.txt' $gateWait = [Diagnostics.Stopwatch]::StartNew() + Set-SupervisorInvocationContext ` + $script:currentSupervisorInvocationTest $Scenario 'PROCESS_WAIT' while (!(Test-Path -LiteralPath $gatePath -PathType Leaf)) { if ($gateWait.ElapsedMilliseconds -ge 45000) { throw 'critical-cancellation fixture did not reach its interruption gate' } Start-Sleep -Milliseconds 25 } + Set-SupervisorInvocationContext ` + $script:currentSupervisorInvocationTest $Scenario 'PROCESS_STATE' Assert-True ((Get-Content -LiteralPath $gatePath -Raw -Encoding ASCII) -ceq $Scenario) ` 'critical-cancellation fixture published the wrong interruption gate' [void]$cancellation.Set() + Set-SupervisorInvocationContext ` + $script:currentSupervisorInvocationTest $Scenario 'PROCESS_WAIT' Assert-True ($process.WaitForExit(90000)) ` 'critical-cancellation supervisor exceeded its fixed completion bound' + Set-SupervisorInvocationContext ` + $script:currentSupervisorInvocationTest $Scenario 'PROCESS_OUTPUT' $output = $process.StandardOutput.ReadToEnd() $errorOutput = $process.StandardError.ReadToEnd() + Set-SupervisorInvocationContext ` + $script:currentSupervisorInvocationTest $Scenario 'PROCESS_STATE' Assert-ProcessTreeGone (Read-FixtureProcessState $stateDirectory) return [PSCustomObject]@{ ExitCode = $process.ExitCode @@ -1998,6 +2359,8 @@ function Test-FailClosedMarkers { } function Test-LiveCancellationAndRedaction { + Set-SupervisorInvocationContext ` + 'LIVE_CANCELLATION_AND_REDACTION' 'CANCELLATION' 'FIXTURE_SETUP' $stateDirectory = New-StateDirectory 'cancellation' $eventName = "Local\ProPRInstalledAppCancellation-$([Guid]::NewGuid().ToString('N'))" $cancellationEvent = [Threading.EventWaitHandle]::new( @@ -2009,9 +2372,13 @@ function Test-LiveCancellationAndRedaction { $process.StartInfo = New-SupervisorStartInfo 'CANCELLATION' $stateDirectory $eventName $false $lines = [Collections.Generic.List[string]]::new() try { + Set-SupervisorInvocationContext ` + 'LIVE_CANCELLATION_AND_REDACTION' 'CANCELLATION' 'PROCESS_START' if (!$process.Start()) { throw 'cancellation supervisor did not start' } $liveAccepted = $false $readStopwatch = [Diagnostics.Stopwatch]::StartNew() + Set-SupervisorInvocationContext ` + 'LIVE_CANCELLATION_AND_REDACTION' 'CANCELLATION' 'PROCESS_OUTPUT' while (!$liveAccepted -and $readStopwatch.ElapsedMilliseconds -lt 8000) { $lineTask = $process.StandardOutput.ReadLineAsync() if (!$lineTask.Wait(8000 - [int]$readStopwatch.ElapsedMilliseconds)) { break } @@ -2025,7 +2392,11 @@ function Test-LiveCancellationAndRedaction { Assert-True $liveAccepted 'accepted transition was not observable live before cancellation' Assert-True (!$process.HasExited) 'supervisor exited before simulated cancellation' [void]$cancellationEvent.Set() + Set-SupervisorInvocationContext ` + 'LIVE_CANCELLATION_AND_REDACTION' 'CANCELLATION' 'PROCESS_WAIT' Assert-True ($process.WaitForExit(8000)) 'cancelled supervisor did not complete within the bound' + Set-SupervisorInvocationContext ` + 'LIVE_CANCELLATION_AND_REDACTION' 'CANCELLATION' 'PROCESS_OUTPUT' $remainingOutput = $process.StandardOutput.ReadToEnd() if ($remainingOutput) { $lines.Add($remainingOutput) } $standardError = $process.StandardError.ReadToEnd() @@ -2041,6 +2412,8 @@ function Test-LiveCancellationAndRedaction { Assert-NotContains $output $forbidden 'live supervisor diagnostics were not redacted' } $state = Read-FixtureProcessState $stateDirectory + Set-SupervisorInvocationContext ` + 'LIVE_CANCELLATION_AND_REDACTION' 'CANCELLATION' 'PROCESS_STATE' Assert-ProcessTreeGone $state Assert-True ([string]::IsNullOrEmpty($standardError)) 'fixture cancellation wrote unexpected stderr' } finally { @@ -2229,7 +2602,15 @@ function Test-PreExistingCleanupOwnership { } $owned = Read-FixtureResourceState $stateDirectory + Set-SupervisorInvocationContext ` + 'PRE_EXISTING_CLEANUP_OWNERSHIP' ` + 'OWNED_RESOURCES_THEN_DEADLINE' ` + 'AUTHORITY_ASSERTION' Assert-OwnedFixtureAuthorityComplete $owned 'OWNED_RESOURCES_THEN_DEADLINE' + Set-SupervisorInvocationContext ` + 'PRE_EXISTING_CLEANUP_OWNERSHIP' ` + 'OWNED_RESOURCES_THEN_DEADLINE' ` + 'RESOURCE_ASSERTION' foreach ($ownedPath in @( $owned.OwnedRoot, $owned.InstallRoot, $owned.ShortcutFolder, $owned.Shortcut, $owned.SmokeDirectory @@ -2489,12 +2870,28 @@ function Test-PreExistingCleanupOwnership { 'pre-existing local user fixture unexpectedly acquired a profile' $gracefulStateDirectory = New-StateDirectory 'graceful-interruption' + Set-SupervisorInvocationContext ` + 'PRE_EXISTING_CLEANUP_OWNERSHIP' ` + 'OWNED_RESOURCES_FOR_INTERRUPTION' ` + 'PIPELINE_START' $graceful = Start-ExternallyInterruptibleSupervisor $gracefulStateDirectory try { + Set-SupervisorInvocationContext ` + 'PRE_EXISTING_CLEANUP_OWNERSHIP' ` + 'OWNED_RESOURCES_FOR_INTERRUPTION' ` + 'PROCESS_STATE' $gracefulProcessState = Read-FixtureProcessState $gracefulStateDirectory $gracefulOwned = Read-FixtureResourceState $gracefulStateDirectory + Set-SupervisorInvocationContext ` + 'PRE_EXISTING_CLEANUP_OWNERSHIP' ` + 'OWNED_RESOURCES_FOR_INTERRUPTION' ` + 'PIPELINE_STOP' $graceful.Pipeline.Stop() try { [void]$graceful.Pipeline.EndInvoke($graceful.AsyncResult) } catch {} + Set-SupervisorInvocationContext ` + 'PRE_EXISTING_CLEANUP_OWNERSHIP' ` + 'OWNED_RESOURCES_FOR_INTERRUPTION' ` + 'RESOURCE_ASSERTION' Assert-ProcessTreeGone $gracefulProcessState Assert-OwnedResourcesGone $gracefulOwned } finally { @@ -2510,12 +2907,28 @@ function Test-PreExistingCleanupOwnership { 'OWNED_RESOURCES_FOR_INTERRUPTION' $workflowStateDirectory '' $false ` $workflowManifest $workflowRunId try { + Set-SupervisorInvocationContext ` + 'PRE_EXISTING_CLEANUP_OWNERSHIP' ` + 'OWNED_RESOURCES_FOR_INTERRUPTION' ` + 'PROCESS_START' if (!$workflowSupervisor.Start()) { throw 'workflow supervisor fixture did not start' } + Set-SupervisorInvocationContext ` + 'PRE_EXISTING_CLEANUP_OWNERSHIP' ` + 'OWNED_RESOURCES_FOR_INTERRUPTION' ` + 'PROCESS_STATE' $workflowProcessState = Read-FixtureProcessState $workflowStateDirectory $workflowOwned = Read-FixtureResourceState $workflowStateDirectory + Set-SupervisorInvocationContext ` + 'PRE_EXISTING_CLEANUP_OWNERSHIP' ` + 'OWNED_RESOURCES_FOR_INTERRUPTION' ` + 'PROCESS_WAIT' $workflowSupervisor.Kill($false) Assert-True ($workflowSupervisor.WaitForExit(5000)) ` 'killed workflow supervisor did not exit within the bound' + Set-SupervisorInvocationContext ` + 'PRE_EXISTING_CLEANUP_OWNERSHIP' ` + 'OWNED_RESOURCES_FOR_INTERRUPTION' ` + 'RESOURCE_ASSERTION' Assert-ProcessTreeGone $workflowProcessState Assert-True (Test-Path -LiteralPath $workflowManifest -PathType Leaf) ` 'killed supervisor did not preserve the durable ownership manifest' @@ -2630,12 +3043,28 @@ function Test-PreExistingCleanupOwnership { 'OWNED_RESOURCES_NORMAL_SUCCESS' $normalStateDirectory '' $false ` $normalManifest $normalRunId try { + Set-SupervisorInvocationContext ` + 'PRE_EXISTING_CLEANUP_OWNERSHIP' ` + 'OWNED_RESOURCES_NORMAL_SUCCESS' ` + 'PROCESS_START' if (!$normalSupervisor.Start()) { throw 'normal workflow supervisor fixture did not start' } + Set-SupervisorInvocationContext ` + 'PRE_EXISTING_CLEANUP_OWNERSHIP' ` + 'OWNED_RESOURCES_NORMAL_SUCCESS' ` + 'PROCESS_STATE' $normalOwned = Read-FixtureResourceState $normalStateDirectory + Set-SupervisorInvocationContext ` + 'PRE_EXISTING_CLEANUP_OWNERSHIP' ` + 'OWNED_RESOURCES_NORMAL_SUCCESS' ` + 'PROCESS_WAIT' Assert-True ($normalSupervisor.WaitForExit(40000)) ` 'normal workflow supervisor fixture exceeded its bound' Assert-True ($normalSupervisor.ExitCode -eq 0) ` 'normal workflow supervisor fixture did not complete successfully' + Set-SupervisorInvocationContext ` + 'PRE_EXISTING_CLEANUP_OWNERSHIP' ` + 'OWNED_RESOURCES_NORMAL_SUCCESS' ` + 'RESOURCE_ASSERTION' Assert-OwnedResourcesGone $normalOwned Assert-True (Test-Path -LiteralPath $normalManifest -PathType Leaf) ` 'normal supervisor did not preserve its empty ownership receipt' @@ -2868,9 +3297,21 @@ function Test-PreExistingAppPathsAuthority { $process.StartInfo = New-SupervisorStartInfo ` 'PRE_EXISTING_APP_PATHS' $testRoot '' $true try { + Set-SupervisorInvocationContext ` + 'PRE_EXISTING_APP_PATHS_AUTHORITY' ` + 'PRE_EXISTING_APP_PATHS' ` + 'PROCESS_START' if (!$process.Start()) { throw 'pre-existing registry supervisor did not start' } + Set-SupervisorInvocationContext ` + 'PRE_EXISTING_APP_PATHS_AUTHORITY' ` + 'PRE_EXISTING_APP_PATHS' ` + 'PROCESS_WAIT' Assert-True ($process.WaitForExit(20000)) ` 'pre-existing registry supervisor exceeded its bound' + Set-SupervisorInvocationContext ` + 'PRE_EXISTING_APP_PATHS_AUTHORITY' ` + 'PRE_EXISTING_APP_PATHS' ` + 'PROCESS_OUTPUT' $output = $process.StandardOutput.ReadToEnd() $errorOutput = $process.StandardError.ReadToEnd() Assert-True ($process.ExitCode -ne 0) ` @@ -3246,19 +3687,42 @@ Initialize-TestInstaller try { Test-WorkflowCleanupStartupProtocol Test-WorkflowCleanupProtocolStateMachine - Test-BootstrapTimeout - Test-WindowsPowerShellCleanupCompatibility - Test-OperationDeadlineAndTreeTermination - Test-NegativeWorkerExitFinalization - Test-FailClosedMarkers - Test-LiveCancellationAndRedaction - Test-MsiTransactionInterruptionGates - Test-PrimaryWorkerFallbackForeignDescendants - Test-PreExistingCleanupOwnership - Test-SmokePromotionInterruptionAuthority - Test-PreExistingAppPathsAuthority - Test-HkcuInstalledValueOwnership - Test-ProvisionalUserMarkerOwnership + Test-SupervisorInvocationAttributionTotality + Invoke-SupervisorAttributedTest 'BOOTSTRAP_TIMEOUT' { Test-BootstrapTimeout } + Invoke-SupervisorAttributedTest 'WINDOWS_POWERSHELL_CLEANUP_COMPATIBILITY' { + Test-WindowsPowerShellCleanupCompatibility + } + Invoke-SupervisorAttributedTest 'OPERATION_DEADLINE_AND_TREE_TERMINATION' { + Test-OperationDeadlineAndTreeTermination + } + Invoke-SupervisorAttributedTest 'NEGATIVE_WORKER_EXIT_FINALIZATION' { + Test-NegativeWorkerExitFinalization + } + Invoke-SupervisorAttributedTest 'FAIL_CLOSED_MARKERS' { Test-FailClosedMarkers } + Invoke-SupervisorAttributedTest 'LIVE_CANCELLATION_AND_REDACTION' { + Test-LiveCancellationAndRedaction + } + Invoke-SupervisorAttributedTest 'MSI_TRANSACTION_INTERRUPTION_GATES' { + Test-MsiTransactionInterruptionGates + } + Invoke-SupervisorAttributedTest 'PRIMARY_WORKER_FALLBACK_FOREIGN_DESCENDANTS' { + Test-PrimaryWorkerFallbackForeignDescendants + } + Invoke-SupervisorAttributedTest 'PRE_EXISTING_CLEANUP_OWNERSHIP' { + Test-PreExistingCleanupOwnership + } + Invoke-SupervisorAttributedTest 'SMOKE_PROMOTION_INTERRUPTION_AUTHORITY' { + Test-SmokePromotionInterruptionAuthority + } + Invoke-SupervisorAttributedTest 'PRE_EXISTING_APP_PATHS_AUTHORITY' { + Test-PreExistingAppPathsAuthority + } + Invoke-SupervisorAttributedTest 'HKCU_INSTALLED_VALUE_OWNERSHIP' { + Test-HkcuInstalledValueOwnership + } + Invoke-SupervisorAttributedTest 'PROVISIONAL_USER_MARKER_OWNERSHIP' { + Test-ProvisionalUserMarkerOwnership + } Write-Host "PROPR_WINDOWS_SUPERVISOR_TESTS:${Architecture}:PASSED" [Console]::Out.Flush() } finally { diff --git a/apps/desktop/src/release-workflow.test.ts b/apps/desktop/src/release-workflow.test.ts index daaeff7da..a2fde2900 100644 --- a/apps/desktop/src/release-workflow.test.ts +++ b/apps/desktop/src/release-workflow.test.ts @@ -596,6 +596,23 @@ describe('desktop trusted release workflow', () => { assert.match(installedWindowsAppSupervisorBehaviorTest, /Test-PreExistingCleanupOwnership/); assert.match(installedWindowsAppSupervisorBehaviorTest, /Start-ExternallyInterruptibleSupervisor/); assert.match(installedWindowsAppSupervisorBehaviorTest, /Invoke-WorkflowCleanupController/); + assert.match(installedWindowsAppSupervisorBehaviorTest, /Test-SupervisorInvocationAttributionTotality/); + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /PROPR_WINDOWS_SUPERVISOR_INVOCATION:TEST:\{0\}:' \+\s*'SCENARIO:\{1\}:PHASE:\{2\}:FAILED/, + ); + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /PROPR_WINDOWS_SUPERVISOR_INVOCATION_ATTRIBUTION:TOTAL:PASSED/, + ); + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /Invoke-SupervisorAttributedTest 'BOOTSTRAP_TIMEOUT'[\s\S]*Invoke-SupervisorAttributedTest 'PROVISIONAL_USER_MARKER_OWNERSHIP'/, + ); + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /Assert-SupervisorInvocationDiagnosticBounded[\s\S]*Cannot bind argument[\s\S]*LiteralPath[\s\S]*Registry::[\s\S]*stdout[\s\S]*stderr/, + ); assert.match(installedWindowsAppSupervisorBehaviorTest, /Test-PreExistingAppPathsAuthority/); assert.match(installedWindowsAppSupervisorBehaviorTest, /Assert-ProcessTreeGone/); assert.match(installedWindowsAppSupervisorBehaviorTest, /Assert-OwnedFixtureAuthorityComplete/); From 44d7af703b350dac708af1b962c4f286b5d74818 Mon Sep 17 00:00:00 2001 From: "propr-dev[bot]" <1316198+propr-dev[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 06:57:15 +0000 Subject: [PATCH 10/33] feat(ai): Implemented the follow-up changes without committing. Implemented the follow-up changes without committing. Changed: - Added exact token-only supervisor diagnostics with `TEST/SCENARIO/PHASE/CALLSITE/FIELD`, replacing loose `-clike` prefix trust with anchored allowlisted validation in [test-installed-windows-app-supervisor.ps1](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T06-42-44/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1:1399). - Split `DURING_MSI` process-state attribution across critical gate path/read, process-state read, and process-tree assertion, with typed state validation. - Split byte-identical resource assertions across `Executable`, `ExecutableBackup`, `ManifestPath`, `RunId`, restore, retry, and final absence checks. - Fixed the byte-identical fixture producer so `ExecutableBackup` and `ByteIdenticalReplacement` are force-published after replacement in [test-installed-windows-app-supervisor-fixture.ps1](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T06-42-44/apps/desktop/scripts/test-installed-windows-app-supervisor-fixture.ps1:660). - Added hostile forged-prefix-plus-secret and exact missing-field attribution regressions. - Updated the release workflow contract assertions in [release-workflow.test.ts](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T06-42-44/apps/desktop/src/release-workflow.test.ts:600). Validation: - `npx tsx --test apps/desktop/src/release-workflow.test.ts` passed. - `npm run test -w @propr/desktop` passed: 177 passed, 6 skipped. - `git diff --check` passed. I could not run native `PROPR_WINDOWS_SUPERVISOR_TESTS:x64:PASSED` or `:arm64:PASSED` here because this container is Linux and has no `pwsh`/Windows runtime available. PR: #2057 Comment by: @integry (ID: 5505584230) Model: gpt-5.5 --- ...stalled-windows-app-supervisor-fixture.ps1 | 31 +- .../test-installed-windows-app-supervisor.ps1 | 563 +++++++++++++++++- apps/desktop/src/release-workflow.test.ts | 12 +- 3 files changed, 574 insertions(+), 32 deletions(-) diff --git a/apps/desktop/scripts/test-installed-windows-app-supervisor-fixture.ps1 b/apps/desktop/scripts/test-installed-windows-app-supervisor-fixture.ps1 index bfa2c2587..da9cfebe8 100644 --- a/apps/desktop/scripts/test-installed-windows-app-supervisor-fixture.ps1 +++ b/apps/desktop/scripts/test-installed-windows-app-supervisor-fixture.ps1 @@ -138,11 +138,27 @@ function Write-FixtureOwnershipManifest($Manifest) { } function Write-FixtureCriticalGate([string]$Name) { - [IO.File]::WriteAllText( - (Join-Path $stateDirectory 'critical-gate.txt'), - $Name, - [Text.Encoding]::ASCII + if (!$stateDirectory -or !(Test-Path -LiteralPath $stateDirectory -PathType Container)) { + throw 'fixture critical gate state directory is invalid' + } + $gatePath = Join-Path $stateDirectory 'critical-gate.txt' + $temporaryGatePath = "$gatePath.$PID.new" + $bytes = [Text.Encoding]::ASCII.GetBytes($Name) + $stream = [IO.FileStream]::new( + $temporaryGatePath, + [IO.FileMode]::Create, + [IO.FileAccess]::Write, + [IO.FileShare]::None, + 4096, + [IO.FileOptions]::WriteThrough ) + try { + $stream.Write($bytes, 0, $bytes.Length) + $stream.Flush($true) + } finally { + $stream.Dispose() + } + [IO.File]::Move($temporaryGatePath, $gatePath, $true) } function Write-FixtureOwnershipToken([string]$Path, [string]$Token) { @@ -647,9 +663,9 @@ function Replace-FixtureExecutableByteIdenticallyViaMove { [IO.File]::Copy($state.Executable, $replacement, $false) Move-Item -LiteralPath $state.Executable -Destination $backup -ErrorAction Stop Move-Item -LiteralPath $replacement -Destination $state.Executable -ErrorAction Stop - $state | Add-Member -NotePropertyName ExecutableBackup -NotePropertyValue $backup + $state | Add-Member -NotePropertyName ExecutableBackup -NotePropertyValue $backup -Force $state | Add-Member -NotePropertyName ByteIdenticalReplacement ` - -NotePropertyValue $true + -NotePropertyValue $true -Force $state | ConvertTo-Json -Compress | Set-Content -LiteralPath ` (Join-Path $stateDirectory 'resources.json') -Encoding ASCII } @@ -776,6 +792,9 @@ try { } $descendant = Start-FixtureDescendant +if ($PID -le 0 -or $descendant.Id -le 0) { + throw 'fixture process state authority is invalid' +} $state = [ordered]@{ WorkerPid = $PID; DescendantPid = $descendant.Id } $processStatePath = Join-Path $stateDirectory 'processes.json' $processStateTemporaryPath = "$processStatePath.$PID.new" diff --git a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 index 8a2f60056..789d5db6c 100644 --- a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 +++ b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 @@ -26,6 +26,8 @@ $dummyInstallerSha256 = $null $script:currentSupervisorInvocationTest = 'UNATTRIBUTED' $script:currentSupervisorInvocationScenario = 'UNATTRIBUTED' $script:currentSupervisorInvocationPhase = 'UNATTRIBUTED' +$script:currentSupervisorInvocationCallsite = 'GENERAL' +$script:currentSupervisorInvocationField = 'NONE' function Assert-True([bool]$Condition, [string]$Message) { if (!$Condition) { throw $Message } @@ -639,6 +641,19 @@ function New-SupervisorStartInfo( } function Read-FixtureProcessState([string]$StateDirectory) { + Set-SupervisorInvocationContext ` + $script:currentSupervisorInvocationTest ` + $script:currentSupervisorInvocationScenario ` + 'PROCESS_STATE' ` + 'PROCESS_STATE_PATH' ` + 'STATE_DIRECTORY' + Assert-True (![string]::IsNullOrWhiteSpace($StateDirectory)) ` + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + $script:currentSupervisorInvocationScenario ` + 'PROCESS_STATE' ` + 'PROCESS_STATE_PATH' ` + 'STATE_DIRECTORY') $statePath = Join-Path $StateDirectory 'processes.json' $stopwatch = [Diagnostics.Stopwatch]::StartNew() while (!(Test-Path -LiteralPath $statePath -PathType Leaf)) { @@ -647,7 +662,31 @@ function Read-FixtureProcessState([string]$StateDirectory) { } Start-Sleep -Milliseconds 25 } - return Get-Content -LiteralPath $statePath -Raw -Encoding ASCII | ConvertFrom-Json + Set-SupervisorInvocationContext ` + $script:currentSupervisorInvocationTest ` + $script:currentSupervisorInvocationScenario ` + 'PROCESS_STATE' ` + 'PROCESS_STATE_READ' ` + 'PROCESS_STATE_PATH' + $state = Get-Content -LiteralPath $statePath -Raw -Encoding ASCII | + ConvertFrom-Json -ErrorAction Stop + foreach ($field in @('WorkerPid','DescendantPid')) { + $property = $state.PSObject.Properties[$field] + $pidValue = 0 + Assert-True ($null -ne $property -and [int]::TryParse( + [string]$property.Value, + [Globalization.NumberStyles]::None, + [Globalization.CultureInfo]::InvariantCulture, + [ref]$pidValue + ) -and $pidValue -gt 0) ` + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + $script:currentSupervisorInvocationScenario ` + 'PROCESS_STATE' ` + 'PROCESS_STATE_READ' ` + (($field -creplace '([a-z])([A-Z])', '$1_$2').ToUpperInvariant())) + } + return $state } function Read-FixtureResourceState([string]$StateDirectory) { @@ -667,6 +706,12 @@ function Read-FixtureResourceState([string]$StateDirectory) { } function Assert-ProcessTreeGone($State) { + Set-SupervisorInvocationContext ` + $script:currentSupervisorInvocationTest ` + $script:currentSupervisorInvocationScenario ` + $script:currentSupervisorInvocationPhase ` + 'PROCESS_TREE_ASSERTION' ` + 'PROCESS_TREE' $stopwatch = [Diagnostics.Stopwatch]::StartNew() do { $worker = Get-Process -Id ([int]$State.WorkerPid) -ErrorAction SilentlyContinue @@ -1011,35 +1056,143 @@ function Assert-OwnedResourcesGone($Owned) { 'external cleanup left the run-owned profile behind' } +function Convert-FixtureAuthorityFieldToken([string]$Field) { + $token = ($Field -creplace '([a-z])([A-Z])', '$1_$2').ToUpperInvariant() + return Get-SanitizedSupervisorFieldToken $token +} + +function Assert-FixtureStateFieldsComplete( + $State, + [string]$Scenario, + [string]$Callsite, + [string[]]$Fields +) { + foreach ($field in $Fields) { + $fieldToken = Convert-FixtureAuthorityFieldToken $field + Set-SupervisorInvocationContext ` + $script:currentSupervisorInvocationTest ` + $Scenario ` + $script:currentSupervisorInvocationPhase ` + $Callsite ` + $fieldToken + $property = $State.PSObject.Properties[$field] + Assert-True ($null -ne $property -and + ![string]::IsNullOrWhiteSpace([string]$property.Value)) ` + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + $Scenario ` + $script:currentSupervisorInvocationPhase ` + $Callsite ` + $fieldToken) + } +} + function Restore-ReplacedFixtureAuthority($Owned) { Set-SupervisorInvocationContext ` $script:currentSupervisorInvocationTest ` $script:currentSupervisorInvocationScenario ` - 'AUTHORITY_RESTORE' + 'AUTHORITY_RESTORE' ` + 'AUTHORITY_RESTORE_WRITE' ` + 'TOKEN' + Assert-FixtureStateFieldsComplete ` + $Owned $script:currentSupervisorInvocationScenario ` + 'AUTHORITY_RESTORE_WRITE' @('OwnedRoot','Token') [IO.File]::WriteAllText( (Join-Path $Owned.OwnedRoot '.propr-installed-app-owner'), [string]$Owned.Token, [Text.Encoding]::ASCII ) if ($Owned.PSObject.Properties['InstallRootBackup']) { + Assert-FixtureStateFieldsComplete ` + $Owned $script:currentSupervisorInvocationScenario ` + 'AUTHORITY_RESTORE_REMOVE' @('InstallRoot') + Set-SupervisorInvocationContext ` + $script:currentSupervisorInvocationTest ` + $script:currentSupervisorInvocationScenario ` + 'AUTHORITY_RESTORE' ` + 'AUTHORITY_RESTORE_REMOVE' ` + 'INSTALL_ROOT' Remove-Item -LiteralPath $Owned.InstallRoot -Recurse -Force -ErrorAction Stop + Assert-FixtureStateFieldsComplete ` + $Owned $script:currentSupervisorInvocationScenario ` + 'AUTHORITY_RESTORE_MOVE' @('InstallRootBackup','InstallRoot') + Set-SupervisorInvocationContext ` + $script:currentSupervisorInvocationTest ` + $script:currentSupervisorInvocationScenario ` + 'AUTHORITY_RESTORE' ` + 'AUTHORITY_RESTORE_MOVE' ` + 'INSTALL_ROOT_BACKUP' Move-Item -LiteralPath $Owned.InstallRootBackup -Destination $Owned.InstallRoot ` -ErrorAction Stop } elseif ($Owned.PSObject.Properties['ExecutableBackup']) { + Assert-FixtureStateFieldsComplete ` + $Owned $script:currentSupervisorInvocationScenario ` + 'AUTHORITY_RESTORE_REMOVE' @('Executable') + Set-SupervisorInvocationContext ` + $script:currentSupervisorInvocationTest ` + $script:currentSupervisorInvocationScenario ` + 'AUTHORITY_RESTORE' ` + 'AUTHORITY_RESTORE_REMOVE' ` + 'EXECUTABLE' Remove-Item -LiteralPath $Owned.Executable -Force -ErrorAction Stop + Assert-FixtureStateFieldsComplete ` + $Owned $script:currentSupervisorInvocationScenario ` + 'AUTHORITY_RESTORE_MOVE' @('ExecutableBackup','Executable') + Set-SupervisorInvocationContext ` + $script:currentSupervisorInvocationTest ` + $script:currentSupervisorInvocationScenario ` + 'AUTHORITY_RESTORE' ` + 'AUTHORITY_RESTORE_MOVE' ` + 'EXECUTABLE_BACKUP' Move-Item -LiteralPath $Owned.ExecutableBackup -Destination $Owned.Executable ` -ErrorAction Stop } + Assert-FixtureStateFieldsComplete ` + $Owned $script:currentSupervisorInvocationScenario ` + 'AUTHORITY_RESTORE_WRITE' @('ShortcutFolder','Token') + Set-SupervisorInvocationContext ` + $script:currentSupervisorInvocationTest ` + $script:currentSupervisorInvocationScenario ` + 'AUTHORITY_RESTORE' ` + 'AUTHORITY_RESTORE_WRITE' ` + 'SHORTCUT_FOLDER' [IO.File]::WriteAllText( (Join-Path $Owned.ShortcutFolder '.propr-installed-app-owner'), [string]$Owned.Token, [Text.Encoding]::ASCII ) if ($Owned.PSObject.Properties['ShortcutBackup']) { + Assert-FixtureStateFieldsComplete ` + $Owned $script:currentSupervisorInvocationScenario ` + 'AUTHORITY_RESTORE_REMOVE' @('Shortcut') + Set-SupervisorInvocationContext ` + $script:currentSupervisorInvocationTest ` + $script:currentSupervisorInvocationScenario ` + 'AUTHORITY_RESTORE' ` + 'AUTHORITY_RESTORE_REMOVE' ` + 'SHORTCUT' Remove-Item -LiteralPath $Owned.Shortcut -Force -ErrorAction Stop + Assert-FixtureStateFieldsComplete ` + $Owned $script:currentSupervisorInvocationScenario ` + 'AUTHORITY_RESTORE_MOVE' @('ShortcutBackup','Shortcut') + Set-SupervisorInvocationContext ` + $script:currentSupervisorInvocationTest ` + $script:currentSupervisorInvocationScenario ` + 'AUTHORITY_RESTORE' ` + 'AUTHORITY_RESTORE_MOVE' ` + 'SHORTCUT_BACKUP' Move-Item -LiteralPath $Owned.ShortcutBackup -Destination $Owned.Shortcut ` -ErrorAction Stop } + Assert-FixtureStateFieldsComplete ` + $Owned $script:currentSupervisorInvocationScenario ` + 'AUTHORITY_RESTORE_WRITE' @('RegistryPath','Token') + Set-SupervisorInvocationContext ` + $script:currentSupervisorInvocationTest ` + $script:currentSupervisorInvocationScenario ` + 'AUTHORITY_RESTORE' ` + 'AUTHORITY_RESTORE_WRITE' ` + 'REGISTRY_PATH' Set-ItemProperty -LiteralPath $Owned.RegistryPath ` -Name 'ProPRInstalledAppOwner' -Value ([string]$Owned.Token) } @@ -1063,7 +1216,12 @@ function Assert-ReplacedExecutableSurvives($Owned) { Set-SupervisorInvocationContext ` $script:currentSupervisorInvocationTest ` $script:currentSupervisorInvocationScenario ` - 'RESOURCE_ASSERTION' + 'RESOURCE_ASSERTION' ` + 'REPLACEMENT_SURVIVAL_READ' ` + 'EXECUTABLE' + Assert-FixtureStateFieldsComplete ` + $Owned $script:currentSupervisorInvocationScenario ` + 'REPLACEMENT_SURVIVAL_READ' @('Executable') $expected = if ($Owned.PSObject.Properties['ByteIdenticalReplacement']) { 'owned-executable' } else { 'foreign-executable' } @@ -1238,27 +1396,90 @@ function Get-SupervisorInvocationPhases { ) } +function Get-SupervisorInvocationCallsites { + return @( + 'UNATTRIBUTED', + 'GENERAL', + 'CRITICAL_GATE_PATH', + 'CRITICAL_GATE_READ', + 'PROCESS_STATE_PATH', + 'PROCESS_STATE_READ', + 'PROCESS_TREE_ASSERTION', + 'RESOURCE_FIELD_VALIDATION', + 'REPLACEMENT_SURVIVAL_READ', + 'AUTHORITY_RESTORE_WRITE', + 'AUTHORITY_RESTORE_REMOVE', + 'AUTHORITY_RESTORE_MOVE', + 'WORKFLOW_CLEANUP_RETRY', + 'FINAL_ABSENCE_CHECK' + ) +} + +function Get-SupervisorInvocationFields { + return @( + 'NONE', + 'STATE_DIRECTORY', + 'CRITICAL_GATE_PATH', + 'CRITICAL_GATE_CONTENT', + 'PROCESS_STATE_PATH', + 'WORKER_PID', + 'DESCENDANT_PID', + 'PROCESS_TREE', + 'OWNED_ROOT', + 'INSTALL_ROOT', + 'SHORTCUT_FOLDER', + 'SHORTCUT', + 'SMOKE_DIRECTORY', + 'REGISTRY_PATH', + 'REGISTRY_ROOT', + 'USER_NAME', + 'USER_SID', + 'PROFILE_PATH', + 'MANIFEST_PATH', + 'RUN_ID', + 'TOKEN', + 'EXECUTABLE', + 'EXECUTABLE_BACKUP', + 'SHORTCUT_BACKUP', + 'INSTALL_ROOT_BACKUP', + 'BYTE_IDENTICAL_REPLACEMENT' + ) +} + function Get-SanitizedSupervisorInvocationToken([string]$Token, [string[]]$AllowList) { if ($Token -cin $AllowList) { return $Token } return 'UNATTRIBUTED' } +function Get-SanitizedSupervisorFieldToken([string]$Token) { + if ($Token -cin (Get-SupervisorInvocationFields)) { return $Token } + return 'NONE' +} + function Get-SanitizedSupervisorInvocationDiagnostic( [string]$Test, [string]$Scenario, - [string]$Phase + [string]$Phase, + [string]$Callsite = 'GENERAL', + [string]$Field = 'NONE' ) { $testName = Get-SanitizedSupervisorInvocationToken $Test (Get-SupervisorInvocationTests) $scenarioName = Get-SanitizedSupervisorInvocationToken $Scenario (Get-SupervisorInvocationScenarios) $phaseName = Get-SanitizedSupervisorInvocationToken $Phase (Get-SupervisorInvocationPhases) + $callsiteName = Get-SanitizedSupervisorInvocationToken ` + $Callsite (Get-SupervisorInvocationCallsites) + $fieldName = Get-SanitizedSupervisorFieldToken $Field return ('PROPR_WINDOWS_SUPERVISOR_INVOCATION:TEST:{0}:' + - 'SCENARIO:{1}:PHASE:{2}:FAILED') -f $testName, $scenarioName, $phaseName + 'SCENARIO:{1}:PHASE:{2}:CALLSITE:{3}:FIELD:{4}:FAILED') -f ` + $testName, $scenarioName, $phaseName, $callsiteName, $fieldName } function Set-SupervisorInvocationContext( [string]$Test, [string]$Scenario, - [string]$Phase + [string]$Phase, + [string]$Callsite = 'GENERAL', + [string]$Field = 'NONE' ) { $script:currentSupervisorInvocationTest = Get-SanitizedSupervisorInvocationToken $Test (Get-SupervisorInvocationTests) @@ -1266,32 +1487,58 @@ function Set-SupervisorInvocationContext( Get-SanitizedSupervisorInvocationToken $Scenario (Get-SupervisorInvocationScenarios) $script:currentSupervisorInvocationPhase = Get-SanitizedSupervisorInvocationToken $Phase (Get-SupervisorInvocationPhases) + $script:currentSupervisorInvocationCallsite = + Get-SanitizedSupervisorInvocationToken $Callsite (Get-SupervisorInvocationCallsites) + $script:currentSupervisorInvocationField = Get-SanitizedSupervisorFieldToken $Field +} + +function Test-SupervisorInvocationDiagnosticExact([string]$Diagnostic) { + $match = [regex]::Match( + [string]$Diagnostic, + ('^PROPR_WINDOWS_SUPERVISOR_INVOCATION:TEST:([A-Z_]+):' + + 'SCENARIO:([A-Z_]+):PHASE:([A-Z_]+):CALLSITE:([A-Z_]+):' + + 'FIELD:([A-Z_]+):FAILED$') + ) + if (!$match.Success) { return $false } + return $match.Groups[1].Value -cin (Get-SupervisorInvocationTests) -and + $match.Groups[2].Value -cin (Get-SupervisorInvocationScenarios) -and + $match.Groups[3].Value -cin (Get-SupervisorInvocationPhases) -and + $match.Groups[4].Value -cin (Get-SupervisorInvocationCallsites) -and + $match.Groups[5].Value -cin (Get-SupervisorInvocationFields) } function Invoke-SupervisorAttributedBoundary( [string]$Test, [string]$Scenario, [string]$Phase, - [scriptblock]$Action + [scriptblock]$Action, + [string]$Callsite = 'GENERAL', + [string]$Field = 'NONE' ) { $previousTest = $script:currentSupervisorInvocationTest $previousScenario = $script:currentSupervisorInvocationScenario $previousPhase = $script:currentSupervisorInvocationPhase - Set-SupervisorInvocationContext $Test $Scenario $Phase + $previousCallsite = $script:currentSupervisorInvocationCallsite + $previousField = $script:currentSupervisorInvocationField + Set-SupervisorInvocationContext $Test $Scenario $Phase $Callsite $Field try { & $Action } catch { - if ($_.Exception.Message -clike 'PROPR_WINDOWS_SUPERVISOR_INVOCATION:*') { + if (Test-SupervisorInvocationDiagnosticExact $_.Exception.Message) { throw } throw (Get-SanitizedSupervisorInvocationDiagnostic ` $script:currentSupervisorInvocationTest ` $script:currentSupervisorInvocationScenario ` - $script:currentSupervisorInvocationPhase) + $script:currentSupervisorInvocationPhase ` + $script:currentSupervisorInvocationCallsite ` + $script:currentSupervisorInvocationField) } finally { $script:currentSupervisorInvocationTest = $previousTest $script:currentSupervisorInvocationScenario = $previousScenario $script:currentSupervisorInvocationPhase = $previousPhase + $script:currentSupervisorInvocationCallsite = $previousCallsite + $script:currentSupervisorInvocationField = $previousField } } @@ -1306,16 +1553,20 @@ function Assert-SupervisorInvocationDiagnosticBounded( [string]$Diagnostic, [string]$Test, [string]$Scenario, - [string]$Phase + [string]$Phase, + [string]$Callsite = 'GENERAL', + [string]$Field = 'NONE' ) { - $expected = Get-SanitizedSupervisorInvocationDiagnostic $Test $Scenario $Phase + $expected = Get-SanitizedSupervisorInvocationDiagnostic ` + $Test $Scenario $Phase $Callsite $Field Assert-True ($Diagnostic -ceq $expected) ` 'supervisor invocation attribution was not the exact fixed diagnostic' - Assert-True ([Text.Encoding]::ASCII.GetByteCount($Diagnostic) -le 224) ` + Assert-True ([Text.Encoding]::ASCII.GetByteCount($Diagnostic) -le 320) ` 'supervisor invocation attribution exceeded its bounded size' Assert-True ($Diagnostic -cmatch ( '^PROPR_WINDOWS_SUPERVISOR_INVOCATION:TEST:[A-Z_]+:' + - 'SCENARIO:[A-Z_]+:PHASE:[A-Z_]+:FAILED$' + 'SCENARIO:[A-Z_]+:PHASE:[A-Z_]+:CALLSITE:[A-Z_]+:' + + 'FIELD:[A-Z_]+:FAILED$' )) 'supervisor invocation attribution used a non-allowlisted token format' foreach ($forbidden in @( $secretNeedle, @@ -1327,7 +1578,6 @@ function Assert-SupervisorInvocationDiagnosticBounded( 'S-1-5-', 'fixture-user', 'credential', - 'manifest', 'stdout', 'stderr' )) { @@ -2029,12 +2279,63 @@ function Test-SupervisorInvocationAttributionTotality { [PSCustomObject]@{ Test='PROVISIONAL_USER_MARKER_OWNERSHIP'; Scenario='USER_MARKER_REPLACEMENT' Phase='WORKFLOW_CLEANUP_CONTROLLER' + }, + [PSCustomObject]@{ + Test='MSI_TRANSACTION_INTERRUPTION_GATES'; Scenario='DURING_MSI' + Phase='PROCESS_STATE'; Callsite='CRITICAL_GATE_PATH'; Field='STATE_DIRECTORY' + }, + [PSCustomObject]@{ + Test='MSI_TRANSACTION_INTERRUPTION_GATES'; Scenario='DURING_MSI' + Phase='PROCESS_STATE'; Callsite='CRITICAL_GATE_READ'; Field='CRITICAL_GATE_CONTENT' + }, + [PSCustomObject]@{ + Test='MSI_TRANSACTION_INTERRUPTION_GATES'; Scenario='DURING_MSI' + Phase='PROCESS_STATE'; Callsite='PROCESS_STATE_READ'; Field='PROCESS_STATE_PATH' + }, + [PSCustomObject]@{ + Test='MSI_TRANSACTION_INTERRUPTION_GATES'; Scenario='DURING_MSI' + Phase='PROCESS_STATE'; Callsite='PROCESS_TREE_ASSERTION'; Field='PROCESS_TREE' + }, + [PSCustomObject]@{ + Test='PRE_EXISTING_CLEANUP_OWNERSHIP' + Scenario='OWNED_EXECUTABLE_BYTE_IDENTICAL_REPLACED_THEN_DEADLINE' + Phase='RESOURCE_ASSERTION'; Callsite='REPLACEMENT_SURVIVAL_READ'; Field='EXECUTABLE' + }, + [PSCustomObject]@{ + Test='PRE_EXISTING_CLEANUP_OWNERSHIP' + Scenario='OWNED_EXECUTABLE_BYTE_IDENTICAL_REPLACED_THEN_DEADLINE' + Phase='RESOURCE_ASSERTION'; Callsite='RESOURCE_FIELD_VALIDATION'; Field='EXECUTABLE_BACKUP' + }, + [PSCustomObject]@{ + Test='PRE_EXISTING_CLEANUP_OWNERSHIP' + Scenario='OWNED_EXECUTABLE_BYTE_IDENTICAL_REPLACED_THEN_DEADLINE' + Phase='RESOURCE_ASSERTION'; Callsite='RESOURCE_FIELD_VALIDATION'; Field='MANIFEST_PATH' + }, + [PSCustomObject]@{ + Test='PRE_EXISTING_CLEANUP_OWNERSHIP' + Scenario='OWNED_EXECUTABLE_BYTE_IDENTICAL_REPLACED_THEN_DEADLINE' + Phase='RESOURCE_ASSERTION'; Callsite='WORKFLOW_CLEANUP_RETRY'; Field='RUN_ID' + }, + [PSCustomObject]@{ + Test='PRE_EXISTING_CLEANUP_OWNERSHIP' + Scenario='OWNED_EXECUTABLE_BYTE_IDENTICAL_REPLACED_THEN_DEADLINE' + Phase='RESOURCE_ASSERTION'; Callsite='FINAL_ABSENCE_CHECK'; Field='MANIFEST_PATH' } )) { $diagnostic = '' + $callsite = if ($case.PSObject.Properties['Callsite']) { + [string]$case.Callsite + } else { 'GENERAL' } + $field = if ($case.PSObject.Properties['Field']) { + [string]$case.Field + } else { 'NONE' } try { Invoke-SupervisorAttributedTest $case.Test { - Invoke-SupervisorAttributedBoundary $case.Test $case.Scenario $case.Phase { + Invoke-SupervisorAttributedBoundary ` + $case.Test $case.Scenario $case.Phase ` + -Callsite $callsite ` + -Field $field ` + -Action { throw ( "Cannot bind argument to parameter 'LiteralPath' because it is null. " + "$secretNeedle $testRoot Registry::HKEY_LOCAL_MACHINE S-1-5-21 " + @@ -2046,18 +2347,99 @@ function Test-SupervisorInvocationAttributionTotality { $diagnostic = $_.Exception.Message } Assert-SupervisorInvocationDiagnosticBounded ` - $diagnostic $case.Test $case.Scenario $case.Phase + $diagnostic $case.Test $case.Scenario $case.Phase $callsite $field } + $forgedDiagnostic = '' + try { + Invoke-SupervisorAttributedBoundary ` + 'PRE_EXISTING_CLEANUP_OWNERSHIP' ` + 'OWNED_EXECUTABLE_BYTE_IDENTICAL_REPLACED_THEN_DEADLINE' ` + 'RESOURCE_ASSERTION' ` + -Callsite 'RESOURCE_FIELD_VALIDATION' ` + -Field 'EXECUTABLE_BACKUP' ` + -Action { + throw ( + 'PROPR_WINDOWS_SUPERVISOR_INVOCATION:TEST:PRE_EXISTING_CLEANUP_OWNERSHIP:' + + 'SCENARIO:OWNED_EXECUTABLE_BYTE_IDENTICAL_REPLACED_THEN_DEADLINE:' + + 'PHASE:RESOURCE_ASSERTION:CALLSITE:RESOURCE_FIELD_VALIDATION:' + + "FIELD:EXECUTABLE_BACKUP:FAILED:$secretNeedle" + ) + } + } catch { + $forgedDiagnostic = $_.Exception.Message + } + Assert-SupervisorInvocationDiagnosticBounded ` + $forgedDiagnostic ` + 'PRE_EXISTING_CLEANUP_OWNERSHIP' ` + 'OWNED_EXECUTABLE_BYTE_IDENTICAL_REPLACED_THEN_DEADLINE' ` + 'RESOURCE_ASSERTION' ` + 'RESOURCE_FIELD_VALIDATION' ` + 'EXECUTABLE_BACKUP' + + $missingGateStateDirectoryDiagnostic = '' + try { + Set-SupervisorInvocationContext ` + 'MSI_TRANSACTION_INTERRUPTION_GATES' ` + 'DURING_MSI' ` + 'PROCESS_STATE' ` + 'CRITICAL_GATE_PATH' ` + 'STATE_DIRECTORY' + Assert-True (![string]::IsNullOrWhiteSpace('')) ` + (Get-SanitizedSupervisorInvocationDiagnostic ` + 'MSI_TRANSACTION_INTERRUPTION_GATES' ` + 'DURING_MSI' ` + 'PROCESS_STATE' ` + 'CRITICAL_GATE_PATH' ` + 'STATE_DIRECTORY') + } catch { + $missingGateStateDirectoryDiagnostic = $_.Exception.Message + } + Assert-SupervisorInvocationDiagnosticBounded ` + $missingGateStateDirectoryDiagnostic ` + 'MSI_TRANSACTION_INTERRUPTION_GATES' ` + 'DURING_MSI' ` + 'PROCESS_STATE' ` + 'CRITICAL_GATE_PATH' ` + 'STATE_DIRECTORY' + + $missingExecutableBackupDiagnostic = '' + try { + Set-SupervisorInvocationContext ` + 'PRE_EXISTING_CLEANUP_OWNERSHIP' ` + 'OWNED_EXECUTABLE_BYTE_IDENTICAL_REPLACED_THEN_DEADLINE' ` + 'RESOURCE_ASSERTION' ` + 'RESOURCE_FIELD_VALIDATION' ` + 'EXECUTABLE_BACKUP' + Assert-FixtureStateFieldsComplete ` + ([PSCustomObject]@{ + Executable = 'EXECUTABLE' + ManifestPath = 'MANIFEST_PATH' + RunId = 'RUN_ID' + }) ` + 'OWNED_EXECUTABLE_BYTE_IDENTICAL_REPLACED_THEN_DEADLINE' ` + 'RESOURCE_FIELD_VALIDATION' ` + @('ExecutableBackup') + } catch { + $missingExecutableBackupDiagnostic = $_.Exception.Message + } + Assert-SupervisorInvocationDiagnosticBounded ` + $missingExecutableBackupDiagnostic ` + 'PRE_EXISTING_CLEANUP_OWNERSHIP' ` + 'OWNED_EXECUTABLE_BYTE_IDENTICAL_REPLACED_THEN_DEADLINE' ` + 'RESOURCE_ASSERTION' ` + 'RESOURCE_FIELD_VALIDATION' ` + 'EXECUTABLE_BACKUP' foreach ($testName in Get-SupervisorInvocationTests) { foreach ($scenarioName in Get-SupervisorInvocationScenarios) { foreach ($phaseName in Get-SupervisorInvocationPhases) { $diagnostic = Get-SanitizedSupervisorInvocationDiagnostic ` $testName $scenarioName $phaseName - Assert-True ([Text.Encoding]::ASCII.GetByteCount($diagnostic) -le 224) ` + Assert-True ([Text.Encoding]::ASCII.GetByteCount($diagnostic) -le 320) ` 'an allowlisted supervisor invocation diagnostic exceeded its byte bound' Assert-True ($diagnostic -cmatch ( '^PROPR_WINDOWS_SUPERVISOR_INVOCATION:TEST:[A-Z_]+:' + - 'SCENARIO:[A-Z_]+:PHASE:[A-Z_]+:FAILED$' + 'SCENARIO:[A-Z_]+:PHASE:[A-Z_]+:CALLSITE:[A-Z_]+:' + + 'FIELD:[A-Z_]+:FAILED$' )) 'an allowlisted supervisor invocation diagnostic was not token-only' } } @@ -2199,10 +2581,27 @@ function Invoke-CriticalCancellationScenario([string]$Scenario) { Set-SupervisorInvocationContext ` $script:currentSupervisorInvocationTest $Scenario 'PROCESS_START' if (!$process.Start()) { throw 'critical-cancellation supervisor did not start' } + Set-SupervisorInvocationContext ` + $script:currentSupervisorInvocationTest ` + $Scenario ` + 'PROCESS_STATE' ` + 'CRITICAL_GATE_PATH' ` + 'STATE_DIRECTORY' + Assert-True (![string]::IsNullOrWhiteSpace($stateDirectory)) ` + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + $Scenario ` + 'PROCESS_STATE' ` + 'CRITICAL_GATE_PATH' ` + 'STATE_DIRECTORY') $gatePath = Join-Path $stateDirectory 'critical-gate.txt' $gateWait = [Diagnostics.Stopwatch]::StartNew() Set-SupervisorInvocationContext ` - $script:currentSupervisorInvocationTest $Scenario 'PROCESS_WAIT' + $script:currentSupervisorInvocationTest ` + $Scenario ` + 'PROCESS_WAIT' ` + 'CRITICAL_GATE_PATH' ` + 'CRITICAL_GATE_PATH' while (!(Test-Path -LiteralPath $gatePath -PathType Leaf)) { if ($gateWait.ElapsedMilliseconds -ge 45000) { throw 'critical-cancellation fixture did not reach its interruption gate' @@ -2210,7 +2609,11 @@ function Invoke-CriticalCancellationScenario([string]$Scenario) { Start-Sleep -Milliseconds 25 } Set-SupervisorInvocationContext ` - $script:currentSupervisorInvocationTest $Scenario 'PROCESS_STATE' + $script:currentSupervisorInvocationTest ` + $Scenario ` + 'PROCESS_STATE' ` + 'CRITICAL_GATE_READ' ` + 'CRITICAL_GATE_CONTENT' Assert-True ((Get-Content -LiteralPath $gatePath -Raw -Encoding ASCII) -ceq $Scenario) ` 'critical-cancellation fixture published the wrong interruption gate' [void]$cancellation.Set() @@ -2223,8 +2626,25 @@ function Invoke-CriticalCancellationScenario([string]$Scenario) { $output = $process.StandardOutput.ReadToEnd() $errorOutput = $process.StandardError.ReadToEnd() Set-SupervisorInvocationContext ` - $script:currentSupervisorInvocationTest $Scenario 'PROCESS_STATE' + $script:currentSupervisorInvocationTest ` + $Scenario ` + 'PROCESS_STATE' ` + 'PROCESS_TREE_ASSERTION' ` + 'PROCESS_TREE' Assert-ProcessTreeGone (Read-FixtureProcessState $stateDirectory) + Set-SupervisorInvocationContext ` + $script:currentSupervisorInvocationTest ` + $Scenario ` + 'PROCESS_STATE' ` + 'PROCESS_STATE_PATH' ` + 'STATE_DIRECTORY' + Assert-True (![string]::IsNullOrWhiteSpace($stateDirectory)) ` + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + $Scenario ` + 'PROCESS_STATE' ` + 'PROCESS_STATE_PATH' ` + 'STATE_DIRECTORY') return [PSCustomObject]@{ ExitCode = $process.ExitCode Output = $output @@ -2251,6 +2671,20 @@ function Test-MsiTransactionInterruptionGates { Assert-Contains $duringMsi.Output ` 'PROPR_WINDOWS_INSTALLED_SMOKE:WATCHDOG:POST_TERMINATION_CLEANUP:COMPLETE' ` 'DURING_MSI clean rollback did not complete bounded cleanup' + Set-SupervisorInvocationContext ` + 'MSI_TRANSACTION_INTERRUPTION_GATES' ` + 'DURING_MSI' ` + 'PROCESS_STATE' ` + 'FINAL_ABSENCE_CHECK' ` + 'OWNED_ROOT' + Assert-FixtureStateFieldsComplete ` + $duringMsi 'DURING_MSI' 'FINAL_ABSENCE_CHECK' @('StateDirectory') + Set-SupervisorInvocationContext ` + 'MSI_TRANSACTION_INTERRUPTION_GATES' ` + 'DURING_MSI' ` + 'PROCESS_STATE' ` + 'FINAL_ABSENCE_CHECK' ` + 'OWNED_ROOT' Assert-True (!(Test-Path -LiteralPath (Join-Path $duringMsi.StateDirectory 'owned'))) ` 'DURING_MSI rollback did not retain the exact clean fixture baseline' @@ -2782,22 +3216,101 @@ function Test-PreExistingCleanupOwnership { 'byte-identical replace-via-move did not fail closed on entry identity' $byteIdenticalOwned = Read-FixtureResourceState $byteIdenticalDirectory Assert-ReplacedExecutableSurvives $byteIdenticalOwned + Set-SupervisorInvocationContext ` + 'PRE_EXISTING_CLEANUP_OWNERSHIP' ` + 'OWNED_EXECUTABLE_BYTE_IDENTICAL_REPLACED_THEN_DEADLINE' ` + 'RESOURCE_ASSERTION' ` + 'RESOURCE_FIELD_VALIDATION' ` + 'MANIFEST_PATH' + Assert-FixtureStateFieldsComplete ` + $byteIdenticalOwned ` + 'OWNED_EXECUTABLE_BYTE_IDENTICAL_REPLACED_THEN_DEADLINE' ` + 'RESOURCE_FIELD_VALIDATION' ` + @('ManifestPath','RunId','Executable','ExecutableBackup') + Set-SupervisorInvocationContext ` + 'PRE_EXISTING_CLEANUP_OWNERSHIP' ` + 'OWNED_EXECUTABLE_BYTE_IDENTICAL_REPLACED_THEN_DEADLINE' ` + 'RESOURCE_ASSERTION' ` + 'REPLACEMENT_SURVIVAL_READ' ` + 'MANIFEST_PATH' $byteIdenticalManifest = Get-Content -LiteralPath $byteIdenticalOwned.ManifestPath ` -Raw -Encoding UTF8 | ConvertFrom-Json -ErrorAction Stop Assert-True ($byteIdenticalManifest.State -ceq 'ACTIVE') ` 'byte-identical replace-via-move discarded ACTIVE recovery authority' + Set-SupervisorInvocationContext ` + 'PRE_EXISTING_CLEANUP_OWNERSHIP' ` + 'OWNED_EXECUTABLE_BYTE_IDENTICAL_REPLACED_THEN_DEADLINE' ` + 'RESOURCE_ASSERTION' ` + 'AUTHORITY_RESTORE_REMOVE' ` + 'EXECUTABLE' + Assert-FixtureStateFieldsComplete ` + $byteIdenticalOwned ` + 'OWNED_EXECUTABLE_BYTE_IDENTICAL_REPLACED_THEN_DEADLINE' ` + 'AUTHORITY_RESTORE_REMOVE' ` + @('Executable') Remove-Item -LiteralPath $byteIdenticalOwned.Executable -Force -ErrorAction Stop + Set-SupervisorInvocationContext ` + 'PRE_EXISTING_CLEANUP_OWNERSHIP' ` + 'OWNED_EXECUTABLE_BYTE_IDENTICAL_REPLACED_THEN_DEADLINE' ` + 'RESOURCE_ASSERTION' ` + 'AUTHORITY_RESTORE_MOVE' ` + 'EXECUTABLE_BACKUP' + Assert-FixtureStateFieldsComplete ` + $byteIdenticalOwned ` + 'OWNED_EXECUTABLE_BYTE_IDENTICAL_REPLACED_THEN_DEADLINE' ` + 'AUTHORITY_RESTORE_MOVE' ` + @('ExecutableBackup','Executable') + Set-SupervisorInvocationContext ` + 'PRE_EXISTING_CLEANUP_OWNERSHIP' ` + 'OWNED_EXECUTABLE_BYTE_IDENTICAL_REPLACED_THEN_DEADLINE' ` + 'RESOURCE_ASSERTION' ` + 'AUTHORITY_RESTORE_MOVE' ` + 'EXECUTABLE_BACKUP' Move-Item -LiteralPath $byteIdenticalOwned.ExecutableBackup ` -Destination $byteIdenticalOwned.Executable -ErrorAction Stop + Set-SupervisorInvocationContext ` + 'PRE_EXISTING_CLEANUP_OWNERSHIP' ` + 'OWNED_EXECUTABLE_BYTE_IDENTICAL_REPLACED_THEN_DEADLINE' ` + 'RESOURCE_ASSERTION' ` + 'WORKFLOW_CLEANUP_RETRY' ` + 'RUN_ID' + Assert-FixtureStateFieldsComplete ` + $byteIdenticalOwned ` + 'OWNED_EXECUTABLE_BYTE_IDENTICAL_REPLACED_THEN_DEADLINE' ` + 'WORKFLOW_CLEANUP_RETRY' ` + @('ManifestPath','RunId') $byteIdenticalRetry = Invoke-WorkflowCleanupController ` 'EXECUTABLE_IDENTITY_RETRY' $byteIdenticalOwned.ManifestPath ` $byteIdenticalOwned.RunId $byteIdenticalDirectory Assert-True ($byteIdenticalRetry.ExitCode -eq 0 -and $byteIdenticalRetry.Result -ceq 'COMPLETE') ` 'byte-identical file cleanup did not succeed after exact entry identity restoration' - Assert-True (!(Test-Path -LiteralPath $byteIdenticalOwned.Executable) -and - !(Test-Path -LiteralPath $byteIdenticalOwned.ManifestPath)) ` - 'byte-identical file retry did not consume the exact owned entry and authority' + Set-SupervisorInvocationContext ` + 'PRE_EXISTING_CLEANUP_OWNERSHIP' ` + 'OWNED_EXECUTABLE_BYTE_IDENTICAL_REPLACED_THEN_DEADLINE' ` + 'RESOURCE_ASSERTION' ` + 'FINAL_ABSENCE_CHECK' ` + 'EXECUTABLE' + Assert-FixtureStateFieldsComplete ` + $byteIdenticalOwned ` + 'OWNED_EXECUTABLE_BYTE_IDENTICAL_REPLACED_THEN_DEADLINE' ` + 'FINAL_ABSENCE_CHECK' ` + @('Executable') + Assert-True (!(Test-Path -LiteralPath $byteIdenticalOwned.Executable)) ` + 'byte-identical file retry did not consume the exact owned entry' + Set-SupervisorInvocationContext ` + 'PRE_EXISTING_CLEANUP_OWNERSHIP' ` + 'OWNED_EXECUTABLE_BYTE_IDENTICAL_REPLACED_THEN_DEADLINE' ` + 'RESOURCE_ASSERTION' ` + 'FINAL_ABSENCE_CHECK' ` + 'MANIFEST_PATH' + Assert-FixtureStateFieldsComplete ` + $byteIdenticalOwned ` + 'OWNED_EXECUTABLE_BYTE_IDENTICAL_REPLACED_THEN_DEADLINE' ` + 'FINAL_ABSENCE_CHECK' ` + @('ManifestPath') + Assert-True (!(Test-Path -LiteralPath $byteIdenticalOwned.ManifestPath)) ` + 'byte-identical file retry did not consume the exact recovery authority' $foreignChildStateDirectory = New-StateDirectory 'in-place-foreign-child' $foreignChildResult = Invoke-FixtureScenario ` diff --git a/apps/desktop/src/release-workflow.test.ts b/apps/desktop/src/release-workflow.test.ts index a2fde2900..aef1f3319 100644 --- a/apps/desktop/src/release-workflow.test.ts +++ b/apps/desktop/src/release-workflow.test.ts @@ -599,7 +599,13 @@ describe('desktop trusted release workflow', () => { assert.match(installedWindowsAppSupervisorBehaviorTest, /Test-SupervisorInvocationAttributionTotality/); assert.match( installedWindowsAppSupervisorBehaviorTest, - /PROPR_WINDOWS_SUPERVISOR_INVOCATION:TEST:\{0\}:' \+\s*'SCENARIO:\{1\}:PHASE:\{2\}:FAILED/, + /PROPR_WINDOWS_SUPERVISOR_INVOCATION:TEST:\{0\}:' \+\s*'SCENARIO:\{1\}:PHASE:\{2\}:CALLSITE:\{3\}:FIELD:\{4\}:FAILED/, + ); + assert.match(installedWindowsAppSupervisorBehaviorTest, /Test-SupervisorInvocationDiagnosticExact/); + assert.doesNotMatch(installedWindowsAppSupervisorBehaviorTest, /-clike 'PROPR_WINDOWS_SUPERVISOR_INVOCATION:\*'/); + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /FIELD:EXECUTABLE_BACKUP:FAILED:\$secretNeedle[\s\S]*Assert-SupervisorInvocationDiagnosticBounded/, ); assert.match( installedWindowsAppSupervisorBehaviorTest, @@ -624,6 +630,10 @@ describe('desktop trusted release workflow', () => { installedWindowsAppSupervisorFixture, /\$registryRoot = "Registry::HKEY_LOCAL_MACHINE\\Software\\ProPRSupervisorFixture\\\$\(\$manifest\.RunId\)"[\s\S]*RegistryRoot = \$registryRoot/, ); + assert.match( + installedWindowsAppSupervisorFixture, + /ExecutableBackup -NotePropertyValue \$backup -Force[\s\S]*ByteIdenticalReplacement[\s\S]*-NotePropertyValue \$true -Force/, + ); assert.ok( installedWindowsAppSupervisorBehaviorTest.indexOf( "Assert-OwnedFixtureAuthorityComplete $owned 'OWNED_RESOURCES_THEN_DEADLINE'", From 4eeba6636f14d3d3ce1a4f209b96b21216c98651 Mon Sep 17 00:00:00 2001 From: "propr-dev[bot]" <1316198+propr-dev[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 07:11:00 +0000 Subject: [PATCH 11/33] feat(ai): Implemented the narrow follow-up on head `44d7af7`. Implemented the narrow follow-up on head `44d7af7`. Changed: - [test-installed-windows-app-supervisor.ps1](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T07-00-45/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1:1510): added fixed allowlisted attribution-totality case ids and failure evidence in the form `CASE::EXPECTED::OBSERVED:`. - Preserved exact nested diagnostics only through the anchored allowlist validator, and made the totality harness use named parameter binding for the nested wrapper path. - Added deterministic coverage for forged-prefix sanitization, explicit producer cases, every allowlisted callsite/field token, and PS5.1 gate publication compatibility. - [test-installed-windows-app-supervisor-fixture.ps1](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T07-00-45/apps/desktop/scripts/test-installed-windows-app-supervisor-fixture.ps1:140): replaced the critical-gate `[IO.File]::Move(source,destination,$true)` with flushed same-directory temp publication, explicit destination handling, and PS5.1-supported two-argument `File.Move`. - [release-workflow.test.ts](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T07-00-45/apps/desktop/src/release-workflow.test.ts:602): added static release-contract assertions for the new attribution evidence and gate publisher shape. Verified: - `npx tsx --test apps/desktop/src/release-workflow.test.ts` passes. - `git diff --check` passes. - Static sweep confirms the critical-gate three-argument move and old generic exact-diagnostic failure message are gone. I could not run fresh native Windows x64/ARM64 here because this environment has no `pwsh`/Windows runtime. PR: #2057 Comment by: @integry (ID: 5505764254) Model: gpt-5.5 --- ...stalled-windows-app-supervisor-fixture.ps1 | 8 +- .../test-installed-windows-app-supervisor.ps1 | 193 ++++++++++++++++-- apps/desktop/src/release-workflow.test.ts | 28 +++ 3 files changed, 208 insertions(+), 21 deletions(-) diff --git a/apps/desktop/scripts/test-installed-windows-app-supervisor-fixture.ps1 b/apps/desktop/scripts/test-installed-windows-app-supervisor-fixture.ps1 index da9cfebe8..1ec74466c 100644 --- a/apps/desktop/scripts/test-installed-windows-app-supervisor-fixture.ps1 +++ b/apps/desktop/scripts/test-installed-windows-app-supervisor-fixture.ps1 @@ -158,7 +158,13 @@ function Write-FixtureCriticalGate([string]$Name) { } finally { $stream.Dispose() } - [IO.File]::Move($temporaryGatePath, $gatePath, $true) + if ([IO.Directory]::Exists($gatePath)) { + throw 'fixture critical gate destination is not a file' + } + if ([IO.File]::Exists($gatePath)) { + [IO.File]::Delete($gatePath) + } + [IO.File]::Move($temporaryGatePath, $gatePath) } function Write-FixtureOwnershipToken([string]$Path, [string]$Token) { diff --git a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 index 789d5db6c..7b8d089c4 100644 --- a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 +++ b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 @@ -1507,6 +1507,78 @@ function Test-SupervisorInvocationDiagnosticExact([string]$Diagnostic) { $match.Groups[5].Value -cin (Get-SupervisorInvocationFields) } +function Get-SupervisorAttributionTotalityCases { + return @( + 'GENERAL', + 'BOUNDARY_BOOTSTRAP_TIMEOUT', + 'BOUNDARY_WINDOWS_POWERSHELL_COMPATIBILITY', + 'BOUNDARY_OPERATION_DEADLINE', + 'BOUNDARY_NEGATIVE_WORKER_EXIT', + 'BOUNDARY_FAIL_CLOSED_MARKER', + 'BOUNDARY_LIVE_CANCELLATION', + 'BOUNDARY_MSI_INTERRUPTION', + 'BOUNDARY_PRIMARY_FALLBACK', + 'BOUNDARY_PRE_EXISTING_CLEANUP', + 'BOUNDARY_SMOKE_PROMOTION', + 'BOUNDARY_APP_PATHS_AUTHORITY', + 'BOUNDARY_HKCU_OWNERSHIP', + 'BOUNDARY_USER_MARKER', + 'CALLSITE_CRITICAL_GATE_PATH', + 'CALLSITE_CRITICAL_GATE_READ', + 'CALLSITE_PROCESS_STATE_READ', + 'CALLSITE_PROCESS_TREE_ASSERTION', + 'CALLSITE_REPLACEMENT_SURVIVAL_READ', + 'FIELD_EXECUTABLE_BACKUP', + 'FIELD_MANIFEST_PATH', + 'CALLSITE_WORKFLOW_CLEANUP_RETRY', + 'CALLSITE_FINAL_ABSENCE_CHECK', + 'FORGED_PREFIX_SECRET', + 'MISSING_GATE_STATE_DIRECTORY', + 'MISSING_EXECUTABLE_BACKUP', + 'ALLOWLISTED_TEST_SCENARIO_PHASE', + 'ALLOWLISTED_CALLSITE_FIELD', + 'POWERSHELL_GATE_PUBLICATION' + ) +} + +function Get-SanitizedSupervisorAttributionCaseToken([string]$Token) { + if ($Token -cin (Get-SupervisorAttributionTotalityCases)) { return $Token } + return 'GENERAL' +} + +function Get-SupervisorInvocationDiagnosticClassification( + [string]$Diagnostic, + [string]$Expected +) { + if ([string]::IsNullOrWhiteSpace($Diagnostic)) { return 'missing' } + if (Test-SupervisorInvocationDiagnosticExact $Diagnostic) { + if ($Diagnostic -ceq $Expected) { return 'exact' } + return 'sanitized-to-context' + } + return 'malformed' +} + +function Get-SupervisorAttributionTotalityAssertionMessage( + [string]$CaseId, + [string]$ExpectedClassification, + [string]$ObservedClassification +) { + $caseName = Get-SanitizedSupervisorAttributionCaseToken $CaseId + if ($ExpectedClassification -cnotin @( + 'exact','sanitized-to-context','malformed','missing' + )) { + $ExpectedClassification = 'malformed' + } + if ($ObservedClassification -cnotin @( + 'exact','sanitized-to-context','malformed','missing' + )) { + $ObservedClassification = 'malformed' + } + return ('supervisor invocation attribution totality diverged:' + + 'CASE:{0}:EXPECTED:{1}:OBSERVED:{2}') -f ` + $caseName, $ExpectedClassification, $ObservedClassification +} + function Invoke-SupervisorAttributedBoundary( [string]$Test, [string]$Scenario, @@ -1546,7 +1618,11 @@ function Invoke-SupervisorAttributedTest( [string]$Test, [scriptblock]$Action ) { - Invoke-SupervisorAttributedBoundary $Test 'TEST' 'TEST' $Action + Invoke-SupervisorAttributedBoundary ` + -Test $Test ` + -Scenario 'TEST' ` + -Phase 'TEST' ` + -Action $Action } function Assert-SupervisorInvocationDiagnosticBounded( @@ -1555,19 +1631,26 @@ function Assert-SupervisorInvocationDiagnosticBounded( [string]$Scenario, [string]$Phase, [string]$Callsite = 'GENERAL', - [string]$Field = 'NONE' + [string]$Field = 'NONE', + [string]$CaseId = 'GENERAL' ) { $expected = Get-SanitizedSupervisorInvocationDiagnostic ` $Test $Scenario $Phase $Callsite $Field Assert-True ($Diagnostic -ceq $expected) ` - 'supervisor invocation attribution was not the exact fixed diagnostic' + (Get-SupervisorAttributionTotalityAssertionMessage ` + $CaseId 'exact' ` + (Get-SupervisorInvocationDiagnosticClassification $Diagnostic $expected)) Assert-True ([Text.Encoding]::ASCII.GetByteCount($Diagnostic) -le 320) ` - 'supervisor invocation attribution exceeded its bounded size' + (Get-SupervisorAttributionTotalityAssertionMessage ` + $CaseId 'exact' ` + (Get-SupervisorInvocationDiagnosticClassification $Diagnostic $expected)) Assert-True ($Diagnostic -cmatch ( '^PROPR_WINDOWS_SUPERVISOR_INVOCATION:TEST:[A-Z_]+:' + 'SCENARIO:[A-Z_]+:PHASE:[A-Z_]+:CALLSITE:[A-Z_]+:' + 'FIELD:[A-Z_]+:FAILED$' - )) 'supervisor invocation attribution used a non-allowlisted token format' + )) (Get-SupervisorAttributionTotalityAssertionMessage ` + $CaseId 'exact' ` + (Get-SupervisorInvocationDiagnosticClassification $Diagnostic $expected)) foreach ($forbidden in @( $secretNeedle, $testRoot, @@ -1582,10 +1665,32 @@ function Assert-SupervisorInvocationDiagnosticBounded( 'stderr' )) { Assert-NotContains $Diagnostic $forbidden ` - 'supervisor invocation attribution disclosed raw failure context' + (Get-SupervisorAttributionTotalityAssertionMessage ` + $CaseId 'exact' ` + (Get-SupervisorInvocationDiagnosticClassification $Diagnostic $expected)) } } +function Test-CriticalGatePublisherPowerShellCompatibility { + $fixtureText = Get-Content -LiteralPath $fixtureWorkerPath -Raw -Encoding UTF8 + $match = [regex]::Match( + $fixtureText, + '(?sm)function Write-FixtureCriticalGate\(\[string\]\$Name\) \{(?.*?)^\}' + ) + Assert-True ($match.Success) ` + (Get-SupervisorAttributionTotalityAssertionMessage ` + 'POWERSHELL_GATE_PUBLICATION' 'exact' 'missing') + $body = $match.Groups['body'].Value + Assert-True ($body -cmatch '\[IO\.FileOptions\]::WriteThrough' -and + $body -cmatch '\$stream\.Flush\(\$true\)' -and + $body -cmatch '\[IO\.Directory\]::Exists\(\$gatePath\)' -and + $body -cmatch '\[IO\.File\]::Delete\(\$gatePath\)' -and + $body -cmatch '\[IO\.File\]::Move\(\$temporaryGatePath, \$gatePath\)' -and + $body -cnotmatch '\[IO\.File\]::Move\(\$temporaryGatePath, \$gatePath, \$true\)') ` + (Get-SupervisorAttributionTotalityAssertionMessage ` + 'POWERSHELL_GATE_PUBLICATION' 'exact' 'malformed') +} + function Get-WorkflowCleanupProtocolMismatchDiagnostic( [string]$InvocationIdentifier, [string]$ObservedLineCategory, @@ -2230,93 +2335,115 @@ function Test-WorkflowCleanupProtocolStateMachine { function Test-SupervisorInvocationAttributionTotality { foreach ($case in @( [PSCustomObject]@{ + CaseId='BOUNDARY_BOOTSTRAP_TIMEOUT' Test='BOOTSTRAP_TIMEOUT'; Scenario='NO_MARKER'; Phase='SUPERVISOR_PROCESS' }, [PSCustomObject]@{ + CaseId='BOUNDARY_WINDOWS_POWERSHELL_COMPATIBILITY' Test='WINDOWS_POWERSHELL_CLEANUP_COMPATIBILITY' Scenario='NO_MARKER_WINDOWS_POWERSHELL'; Phase='SUPERVISOR_PROCESS' }, [PSCustomObject]@{ + CaseId='BOUNDARY_OPERATION_DEADLINE' Test='OPERATION_DEADLINE_AND_TREE_TERMINATION' Scenario='VALID_THEN_DEADLINE'; Phase='SUPERVISOR_PROCESS' }, [PSCustomObject]@{ + CaseId='BOUNDARY_NEGATIVE_WORKER_EXIT' Test='NEGATIVE_WORKER_EXIT_FINALIZATION' Scenario='NEGATIVE_EXIT'; Phase='SUPERVISOR_PROCESS' }, [PSCustomObject]@{ + CaseId='BOUNDARY_FAIL_CLOSED_MARKER' Test='FAIL_CLOSED_MARKERS'; Scenario='MALFORMED_MARKER' Phase='SUPERVISOR_PROCESS' }, [PSCustomObject]@{ + CaseId='BOUNDARY_LIVE_CANCELLATION' Test='LIVE_CANCELLATION_AND_REDACTION'; Scenario='CANCELLATION' Phase='PROCESS_OUTPUT' }, [PSCustomObject]@{ + CaseId='BOUNDARY_MSI_INTERRUPTION' Test='MSI_TRANSACTION_INTERRUPTION_GATES'; Scenario='DURING_MSI' Phase='PROCESS_WAIT' }, [PSCustomObject]@{ + CaseId='BOUNDARY_PRIMARY_FALLBACK' Test='PRIMARY_WORKER_FALLBACK_FOREIGN_DESCENDANTS' Scenario='PRIMARY_FALLBACK_FOREIGN_DESCENDANTS'; Phase='SUPERVISOR_PROCESS' }, [PSCustomObject]@{ + CaseId='BOUNDARY_PRE_EXISTING_CLEANUP' Test='PRE_EXISTING_CLEANUP_OWNERSHIP' Scenario='OWNED_RESOURCES_THEN_DEADLINE'; Phase='RESOURCE_STATE' }, [PSCustomObject]@{ + CaseId='BOUNDARY_SMOKE_PROMOTION' Test='SMOKE_PROMOTION_INTERRUPTION_AUTHORITY' Scenario='SMOKE_BEFORE_PROMOTION_THEN_DEADLINE'; Phase='RESOURCE_STATE' }, [PSCustomObject]@{ + CaseId='BOUNDARY_APP_PATHS_AUTHORITY' Test='PRE_EXISTING_APP_PATHS_AUTHORITY'; Scenario='PRE_EXISTING_APP_PATHS' Phase='PROCESS_OUTPUT' }, [PSCustomObject]@{ + CaseId='BOUNDARY_HKCU_OWNERSHIP' Test='HKCU_INSTALLED_VALUE_OWNERSHIP'; Scenario='HKCU_BASELINE_RESTORE' Phase='WORKFLOW_CLEANUP_CONTROLLER' }, [PSCustomObject]@{ + CaseId='BOUNDARY_USER_MARKER' Test='PROVISIONAL_USER_MARKER_OWNERSHIP'; Scenario='USER_MARKER_REPLACEMENT' Phase='WORKFLOW_CLEANUP_CONTROLLER' }, [PSCustomObject]@{ + CaseId='CALLSITE_CRITICAL_GATE_PATH' Test='MSI_TRANSACTION_INTERRUPTION_GATES'; Scenario='DURING_MSI' Phase='PROCESS_STATE'; Callsite='CRITICAL_GATE_PATH'; Field='STATE_DIRECTORY' }, [PSCustomObject]@{ + CaseId='CALLSITE_CRITICAL_GATE_READ' Test='MSI_TRANSACTION_INTERRUPTION_GATES'; Scenario='DURING_MSI' Phase='PROCESS_STATE'; Callsite='CRITICAL_GATE_READ'; Field='CRITICAL_GATE_CONTENT' }, [PSCustomObject]@{ + CaseId='CALLSITE_PROCESS_STATE_READ' Test='MSI_TRANSACTION_INTERRUPTION_GATES'; Scenario='DURING_MSI' Phase='PROCESS_STATE'; Callsite='PROCESS_STATE_READ'; Field='PROCESS_STATE_PATH' }, [PSCustomObject]@{ + CaseId='CALLSITE_PROCESS_TREE_ASSERTION' Test='MSI_TRANSACTION_INTERRUPTION_GATES'; Scenario='DURING_MSI' Phase='PROCESS_STATE'; Callsite='PROCESS_TREE_ASSERTION'; Field='PROCESS_TREE' }, [PSCustomObject]@{ + CaseId='CALLSITE_REPLACEMENT_SURVIVAL_READ' Test='PRE_EXISTING_CLEANUP_OWNERSHIP' Scenario='OWNED_EXECUTABLE_BYTE_IDENTICAL_REPLACED_THEN_DEADLINE' Phase='RESOURCE_ASSERTION'; Callsite='REPLACEMENT_SURVIVAL_READ'; Field='EXECUTABLE' }, [PSCustomObject]@{ + CaseId='FIELD_EXECUTABLE_BACKUP' Test='PRE_EXISTING_CLEANUP_OWNERSHIP' Scenario='OWNED_EXECUTABLE_BYTE_IDENTICAL_REPLACED_THEN_DEADLINE' Phase='RESOURCE_ASSERTION'; Callsite='RESOURCE_FIELD_VALIDATION'; Field='EXECUTABLE_BACKUP' }, [PSCustomObject]@{ + CaseId='FIELD_MANIFEST_PATH' Test='PRE_EXISTING_CLEANUP_OWNERSHIP' Scenario='OWNED_EXECUTABLE_BYTE_IDENTICAL_REPLACED_THEN_DEADLINE' Phase='RESOURCE_ASSERTION'; Callsite='RESOURCE_FIELD_VALIDATION'; Field='MANIFEST_PATH' }, [PSCustomObject]@{ + CaseId='CALLSITE_WORKFLOW_CLEANUP_RETRY' Test='PRE_EXISTING_CLEANUP_OWNERSHIP' Scenario='OWNED_EXECUTABLE_BYTE_IDENTICAL_REPLACED_THEN_DEADLINE' Phase='RESOURCE_ASSERTION'; Callsite='WORKFLOW_CLEANUP_RETRY'; Field='RUN_ID' }, [PSCustomObject]@{ + CaseId='CALLSITE_FINAL_ABSENCE_CHECK' Test='PRE_EXISTING_CLEANUP_OWNERSHIP' Scenario='OWNED_EXECUTABLE_BYTE_IDENTICAL_REPLACED_THEN_DEADLINE' Phase='RESOURCE_ASSERTION'; Callsite='FINAL_ABSENCE_CHECK'; Field='MANIFEST_PATH' @@ -2330,24 +2457,27 @@ function Test-SupervisorInvocationAttributionTotality { [string]$case.Field } else { 'NONE' } try { - Invoke-SupervisorAttributedTest $case.Test { + Invoke-SupervisorAttributedTest -Test $case.Test -Action { Invoke-SupervisorAttributedBoundary ` - $case.Test $case.Scenario $case.Phase ` + -Test $case.Test ` + -Scenario $case.Scenario ` + -Phase $case.Phase ` -Callsite $callsite ` -Field $field ` -Action { - throw ( - "Cannot bind argument to parameter 'LiteralPath' because it is null. " + - "$secretNeedle $testRoot Registry::HKEY_LOCAL_MACHINE S-1-5-21 " + - 'manifest stdout stderr' - ) - } + throw ( + "Cannot bind argument to parameter 'LiteralPath' because it is null. " + + "$secretNeedle $testRoot Registry::HKEY_LOCAL_MACHINE S-1-5-21 " + + 'manifest stdout stderr' + ) + } } } catch { $diagnostic = $_.Exception.Message } Assert-SupervisorInvocationDiagnosticBounded ` - $diagnostic $case.Test $case.Scenario $case.Phase $callsite $field + $diagnostic $case.Test $case.Scenario $case.Phase $callsite $field ` + $case.CaseId } $forgedDiagnostic = '' try { @@ -2374,7 +2504,8 @@ function Test-SupervisorInvocationAttributionTotality { 'OWNED_EXECUTABLE_BYTE_IDENTICAL_REPLACED_THEN_DEADLINE' ` 'RESOURCE_ASSERTION' ` 'RESOURCE_FIELD_VALIDATION' ` - 'EXECUTABLE_BACKUP' + 'EXECUTABLE_BACKUP' ` + 'FORGED_PREFIX_SECRET' $missingGateStateDirectoryDiagnostic = '' try { @@ -2400,7 +2531,8 @@ function Test-SupervisorInvocationAttributionTotality { 'DURING_MSI' ` 'PROCESS_STATE' ` 'CRITICAL_GATE_PATH' ` - 'STATE_DIRECTORY' + 'STATE_DIRECTORY' ` + 'MISSING_GATE_STATE_DIRECTORY' $missingExecutableBackupDiagnostic = '' try { @@ -2428,22 +2560,43 @@ function Test-SupervisorInvocationAttributionTotality { 'OWNED_EXECUTABLE_BYTE_IDENTICAL_REPLACED_THEN_DEADLINE' ` 'RESOURCE_ASSERTION' ` 'RESOURCE_FIELD_VALIDATION' ` - 'EXECUTABLE_BACKUP' + 'EXECUTABLE_BACKUP' ` + 'MISSING_EXECUTABLE_BACKUP' foreach ($testName in Get-SupervisorInvocationTests) { foreach ($scenarioName in Get-SupervisorInvocationScenarios) { foreach ($phaseName in Get-SupervisorInvocationPhases) { $diagnostic = Get-SanitizedSupervisorInvocationDiagnostic ` $testName $scenarioName $phaseName Assert-True ([Text.Encoding]::ASCII.GetByteCount($diagnostic) -le 320) ` - 'an allowlisted supervisor invocation diagnostic exceeded its byte bound' + (Get-SupervisorAttributionTotalityAssertionMessage ` + 'ALLOWLISTED_TEST_SCENARIO_PHASE' 'exact' 'malformed') Assert-True ($diagnostic -cmatch ( '^PROPR_WINDOWS_SUPERVISOR_INVOCATION:TEST:[A-Z_]+:' + 'SCENARIO:[A-Z_]+:PHASE:[A-Z_]+:CALLSITE:[A-Z_]+:' + 'FIELD:[A-Z_]+:FAILED$' - )) 'an allowlisted supervisor invocation diagnostic was not token-only' + )) (Get-SupervisorAttributionTotalityAssertionMessage ` + 'ALLOWLISTED_TEST_SCENARIO_PHASE' 'exact' ` + (Get-SupervisorInvocationDiagnosticClassification ` + $diagnostic $diagnostic)) } } } + foreach ($callsiteName in Get-SupervisorInvocationCallsites) { + foreach ($fieldName in Get-SupervisorInvocationFields) { + $diagnostic = Get-SanitizedSupervisorInvocationDiagnostic ` + 'ATTRIBUTION_TOTALITY' 'PROTOCOL_REGRESSION' 'TEST' ` + $callsiteName $fieldName + Assert-True ([Text.Encoding]::ASCII.GetByteCount($diagnostic) -le 320) ` + (Get-SupervisorAttributionTotalityAssertionMessage ` + 'ALLOWLISTED_CALLSITE_FIELD' 'exact' 'malformed') + Assert-True (Test-SupervisorInvocationDiagnosticExact $diagnostic) ` + (Get-SupervisorAttributionTotalityAssertionMessage ` + 'ALLOWLISTED_CALLSITE_FIELD' 'exact' ` + (Get-SupervisorInvocationDiagnosticClassification ` + $diagnostic $diagnostic)) + } + } + Test-CriticalGatePublisherPowerShellCompatibility Write-Host 'PROPR_WINDOWS_SUPERVISOR_INVOCATION_ATTRIBUTION:TOTAL:PASSED' [Console]::Out.Flush() } diff --git a/apps/desktop/src/release-workflow.test.ts b/apps/desktop/src/release-workflow.test.ts index aef1f3319..1682b0bcf 100644 --- a/apps/desktop/src/release-workflow.test.ts +++ b/apps/desktop/src/release-workflow.test.ts @@ -602,6 +602,26 @@ describe('desktop trusted release workflow', () => { /PROPR_WINDOWS_SUPERVISOR_INVOCATION:TEST:\{0\}:' \+\s*'SCENARIO:\{1\}:PHASE:\{2\}:CALLSITE:\{3\}:FIELD:\{4\}:FAILED/, ); assert.match(installedWindowsAppSupervisorBehaviorTest, /Test-SupervisorInvocationDiagnosticExact/); + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /Get-SupervisorAttributionTotalityAssertionMessage/, + ); + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /CASE:\{0\}:EXPECTED:\{1\}:OBSERVED:\{2\}/, + ); + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /CaseId='BOUNDARY_BOOTSTRAP_TIMEOUT'[\s\S]*Invoke-SupervisorAttributedTest -Test \$case\.Test -Action/, + ); + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /foreach \(\$callsiteName in Get-SupervisorInvocationCallsites\)[\s\S]*foreach \(\$fieldName in Get-SupervisorInvocationFields\)/, + ); + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /Test-CriticalGatePublisherPowerShellCompatibility/, + ); assert.doesNotMatch(installedWindowsAppSupervisorBehaviorTest, /-clike 'PROPR_WINDOWS_SUPERVISOR_INVOCATION:\*'/); assert.match( installedWindowsAppSupervisorBehaviorTest, @@ -634,6 +654,14 @@ describe('desktop trusted release workflow', () => { installedWindowsAppSupervisorFixture, /ExecutableBackup -NotePropertyValue \$backup -Force[\s\S]*ByteIdenticalReplacement[\s\S]*-NotePropertyValue \$true -Force/, ); + assert.match( + installedWindowsAppSupervisorFixture, + /\[IO\.FileOptions\]::WriteThrough[\s\S]*\$stream\.Flush\(\$true\)[\s\S]*\[IO\.Directory\]::Exists\(\$gatePath\)[\s\S]*\[IO\.File\]::Delete\(\$gatePath\)[\s\S]*\[IO\.File\]::Move\(\$temporaryGatePath, \$gatePath\)/, + ); + assert.doesNotMatch( + installedWindowsAppSupervisorFixture, + /\[IO\.File\]::Move\(\$temporaryGatePath, \$gatePath, \$true\)/, + ); assert.ok( installedWindowsAppSupervisorBehaviorTest.indexOf( "Assert-OwnedFixtureAuthorityComplete $owned 'OWNED_RESOURCES_THEN_DEADLINE'", From 8aa76a918d0ac5e7b348066a159d6e1885e0cb23 Mon Sep 17 00:00:00 2001 From: "propr-dev[bot]" <1316198+propr-dev[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 07:19:37 +0000 Subject: [PATCH 12/33] feat(ai): Implemented the narrow test-producer fix in [test-installed-windows-app-supervisor.ps1](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T07-13-53/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1:2455). MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implemented the narrow test-producer fix in [test-installed-windows-app-supervisor.ps1](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T07-13-53/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1:2455). Changes made: - Replaced the attribution-totality loop’s `$callsite` / `$field` locals with unique captured case locals: `$caseExpectedCallsite` / `$caseExpectedField`. - Switched the touched attribution producer/assertion calls to named parameters. - Added deterministic nested `GENERAL`/`NONE` outer-boundary regressions for: - `CRITICAL_GATE_PATH` / `STATE_DIRECTORY` - `PROCESS_STATE_READ` / `PROCESS_STATE_PATH` - Added fixed allowlisted case ids for those regressions. No production/controller files were changed. Validation: - `git diff --check` passes. - `pwsh`/Windows native runners are not available in this Linux environment, so I could not locally run x64/ARM64 `ATTRIBUTION_TOTALITY`. PR: #2057 Comment by: @integry (ID: 5505899421) Model: gpt-5.5 --- .../test-installed-windows-app-supervisor.ps1 | 187 ++++++++++++------ 1 file changed, 131 insertions(+), 56 deletions(-) diff --git a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 index 7b8d089c4..36cc32cae 100644 --- a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 +++ b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 @@ -1535,6 +1535,8 @@ function Get-SupervisorAttributionTotalityCases { 'FORGED_PREFIX_SECRET', 'MISSING_GATE_STATE_DIRECTORY', 'MISSING_EXECUTABLE_BACKUP', + 'NESTED_SHADOW_CRITICAL_GATE_PATH', + 'NESTED_SHADOW_PROCESS_STATE_READ', 'ALLOWLISTED_TEST_SCENARIO_PHASE', 'ALLOWLISTED_CALLSITE_FIELD', 'POWERSHELL_GATE_PUBLICATION' @@ -2450,20 +2452,24 @@ function Test-SupervisorInvocationAttributionTotality { } )) { $diagnostic = '' - $callsite = if ($case.PSObject.Properties['Callsite']) { + $caseExpectedTest = [string]$case.Test + $caseExpectedScenario = [string]$case.Scenario + $caseExpectedPhase = [string]$case.Phase + $caseExpectedCallsite = if ($case.PSObject.Properties['Callsite']) { [string]$case.Callsite } else { 'GENERAL' } - $field = if ($case.PSObject.Properties['Field']) { + $caseExpectedField = if ($case.PSObject.Properties['Field']) { [string]$case.Field } else { 'NONE' } + $caseExpectedId = [string]$case.CaseId try { - Invoke-SupervisorAttributedTest -Test $case.Test -Action { + Invoke-SupervisorAttributedTest -Test $caseExpectedTest -Action { Invoke-SupervisorAttributedBoundary ` - -Test $case.Test ` - -Scenario $case.Scenario ` - -Phase $case.Phase ` - -Callsite $callsite ` - -Field $field ` + -Test $caseExpectedTest ` + -Scenario $caseExpectedScenario ` + -Phase $caseExpectedPhase ` + -Callsite $caseExpectedCallsite ` + -Field $caseExpectedField ` -Action { throw ( "Cannot bind argument to parameter 'LiteralPath' because it is null. " + @@ -2476,15 +2482,79 @@ function Test-SupervisorInvocationAttributionTotality { $diagnostic = $_.Exception.Message } Assert-SupervisorInvocationDiagnosticBounded ` - $diagnostic $case.Test $case.Scenario $case.Phase $callsite $field ` - $case.CaseId + -Diagnostic $diagnostic ` + -Test $caseExpectedTest ` + -Scenario $caseExpectedScenario ` + -Phase $caseExpectedPhase ` + -Callsite $caseExpectedCallsite ` + -Field $caseExpectedField ` + -CaseId $caseExpectedId + } + foreach ($nestedShadowCase in @( + [PSCustomObject]@{ + CaseId='NESTED_SHADOW_CRITICAL_GATE_PATH' + Test='MSI_TRANSACTION_INTERRUPTION_GATES' + Scenario='DURING_MSI' + Phase='PROCESS_STATE' + Callsite='CRITICAL_GATE_PATH' + Field='STATE_DIRECTORY' + }, + [PSCustomObject]@{ + CaseId='NESTED_SHADOW_PROCESS_STATE_READ' + Test='MSI_TRANSACTION_INTERRUPTION_GATES' + Scenario='DURING_MSI' + Phase='PROCESS_STATE' + Callsite='PROCESS_STATE_READ' + Field='PROCESS_STATE_PATH' + } + )) { + $nestedShadowDiagnostic = '' + $nestedExpectedTest = [string]$nestedShadowCase.Test + $nestedExpectedScenario = [string]$nestedShadowCase.Scenario + $nestedExpectedPhase = [string]$nestedShadowCase.Phase + $nestedExpectedCallsite = [string]$nestedShadowCase.Callsite + $nestedExpectedField = [string]$nestedShadowCase.Field + $nestedExpectedId = [string]$nestedShadowCase.CaseId + try { + Invoke-SupervisorAttributedBoundary ` + -Test 'ATTRIBUTION_TOTALITY' ` + -Scenario 'PROTOCOL_REGRESSION' ` + -Phase 'TEST' ` + -Callsite 'GENERAL' ` + -Field 'NONE' ` + -Action { + Invoke-SupervisorAttributedBoundary ` + -Test $nestedExpectedTest ` + -Scenario $nestedExpectedScenario ` + -Phase $nestedExpectedPhase ` + -Callsite $nestedExpectedCallsite ` + -Field $nestedExpectedField ` + -Action { + throw ( + "Cannot bind argument to parameter 'LiteralPath' because it is null. " + + "$secretNeedle $testRoot Registry::HKEY_LOCAL_MACHINE S-1-5-21 " + + 'manifest stdout stderr' + ) + } + } + } catch { + $nestedShadowDiagnostic = $_.Exception.Message + } + Assert-SupervisorInvocationDiagnosticBounded ` + -Diagnostic $nestedShadowDiagnostic ` + -Test $nestedExpectedTest ` + -Scenario $nestedExpectedScenario ` + -Phase $nestedExpectedPhase ` + -Callsite $nestedExpectedCallsite ` + -Field $nestedExpectedField ` + -CaseId $nestedExpectedId } $forgedDiagnostic = '' try { Invoke-SupervisorAttributedBoundary ` - 'PRE_EXISTING_CLEANUP_OWNERSHIP' ` - 'OWNED_EXECUTABLE_BYTE_IDENTICAL_REPLACED_THEN_DEADLINE' ` - 'RESOURCE_ASSERTION' ` + -Test 'PRE_EXISTING_CLEANUP_OWNERSHIP' ` + -Scenario 'OWNED_EXECUTABLE_BYTE_IDENTICAL_REPLACED_THEN_DEADLINE' ` + -Phase 'RESOURCE_ASSERTION' ` -Callsite 'RESOURCE_FIELD_VALIDATION' ` -Field 'EXECUTABLE_BACKUP' ` -Action { @@ -2499,74 +2569,76 @@ function Test-SupervisorInvocationAttributionTotality { $forgedDiagnostic = $_.Exception.Message } Assert-SupervisorInvocationDiagnosticBounded ` - $forgedDiagnostic ` - 'PRE_EXISTING_CLEANUP_OWNERSHIP' ` - 'OWNED_EXECUTABLE_BYTE_IDENTICAL_REPLACED_THEN_DEADLINE' ` - 'RESOURCE_ASSERTION' ` - 'RESOURCE_FIELD_VALIDATION' ` - 'EXECUTABLE_BACKUP' ` - 'FORGED_PREFIX_SECRET' + -Diagnostic $forgedDiagnostic ` + -Test 'PRE_EXISTING_CLEANUP_OWNERSHIP' ` + -Scenario 'OWNED_EXECUTABLE_BYTE_IDENTICAL_REPLACED_THEN_DEADLINE' ` + -Phase 'RESOURCE_ASSERTION' ` + -Callsite 'RESOURCE_FIELD_VALIDATION' ` + -Field 'EXECUTABLE_BACKUP' ` + -CaseId 'FORGED_PREFIX_SECRET' $missingGateStateDirectoryDiagnostic = '' try { Set-SupervisorInvocationContext ` - 'MSI_TRANSACTION_INTERRUPTION_GATES' ` - 'DURING_MSI' ` - 'PROCESS_STATE' ` - 'CRITICAL_GATE_PATH' ` - 'STATE_DIRECTORY' + -Test 'MSI_TRANSACTION_INTERRUPTION_GATES' ` + -Scenario 'DURING_MSI' ` + -Phase 'PROCESS_STATE' ` + -Callsite 'CRITICAL_GATE_PATH' ` + -Field 'STATE_DIRECTORY' Assert-True (![string]::IsNullOrWhiteSpace('')) ` (Get-SanitizedSupervisorInvocationDiagnostic ` - 'MSI_TRANSACTION_INTERRUPTION_GATES' ` - 'DURING_MSI' ` - 'PROCESS_STATE' ` - 'CRITICAL_GATE_PATH' ` - 'STATE_DIRECTORY') + -Test 'MSI_TRANSACTION_INTERRUPTION_GATES' ` + -Scenario 'DURING_MSI' ` + -Phase 'PROCESS_STATE' ` + -Callsite 'CRITICAL_GATE_PATH' ` + -Field 'STATE_DIRECTORY') } catch { $missingGateStateDirectoryDiagnostic = $_.Exception.Message } Assert-SupervisorInvocationDiagnosticBounded ` - $missingGateStateDirectoryDiagnostic ` - 'MSI_TRANSACTION_INTERRUPTION_GATES' ` - 'DURING_MSI' ` - 'PROCESS_STATE' ` - 'CRITICAL_GATE_PATH' ` - 'STATE_DIRECTORY' ` - 'MISSING_GATE_STATE_DIRECTORY' + -Diagnostic $missingGateStateDirectoryDiagnostic ` + -Test 'MSI_TRANSACTION_INTERRUPTION_GATES' ` + -Scenario 'DURING_MSI' ` + -Phase 'PROCESS_STATE' ` + -Callsite 'CRITICAL_GATE_PATH' ` + -Field 'STATE_DIRECTORY' ` + -CaseId 'MISSING_GATE_STATE_DIRECTORY' $missingExecutableBackupDiagnostic = '' try { Set-SupervisorInvocationContext ` - 'PRE_EXISTING_CLEANUP_OWNERSHIP' ` - 'OWNED_EXECUTABLE_BYTE_IDENTICAL_REPLACED_THEN_DEADLINE' ` - 'RESOURCE_ASSERTION' ` - 'RESOURCE_FIELD_VALIDATION' ` - 'EXECUTABLE_BACKUP' + -Test 'PRE_EXISTING_CLEANUP_OWNERSHIP' ` + -Scenario 'OWNED_EXECUTABLE_BYTE_IDENTICAL_REPLACED_THEN_DEADLINE' ` + -Phase 'RESOURCE_ASSERTION' ` + -Callsite 'RESOURCE_FIELD_VALIDATION' ` + -Field 'EXECUTABLE_BACKUP' Assert-FixtureStateFieldsComplete ` - ([PSCustomObject]@{ + -State ([PSCustomObject]@{ Executable = 'EXECUTABLE' ManifestPath = 'MANIFEST_PATH' RunId = 'RUN_ID' }) ` - 'OWNED_EXECUTABLE_BYTE_IDENTICAL_REPLACED_THEN_DEADLINE' ` - 'RESOURCE_FIELD_VALIDATION' ` - @('ExecutableBackup') + -Scenario 'OWNED_EXECUTABLE_BYTE_IDENTICAL_REPLACED_THEN_DEADLINE' ` + -Callsite 'RESOURCE_FIELD_VALIDATION' ` + -Fields @('ExecutableBackup') } catch { $missingExecutableBackupDiagnostic = $_.Exception.Message } Assert-SupervisorInvocationDiagnosticBounded ` - $missingExecutableBackupDiagnostic ` - 'PRE_EXISTING_CLEANUP_OWNERSHIP' ` - 'OWNED_EXECUTABLE_BYTE_IDENTICAL_REPLACED_THEN_DEADLINE' ` - 'RESOURCE_ASSERTION' ` - 'RESOURCE_FIELD_VALIDATION' ` - 'EXECUTABLE_BACKUP' ` - 'MISSING_EXECUTABLE_BACKUP' + -Diagnostic $missingExecutableBackupDiagnostic ` + -Test 'PRE_EXISTING_CLEANUP_OWNERSHIP' ` + -Scenario 'OWNED_EXECUTABLE_BYTE_IDENTICAL_REPLACED_THEN_DEADLINE' ` + -Phase 'RESOURCE_ASSERTION' ` + -Callsite 'RESOURCE_FIELD_VALIDATION' ` + -Field 'EXECUTABLE_BACKUP' ` + -CaseId 'MISSING_EXECUTABLE_BACKUP' foreach ($testName in Get-SupervisorInvocationTests) { foreach ($scenarioName in Get-SupervisorInvocationScenarios) { foreach ($phaseName in Get-SupervisorInvocationPhases) { $diagnostic = Get-SanitizedSupervisorInvocationDiagnostic ` - $testName $scenarioName $phaseName + -Test $testName ` + -Scenario $scenarioName ` + -Phase $phaseName Assert-True ([Text.Encoding]::ASCII.GetByteCount($diagnostic) -le 320) ` (Get-SupervisorAttributionTotalityAssertionMessage ` 'ALLOWLISTED_TEST_SCENARIO_PHASE' 'exact' 'malformed') @@ -2584,8 +2656,11 @@ function Test-SupervisorInvocationAttributionTotality { foreach ($callsiteName in Get-SupervisorInvocationCallsites) { foreach ($fieldName in Get-SupervisorInvocationFields) { $diagnostic = Get-SanitizedSupervisorInvocationDiagnostic ` - 'ATTRIBUTION_TOTALITY' 'PROTOCOL_REGRESSION' 'TEST' ` - $callsiteName $fieldName + -Test 'ATTRIBUTION_TOTALITY' ` + -Scenario 'PROTOCOL_REGRESSION' ` + -Phase 'TEST' ` + -Callsite $callsiteName ` + -Field $fieldName Assert-True ([Text.Encoding]::ASCII.GetByteCount($diagnostic) -le 320) ` (Get-SupervisorAttributionTotalityAssertionMessage ` 'ALLOWLISTED_CALLSITE_FIELD' 'exact' 'malformed') From 44d2ee1b1e34a459fdae40d1c160eb447a506842 Mon Sep 17 00:00:00 2001 From: "propr-dev[bot]" <1316198+propr-dev[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 13:04:33 +0000 Subject: [PATCH 13/33] feat(ai): Implemented the follow-up changes on head `8aa76a918d0ac5e7b348066a159d6e1885e0cb23`. Implemented the follow-up changes on head `8aa76a918d0ac5e7b348066a159d6e1885e0cb23`. Changed: - [test-installed-windows-app-supervisor.ps1](/home/node/workspace/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1:725): added fine-grained allowlisted attribution for the `EARLY_INITIALIZATION_TIMEOUT` controller call, result fields, early process-state path/read, `WorkerPid`, `DescendantPid`, tree-gone assertion, and manifest preservation. - [run-installed-windows-app-workflow-cleanup-body.ps1](/home/node/workspace/apps/desktop/scripts/run-installed-windows-app-workflow-cleanup-body.ps1:500): fixed the fixture/controller race by waiting for early PID state publication before starting the 5s cleanup timeout. - [cleanup-installed-windows-app.ps1](/home/node/workspace/apps/desktop/scripts/cleanup-installed-windows-app.ps1:83): tightened the early-state producer to validate and publish integer `WorkerPid` / `DescendantPid`. Verification: - `git diff --check` passed. - Pulled the cited native x64/ARM64 job logs with `gh`; they only contained the previous broad `GENERAL/NONE` token, no finer producer evidence. - Could not run native `PROPR_WINDOWS_SUPERVISOR_TESTS:x64:PASSED` / `arm64:PASSED` locally because this container has no `pwsh` and is not Windows. PR: #2057 Comment by: @integry (ID: 5509811243) Model: gpt-5.5 --- .../scripts/cleanup-installed-windows-app.ps1 | 6 +- ...lled-windows-app-workflow-cleanup-body.ps1 | 16 + .../test-installed-windows-app-supervisor.ps1 | 366 +++++++++++++++++- 3 files changed, 374 insertions(+), 14 deletions(-) diff --git a/apps/desktop/scripts/cleanup-installed-windows-app.ps1 b/apps/desktop/scripts/cleanup-installed-windows-app.ps1 index 414edbefa..83e781eab 100644 --- a/apps/desktop/scripts/cleanup-installed-windows-app.ps1 +++ b/apps/desktop/scripts/cleanup-installed-windows-app.ps1 @@ -80,10 +80,14 @@ if ($FixtureEarlyInitializationChild) { $fixtureChild = [Diagnostics.Process]::new() $fixtureChild.StartInfo = $fixtureChildStartInfo if (!$fixtureChild.Start()) { exit 1 } + if ($PID -le 0 -or $fixtureChild.Id -le 0) { exit 1 } $fixtureStatePath = Join-Path $fixtureEarlyRoot 'workflow-cleanup-early-processes.json' $fixtureStateTemporaryPath = "$fixtureStatePath.$PID.new" $fixtureStateBytes = [Text.Encoding]::ASCII.GetBytes(( - [ordered]@{ WorkerPid = $PID; DescendantPid = $fixtureChild.Id } | + [ordered]@{ + WorkerPid = [int]$PID + DescendantPid = [int]$fixtureChild.Id + } | ConvertTo-Json -Compress )) $fixtureStateStream = [IO.FileStream]::new( diff --git a/apps/desktop/scripts/run-installed-windows-app-workflow-cleanup-body.ps1 b/apps/desktop/scripts/run-installed-windows-app-workflow-cleanup-body.ps1 index d20b8e15e..a0070cabc 100644 --- a/apps/desktop/scripts/run-installed-windows-app-workflow-cleanup-body.ps1 +++ b/apps/desktop/scripts/run-installed-windows-app-workflow-cleanup-body.ps1 @@ -497,6 +497,22 @@ if ($FixtureResultEmissionFailure -and !$FixtureRoot) { $outputDrain = [ProPRWorkflowCleanupOutputDrain]::new() $outputDrain.Start($cleanupProcess) [void]$cleanupReadyEvent.Set() + if ($FixtureEarlyInitializationChild) { + $controllerPhase = 'PROCESS_WAIT' + $controllerLine = 'WAIT' + # This fixture's timeout covers cleanup of the published early tree, not + # native host cold-start time before the PID state exists. + $fixtureEarlyStatePath = Join-Path ` + $FixtureRoot 'workflow-cleanup-early-processes.json' + $fixtureEarlyStateWatch = [Diagnostics.Stopwatch]::StartNew() + while (![IO.File]::Exists($fixtureEarlyStatePath)) { + if ($cleanupProcess.HasExited) { break } + if ($fixtureEarlyStateWatch.ElapsedMilliseconds -ge 15000) { + throw 'early initialization fixture did not publish process state' + } + [Threading.Thread]::Sleep(25) + } + } } catch { try { $cleanupTreeZeroVerified = $cleanupJob.TerminateAndWait( diff --git a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 index 36cc32cae..e2b2c6ca2 100644 --- a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 +++ b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 @@ -722,6 +722,82 @@ function Assert-ProcessTreeGone($State) { throw 'owned worker process tree survived supervisor completion' } +function Read-EarlyWorkflowCleanupProcessState( + [string]$Scenario, + [string]$StateDirectory +) { + $statePath = Invoke-SupervisorAttributedOperation ` + -Scenario $Scenario ` + -Phase 'PROCESS_STATE' ` + -Callsite 'EARLY_PROCESS_STATE_PATH' ` + -Field 'STATE_DIRECTORY' ` + -Action { + Assert-True (![string]::IsNullOrWhiteSpace($StateDirectory)) ` + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + $Scenario ` + 'PROCESS_STATE' ` + 'EARLY_PROCESS_STATE_PATH' ` + 'STATE_DIRECTORY') + Join-Path $StateDirectory 'workflow-cleanup-early-processes.json' + } + Invoke-SupervisorAttributedOperation ` + -Scenario $Scenario ` + -Phase 'PROCESS_STATE' ` + -Callsite 'EARLY_PROCESS_STATE_PATH' ` + -Field 'PROCESS_STATE_PATH' ` + -Action { + Assert-True (![string]::IsNullOrWhiteSpace([string]$statePath)) ` + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + $Scenario ` + 'PROCESS_STATE' ` + 'EARLY_PROCESS_STATE_PATH' ` + 'PROCESS_STATE_PATH') + Assert-True (Test-Path -LiteralPath $statePath -PathType Leaf) ` + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + $Scenario ` + 'PROCESS_STATE' ` + 'EARLY_PROCESS_STATE_PATH' ` + 'PROCESS_STATE_PATH') + } + $state = Invoke-SupervisorAttributedOperation ` + -Scenario $Scenario ` + -Phase 'PROCESS_STATE' ` + -Callsite 'EARLY_PROCESS_STATE_READ' ` + -Field 'PROCESS_STATE_PATH' ` + -Action { + Get-Content -LiteralPath $statePath -Raw -Encoding ASCII | + ConvertFrom-Json -ErrorAction Stop + } + foreach ($earlyStatePropertyName in @('WorkerPid','DescendantPid')) { + $earlyStateFieldToken = Convert-FixtureAuthorityFieldToken $earlyStatePropertyName + Invoke-SupervisorAttributedOperation ` + -Scenario $Scenario ` + -Phase 'PROCESS_STATE' ` + -Callsite 'EARLY_PROCESS_STATE_READ' ` + -Field $earlyStateFieldToken ` + -Action { + $property = $state.PSObject.Properties[$earlyStatePropertyName] + $pidValue = 0 + Assert-True ($null -ne $property -and [int]::TryParse( + [string]$property.Value, + [Globalization.NumberStyles]::None, + [Globalization.CultureInfo]::InvariantCulture, + [ref]$pidValue + ) -and $pidValue -gt 0) ` + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + $Scenario ` + 'PROCESS_STATE' ` + 'EARLY_PROCESS_STATE_READ' ` + $earlyStateFieldToken) + } + } + return $state +} + function Get-SanitizedSupervisorMarkerDiagnostic($Result) { $bootstrapTimedOutPresent = [regex]::IsMatch( [string]$Result.Output, @@ -1405,6 +1481,12 @@ function Get-SupervisorInvocationCallsites { 'PROCESS_STATE_PATH', 'PROCESS_STATE_READ', 'PROCESS_TREE_ASSERTION', + 'CONTROLLER_INVOCATION_INPUT', + 'CONTROLLER_PROTOCOL_PARSE', + 'CONTROLLER_RESULT_FIELD', + 'EARLY_PROCESS_STATE_PATH', + 'EARLY_PROCESS_STATE_READ', + 'MANIFEST_PRESERVATION', 'RESOURCE_FIELD_VALIDATION', 'REPLACEMENT_SURVIVAL_READ', 'AUTHORITY_RESTORE_WRITE', @@ -1425,6 +1507,10 @@ function Get-SupervisorInvocationFields { 'WORKER_PID', 'DESCENDANT_PID', 'PROCESS_TREE', + 'PROTOCOL', + 'EXIT_CODE', + 'REPORTED_EXIT_CODE', + 'RESULT', 'OWNED_ROOT', 'INSTALL_ROOT', 'SHORTCUT_FOLDER', @@ -1527,6 +1613,19 @@ function Get-SupervisorAttributionTotalityCases { 'CALLSITE_CRITICAL_GATE_READ', 'CALLSITE_PROCESS_STATE_READ', 'CALLSITE_PROCESS_TREE_ASSERTION', + 'CALLSITE_CONTROLLER_INPUT_MANIFEST', + 'CALLSITE_CONTROLLER_INPUT_RUN_ID', + 'CALLSITE_CONTROLLER_INPUT_STATE_DIRECTORY', + 'CALLSITE_CONTROLLER_PROTOCOL_PARSE', + 'CALLSITE_CONTROLLER_RESULT_EXIT_CODE', + 'CALLSITE_CONTROLLER_RESULT_REPORTED_EXIT_CODE', + 'CALLSITE_CONTROLLER_RESULT_RESULT', + 'CALLSITE_EARLY_PROCESS_STATE_PATH', + 'CALLSITE_EARLY_PROCESS_STATE_READ', + 'CALLSITE_EARLY_WORKER_PID', + 'CALLSITE_EARLY_DESCENDANT_PID', + 'CALLSITE_EARLY_PROCESS_TREE_ASSERTION', + 'CALLSITE_EARLY_MANIFEST_PRESERVATION', 'CALLSITE_REPLACEMENT_SURVIVAL_READ', 'FIELD_EXECUTABLE_BACKUP', 'FIELD_MANIFEST_PATH', @@ -1627,6 +1726,22 @@ function Invoke-SupervisorAttributedTest( -Action $Action } +function Invoke-SupervisorAttributedOperation( + [string]$Scenario, + [string]$Phase, + [string]$Callsite, + [string]$Field, + [scriptblock]$Action +) { + Invoke-SupervisorAttributedBoundary ` + -Test $script:currentSupervisorInvocationTest ` + -Scenario $Scenario ` + -Phase $Phase ` + -Callsite $Callsite ` + -Field $Field ` + -Action $Action +} + function Assert-SupervisorInvocationDiagnosticBounded( [string]$Diagnostic, [string]$Test, @@ -1782,6 +1897,53 @@ function Invoke-WorkflowCleanupController( $script:currentSupervisorInvocationTest ` $InvocationIdentifier ` 'WORKFLOW_CLEANUP_CONTROLLER' + Set-SupervisorInvocationContext ` + $script:currentSupervisorInvocationTest ` + $InvocationIdentifier ` + 'WORKFLOW_CLEANUP_CONTROLLER' ` + 'CONTROLLER_INVOCATION_INPUT' ` + 'MANIFEST_PATH' + Assert-True (![string]::IsNullOrWhiteSpace([string]$ManifestPath)) ` + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + $InvocationIdentifier ` + 'WORKFLOW_CLEANUP_CONTROLLER' ` + 'CONTROLLER_INVOCATION_INPUT' ` + 'MANIFEST_PATH') + Set-SupervisorInvocationContext ` + $script:currentSupervisorInvocationTest ` + $InvocationIdentifier ` + 'WORKFLOW_CLEANUP_CONTROLLER' ` + 'CONTROLLER_INVOCATION_INPUT' ` + 'RUN_ID' + Assert-True (![string]::IsNullOrWhiteSpace([string]$RunId)) ` + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + $InvocationIdentifier ` + 'WORKFLOW_CLEANUP_CONTROLLER' ` + 'CONTROLLER_INVOCATION_INPUT' ` + 'RUN_ID') + if ($FixtureRoot) { + Set-SupervisorInvocationContext ` + $script:currentSupervisorInvocationTest ` + $InvocationIdentifier ` + 'WORKFLOW_CLEANUP_CONTROLLER' ` + 'CONTROLLER_INVOCATION_INPUT' ` + 'STATE_DIRECTORY' + Assert-True (![string]::IsNullOrWhiteSpace([string]$FixtureRoot)) ` + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + $InvocationIdentifier ` + 'WORKFLOW_CLEANUP_CONTROLLER' ` + 'CONTROLLER_INVOCATION_INPUT' ` + 'STATE_DIRECTORY') + } + Set-SupervisorInvocationContext ` + $script:currentSupervisorInvocationTest ` + $InvocationIdentifier ` + 'WORKFLOW_CLEANUP_CONTROLLER' ` + 'CONTROLLER_PROTOCOL_PARSE' ` + 'PROTOCOL' $startInfo = [Diagnostics.ProcessStartInfo]::new() $startInfo.FileName = $hostPath $startInfo.UseShellExecute = $false @@ -1798,10 +1960,22 @@ function Invoke-WorkflowCleanupController( $startInfo.ArgumentList.Add($argument) } if ($FixtureRoot) { + Set-SupervisorInvocationContext ` + $script:currentSupervisorInvocationTest ` + $InvocationIdentifier ` + 'WORKFLOW_CLEANUP_CONTROLLER' ` + 'CONTROLLER_INVOCATION_INPUT' ` + 'STATE_DIRECTORY' $startInfo.ArgumentList.Add('-FixtureRoot') $startInfo.ArgumentList.Add($FixtureRoot) } if ($FixtureEarlyInitializationChild) { + Set-SupervisorInvocationContext ` + $script:currentSupervisorInvocationTest ` + $InvocationIdentifier ` + 'WORKFLOW_CLEANUP_CONTROLLER' ` + 'CONTROLLER_INVOCATION_INPUT' ` + 'STATE_DIRECTORY' $startInfo.ArgumentList.Add('-FixtureEarlyInitializationChild') } if ($FixtureResultEmissionFailure) { @@ -1822,8 +1996,20 @@ function Invoke-WorkflowCleanupController( [Globalization.CultureInfo]::InvariantCulture, [ref]$invocationTimeout ) -or $invocationTimeout -lt 1 -or $invocationTimeout -gt 40000) { + Set-SupervisorInvocationContext ` + $script:currentSupervisorInvocationTest ` + $InvocationIdentifier ` + 'WORKFLOW_CLEANUP_CONTROLLER' ` + 'CONTROLLER_INVOCATION_INPUT' ` + 'STATE_DIRECTORY' throw 'workflow cleanup invocation timeout is invalid' } + Set-SupervisorInvocationContext ` + $script:currentSupervisorInvocationTest ` + $InvocationIdentifier ` + 'WORKFLOW_CLEANUP_CONTROLLER' ` + 'CONTROLLER_PROTOCOL_PARSE' ` + 'PROTOCOL' $process = [Diagnostics.Process]::new() $process.StartInfo = $startInfo $job = $null @@ -2420,6 +2606,97 @@ function Test-SupervisorInvocationAttributionTotality { Test='MSI_TRANSACTION_INTERRUPTION_GATES'; Scenario='DURING_MSI' Phase='PROCESS_STATE'; Callsite='PROCESS_TREE_ASSERTION'; Field='PROCESS_TREE' }, + [PSCustomObject]@{ + CaseId='CALLSITE_CONTROLLER_INPUT_MANIFEST' + Test='PRE_EXISTING_CLEANUP_OWNERSHIP' + Scenario='EARLY_INITIALIZATION_TIMEOUT' + Phase='WORKFLOW_CLEANUP_CONTROLLER' + Callsite='CONTROLLER_INVOCATION_INPUT'; Field='MANIFEST_PATH' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_CONTROLLER_INPUT_RUN_ID' + Test='PRE_EXISTING_CLEANUP_OWNERSHIP' + Scenario='EARLY_INITIALIZATION_TIMEOUT' + Phase='WORKFLOW_CLEANUP_CONTROLLER' + Callsite='CONTROLLER_INVOCATION_INPUT'; Field='RUN_ID' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_CONTROLLER_INPUT_STATE_DIRECTORY' + Test='PRE_EXISTING_CLEANUP_OWNERSHIP' + Scenario='EARLY_INITIALIZATION_TIMEOUT' + Phase='WORKFLOW_CLEANUP_CONTROLLER' + Callsite='CONTROLLER_INVOCATION_INPUT'; Field='STATE_DIRECTORY' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_CONTROLLER_PROTOCOL_PARSE' + Test='PRE_EXISTING_CLEANUP_OWNERSHIP' + Scenario='EARLY_INITIALIZATION_TIMEOUT' + Phase='WORKFLOW_CLEANUP_CONTROLLER' + Callsite='CONTROLLER_PROTOCOL_PARSE'; Field='PROTOCOL' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_CONTROLLER_RESULT_EXIT_CODE' + Test='PRE_EXISTING_CLEANUP_OWNERSHIP' + Scenario='EARLY_INITIALIZATION_TIMEOUT' + Phase='WORKFLOW_CLEANUP_CONTROLLER' + Callsite='CONTROLLER_RESULT_FIELD'; Field='EXIT_CODE' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_CONTROLLER_RESULT_REPORTED_EXIT_CODE' + Test='PRE_EXISTING_CLEANUP_OWNERSHIP' + Scenario='EARLY_INITIALIZATION_TIMEOUT' + Phase='WORKFLOW_CLEANUP_CONTROLLER' + Callsite='CONTROLLER_RESULT_FIELD'; Field='REPORTED_EXIT_CODE' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_CONTROLLER_RESULT_RESULT' + Test='PRE_EXISTING_CLEANUP_OWNERSHIP' + Scenario='EARLY_INITIALIZATION_TIMEOUT' + Phase='WORKFLOW_CLEANUP_CONTROLLER' + Callsite='CONTROLLER_RESULT_FIELD'; Field='RESULT' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_EARLY_PROCESS_STATE_PATH' + Test='PRE_EXISTING_CLEANUP_OWNERSHIP' + Scenario='EARLY_INITIALIZATION_TIMEOUT' + Phase='PROCESS_STATE' + Callsite='EARLY_PROCESS_STATE_PATH'; Field='PROCESS_STATE_PATH' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_EARLY_PROCESS_STATE_READ' + Test='PRE_EXISTING_CLEANUP_OWNERSHIP' + Scenario='EARLY_INITIALIZATION_TIMEOUT' + Phase='PROCESS_STATE' + Callsite='EARLY_PROCESS_STATE_READ'; Field='PROCESS_STATE_PATH' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_EARLY_WORKER_PID' + Test='PRE_EXISTING_CLEANUP_OWNERSHIP' + Scenario='EARLY_INITIALIZATION_TIMEOUT' + Phase='PROCESS_STATE' + Callsite='EARLY_PROCESS_STATE_READ'; Field='WORKER_PID' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_EARLY_DESCENDANT_PID' + Test='PRE_EXISTING_CLEANUP_OWNERSHIP' + Scenario='EARLY_INITIALIZATION_TIMEOUT' + Phase='PROCESS_STATE' + Callsite='EARLY_PROCESS_STATE_READ'; Field='DESCENDANT_PID' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_EARLY_PROCESS_TREE_ASSERTION' + Test='PRE_EXISTING_CLEANUP_OWNERSHIP' + Scenario='EARLY_INITIALIZATION_TIMEOUT' + Phase='PROCESS_STATE' + Callsite='PROCESS_TREE_ASSERTION'; Field='PROCESS_TREE' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_EARLY_MANIFEST_PRESERVATION' + Test='PRE_EXISTING_CLEANUP_OWNERSHIP' + Scenario='EARLY_INITIALIZATION_TIMEOUT' + Phase='MANIFEST_ASSERTION' + Callsite='MANIFEST_PRESERVATION'; Field='MANIFEST_PATH' + }, [PSCustomObject]@{ CaseId='CALLSITE_REPLACEMENT_SURVIVAL_READ' Test='PRE_EXISTING_CLEANUP_OWNERSHIP' @@ -3684,19 +3961,82 @@ function Test-PreExistingCleanupOwnership { )) 'controller parameter failure was not caught and phase-classified' Assert-True (Test-Path -LiteralPath $workflowManifest -PathType Leaf) ` 'controller parameter failure discarded authenticated recovery authority' - $earlyInitializationTimeout = Invoke-WorkflowCleanupController ` - 'EARLY_INITIALIZATION_TIMEOUT' $workflowManifest $workflowRunId ` - $workflowStateDirectory 5000 $true - Assert-True ($earlyInitializationTimeout.ExitCode -eq 124 -and - $earlyInitializationTimeout.ReportedExitCode -eq 124 -and - $earlyInitializationTimeout.Result -ceq 'TIMED_OUT') ` - 'early-initialization child cleanup did not report its fixed timeout' - $earlyInitializationState = Get-Content -LiteralPath ` - (Join-Path $workflowStateDirectory 'workflow-cleanup-early-processes.json') ` - -Raw -Encoding ASCII | ConvertFrom-Json -ErrorAction Stop - Assert-ProcessTreeGone $earlyInitializationState - Assert-True (Test-Path -LiteralPath $workflowManifest -PathType Leaf) ` - 'early-initialization timeout discarded authenticated recovery authority' + $earlyInitializationTimeout = Invoke-SupervisorAttributedOperation ` + -Scenario 'EARLY_INITIALIZATION_TIMEOUT' ` + -Phase 'WORKFLOW_CLEANUP_CONTROLLER' ` + -Callsite 'CONTROLLER_PROTOCOL_PARSE' ` + -Field 'PROTOCOL' ` + -Action { + Invoke-WorkflowCleanupController ` + 'EARLY_INITIALIZATION_TIMEOUT' $workflowManifest $workflowRunId ` + $workflowStateDirectory 5000 $true + } + Invoke-SupervisorAttributedOperation ` + -Scenario 'EARLY_INITIALIZATION_TIMEOUT' ` + -Phase 'WORKFLOW_CLEANUP_CONTROLLER' ` + -Callsite 'CONTROLLER_RESULT_FIELD' ` + -Field 'EXIT_CODE' ` + -Action { + Assert-True ($earlyInitializationTimeout.ExitCode -eq 124) ` + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + 'EARLY_INITIALIZATION_TIMEOUT' ` + 'WORKFLOW_CLEANUP_CONTROLLER' ` + 'CONTROLLER_RESULT_FIELD' ` + 'EXIT_CODE') + } + Invoke-SupervisorAttributedOperation ` + -Scenario 'EARLY_INITIALIZATION_TIMEOUT' ` + -Phase 'WORKFLOW_CLEANUP_CONTROLLER' ` + -Callsite 'CONTROLLER_RESULT_FIELD' ` + -Field 'REPORTED_EXIT_CODE' ` + -Action { + Assert-True ($earlyInitializationTimeout.ReportedExitCode -eq 124) ` + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + 'EARLY_INITIALIZATION_TIMEOUT' ` + 'WORKFLOW_CLEANUP_CONTROLLER' ` + 'CONTROLLER_RESULT_FIELD' ` + 'REPORTED_EXIT_CODE') + } + Invoke-SupervisorAttributedOperation ` + -Scenario 'EARLY_INITIALIZATION_TIMEOUT' ` + -Phase 'WORKFLOW_CLEANUP_CONTROLLER' ` + -Callsite 'CONTROLLER_RESULT_FIELD' ` + -Field 'RESULT' ` + -Action { + Assert-True ($earlyInitializationTimeout.Result -ceq 'TIMED_OUT') ` + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + 'EARLY_INITIALIZATION_TIMEOUT' ` + 'WORKFLOW_CLEANUP_CONTROLLER' ` + 'CONTROLLER_RESULT_FIELD' ` + 'RESULT') + } + $earlyInitializationState = Read-EarlyWorkflowCleanupProcessState ` + 'EARLY_INITIALIZATION_TIMEOUT' $workflowStateDirectory + Invoke-SupervisorAttributedOperation ` + -Scenario 'EARLY_INITIALIZATION_TIMEOUT' ` + -Phase 'PROCESS_STATE' ` + -Callsite 'PROCESS_TREE_ASSERTION' ` + -Field 'PROCESS_TREE' ` + -Action { + Assert-ProcessTreeGone $earlyInitializationState + } + Invoke-SupervisorAttributedOperation ` + -Scenario 'EARLY_INITIALIZATION_TIMEOUT' ` + -Phase 'MANIFEST_ASSERTION' ` + -Callsite 'MANIFEST_PRESERVATION' ` + -Field 'MANIFEST_PATH' ` + -Action { + Assert-True (Test-Path -LiteralPath $workflowManifest -PathType Leaf) ` + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + 'EARLY_INITIALIZATION_TIMEOUT' ` + 'MANIFEST_ASSERTION' ` + 'MANIFEST_PRESERVATION' ` + 'MANIFEST_PATH') + } $timedOutCleanup = Invoke-WorkflowCleanupController ` 'CLEANUP_TIMEOUT' $workflowManifest $workflowRunId $workflowStateDirectory 1 Assert-True ($timedOutCleanup.ExitCode -eq 124 -and From 753f45e902349c79ee9c9eb9bf3a60412ea2fa50 Mon Sep 17 00:00:00 2001 From: "propr-dev[bot]" <1316198+propr-dev[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 13:28:33 +0000 Subject: [PATCH 14/33] feat(ai): Implemented the narrow follow-up on head `44d2ee1b1e34a459fdae40d1c160eb447a506842`. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implemented the narrow follow-up on head `44d2ee1b1e34a459fdae40d1c160eb447a506842`. Changed [test-installed-windows-app-supervisor.ps1](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T13-13-47/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1:1596) to add an exact validator for bounded `PROPR_WORKFLOW_CLEANUP_FIXTURE:PROTOCOL_MISMATCH` diagnostics, including fixed enums and bounded `STDERR_COUNT <= 4096`. The outer attribution boundary now preserves those exact protocol mismatch diagnostics instead of overwriting them with `CONTROLLER_PROTOCOL_PARSE/PROTOCOL`. Updated [release-workflow.test.ts](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T13-13-47/apps/desktop/src/release-workflow.test.ts:613) so the static assertion recognizes the non-colliding `$caseExpectedTest` invocation, and added static coverage for protocol-mismatch preservation plus the fixture’s early process-state/PID contract. Validation: - `npx tsx --test apps/desktop/src/release-workflow.test.ts` passed: 23/23. - `npm run test:prepare` passed. - `git diff --check` passed. - `npm run test:full` was attempted; it built successfully and reached the test suite, but this host has no `redis-server`, `docker`, or `pwsh`. The run blocked at Redis connection retries in `test/llmMetrics.test.ts`, so I stopped it to avoid leaving a running process. - Native Windows x64/ARM64 supervisor runs could not be executed in this Linux environment. PR: #2057 Comment by: @integry (ID: 5510103061) Model: gpt-5.5 --- .../test-installed-windows-app-supervisor.ps1 | 43 ++++++++++++++++++- apps/desktop/src/release-workflow.test.ts | 18 +++++++- 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 index e2b2c6ca2..8909f855f 100644 --- a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 +++ b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 @@ -1593,6 +1593,45 @@ function Test-SupervisorInvocationDiagnosticExact([string]$Diagnostic) { $match.Groups[5].Value -cin (Get-SupervisorInvocationFields) } +function Test-WorkflowCleanupProtocolMismatchDiagnosticExact([string]$Diagnostic) { + $match = [regex]::Match( + [string]$Diagnostic, + ('^PROPR_WORKFLOW_CLEANUP_FIXTURE:PROTOCOL_MISMATCH:' + + 'INVOCATION:([A-Z_]+):OBSERVED:' + + '(NONE|STARTUP|TERMINAL|MALFORMED|PARTIAL|DUPLICATE|REORDERED|EXTRA|OVERSIZED):' + + 'LINE_COUNT:(0|1|2|3\+):STDERR_COUNT:(0|[1-9][0-9]{0,3}):' + + 'PROCESS_EXIT:(0|20|21|122|123|124|125|INVALID):' + + 'LIFECYCLE:(EXITED|PROCESS_CREATION_FAILURE|OWNERSHIP_FAILURE|' + + 'TIMEOUT_BEFORE_STARTUP|TIMEOUT_AFTER_STARTUP|' + + 'CANCELLED_BEFORE_STARTUP|CANCELLED_AFTER_STARTUP|' + + 'ACTIVE_TREE_AFTER_EXIT|DRAIN_TIMEOUT|DRAIN_FAILURE):' + + 'TREE_TERMINATION:(NOT_REQUIRED|COMPLETE|FAILED):' + + 'STARTUP_CLASS:(NONE|READY|PARSER|PARAMETER_BINDING|TYPE_LOAD|OTHER):' + + 'LINE_NUMBER:([0-3])$'), + [Text.RegularExpressions.RegexOptions]::CultureInvariant) + if (!$match.Success) { return $false } + $standardErrorCount = 0 + if (![int]::TryParse( + $match.Groups[4].Value, + [Globalization.NumberStyles]::None, + [Globalization.CultureInfo]::InvariantCulture, + [ref]$standardErrorCount + ) -or $standardErrorCount -gt 4096) { + return $false + } + return $match.Groups[1].Value -cin @( + 'STARTUP_PROTOCOL','REPLACEMENT_RETRY','REPLACED_ENTRY_RETRY', + 'PROFILE_ALTERNATE_LEAF','PROFILE_RETRY','EXECUTABLE_IDENTITY_RETRY', + 'FOREIGN_CHILD_RETRY','TERMINATION_RETRY','PARAMETER_VALIDATION', + 'EARLY_INITIALIZATION_TIMEOUT','CLEANUP_TIMEOUT','INSTALLER_REPLACEMENT', + 'RESOURCE_COLLISION','WORKFLOW_RETRY','NORMAL_CLEANUP','MANIFEST_VALIDATION', + 'SMOKE_PROMOTION_RETRY','SMOKE_TOKEN_MISSING','SMOKE_TOKEN_RETRY', + 'APP_PATH_MISMATCH','HKCU_BASELINE_RESTORE','HKCU_PENDING_RECEIPT', + 'HKCU_NONEMPTY','HKCU_EMPTY','HKCU_CONFLICT','HKCU_PROVISIONAL', + 'USER_MARKER_OWNED','USER_MARKER_REPLACEMENT','PROTOCOL_REGRESSION' + ) +} + function Get-SupervisorAttributionTotalityCases { return @( 'GENERAL', @@ -1697,7 +1736,9 @@ function Invoke-SupervisorAttributedBoundary( try { & $Action } catch { - if (Test-SupervisorInvocationDiagnosticExact $_.Exception.Message) { + $diagnosticMessage = [string]$_.Exception.Message + if ((Test-SupervisorInvocationDiagnosticExact $diagnosticMessage) -or + (Test-WorkflowCleanupProtocolMismatchDiagnosticExact $diagnosticMessage)) { throw } throw (Get-SanitizedSupervisorInvocationDiagnostic ` diff --git a/apps/desktop/src/release-workflow.test.ts b/apps/desktop/src/release-workflow.test.ts index 1682b0bcf..caabd47f7 100644 --- a/apps/desktop/src/release-workflow.test.ts +++ b/apps/desktop/src/release-workflow.test.ts @@ -612,7 +612,15 @@ describe('desktop trusted release workflow', () => { ); assert.match( installedWindowsAppSupervisorBehaviorTest, - /CaseId='BOUNDARY_BOOTSTRAP_TIMEOUT'[\s\S]*Invoke-SupervisorAttributedTest -Test \$case\.Test -Action/, + /CaseId='BOUNDARY_BOOTSTRAP_TIMEOUT'[\s\S]*\$caseExpectedTest = \[string\]\$case\.Test[\s\S]*Invoke-SupervisorAttributedTest -Test \$caseExpectedTest -Action/, + ); + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /function Test-WorkflowCleanupProtocolMismatchDiagnosticExact[\s\S]*PROPR_WORKFLOW_CLEANUP_FIXTURE:PROTOCOL_MISMATCH:/, + ); + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /Test-SupervisorInvocationDiagnosticExact \$diagnosticMessage\) -or\s*\(Test-WorkflowCleanupProtocolMismatchDiagnosticExact \$diagnosticMessage\)/, ); assert.match( installedWindowsAppSupervisorBehaviorTest, @@ -976,7 +984,13 @@ describe('desktop trusted release workflow', () => { < installedWindowsAppCleanup.indexOf("Add-Type -TypeDefinition @'"), 'cleanup worker ownership handshake must precede cold type loading', ); - assert.match(installedWindowsAppSupervisorBehaviorTest, /early-initialization child cleanup/); + assert.match(installedWindowsAppSupervisorBehaviorTest, /-FixtureEarlyInitializationChild/); + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /workflow-cleanup-early-processes\.json/, + ); + assert.match(installedWindowsAppSupervisorBehaviorTest, /'WorkerPid','DescendantPid'/); + assert.match(installedWindowsAppSupervisorBehaviorTest, /\$pidValue -gt 0/); assert.match(installedWindowsAppCleanup, /workflow-cleanup-early-processes\.json/); assert.match(installedWindowsAppWorkflowCleanup, /MANIFEST_VALIDATION_FAILURE/); assert.match(installedWindowsAppWorkflowCleanup, /OWNED_RESOURCE_CLEANUP_FAILURE/); From 3be32b723fedd036b2680e40a6a566b9c8c39dd7 Mon Sep 17 00:00:00 2001 From: "propr-dev[bot]" <1316198+propr-dev[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 13:52:51 +0000 Subject: [PATCH 15/33] feat(ai): Implemented the narrow attribution split on head `753f45e902349c79ee9c9eb9bf3a60412ea2fa50`. Implemented the narrow attribution split on head `753f45e902349c79ee9c9eb9bf3a60412ea2fa50`. Changed [test-installed-windows-app-supervisor.ps1](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T13-35-03/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1:643) so `Read-FixtureProcessState` now attributes separate fixed predicates for state-directory input, `processes.json` path construction, bounded publication wait, read/parse, `WorkerPid`, and `DescendantPid`. The old raw publication timeout throw is gone, and exceptions inside those predicates now map to allowlisted `test/scenario/phase/callsite/field` tokens only. Updated [release-workflow.test.ts](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T13-35-03/apps/desktop/src/release-workflow.test.ts:647) to statically lock in those callsites and prevent the raw process-state publication timeout from returning. I did not change the fixture/state producer: inspection showed it already writes `processes.json` atomically under the owned fixture state directory before opening the inaccessible marker, so the native evidence only proved collapsed reader attribution, not a producer collision. Validation run: - `npm run build` passed - `npm run test:prepare` passed - `npm run typecheck -w @propr/desktop` passed - `npm run test -w @propr/desktop` passed - `npx tsx --test apps/desktop/src/release-workflow.test.ts` passed - `git diff --check` passed Native `PROPR_WINDOWS_SUPERVISOR_TESTS::PASSED` could not be run locally because this container has no `pwsh`/Windows PowerShell. I checked the fresh CI evidence available before this change: x64 had the collapsed `FAIL_CLOSED_MARKERS / INACCESSIBLE_MARKER / PROCESS_STATE / PROCESS_STATE_PATH / STATE_DIRECTORY` token, while ARM64 completed with a differing earlier workflow cleanup protocol mismatch token. Full `npm run test:full` also could not complete locally because Redis/Docker are unavailable; it hit Redis `ECONNREFUSED`. PR: #2057 Comment by: @integry (ID: 5510384822) Model: gpt-5.5 --- .../test-installed-windows-app-supervisor.ps1 | 175 +++++++++++++----- apps/desktop/src/release-workflow.test.ts | 21 ++- 2 files changed, 151 insertions(+), 45 deletions(-) diff --git a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 index 8909f855f..9603271b2 100644 --- a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 +++ b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 @@ -641,52 +641,97 @@ function New-SupervisorStartInfo( } function Read-FixtureProcessState([string]$StateDirectory) { - Set-SupervisorInvocationContext ` - $script:currentSupervisorInvocationTest ` - $script:currentSupervisorInvocationScenario ` - 'PROCESS_STATE' ` - 'PROCESS_STATE_PATH' ` - 'STATE_DIRECTORY' - Assert-True (![string]::IsNullOrWhiteSpace($StateDirectory)) ` - (Get-SanitizedSupervisorInvocationDiagnostic ` - $script:currentSupervisorInvocationTest ` - $script:currentSupervisorInvocationScenario ` - 'PROCESS_STATE' ` - 'PROCESS_STATE_PATH' ` - 'STATE_DIRECTORY') - $statePath = Join-Path $StateDirectory 'processes.json' - $stopwatch = [Diagnostics.Stopwatch]::StartNew() - while (!(Test-Path -LiteralPath $statePath -PathType Leaf)) { - if ($stopwatch.ElapsedMilliseconds -ge 15000) { - throw 'fixture did not publish process state' + $processStateScenario = $script:currentSupervisorInvocationScenario + $processStateDirectory = $StateDirectory + Invoke-SupervisorAttributedOperation ` + -Scenario $processStateScenario ` + -Phase 'PROCESS_STATE' ` + -Callsite 'PROCESS_STATE_DIRECTORY_INPUT' ` + -Field 'STATE_DIRECTORY' ` + -Action { + Assert-True (![string]::IsNullOrWhiteSpace([string]$processStateDirectory)) ` + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + $processStateScenario ` + 'PROCESS_STATE' ` + 'PROCESS_STATE_DIRECTORY_INPUT' ` + 'STATE_DIRECTORY') } - Start-Sleep -Milliseconds 25 - } - Set-SupervisorInvocationContext ` - $script:currentSupervisorInvocationTest ` - $script:currentSupervisorInvocationScenario ` - 'PROCESS_STATE' ` - 'PROCESS_STATE_READ' ` - 'PROCESS_STATE_PATH' - $state = Get-Content -LiteralPath $statePath -Raw -Encoding ASCII | - ConvertFrom-Json -ErrorAction Stop - foreach ($field in @('WorkerPid','DescendantPid')) { - $property = $state.PSObject.Properties[$field] - $pidValue = 0 - Assert-True ($null -ne $property -and [int]::TryParse( - [string]$property.Value, - [Globalization.NumberStyles]::None, - [Globalization.CultureInfo]::InvariantCulture, - [ref]$pidValue - ) -and $pidValue -gt 0) ` - (Get-SanitizedSupervisorInvocationDiagnostic ` - $script:currentSupervisorInvocationTest ` - $script:currentSupervisorInvocationScenario ` - 'PROCESS_STATE' ` - 'PROCESS_STATE_READ' ` - (($field -creplace '([a-z])([A-Z])', '$1_$2').ToUpperInvariant())) + $processStatePath = Invoke-SupervisorAttributedOperation ` + -Scenario $processStateScenario ` + -Phase 'PROCESS_STATE' ` + -Callsite 'PROCESS_STATE_PATH_CONSTRUCTION' ` + -Field 'PROCESS_STATE_PATH' ` + -Action { + $constructedProcessStatePath = Join-Path $processStateDirectory 'processes.json' + Assert-True (![string]::IsNullOrWhiteSpace([string]$constructedProcessStatePath)) ` + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + $processStateScenario ` + 'PROCESS_STATE' ` + 'PROCESS_STATE_PATH_CONSTRUCTION' ` + 'PROCESS_STATE_PATH') + $constructedProcessStatePath + } + Invoke-SupervisorAttributedOperation ` + -Scenario $processStateScenario ` + -Phase 'PROCESS_STATE' ` + -Callsite 'PROCESS_STATE_PUBLICATION_WAIT' ` + -Field 'PROCESS_STATE_PATH' ` + -Action { + $processStateWait = [Diagnostics.Stopwatch]::StartNew() + while (!(Test-Path -LiteralPath $processStatePath -PathType Leaf)) { + if ($processStateWait.ElapsedMilliseconds -ge 15000) { + Assert-True $false ` + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + $processStateScenario ` + 'PROCESS_STATE' ` + 'PROCESS_STATE_PUBLICATION_WAIT' ` + 'PROCESS_STATE_PATH') + } + Start-Sleep -Milliseconds 25 + } + } + $processState = Invoke-SupervisorAttributedOperation ` + -Scenario $processStateScenario ` + -Phase 'PROCESS_STATE' ` + -Callsite 'PROCESS_STATE_READ_PARSE' ` + -Field 'PROCESS_STATE_PATH' ` + -Action { + Get-Content -LiteralPath $processStatePath -Raw -Encoding ASCII | + ConvertFrom-Json -ErrorAction Stop + } + foreach ($processStatePidCase in @( + @{ Property = 'WorkerPid'; Callsite = 'PROCESS_STATE_WORKER_PID'; Field = 'WORKER_PID' }, + @{ Property = 'DescendantPid'; Callsite = 'PROCESS_STATE_DESCENDANT_PID'; Field = 'DESCENDANT_PID' } + )) { + $processStatePropertyName = [string]$processStatePidCase.Property + $processStateCallsiteToken = [string]$processStatePidCase.Callsite + $processStateFieldToken = [string]$processStatePidCase.Field + Invoke-SupervisorAttributedOperation ` + -Scenario $processStateScenario ` + -Phase 'PROCESS_STATE' ` + -Callsite $processStateCallsiteToken ` + -Field $processStateFieldToken ` + -Action { + $processStateProperty = $processState.PSObject.Properties[$processStatePropertyName] + $processStatePidValue = 0 + Assert-True ($null -ne $processStateProperty -and [int]::TryParse( + [string]$processStateProperty.Value, + [Globalization.NumberStyles]::None, + [Globalization.CultureInfo]::InvariantCulture, + [ref]$processStatePidValue + ) -and $processStatePidValue -gt 0) ` + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + $processStateScenario ` + 'PROCESS_STATE' ` + $processStateCallsiteToken ` + $processStateFieldToken) + } } - return $state + return $processState } function Read-FixtureResourceState([string]$StateDirectory) { @@ -1480,6 +1525,12 @@ function Get-SupervisorInvocationCallsites { 'CRITICAL_GATE_READ', 'PROCESS_STATE_PATH', 'PROCESS_STATE_READ', + 'PROCESS_STATE_DIRECTORY_INPUT', + 'PROCESS_STATE_PATH_CONSTRUCTION', + 'PROCESS_STATE_PUBLICATION_WAIT', + 'PROCESS_STATE_READ_PARSE', + 'PROCESS_STATE_WORKER_PID', + 'PROCESS_STATE_DESCENDANT_PID', 'PROCESS_TREE_ASSERTION', 'CONTROLLER_INVOCATION_INPUT', 'CONTROLLER_PROTOCOL_PARSE', @@ -1651,6 +1702,12 @@ function Get-SupervisorAttributionTotalityCases { 'CALLSITE_CRITICAL_GATE_PATH', 'CALLSITE_CRITICAL_GATE_READ', 'CALLSITE_PROCESS_STATE_READ', + 'CALLSITE_FIXTURE_PROCESS_STATE_DIRECTORY_INPUT', + 'CALLSITE_FIXTURE_PROCESS_STATE_PATH_CONSTRUCTION', + 'CALLSITE_FIXTURE_PROCESS_STATE_PUBLICATION_WAIT', + 'CALLSITE_FIXTURE_PROCESS_STATE_READ_PARSE', + 'CALLSITE_FIXTURE_PROCESS_STATE_WORKER_PID', + 'CALLSITE_FIXTURE_PROCESS_STATE_DESCENDANT_PID', 'CALLSITE_PROCESS_TREE_ASSERTION', 'CALLSITE_CONTROLLER_INPUT_MANIFEST', 'CALLSITE_CONTROLLER_INPUT_RUN_ID', @@ -2642,6 +2699,36 @@ function Test-SupervisorInvocationAttributionTotality { Test='MSI_TRANSACTION_INTERRUPTION_GATES'; Scenario='DURING_MSI' Phase='PROCESS_STATE'; Callsite='PROCESS_STATE_READ'; Field='PROCESS_STATE_PATH' }, + [PSCustomObject]@{ + CaseId='CALLSITE_FIXTURE_PROCESS_STATE_DIRECTORY_INPUT' + Test='FAIL_CLOSED_MARKERS'; Scenario='INACCESSIBLE_MARKER' + Phase='PROCESS_STATE'; Callsite='PROCESS_STATE_DIRECTORY_INPUT'; Field='STATE_DIRECTORY' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_FIXTURE_PROCESS_STATE_PATH_CONSTRUCTION' + Test='FAIL_CLOSED_MARKERS'; Scenario='INACCESSIBLE_MARKER' + Phase='PROCESS_STATE'; Callsite='PROCESS_STATE_PATH_CONSTRUCTION'; Field='PROCESS_STATE_PATH' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_FIXTURE_PROCESS_STATE_PUBLICATION_WAIT' + Test='FAIL_CLOSED_MARKERS'; Scenario='INACCESSIBLE_MARKER' + Phase='PROCESS_STATE'; Callsite='PROCESS_STATE_PUBLICATION_WAIT'; Field='PROCESS_STATE_PATH' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_FIXTURE_PROCESS_STATE_READ_PARSE' + Test='FAIL_CLOSED_MARKERS'; Scenario='INACCESSIBLE_MARKER' + Phase='PROCESS_STATE'; Callsite='PROCESS_STATE_READ_PARSE'; Field='PROCESS_STATE_PATH' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_FIXTURE_PROCESS_STATE_WORKER_PID' + Test='FAIL_CLOSED_MARKERS'; Scenario='INACCESSIBLE_MARKER' + Phase='PROCESS_STATE'; Callsite='PROCESS_STATE_WORKER_PID'; Field='WORKER_PID' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_FIXTURE_PROCESS_STATE_DESCENDANT_PID' + Test='FAIL_CLOSED_MARKERS'; Scenario='INACCESSIBLE_MARKER' + Phase='PROCESS_STATE'; Callsite='PROCESS_STATE_DESCENDANT_PID'; Field='DESCENDANT_PID' + }, [PSCustomObject]@{ CaseId='CALLSITE_PROCESS_TREE_ASSERTION' Test='MSI_TRANSACTION_INTERRUPTION_GATES'; Scenario='DURING_MSI' diff --git a/apps/desktop/src/release-workflow.test.ts b/apps/desktop/src/release-workflow.test.ts index caabd47f7..023be8929 100644 --- a/apps/desktop/src/release-workflow.test.ts +++ b/apps/desktop/src/release-workflow.test.ts @@ -647,6 +647,25 @@ describe('desktop trusted release workflow', () => { installedWindowsAppSupervisorBehaviorTest, /Assert-SupervisorInvocationDiagnosticBounded[\s\S]*Cannot bind argument[\s\S]*LiteralPath[\s\S]*Registry::[\s\S]*stdout[\s\S]*stderr/, ); + const fixtureProcessStateReader = installedWindowsAppSupervisorBehaviorTest.slice( + installedWindowsAppSupervisorBehaviorTest.indexOf('function Read-FixtureProcessState'), + installedWindowsAppSupervisorBehaviorTest.indexOf('function Read-FixtureResourceState'), + ); + for (const callsite of [ + 'PROCESS_STATE_DIRECTORY_INPUT', + 'PROCESS_STATE_PATH_CONSTRUCTION', + 'PROCESS_STATE_PUBLICATION_WAIT', + 'PROCESS_STATE_READ_PARSE', + 'PROCESS_STATE_WORKER_PID', + 'PROCESS_STATE_DESCENDANT_PID', + ]) { + assert.match(fixtureProcessStateReader, new RegExp(callsite)); + assert.match(installedWindowsAppSupervisorBehaviorTest, new RegExp(`'${callsite}'`)); + } + assert.doesNotMatch( + fixtureProcessStateReader, + /throw 'fixture did not publish process state'/, + ); assert.match(installedWindowsAppSupervisorBehaviorTest, /Test-PreExistingAppPathsAuthority/); assert.match(installedWindowsAppSupervisorBehaviorTest, /Assert-ProcessTreeGone/); assert.match(installedWindowsAppSupervisorBehaviorTest, /Assert-OwnedFixtureAuthorityComplete/); @@ -990,7 +1009,7 @@ describe('desktop trusted release workflow', () => { /workflow-cleanup-early-processes\.json/, ); assert.match(installedWindowsAppSupervisorBehaviorTest, /'WorkerPid','DescendantPid'/); - assert.match(installedWindowsAppSupervisorBehaviorTest, /\$pidValue -gt 0/); + assert.match(installedWindowsAppSupervisorBehaviorTest, /\$processStatePidValue -gt 0/); assert.match(installedWindowsAppCleanup, /workflow-cleanup-early-processes\.json/); assert.match(installedWindowsAppWorkflowCleanup, /MANIFEST_VALIDATION_FAILURE/); assert.match(installedWindowsAppWorkflowCleanup, /OWNED_RESOURCE_CLEANUP_FAILURE/); From 3e38e7d946ff482194bc8b681173c1690708f5c7 Mon Sep 17 00:00:00 2001 From: "propr-dev[bot]" <1316198+propr-dev[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 14:15:30 +0000 Subject: [PATCH 16/33] feat(ai): Implemented the narrow contract correction on head `3be32b723fedd036b2680e40a6a566b9c8c39dd7`. Implemented the narrow contract correction on head `3be32b723fedd036b2680e40a6a566b9c8c39dd7`. Changed [run-installed-windows-app-workflow-cleanup.ps1](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T14-00-30/apps/desktop/scripts/run-installed-windows-app-workflow-cleanup.ps1:1) to make the wrapper an advanced script with `PositionalBinding=$false` and declare `FixtureEarlyInitializationChild` as `[switch]`. The existing forwarding behavior remains exact: the body receives `FixtureEarlyInitializationChild = $true` only when the switch is present. Added regression coverage in [test-installed-windows-app-supervisor.ps1](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T14-00-30/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1:468) and static release-contract checks in [release-workflow.test.ts](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T14-00-30/apps/desktop/src/release-workflow.test.ts:1041). The new regression checks parser validity, switch typing, omitted=false behavior, valueless flag=true behavior, true-only forwarding, and rejection of stray string/object coercion. Validation: - `npm --prefix ... --workspace @propr/desktop test -- src/release-workflow.test.ts`: passed, 177 passed / 6 skipped. - `git diff --check`: passed. - `npm --prefix ... run test:full`: build/test prepare passed and test runner reached 196/333 with no assertion failures, then stalled on missing local Redis with repeated `ECONNREFUSED 127.0.0.1:6379`; stopped manually. - Native Windows x64/ARM64 supervisor suites were not runnable in this Linux container because `pwsh`, `redis-server`, and Docker are unavailable locally. PR: #2057 Comment by: @integry (ID: 5510740792) Model: gpt-5.5 --- ...installed-windows-app-workflow-cleanup.ps1 | 3 +- .../test-installed-windows-app-supervisor.ps1 | 135 ++++++++++++++++++ apps/desktop/src/release-workflow.test.ts | 24 ++++ 3 files changed, 161 insertions(+), 1 deletion(-) diff --git a/apps/desktop/scripts/run-installed-windows-app-workflow-cleanup.ps1 b/apps/desktop/scripts/run-installed-windows-app-workflow-cleanup.ps1 index 82f9abc02..398194de8 100644 --- a/apps/desktop/scripts/run-installed-windows-app-workflow-cleanup.ps1 +++ b/apps/desktop/scripts/run-installed-windows-app-workflow-cleanup.ps1 @@ -1,3 +1,4 @@ +[CmdletBinding(PositionalBinding=$false)] param( [object]$OwnershipManifest, [object]$Installer, @@ -5,7 +6,7 @@ param( [object]$CleanupTimeoutMilliseconds = 4 * 60 * 1000, [object]$TerminationTimeoutMilliseconds = 30 * 1000, [object]$FixtureRoot, - [object]$FixtureEarlyInitializationChild, + [switch]$FixtureEarlyInitializationChild, [switch]$FixtureResultEmissionFailure, [object]$StartupFailureClass, [object]$ProtocolFixture diff --git a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 index 9603271b2..d6b74d1e3 100644 --- a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 +++ b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 @@ -465,6 +465,140 @@ function Test-WorkflowCleanupBodyParserRegression { 'workflow cleanup production body failed whole-file parser regression' } +function Test-WorkflowCleanupWrapperParserRegression { + $tokens = $null + $parseErrors = $null + $wrapperAst = [System.Management.Automation.Language.Parser]::ParseFile( + $workflowCleanupPath, + [ref]$tokens, + [ref]$parseErrors + ) + Assert-True ($parseErrors.Count -eq 0) ` + 'workflow cleanup wrapper failed whole-file parser regression' + $scriptText = Get-Content -LiteralPath $workflowCleanupPath -Raw -Encoding UTF8 + Assert-True ($scriptText -cmatch ( + '^\[CmdletBinding\(PositionalBinding=\$false\)\]\r?\nparam\(')) ` + 'workflow cleanup wrapper did not disable positional switch coercion' + + $cmdletBinding = @($wrapperAst.ParamBlock.Attributes | Where-Object { + $_.TypeName.FullName -ceq 'CmdletBinding' + }) + Assert-True ($cmdletBinding.Count -eq 1) ` + 'workflow cleanup wrapper has no single CmdletBinding contract' + $positionalBinding = @($cmdletBinding[0].NamedArguments | Where-Object { + $_.ArgumentName -ceq 'PositionalBinding' + }) + Assert-True ($positionalBinding.Count -eq 1 -and + $positionalBinding[0].Argument.Extent.Text -ceq '$false') ` + 'workflow cleanup wrapper positional binding was not fail-closed' + + $fixtureParameter = @($wrapperAst.ParamBlock.Parameters | Where-Object { + $_.Name.VariablePath.UserPath -ceq 'FixtureEarlyInitializationChild' + }) + Assert-True ($fixtureParameter.Count -eq 1) ` + 'workflow cleanup wrapper fixture switch parameter is missing' + $fixtureParameterTypes = @($fixtureParameter[0].Attributes | ForEach-Object { + $_.TypeName.FullName + }) + Assert-True (@($fixtureParameterTypes | Where-Object { + $_ -cmatch '^(?:switch|System\.Management\.Automation\.SwitchParameter)$' + }).Count -eq 1) ` + 'workflow cleanup wrapper fixture parameter does not accept a valueless flag' + Assert-True (@($fixtureParameterTypes | Where-Object { + $_ -cmatch '^(?:object|string|System\.Object|System\.String)$' + }).Count -eq 0) ` + 'workflow cleanup wrapper fixture parameter allows object or string coercion' + Assert-True ($null -eq $fixtureParameter[0].DefaultValue) ` + 'workflow cleanup wrapper fixture switch omission was not left false' + Assert-True ($scriptText -cmatch ( + 'if \(\[bool\]\$FixtureEarlyInitializationChild\) \{\s*' + + '\$bodyParameters\.FixtureEarlyInitializationChild = \$true\s*\}')) ` + 'workflow cleanup wrapper did not forward true only to the fixture body' + Assert-True ($scriptText -cnotmatch + '\$bodyParameters\.FixtureEarlyInitializationChild\s*=\s*\$false') ` + 'workflow cleanup wrapper forwarded a false fixture body value' + + $probePath = Join-Path ([IO.Path]::GetTempPath()) ( + "propr-workflow-cleanup-wrapper-switch-$([Guid]::NewGuid().ToString('N')).ps1") + $probeText = @' +[CmdletBinding(PositionalBinding=$false)] +param( + [switch]$FixtureEarlyInitializationChild +) + +if ([bool]$FixtureEarlyInitializationChild) { + [Console]::Out.WriteLine('FORWARD_TRUE') +} else { + [Console]::Out.WriteLine('FORWARD_FALSE') +} +'@ + [IO.File]::WriteAllText( + $probePath, + $probeText, + [Text.UTF8Encoding]::new($false) + ) + try { + function Invoke-FixtureSwitchBindingProbe( + [string]$Path, + [string[]]$Arguments + ) { + $startInfo = [Diagnostics.ProcessStartInfo]::new() + $startInfo.FileName = $hostPath + $startInfo.UseShellExecute = $false + $startInfo.RedirectStandardOutput = $true + $startInfo.RedirectStandardError = $true + foreach ($argument in @( + '-NoLogo', '-NoProfile', '-NonInteractive', '-File', $Path + )) { + $startInfo.ArgumentList.Add($argument) + } + foreach ($argument in $Arguments) { + $startInfo.ArgumentList.Add($argument) + } + $process = [Diagnostics.Process]::new() + $process.StartInfo = $startInfo + try { + if (!$process.Start()) { throw 'fixture switch binding probe did not start' } + if (!$process.WaitForExit(5000)) { + $process.Kill($true) + throw 'fixture switch binding probe timed out' + } + return [PSCustomObject]@{ + ExitCode = $process.ExitCode + StandardOutput = $process.StandardOutput.ReadToEnd().Trim() + StandardError = $process.StandardError.ReadToEnd() + } + } finally { + $process.Dispose() + } + } + + $omitted = Invoke-FixtureSwitchBindingProbe $probePath @() + Assert-True ($omitted.ExitCode -eq 0 -and + $omitted.StandardOutput -ceq 'FORWARD_FALSE' -and + $omitted.StandardError.Length -eq 0) ` + 'workflow cleanup wrapper fixture switch omission did not remain false' + $valueless = Invoke-FixtureSwitchBindingProbe ` + $probePath @('-FixtureEarlyInitializationChild') + Assert-True ($valueless.ExitCode -eq 0 -and + $valueless.StandardOutput -ceq 'FORWARD_TRUE' -and + $valueless.StandardError.Length -eq 0) ` + 'workflow cleanup wrapper fixture switch did not accept a valueless flag' + $invalidString = Invoke-FixtureSwitchBindingProbe ` + $probePath @('-FixtureEarlyInitializationChild', 'arbitrary') + Assert-True ($invalidString.ExitCode -ne 0 -and + $invalidString.StandardOutput.Length -eq 0) ` + 'workflow cleanup wrapper fixture switch accepted a stray string value' + $invalidObject = Invoke-FixtureSwitchBindingProbe ` + $probePath @('-FixtureEarlyInitializationChild:[object]::new()') + Assert-True ($invalidObject.ExitCode -ne 0 -and + $invalidObject.StandardOutput.Length -eq 0) ` + 'workflow cleanup wrapper fixture switch accepted arbitrary object coercion' + } finally { + Remove-Item -LiteralPath $probePath -Force -ErrorAction SilentlyContinue + } +} + function New-StateDirectory([string]$Name) { $path = Join-Path $testRoot $Name [void](New-Item -ItemType Directory -Path $path -ErrorAction Stop) @@ -4891,6 +5025,7 @@ Assert-True ($actualArchitecture -ceq $Architecture) ` "supervisor behavior tests expected $Architecture but are running on $actualArchitecture" Test-WorkflowCleanupBodyParserRegression +Test-WorkflowCleanupWrapperParserRegression [void](New-Item -ItemType Directory -Path $testRoot -ErrorAction Stop) Initialize-TestInstaller try { diff --git a/apps/desktop/src/release-workflow.test.ts b/apps/desktop/src/release-workflow.test.ts index 023be8929..77e2c3126 100644 --- a/apps/desktop/src/release-workflow.test.ts +++ b/apps/desktop/src/release-workflow.test.ts @@ -1038,6 +1038,30 @@ describe('desktop trusted release workflow', () => { installedWindowsAppWorkflowCleanupWrapper, /\[object\]\$OwnershipManifest[\s\S]*\[object\]\$Installer[\s\S]*\[object\]\$ExpectedRunId/, ); + assert.match( + installedWindowsAppWorkflowCleanupWrapper, + /^\[CmdletBinding\(PositionalBinding=\$false\)\]\nparam\(/, + ); + assert.match( + installedWindowsAppWorkflowCleanupWrapper, + /\[switch\]\$FixtureEarlyInitializationChild/, + ); + assert.doesNotMatch( + installedWindowsAppWorkflowCleanupWrapper, + /\[(?:object|string)\]\$FixtureEarlyInitializationChild/, + ); + assert.match( + installedWindowsAppWorkflowCleanupWrapper, + /if \(\[bool\]\$FixtureEarlyInitializationChild\) \{\n\s+\$bodyParameters\.FixtureEarlyInitializationChild = \$true\n\s+\}/, + ); + assert.doesNotMatch( + installedWindowsAppWorkflowCleanupWrapper, + /\$bodyParameters\.FixtureEarlyInitializationChild\s*=\s*\$false/, + ); + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /Test-WorkflowCleanupWrapperParserRegression[\s\S]*FixtureEarlyInitializationChild[\s\S]*FORWARD_FALSE[\s\S]*FORWARD_TRUE[\s\S]*arbitrary/, + ); assert.match( installedWindowsAppWorkflowCleanupWrapper, /'PARSER'[\s\S]*'PARAMETER_BINDING'[\s\S]*'TYPE_LOAD'[\s\S]*'OTHER'/, From 3f29aec0f7b0c82091d72d0195394d062d4cc887 Mon Sep 17 00:00:00 2001 From: "propr-dev[bot]" <1316198+propr-dev[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 14:36:31 +0000 Subject: [PATCH 17/33] feat(ai): Implemented the follow-up on head `3e38e7d946ff482194bc8b681173c1690708f5c7`. Implemented the follow-up on head `3e38e7d946ff482194bc8b681173c1690708f5c7`. Changed [test-installed-windows-app-supervisor.ps1](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T14-23-11/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1:871) to: - Split `Read-FixtureResourceState` into exact `RESOURCE_STATE_*` callsites with `STATE_DIRECTORY` / `RESOURCE_STATE_PATH`. - Replace the stale `PROCESS_STATE_PATH/STATE_DIRECTORY` context left by `Invoke-CriticalCancellationScenario` with exact `CRITICAL_RESULT_FIELD` / `CRITICAL_OUTPUT_MARKER` predicates. - Split `Assert-OwnedResourcesGone` into exact filesystem, shortcut, registry value/path/root, user, and profile absence predicates. - Add a shared bounded 5s cleanup stabilization for final absence checks, with 15s fixture readiness preserved. - Preserve the audited `[switch]$FixtureEarlyInitializationChild` wrapper correction unchanged. Added static/totality coverage in [release-workflow.test.ts](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T14-23-11/apps/desktop/src/release-workflow.test.ts:669) for the new classifications and timing invariants. Validation: - `npm --workspace @propr/desktop test -- src/release-workflow.test.ts`: passed, `177` passed, `6` skipped. - `git diff --check`: passed. - Native Windows x64/ARM64 full suites were not runnable here: this host is Linux and `pwsh` is not installed, so I could not produce new native job IDs or observe new first exact predicates. The prior broad native job IDs remain x64 `100283423676` and ARM64 `100283423692`. PR: #2057 Comment by: @integry (ID: 5511070801) Model: gpt-5.5 --- .../test-installed-windows-app-supervisor.ps1 | 511 ++++++++++++++++-- apps/desktop/src/release-workflow.test.ts | 87 ++- 2 files changed, 540 insertions(+), 58 deletions(-) diff --git a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 index d6b74d1e3..df1ec6c7c 100644 --- a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 +++ b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 @@ -869,19 +869,67 @@ function Read-FixtureProcessState([string]$StateDirectory) { } function Read-FixtureResourceState([string]$StateDirectory) { - Set-SupervisorInvocationContext ` - $script:currentSupervisorInvocationTest ` - $script:currentSupervisorInvocationScenario ` - 'RESOURCE_STATE' - $statePath = Join-Path $StateDirectory 'resources.json' - $stopwatch = [Diagnostics.Stopwatch]::StartNew() - while (!(Test-Path -LiteralPath $statePath -PathType Leaf)) { - if ($stopwatch.ElapsedMilliseconds -ge 45000) { - throw 'fixture did not publish owned resource state' + $resourceStateScenario = $script:currentSupervisorInvocationScenario + $resourceStateDirectory = $StateDirectory + Invoke-SupervisorAttributedOperation ` + -Scenario $resourceStateScenario ` + -Phase 'RESOURCE_STATE' ` + -Callsite 'RESOURCE_STATE_DIRECTORY_INPUT' ` + -Field 'STATE_DIRECTORY' ` + -Action { + Assert-True (![string]::IsNullOrWhiteSpace([string]$resourceStateDirectory)) ` + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + $resourceStateScenario ` + 'RESOURCE_STATE' ` + 'RESOURCE_STATE_DIRECTORY_INPUT' ` + 'STATE_DIRECTORY') + } + $statePath = Invoke-SupervisorAttributedOperation ` + -Scenario $resourceStateScenario ` + -Phase 'RESOURCE_STATE' ` + -Callsite 'RESOURCE_STATE_PATH_CONSTRUCTION' ` + -Field 'RESOURCE_STATE_PATH' ` + -Action { + $constructedResourceStatePath = Join-Path $resourceStateDirectory 'resources.json' + Assert-True (![string]::IsNullOrWhiteSpace([string]$constructedResourceStatePath)) ` + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + $resourceStateScenario ` + 'RESOURCE_STATE' ` + 'RESOURCE_STATE_PATH_CONSTRUCTION' ` + 'RESOURCE_STATE_PATH') + $constructedResourceStatePath + } + Invoke-SupervisorAttributedOperation ` + -Scenario $resourceStateScenario ` + -Phase 'RESOURCE_STATE' ` + -Callsite 'RESOURCE_STATE_PUBLICATION_WAIT' ` + -Field 'RESOURCE_STATE_PATH' ` + -Action { + $stopwatch = [Diagnostics.Stopwatch]::StartNew() + while (!(Test-Path -LiteralPath $statePath -PathType Leaf)) { + if ($stopwatch.ElapsedMilliseconds -ge 15000) { + Assert-True $false ` + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + $resourceStateScenario ` + 'RESOURCE_STATE' ` + 'RESOURCE_STATE_PUBLICATION_WAIT' ` + 'RESOURCE_STATE_PATH') + } + Start-Sleep -Milliseconds 25 + } + } + return Invoke-SupervisorAttributedOperation ` + -Scenario $resourceStateScenario ` + -Phase 'RESOURCE_STATE' ` + -Callsite 'RESOURCE_STATE_READ_PARSE' ` + -Field 'RESOURCE_STATE_PATH' ` + -Action { + Get-Content -LiteralPath $statePath -Raw -Encoding ASCII | + ConvertFrom-Json -ErrorAction Stop } - Start-Sleep -Milliseconds 25 - } - return Get-Content -LiteralPath $statePath -Raw -Encoding ASCII | ConvertFrom-Json } function Assert-ProcessTreeGone($State) { @@ -1287,28 +1335,120 @@ function Get-WorkflowCleanupControllerStatusMatch([string]$TerminalLine) { ) } +function Test-OwnedRegistryValueAbsent([string]$Path, [string]$Name) { + if (!(Test-Path -LiteralPath $Path)) { return $true } + $property = Get-ItemProperty -LiteralPath $Path -Name $Name ` + -ErrorAction SilentlyContinue + if ($null -eq $property) { return $true } + return $null -eq $property.PSObject.Properties[$Name] +} + +function Assert-OwnedResourcePredicate( + [string]$Scenario, + [string]$Callsite, + [string]$Field, + [Diagnostics.Stopwatch]$CleanupStopwatch, + [scriptblock]$Predicate +) { + $predicateScenario = $Scenario + $predicateCallsite = $Callsite + $predicateField = $Field + $predicateCleanupStopwatch = $CleanupStopwatch + Invoke-SupervisorAttributedOperation ` + -Scenario $predicateScenario ` + -Phase 'RESOURCE_ASSERTION' ` + -Callsite $predicateCallsite ` + -Field $predicateField ` + -Action { + do { + if (& $Predicate) { return } + if ($predicateCleanupStopwatch.ElapsedMilliseconds -ge 5000) { break } + Start-Sleep -Milliseconds 25 + } while ($true) + Assert-True $false ` + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + $predicateScenario ` + 'RESOURCE_ASSERTION' ` + $predicateCallsite ` + $predicateField) + } +} + function Assert-OwnedResourcesGone($Owned) { - Set-SupervisorInvocationContext ` - $script:currentSupervisorInvocationTest ` - $script:currentSupervisorInvocationScenario ` - 'RESOURCE_ASSERTION' - foreach ($ownedPath in @( - $Owned.OwnedRoot, $Owned.InstallRoot, $Owned.ShortcutFolder, - $Owned.Shortcut, $Owned.SmokeDirectory - )) { - Assert-True (!(Test-Path -LiteralPath $ownedPath)) ` - 'external cleanup left a run-owned file-system resource behind' + $resourceScenario = $script:currentSupervisorInvocationScenario + $cleanupStopwatch = [Diagnostics.Stopwatch]::StartNew() + foreach ($ownedDirectoryCase in @( + @{ Path = $Owned.OwnedRoot; Field = 'OWNED_ROOT' }, + @{ Path = $Owned.InstallRoot; Field = 'INSTALL_ROOT' }, + @{ Path = $Owned.ShortcutFolder; Field = 'SHORTCUT_FOLDER' }, + @{ Path = $Owned.SmokeDirectory; Field = 'SMOKE_DIRECTORY' } + )) { + $ownedDirectoryPath = [string]$ownedDirectoryCase.Path + $ownedDirectoryField = [string]$ownedDirectoryCase.Field + Assert-OwnedResourcePredicate ` + -Scenario $resourceScenario ` + -Callsite 'FINAL_FILESYSTEM_DIRECTORY_ABSENCE' ` + -Field $ownedDirectoryField ` + -CleanupStopwatch $cleanupStopwatch ` + -Predicate { !(Test-Path -LiteralPath $ownedDirectoryPath) } + } + $ownedExecutablePath = [string]$Owned.Executable + if (![string]::IsNullOrWhiteSpace($ownedExecutablePath)) { + Assert-OwnedResourcePredicate ` + -Scenario $resourceScenario ` + -Callsite 'FINAL_FILESYSTEM_FILE_ABSENCE' ` + -Field 'EXECUTABLE' ` + -CleanupStopwatch $cleanupStopwatch ` + -Predicate { !(Test-Path -LiteralPath $ownedExecutablePath) } } - Assert-True (!(Test-Path -LiteralPath $Owned.RegistryPath)) ` - 'external cleanup left a run-owned registry resource behind' - Assert-True (!(Test-Path -LiteralPath $Owned.RegistryRoot)) ` - 'external cleanup left the run-owned registry root behind' - Assert-True ($null -eq (Get-LocalUser -Name $Owned.UserName -ErrorAction SilentlyContinue)) ` - 'external cleanup left the run-owned local user behind' - $ownedProfiles = @(Get-CimInstance -ClassName Win32_UserProfile -ErrorAction Stop | - Where-Object { $_.SID -ceq $Owned.UserSid }) - Assert-True ($ownedProfiles.Count -eq 0) ` - 'external cleanup left the run-owned profile behind' + $ownedShortcutPath = [string]$Owned.Shortcut + Assert-OwnedResourcePredicate ` + -Scenario $resourceScenario ` + -Callsite 'FINAL_SHORTCUT_ABSENCE' ` + -Field 'SHORTCUT' ` + -CleanupStopwatch $cleanupStopwatch ` + -Predicate { !(Test-Path -LiteralPath $ownedShortcutPath) } + $ownedRegistryPath = [string]$Owned.RegistryPath + Assert-OwnedResourcePredicate ` + -Scenario $resourceScenario ` + -Callsite 'FINAL_REGISTRY_VALUE_ABSENCE' ` + -Field 'REGISTRY_VALUE' ` + -CleanupStopwatch $cleanupStopwatch ` + -Predicate { Test-OwnedRegistryValueAbsent $ownedRegistryPath 'ProPRInstalledAppOwner' } + Assert-OwnedResourcePredicate ` + -Scenario $resourceScenario ` + -Callsite 'FINAL_REGISTRY_PATH_ABSENCE' ` + -Field 'REGISTRY_PATH' ` + -CleanupStopwatch $cleanupStopwatch ` + -Predicate { !(Test-Path -LiteralPath $ownedRegistryPath) } + $ownedRegistryRoot = [string]$Owned.RegistryRoot + Assert-OwnedResourcePredicate ` + -Scenario $resourceScenario ` + -Callsite 'FINAL_REGISTRY_ROOT_ABSENCE' ` + -Field 'REGISTRY_ROOT' ` + -CleanupStopwatch $cleanupStopwatch ` + -Predicate { !(Test-Path -LiteralPath $ownedRegistryRoot) } + $ownedUserName = [string]$Owned.UserName + Assert-OwnedResourcePredicate ` + -Scenario $resourceScenario ` + -Callsite 'FINAL_USER_ABSENCE' ` + -Field 'USER_NAME' ` + -CleanupStopwatch $cleanupStopwatch ` + -Predicate { + $null -eq (Get-LocalUser -Name $ownedUserName -ErrorAction SilentlyContinue) + } + $ownedUserSid = [string]$Owned.UserSid + Assert-OwnedResourcePredicate ` + -Scenario $resourceScenario ` + -Callsite 'FINAL_PROFILE_ABSENCE' ` + -Field 'USER_SID' ` + -CleanupStopwatch $cleanupStopwatch ` + -Predicate { + $ownedProfiles = @(Get-CimInstance -ClassName Win32_UserProfile -ErrorAction Stop | + Where-Object { $_.SID -ceq $ownedUserSid }) + $ownedProfiles.Count -eq 0 + } } function Convert-FixtureAuthorityFieldToken([string]$Field) { @@ -1657,6 +1797,8 @@ function Get-SupervisorInvocationCallsites { 'GENERAL', 'CRITICAL_GATE_PATH', 'CRITICAL_GATE_READ', + 'CRITICAL_RESULT_FIELD', + 'CRITICAL_OUTPUT_MARKER', 'PROCESS_STATE_PATH', 'PROCESS_STATE_READ', 'PROCESS_STATE_DIRECTORY_INPUT', @@ -1666,6 +1808,10 @@ function Get-SupervisorInvocationCallsites { 'PROCESS_STATE_WORKER_PID', 'PROCESS_STATE_DESCENDANT_PID', 'PROCESS_TREE_ASSERTION', + 'RESOURCE_STATE_DIRECTORY_INPUT', + 'RESOURCE_STATE_PATH_CONSTRUCTION', + 'RESOURCE_STATE_PUBLICATION_WAIT', + 'RESOURCE_STATE_READ_PARSE', 'CONTROLLER_INVOCATION_INPUT', 'CONTROLLER_PROTOCOL_PARSE', 'CONTROLLER_RESULT_FIELD', @@ -1678,6 +1824,14 @@ function Get-SupervisorInvocationCallsites { 'AUTHORITY_RESTORE_REMOVE', 'AUTHORITY_RESTORE_MOVE', 'WORKFLOW_CLEANUP_RETRY', + 'FINAL_FILESYSTEM_DIRECTORY_ABSENCE', + 'FINAL_FILESYSTEM_FILE_ABSENCE', + 'FINAL_SHORTCUT_ABSENCE', + 'FINAL_REGISTRY_VALUE_ABSENCE', + 'FINAL_REGISTRY_PATH_ABSENCE', + 'FINAL_REGISTRY_ROOT_ABSENCE', + 'FINAL_USER_ABSENCE', + 'FINAL_PROFILE_ABSENCE', 'FINAL_ABSENCE_CHECK' ) } @@ -1688,7 +1842,10 @@ function Get-SupervisorInvocationFields { 'STATE_DIRECTORY', 'CRITICAL_GATE_PATH', 'CRITICAL_GATE_CONTENT', + 'MSI_TRANSACTION_MARKER', + 'POST_TERMINATION_CLEANUP_MARKER', 'PROCESS_STATE_PATH', + 'RESOURCE_STATE_PATH', 'WORKER_PID', 'DESCENDANT_PID', 'PROCESS_TREE', @@ -1703,6 +1860,7 @@ function Get-SupervisorInvocationFields { 'SMOKE_DIRECTORY', 'REGISTRY_PATH', 'REGISTRY_ROOT', + 'REGISTRY_VALUE', 'USER_NAME', 'USER_SID', 'PROFILE_PATH', @@ -1835,6 +1993,10 @@ function Get-SupervisorAttributionTotalityCases { 'BOUNDARY_USER_MARKER', 'CALLSITE_CRITICAL_GATE_PATH', 'CALLSITE_CRITICAL_GATE_READ', + 'CALLSITE_CRITICAL_RESULT_EXIT_CODE', + 'CALLSITE_CRITICAL_RESULT_STATE_DIRECTORY', + 'CALLSITE_CRITICAL_OUTPUT_COMMITTED', + 'CALLSITE_CRITICAL_OUTPUT_CLEANUP_COMPLETE', 'CALLSITE_PROCESS_STATE_READ', 'CALLSITE_FIXTURE_PROCESS_STATE_DIRECTORY_INPUT', 'CALLSITE_FIXTURE_PROCESS_STATE_PATH_CONSTRUCTION', @@ -1843,6 +2005,10 @@ function Get-SupervisorAttributionTotalityCases { 'CALLSITE_FIXTURE_PROCESS_STATE_WORKER_PID', 'CALLSITE_FIXTURE_PROCESS_STATE_DESCENDANT_PID', 'CALLSITE_PROCESS_TREE_ASSERTION', + 'CALLSITE_RESOURCE_STATE_DIRECTORY_INPUT', + 'CALLSITE_RESOURCE_STATE_PATH_CONSTRUCTION', + 'CALLSITE_RESOURCE_STATE_PUBLICATION_WAIT', + 'CALLSITE_RESOURCE_STATE_READ_PARSE', 'CALLSITE_CONTROLLER_INPUT_MANIFEST', 'CALLSITE_CONTROLLER_INPUT_RUN_ID', 'CALLSITE_CONTROLLER_INPUT_STATE_DIRECTORY', @@ -1861,6 +2027,17 @@ function Get-SupervisorAttributionTotalityCases { 'FIELD_MANIFEST_PATH', 'CALLSITE_WORKFLOW_CLEANUP_RETRY', 'CALLSITE_FINAL_ABSENCE_CHECK', + 'CALLSITE_FINAL_OWNED_ROOT_ABSENCE', + 'CALLSITE_FINAL_INSTALL_ROOT_ABSENCE', + 'CALLSITE_FINAL_EXECUTABLE_ABSENCE', + 'CALLSITE_FINAL_SHORTCUT_FOLDER_ABSENCE', + 'CALLSITE_FINAL_SHORTCUT_ABSENCE', + 'CALLSITE_FINAL_SMOKE_DIRECTORY_ABSENCE', + 'CALLSITE_FINAL_REGISTRY_VALUE_ABSENCE', + 'CALLSITE_FINAL_REGISTRY_PATH_ABSENCE', + 'CALLSITE_FINAL_REGISTRY_ROOT_ABSENCE', + 'CALLSITE_FINAL_USER_ABSENCE', + 'CALLSITE_FINAL_PROFILE_ABSENCE', 'FORGED_PREFIX_SECRET', 'MISSING_GATE_STATE_DIRECTORY', 'MISSING_EXECUTABLE_BACKUP', @@ -2828,6 +3005,28 @@ function Test-SupervisorInvocationAttributionTotality { Test='MSI_TRANSACTION_INTERRUPTION_GATES'; Scenario='DURING_MSI' Phase='PROCESS_STATE'; Callsite='CRITICAL_GATE_READ'; Field='CRITICAL_GATE_CONTENT' }, + [PSCustomObject]@{ + CaseId='CALLSITE_CRITICAL_RESULT_EXIT_CODE' + Test='MSI_TRANSACTION_INTERRUPTION_GATES'; Scenario='DURING_OWNERSHIP_CAPTURE' + Phase='PROCESS_OUTPUT'; Callsite='CRITICAL_RESULT_FIELD'; Field='EXIT_CODE' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_CRITICAL_RESULT_STATE_DIRECTORY' + Test='MSI_TRANSACTION_INTERRUPTION_GATES'; Scenario='DURING_OWNERSHIP_CAPTURE' + Phase='PROCESS_OUTPUT'; Callsite='CRITICAL_RESULT_FIELD'; Field='STATE_DIRECTORY' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_CRITICAL_OUTPUT_COMMITTED' + Test='MSI_TRANSACTION_INTERRUPTION_GATES'; Scenario='DURING_OWNERSHIP_CAPTURE' + Phase='PROCESS_OUTPUT'; Callsite='CRITICAL_OUTPUT_MARKER' + Field='MSI_TRANSACTION_MARKER' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_CRITICAL_OUTPUT_CLEANUP_COMPLETE' + Test='MSI_TRANSACTION_INTERRUPTION_GATES'; Scenario='DURING_OWNERSHIP_CAPTURE' + Phase='PROCESS_OUTPUT'; Callsite='CRITICAL_OUTPUT_MARKER' + Field='POST_TERMINATION_CLEANUP_MARKER' + }, [PSCustomObject]@{ CaseId='CALLSITE_PROCESS_STATE_READ' Test='MSI_TRANSACTION_INTERRUPTION_GATES'; Scenario='DURING_MSI' @@ -2868,6 +3067,30 @@ function Test-SupervisorInvocationAttributionTotality { Test='MSI_TRANSACTION_INTERRUPTION_GATES'; Scenario='DURING_MSI' Phase='PROCESS_STATE'; Callsite='PROCESS_TREE_ASSERTION'; Field='PROCESS_TREE' }, + [PSCustomObject]@{ + CaseId='CALLSITE_RESOURCE_STATE_DIRECTORY_INPUT' + Test='MSI_TRANSACTION_INTERRUPTION_GATES'; Scenario='DURING_OWNERSHIP_CAPTURE' + Phase='RESOURCE_STATE'; Callsite='RESOURCE_STATE_DIRECTORY_INPUT' + Field='STATE_DIRECTORY' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_RESOURCE_STATE_PATH_CONSTRUCTION' + Test='MSI_TRANSACTION_INTERRUPTION_GATES'; Scenario='DURING_OWNERSHIP_CAPTURE' + Phase='RESOURCE_STATE'; Callsite='RESOURCE_STATE_PATH_CONSTRUCTION' + Field='RESOURCE_STATE_PATH' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_RESOURCE_STATE_PUBLICATION_WAIT' + Test='MSI_TRANSACTION_INTERRUPTION_GATES'; Scenario='DURING_OWNERSHIP_CAPTURE' + Phase='RESOURCE_STATE'; Callsite='RESOURCE_STATE_PUBLICATION_WAIT' + Field='RESOURCE_STATE_PATH' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_RESOURCE_STATE_READ_PARSE' + Test='MSI_TRANSACTION_INTERRUPTION_GATES'; Scenario='DURING_OWNERSHIP_CAPTURE' + Phase='RESOURCE_STATE'; Callsite='RESOURCE_STATE_READ_PARSE' + Field='RESOURCE_STATE_PATH' + }, [PSCustomObject]@{ CaseId='CALLSITE_CONTROLLER_INPUT_MANIFEST' Test='PRE_EXISTING_CLEANUP_OWNERSHIP' @@ -2988,6 +3211,83 @@ function Test-SupervisorInvocationAttributionTotality { Test='PRE_EXISTING_CLEANUP_OWNERSHIP' Scenario='OWNED_EXECUTABLE_BYTE_IDENTICAL_REPLACED_THEN_DEADLINE' Phase='RESOURCE_ASSERTION'; Callsite='FINAL_ABSENCE_CHECK'; Field='MANIFEST_PATH' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_FINAL_OWNED_ROOT_ABSENCE' + Test='PRE_EXISTING_CLEANUP_OWNERSHIP' + Scenario='OWNED_RESOURCES_FOR_INTERRUPTION' + Phase='RESOURCE_ASSERTION'; Callsite='FINAL_FILESYSTEM_DIRECTORY_ABSENCE' + Field='OWNED_ROOT' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_FINAL_INSTALL_ROOT_ABSENCE' + Test='PRE_EXISTING_CLEANUP_OWNERSHIP' + Scenario='OWNED_RESOURCES_FOR_INTERRUPTION' + Phase='RESOURCE_ASSERTION'; Callsite='FINAL_FILESYSTEM_DIRECTORY_ABSENCE' + Field='INSTALL_ROOT' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_FINAL_EXECUTABLE_ABSENCE' + Test='PRE_EXISTING_CLEANUP_OWNERSHIP' + Scenario='OWNED_RESOURCES_FOR_INTERRUPTION' + Phase='RESOURCE_ASSERTION'; Callsite='FINAL_FILESYSTEM_FILE_ABSENCE' + Field='EXECUTABLE' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_FINAL_SHORTCUT_FOLDER_ABSENCE' + Test='PRE_EXISTING_CLEANUP_OWNERSHIP' + Scenario='OWNED_RESOURCES_FOR_INTERRUPTION' + Phase='RESOURCE_ASSERTION'; Callsite='FINAL_FILESYSTEM_DIRECTORY_ABSENCE' + Field='SHORTCUT_FOLDER' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_FINAL_SHORTCUT_ABSENCE' + Test='PRE_EXISTING_CLEANUP_OWNERSHIP' + Scenario='OWNED_RESOURCES_FOR_INTERRUPTION' + Phase='RESOURCE_ASSERTION'; Callsite='FINAL_SHORTCUT_ABSENCE' + Field='SHORTCUT' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_FINAL_SMOKE_DIRECTORY_ABSENCE' + Test='PRE_EXISTING_CLEANUP_OWNERSHIP' + Scenario='OWNED_RESOURCES_FOR_INTERRUPTION' + Phase='RESOURCE_ASSERTION'; Callsite='FINAL_FILESYSTEM_DIRECTORY_ABSENCE' + Field='SMOKE_DIRECTORY' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_FINAL_REGISTRY_VALUE_ABSENCE' + Test='PRE_EXISTING_CLEANUP_OWNERSHIP' + Scenario='OWNED_RESOURCES_FOR_INTERRUPTION' + Phase='RESOURCE_ASSERTION'; Callsite='FINAL_REGISTRY_VALUE_ABSENCE' + Field='REGISTRY_VALUE' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_FINAL_REGISTRY_PATH_ABSENCE' + Test='PRE_EXISTING_CLEANUP_OWNERSHIP' + Scenario='OWNED_RESOURCES_FOR_INTERRUPTION' + Phase='RESOURCE_ASSERTION'; Callsite='FINAL_REGISTRY_PATH_ABSENCE' + Field='REGISTRY_PATH' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_FINAL_REGISTRY_ROOT_ABSENCE' + Test='PRE_EXISTING_CLEANUP_OWNERSHIP' + Scenario='OWNED_RESOURCES_FOR_INTERRUPTION' + Phase='RESOURCE_ASSERTION'; Callsite='FINAL_REGISTRY_ROOT_ABSENCE' + Field='REGISTRY_ROOT' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_FINAL_USER_ABSENCE' + Test='PRE_EXISTING_CLEANUP_OWNERSHIP' + Scenario='OWNED_RESOURCES_FOR_INTERRUPTION' + Phase='RESOURCE_ASSERTION'; Callsite='FINAL_USER_ABSENCE' + Field='USER_NAME' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_FINAL_PROFILE_ABSENCE' + Test='PRE_EXISTING_CLEANUP_OWNERSHIP' + Scenario='OWNED_RESOURCES_FOR_INTERRUPTION' + Phase='RESOURCE_ASSERTION'; Callsite='FINAL_PROFILE_ABSENCE' + Field='USER_SID' } )) { $diagnostic = '' @@ -3370,7 +3670,7 @@ function Invoke-CriticalCancellationScenario([string]$Scenario) { 'CRITICAL_GATE_PATH' ` 'CRITICAL_GATE_PATH' while (!(Test-Path -LiteralPath $gatePath -PathType Leaf)) { - if ($gateWait.ElapsedMilliseconds -ge 45000) { + if ($gateWait.ElapsedMilliseconds -ge 15000) { throw 'critical-cancellation fixture did not reach its interruption gate' } Start-Sleep -Milliseconds 25 @@ -3402,15 +3702,15 @@ function Invoke-CriticalCancellationScenario([string]$Scenario) { Set-SupervisorInvocationContext ` $script:currentSupervisorInvocationTest ` $Scenario ` - 'PROCESS_STATE' ` - 'PROCESS_STATE_PATH' ` + 'PROCESS_OUTPUT' ` + 'CRITICAL_RESULT_FIELD' ` 'STATE_DIRECTORY' Assert-True (![string]::IsNullOrWhiteSpace($stateDirectory)) ` (Get-SanitizedSupervisorInvocationDiagnostic ` $script:currentSupervisorInvocationTest ` $Scenario ` - 'PROCESS_STATE' ` - 'PROCESS_STATE_PATH' ` + 'PROCESS_OUTPUT' ` + 'CRITICAL_RESULT_FIELD' ` 'STATE_DIRECTORY') return [PSCustomObject]@{ ExitCode = $process.ExitCode @@ -3427,17 +3727,65 @@ function Invoke-CriticalCancellationScenario([string]$Scenario) { function Test-MsiTransactionInterruptionGates { $duringMsi = Invoke-CriticalCancellationScenario 'DURING_MSI' - Assert-True ($duringMsi.ExitCode -eq 125) ` - 'DURING_MSI cancellation did not preserve the supervisor cancellation status' - Assert-Contains $duringMsi.Output ` - 'PROPR_WINDOWS_INSTALLED_SMOKE:WATCHDOG:MSI_TRANSACTION:GRACE' ` - 'DURING_MSI cancellation did not enter the fixed transaction grace' - Assert-Contains $duringMsi.Output ` - 'PROPR_WINDOWS_INSTALLED_SMOKE:WATCHDOG:MSI_TRANSACTION:ROLLED_BACK_CLEAN' ` - 'DURING_MSI cancellation did not prove the exact clean rollback receipt' - Assert-Contains $duringMsi.Output ` - 'PROPR_WINDOWS_INSTALLED_SMOKE:WATCHDOG:POST_TERMINATION_CLEANUP:COMPLETE' ` - 'DURING_MSI clean rollback did not complete bounded cleanup' + Invoke-SupervisorAttributedOperation ` + -Scenario 'DURING_MSI' ` + -Phase 'PROCESS_OUTPUT' ` + -Callsite 'CRITICAL_RESULT_FIELD' ` + -Field 'EXIT_CODE' ` + -Action { + Assert-True ($duringMsi.ExitCode -eq 125) ` + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + 'DURING_MSI' ` + 'PROCESS_OUTPUT' ` + 'CRITICAL_RESULT_FIELD' ` + 'EXIT_CODE') + } + Invoke-SupervisorAttributedOperation ` + -Scenario 'DURING_MSI' ` + -Phase 'PROCESS_OUTPUT' ` + -Callsite 'CRITICAL_OUTPUT_MARKER' ` + -Field 'MSI_TRANSACTION_MARKER' ` + -Action { + Assert-Contains $duringMsi.Output ` + 'PROPR_WINDOWS_INSTALLED_SMOKE:WATCHDOG:MSI_TRANSACTION:GRACE' ` + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + 'DURING_MSI' ` + 'PROCESS_OUTPUT' ` + 'CRITICAL_OUTPUT_MARKER' ` + 'MSI_TRANSACTION_MARKER') + } + Invoke-SupervisorAttributedOperation ` + -Scenario 'DURING_MSI' ` + -Phase 'PROCESS_OUTPUT' ` + -Callsite 'CRITICAL_OUTPUT_MARKER' ` + -Field 'MSI_TRANSACTION_MARKER' ` + -Action { + Assert-Contains $duringMsi.Output ` + 'PROPR_WINDOWS_INSTALLED_SMOKE:WATCHDOG:MSI_TRANSACTION:ROLLED_BACK_CLEAN' ` + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + 'DURING_MSI' ` + 'PROCESS_OUTPUT' ` + 'CRITICAL_OUTPUT_MARKER' ` + 'MSI_TRANSACTION_MARKER') + } + Invoke-SupervisorAttributedOperation ` + -Scenario 'DURING_MSI' ` + -Phase 'PROCESS_OUTPUT' ` + -Callsite 'CRITICAL_OUTPUT_MARKER' ` + -Field 'POST_TERMINATION_CLEANUP_MARKER' ` + -Action { + Assert-Contains $duringMsi.Output ` + 'PROPR_WINDOWS_INSTALLED_SMOKE:WATCHDOG:POST_TERMINATION_CLEANUP:COMPLETE' ` + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + 'DURING_MSI' ` + 'PROCESS_OUTPUT' ` + 'CRITICAL_OUTPUT_MARKER' ` + 'POST_TERMINATION_CLEANUP_MARKER') + } Set-SupervisorInvocationContext ` 'MSI_TRANSACTION_INTERRUPTION_GATES' ` 'DURING_MSI' ` @@ -3456,15 +3804,64 @@ function Test-MsiTransactionInterruptionGates { 'DURING_MSI rollback did not retain the exact clean fixture baseline' $duringCapture = Invoke-CriticalCancellationScenario 'DURING_OWNERSHIP_CAPTURE' - $duringCaptureDiagnostic = Get-SanitizedCriticalCancellationDiagnostic $duringCapture - Assert-True ($duringCapture.ExitCode -eq 125) ` - "DURING_OWNERSHIP_CAPTURE cancellation did not preserve cancellation status:$duringCaptureDiagnostic" - Assert-Contains $duringCapture.Output ` - 'PROPR_WINDOWS_INSTALLED_SMOKE:WATCHDOG:MSI_TRANSACTION:COMMITTED' ` - "DURING_OWNERSHIP_CAPTURE did not publish durable nonprovisional authority:$duringCaptureDiagnostic" - Assert-Contains $duringCapture.Output ` - 'PROPR_WINDOWS_INSTALLED_SMOKE:WATCHDOG:POST_TERMINATION_CLEANUP:COMPLETE' ` - "DURING_OWNERSHIP_CAPTURE durable authority did not complete cleanup:$duringCaptureDiagnostic" + Invoke-SupervisorAttributedOperation ` + -Scenario 'DURING_OWNERSHIP_CAPTURE' ` + -Phase 'PROCESS_OUTPUT' ` + -Callsite 'CRITICAL_RESULT_FIELD' ` + -Field 'EXIT_CODE' ` + -Action { + Assert-True ($duringCapture.ExitCode -eq 125) ` + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + 'DURING_OWNERSHIP_CAPTURE' ` + 'PROCESS_OUTPUT' ` + 'CRITICAL_RESULT_FIELD' ` + 'EXIT_CODE') + } + Invoke-SupervisorAttributedOperation ` + -Scenario 'DURING_OWNERSHIP_CAPTURE' ` + -Phase 'PROCESS_OUTPUT' ` + -Callsite 'CRITICAL_OUTPUT_MARKER' ` + -Field 'MSI_TRANSACTION_MARKER' ` + -Action { + Assert-Contains $duringCapture.Output ` + 'PROPR_WINDOWS_INSTALLED_SMOKE:WATCHDOG:MSI_TRANSACTION:COMMITTED' ` + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + 'DURING_OWNERSHIP_CAPTURE' ` + 'PROCESS_OUTPUT' ` + 'CRITICAL_OUTPUT_MARKER' ` + 'MSI_TRANSACTION_MARKER') + } + Invoke-SupervisorAttributedOperation ` + -Scenario 'DURING_OWNERSHIP_CAPTURE' ` + -Phase 'PROCESS_OUTPUT' ` + -Callsite 'CRITICAL_OUTPUT_MARKER' ` + -Field 'POST_TERMINATION_CLEANUP_MARKER' ` + -Action { + Assert-Contains $duringCapture.Output ` + 'PROPR_WINDOWS_INSTALLED_SMOKE:WATCHDOG:POST_TERMINATION_CLEANUP:COMPLETE' ` + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + 'DURING_OWNERSHIP_CAPTURE' ` + 'PROCESS_OUTPUT' ` + 'CRITICAL_OUTPUT_MARKER' ` + 'POST_TERMINATION_CLEANUP_MARKER') + } + Invoke-SupervisorAttributedOperation ` + -Scenario 'DURING_OWNERSHIP_CAPTURE' ` + -Phase 'PROCESS_OUTPUT' ` + -Callsite 'CRITICAL_RESULT_FIELD' ` + -Field 'STATE_DIRECTORY' ` + -Action { + Assert-True (![string]::IsNullOrWhiteSpace([string]$duringCapture.StateDirectory)) ` + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + 'DURING_OWNERSHIP_CAPTURE' ` + 'PROCESS_OUTPUT' ` + 'CRITICAL_RESULT_FIELD' ` + 'STATE_DIRECTORY') + } $capturedOwned = Read-FixtureResourceState $duringCapture.StateDirectory Assert-OwnedResourcesGone $capturedOwned } diff --git a/apps/desktop/src/release-workflow.test.ts b/apps/desktop/src/release-workflow.test.ts index 77e2c3126..811798ad1 100644 --- a/apps/desktop/src/release-workflow.test.ts +++ b/apps/desktop/src/release-workflow.test.ts @@ -666,6 +666,91 @@ describe('desktop trusted release workflow', () => { fixtureProcessStateReader, /throw 'fixture did not publish process state'/, ); + const fixtureResourceStateReader = installedWindowsAppSupervisorBehaviorTest.slice( + installedWindowsAppSupervisorBehaviorTest.indexOf('function Read-FixtureResourceState'), + installedWindowsAppSupervisorBehaviorTest.indexOf('function Assert-ProcessTreeGone'), + ); + for (const callsite of [ + 'RESOURCE_STATE_DIRECTORY_INPUT', + 'RESOURCE_STATE_PATH_CONSTRUCTION', + 'RESOURCE_STATE_PUBLICATION_WAIT', + 'RESOURCE_STATE_READ_PARSE', + ]) { + assert.match(fixtureResourceStateReader, new RegExp(callsite)); + assert.match(installedWindowsAppSupervisorBehaviorTest, new RegExp(`'${callsite}'`)); + } + assert.match(fixtureResourceStateReader, /RESOURCE_STATE_PATH/); + assert.match(fixtureResourceStateReader, /ElapsedMilliseconds -ge 15000/); + const criticalCancellation = installedWindowsAppSupervisorBehaviorTest.slice( + installedWindowsAppSupervisorBehaviorTest.indexOf( + 'function Invoke-CriticalCancellationScenario', + ), + installedWindowsAppSupervisorBehaviorTest.indexOf( + 'function Test-MsiTransactionInterruptionGates', + ), + ); + assert.match(criticalCancellation, /CRITICAL_RESULT_FIELD[\s\S]*STATE_DIRECTORY/); + assert.match(criticalCancellation, /ElapsedMilliseconds -ge 15000/); + assert.doesNotMatch( + criticalCancellation, + /'PROCESS_STATE'\s+`\s+'PROCESS_STATE_PATH'\s+`\s+'STATE_DIRECTORY'/, + ); + const msiInterruptionGates = installedWindowsAppSupervisorBehaviorTest.slice( + installedWindowsAppSupervisorBehaviorTest.indexOf( + 'function Test-MsiTransactionInterruptionGates', + ), + installedWindowsAppSupervisorBehaviorTest.indexOf('function Test-BootstrapTimeout'), + ); + for (const token of [ + 'CRITICAL_RESULT_FIELD', + 'CRITICAL_OUTPUT_MARKER', + 'MSI_TRANSACTION_MARKER', + 'POST_TERMINATION_CLEANUP_MARKER', + ]) { + assert.match(msiInterruptionGates, new RegExp(token)); + } + const ownedResourceAssertion = installedWindowsAppSupervisorBehaviorTest.slice( + installedWindowsAppSupervisorBehaviorTest.indexOf( + 'function Test-OwnedRegistryValueAbsent', + ), + installedWindowsAppSupervisorBehaviorTest.indexOf( + 'function Convert-FixtureAuthorityFieldToken', + ), + ); + for (const callsite of [ + 'FINAL_FILESYSTEM_DIRECTORY_ABSENCE', + 'FINAL_FILESYSTEM_FILE_ABSENCE', + 'FINAL_SHORTCUT_ABSENCE', + 'FINAL_REGISTRY_VALUE_ABSENCE', + 'FINAL_REGISTRY_PATH_ABSENCE', + 'FINAL_REGISTRY_ROOT_ABSENCE', + 'FINAL_USER_ABSENCE', + 'FINAL_PROFILE_ABSENCE', + ]) { + assert.match(ownedResourceAssertion, new RegExp(callsite)); + assert.match(installedWindowsAppSupervisorBehaviorTest, new RegExp(`'${callsite}'`)); + } + for (const field of [ + 'OWNED_ROOT', + 'INSTALL_ROOT', + 'EXECUTABLE', + 'SHORTCUT_FOLDER', + 'SHORTCUT', + 'SMOKE_DIRECTORY', + 'REGISTRY_VALUE', + 'REGISTRY_PATH', + 'REGISTRY_ROOT', + 'USER_NAME', + 'USER_SID', + ]) { + assert.match(ownedResourceAssertion, new RegExp(field)); + } + assert.match(ownedResourceAssertion, /\$cleanupStopwatch = \[Diagnostics\.Stopwatch\]::StartNew\(\)/); + assert.match(ownedResourceAssertion, /ElapsedMilliseconds -ge 5000/); + assert.doesNotMatch( + ownedResourceAssertion, + /Set-SupervisorInvocationContext[\s\S]*'RESOURCE_ASSERTION'\s*\)/, + ); assert.match(installedWindowsAppSupervisorBehaviorTest, /Test-PreExistingAppPathsAuthority/); assert.match(installedWindowsAppSupervisorBehaviorTest, /Assert-ProcessTreeGone/); assert.match(installedWindowsAppSupervisorBehaviorTest, /Assert-OwnedFixtureAuthorityComplete/); @@ -1258,7 +1343,7 @@ describe('desktop trusted release workflow', () => { ); assert.match( installedWindowsAppSupervisorBehaviorTest, - /did not publish durable nonprovisional authority:\$duringCaptureDiagnostic/, + /CRITICAL_OUTPUT_MARKER[\s\S]*MSI_TRANSACTION_MARKER[\s\S]*WATCHDOG:MSI_TRANSACTION:COMMITTED/, ); assert.match( installedWindowsAppSupervisorBehaviorTest, From 5ae780c5167c901d5bddd883b8494f48793fa980 Mon Sep 17 00:00:00 2001 From: "propr-dev[bot]" <1316198+propr-dev[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 14:46:22 +0000 Subject: [PATCH 18/33] feat(ai): Implemented the timing-only correction on PR #2057 without committing. Implemented the timing-only correction on PR #2057 without committing. Changed: - Restored `Read-FixtureResourceState` publication bound from `15000` to `45000` in [test-installed-windows-app-supervisor.ps1](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T14-40-56/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1). - Restored critical-cancellation gate readiness bound from `15000` to `45000`. - Removed the shared/generic 5-second stabilization from `Assert-OwnedResourcesGone`; each final absence predicate now asserts the immediate post-cleanup state while preserving the new exact callsite/field attribution. - Updated [release-workflow.test.ts](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T14-40-56/apps/desktop/src/release-workflow.test.ts) to enforce `45000` and reject reintroduced `cleanupStopwatch`/`ElapsedMilliseconds -ge 5000`. Current native job evidence from exact head `3f29aec`: - x64 job `100291145566`: `PRE_EXISTING_CLEANUP_OWNERSHIP / RESOURCE_COLLISION / WORKFLOW_CLEANUP_CONTROLLER / CONTROLLER_PROTOCOL_PARSE / PROTOCOL` - ARM64 job `100291145572`: same predicate I did not fix that producer path because it was observed before this timing-only correction, not after a corrected native rerun. Validation run locally: - `npx tsx --test apps/desktop/src/release-workflow.test.ts` passed. - `npm run test -w @propr/desktop` passed: 177 passed, 6 skipped. - `git diff --check` passed. Native Windows x64/ARM64 rerun is still outstanding from this environment: the container is Linux and does not have `pwsh` or native Windows runners, and I did not commit/push per instruction. PR: #2057 Comment by: @integry (ID: 5511341393) Model: gpt-5.5 --- .../test-installed-windows-app-supervisor.ps1 | 22 +++---------------- apps/desktop/src/release-workflow.test.ts | 8 +++---- 2 files changed, 7 insertions(+), 23 deletions(-) diff --git a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 index df1ec6c7c..56eebe5ae 100644 --- a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 +++ b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 @@ -909,7 +909,7 @@ function Read-FixtureResourceState([string]$StateDirectory) { -Action { $stopwatch = [Diagnostics.Stopwatch]::StartNew() while (!(Test-Path -LiteralPath $statePath -PathType Leaf)) { - if ($stopwatch.ElapsedMilliseconds -ge 15000) { + if ($stopwatch.ElapsedMilliseconds -ge 45000) { Assert-True $false ` (Get-SanitizedSupervisorInvocationDiagnostic ` $script:currentSupervisorInvocationTest ` @@ -1347,25 +1347,18 @@ function Assert-OwnedResourcePredicate( [string]$Scenario, [string]$Callsite, [string]$Field, - [Diagnostics.Stopwatch]$CleanupStopwatch, [scriptblock]$Predicate ) { $predicateScenario = $Scenario $predicateCallsite = $Callsite $predicateField = $Field - $predicateCleanupStopwatch = $CleanupStopwatch Invoke-SupervisorAttributedOperation ` -Scenario $predicateScenario ` -Phase 'RESOURCE_ASSERTION' ` -Callsite $predicateCallsite ` -Field $predicateField ` -Action { - do { - if (& $Predicate) { return } - if ($predicateCleanupStopwatch.ElapsedMilliseconds -ge 5000) { break } - Start-Sleep -Milliseconds 25 - } while ($true) - Assert-True $false ` + Assert-True (& $Predicate) ` (Get-SanitizedSupervisorInvocationDiagnostic ` $script:currentSupervisorInvocationTest ` $predicateScenario ` @@ -1377,7 +1370,6 @@ function Assert-OwnedResourcePredicate( function Assert-OwnedResourcesGone($Owned) { $resourceScenario = $script:currentSupervisorInvocationScenario - $cleanupStopwatch = [Diagnostics.Stopwatch]::StartNew() foreach ($ownedDirectoryCase in @( @{ Path = $Owned.OwnedRoot; Field = 'OWNED_ROOT' }, @{ Path = $Owned.InstallRoot; Field = 'INSTALL_ROOT' }, @@ -1390,7 +1382,6 @@ function Assert-OwnedResourcesGone($Owned) { -Scenario $resourceScenario ` -Callsite 'FINAL_FILESYSTEM_DIRECTORY_ABSENCE' ` -Field $ownedDirectoryField ` - -CleanupStopwatch $cleanupStopwatch ` -Predicate { !(Test-Path -LiteralPath $ownedDirectoryPath) } } $ownedExecutablePath = [string]$Owned.Executable @@ -1399,7 +1390,6 @@ function Assert-OwnedResourcesGone($Owned) { -Scenario $resourceScenario ` -Callsite 'FINAL_FILESYSTEM_FILE_ABSENCE' ` -Field 'EXECUTABLE' ` - -CleanupStopwatch $cleanupStopwatch ` -Predicate { !(Test-Path -LiteralPath $ownedExecutablePath) } } $ownedShortcutPath = [string]$Owned.Shortcut @@ -1407,34 +1397,29 @@ function Assert-OwnedResourcesGone($Owned) { -Scenario $resourceScenario ` -Callsite 'FINAL_SHORTCUT_ABSENCE' ` -Field 'SHORTCUT' ` - -CleanupStopwatch $cleanupStopwatch ` -Predicate { !(Test-Path -LiteralPath $ownedShortcutPath) } $ownedRegistryPath = [string]$Owned.RegistryPath Assert-OwnedResourcePredicate ` -Scenario $resourceScenario ` -Callsite 'FINAL_REGISTRY_VALUE_ABSENCE' ` -Field 'REGISTRY_VALUE' ` - -CleanupStopwatch $cleanupStopwatch ` -Predicate { Test-OwnedRegistryValueAbsent $ownedRegistryPath 'ProPRInstalledAppOwner' } Assert-OwnedResourcePredicate ` -Scenario $resourceScenario ` -Callsite 'FINAL_REGISTRY_PATH_ABSENCE' ` -Field 'REGISTRY_PATH' ` - -CleanupStopwatch $cleanupStopwatch ` -Predicate { !(Test-Path -LiteralPath $ownedRegistryPath) } $ownedRegistryRoot = [string]$Owned.RegistryRoot Assert-OwnedResourcePredicate ` -Scenario $resourceScenario ` -Callsite 'FINAL_REGISTRY_ROOT_ABSENCE' ` -Field 'REGISTRY_ROOT' ` - -CleanupStopwatch $cleanupStopwatch ` -Predicate { !(Test-Path -LiteralPath $ownedRegistryRoot) } $ownedUserName = [string]$Owned.UserName Assert-OwnedResourcePredicate ` -Scenario $resourceScenario ` -Callsite 'FINAL_USER_ABSENCE' ` -Field 'USER_NAME' ` - -CleanupStopwatch $cleanupStopwatch ` -Predicate { $null -eq (Get-LocalUser -Name $ownedUserName -ErrorAction SilentlyContinue) } @@ -1443,7 +1428,6 @@ function Assert-OwnedResourcesGone($Owned) { -Scenario $resourceScenario ` -Callsite 'FINAL_PROFILE_ABSENCE' ` -Field 'USER_SID' ` - -CleanupStopwatch $cleanupStopwatch ` -Predicate { $ownedProfiles = @(Get-CimInstance -ClassName Win32_UserProfile -ErrorAction Stop | Where-Object { $_.SID -ceq $ownedUserSid }) @@ -3670,7 +3654,7 @@ function Invoke-CriticalCancellationScenario([string]$Scenario) { 'CRITICAL_GATE_PATH' ` 'CRITICAL_GATE_PATH' while (!(Test-Path -LiteralPath $gatePath -PathType Leaf)) { - if ($gateWait.ElapsedMilliseconds -ge 15000) { + if ($gateWait.ElapsedMilliseconds -ge 45000) { throw 'critical-cancellation fixture did not reach its interruption gate' } Start-Sleep -Milliseconds 25 diff --git a/apps/desktop/src/release-workflow.test.ts b/apps/desktop/src/release-workflow.test.ts index 811798ad1..1431ffea6 100644 --- a/apps/desktop/src/release-workflow.test.ts +++ b/apps/desktop/src/release-workflow.test.ts @@ -680,7 +680,7 @@ describe('desktop trusted release workflow', () => { assert.match(installedWindowsAppSupervisorBehaviorTest, new RegExp(`'${callsite}'`)); } assert.match(fixtureResourceStateReader, /RESOURCE_STATE_PATH/); - assert.match(fixtureResourceStateReader, /ElapsedMilliseconds -ge 15000/); + assert.match(fixtureResourceStateReader, /ElapsedMilliseconds -ge 45000/); const criticalCancellation = installedWindowsAppSupervisorBehaviorTest.slice( installedWindowsAppSupervisorBehaviorTest.indexOf( 'function Invoke-CriticalCancellationScenario', @@ -690,7 +690,7 @@ describe('desktop trusted release workflow', () => { ), ); assert.match(criticalCancellation, /CRITICAL_RESULT_FIELD[\s\S]*STATE_DIRECTORY/); - assert.match(criticalCancellation, /ElapsedMilliseconds -ge 15000/); + assert.match(criticalCancellation, /ElapsedMilliseconds -ge 45000/); assert.doesNotMatch( criticalCancellation, /'PROCESS_STATE'\s+`\s+'PROCESS_STATE_PATH'\s+`\s+'STATE_DIRECTORY'/, @@ -745,8 +745,8 @@ describe('desktop trusted release workflow', () => { ]) { assert.match(ownedResourceAssertion, new RegExp(field)); } - assert.match(ownedResourceAssertion, /\$cleanupStopwatch = \[Diagnostics\.Stopwatch\]::StartNew\(\)/); - assert.match(ownedResourceAssertion, /ElapsedMilliseconds -ge 5000/); + assert.doesNotMatch(ownedResourceAssertion, /cleanupStopwatch/i); + assert.doesNotMatch(ownedResourceAssertion, /ElapsedMilliseconds -ge 5000/); assert.doesNotMatch( ownedResourceAssertion, /Set-SupervisorInvocationContext[\s\S]*'RESOURCE_ASSERTION'\s*\)/, From 7e8851a7b5df0a53b30b44973a21cebbc26f4f2e Mon Sep 17 00:00:00 2001 From: "propr-dev[bot]" <1316198+propr-dev[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 15:01:55 +0000 Subject: [PATCH 19/33] feat(ai): Implemented the attribution-only RESOURCE_COLLISION split on PR #2057. Implemented the attribution-only RESOURCE_COLLISION split on PR #2057. Changes: - Added `CONTROLLER_STATUS` as an allowlisted diagnostic field in [test-installed-windows-app-supervisor.ps1](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T14-54-32/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1:1840). - Split RESOURCE_COLLISION checks into exact fixed contexts for `EXIT_CODE`, `REPORTED_EXIT_CODE`, `RESULT`, `CONTROLLER_STATUS`, registry value survival, and ACTIVE manifest preservation in [test-installed-windows-app-supervisor.ps1](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T14-54-32/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1:4782). - Added attribution-totality/static coverage for the new exact cases and release workflow guards in [release-workflow.test.ts](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T14-54-32/apps/desktop/src/release-workflow.test.ts:629). Validation: - `npx tsx --test apps/desktop/src/release-workflow.test.ts` passed. - `npm run test -w @propr/desktop` passed: 177 pass, 6 skip. - `git diff --check` passed. Native win32 reruns were not started because `Desktop Release Guard` has no `workflow_dispatch` trigger and the requested changes are uncommitted per the instruction not to commit. The latest native jobs on head `5ae780c5167c901d5bddd883b8494f48793fa980` remain: - win32-x64: `100294797232` - win32-arm64: `100294797156` PR: #2057 Comment by: @integry (ID: 5511539429) Model: gpt-5.5 --- .../test-installed-windows-app-supervisor.ps1 | 147 ++++++++++++++++-- apps/desktop/src/release-workflow.test.ts | 51 +++++- 2 files changed, 187 insertions(+), 11 deletions(-) diff --git a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 index 56eebe5ae..5282813c5 100644 --- a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 +++ b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 @@ -1837,6 +1837,7 @@ function Get-SupervisorInvocationFields { 'EXIT_CODE', 'REPORTED_EXIT_CODE', 'RESULT', + 'CONTROLLER_STATUS', 'OWNED_ROOT', 'INSTALL_ROOT', 'SHORTCUT_FOLDER', @@ -2000,6 +2001,12 @@ function Get-SupervisorAttributionTotalityCases { 'CALLSITE_CONTROLLER_RESULT_EXIT_CODE', 'CALLSITE_CONTROLLER_RESULT_REPORTED_EXIT_CODE', 'CALLSITE_CONTROLLER_RESULT_RESULT', + 'CALLSITE_RESOURCE_COLLISION_CONTROLLER_RESULT_EXIT_CODE', + 'CALLSITE_RESOURCE_COLLISION_CONTROLLER_RESULT_REPORTED_EXIT_CODE', + 'CALLSITE_RESOURCE_COLLISION_CONTROLLER_RESULT_RESULT', + 'CALLSITE_RESOURCE_COLLISION_CONTROLLER_RESULT_CONTROLLER_STATUS', + 'CALLSITE_RESOURCE_COLLISION_REPLACEMENT_SURVIVAL_READ', + 'CALLSITE_RESOURCE_COLLISION_MANIFEST_PRESERVATION', 'CALLSITE_EARLY_PROCESS_STATE_PATH', 'CALLSITE_EARLY_PROCESS_STATE_READ', 'CALLSITE_EARLY_WORKER_PID', @@ -3124,6 +3131,48 @@ function Test-SupervisorInvocationAttributionTotality { Phase='WORKFLOW_CLEANUP_CONTROLLER' Callsite='CONTROLLER_RESULT_FIELD'; Field='RESULT' }, + [PSCustomObject]@{ + CaseId='CALLSITE_RESOURCE_COLLISION_CONTROLLER_RESULT_EXIT_CODE' + Test='PRE_EXISTING_CLEANUP_OWNERSHIP' + Scenario='RESOURCE_COLLISION' + Phase='WORKFLOW_CLEANUP_CONTROLLER' + Callsite='CONTROLLER_RESULT_FIELD'; Field='EXIT_CODE' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_RESOURCE_COLLISION_CONTROLLER_RESULT_REPORTED_EXIT_CODE' + Test='PRE_EXISTING_CLEANUP_OWNERSHIP' + Scenario='RESOURCE_COLLISION' + Phase='WORKFLOW_CLEANUP_CONTROLLER' + Callsite='CONTROLLER_RESULT_FIELD'; Field='REPORTED_EXIT_CODE' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_RESOURCE_COLLISION_CONTROLLER_RESULT_RESULT' + Test='PRE_EXISTING_CLEANUP_OWNERSHIP' + Scenario='RESOURCE_COLLISION' + Phase='WORKFLOW_CLEANUP_CONTROLLER' + Callsite='CONTROLLER_RESULT_FIELD'; Field='RESULT' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_RESOURCE_COLLISION_CONTROLLER_RESULT_CONTROLLER_STATUS' + Test='PRE_EXISTING_CLEANUP_OWNERSHIP' + Scenario='RESOURCE_COLLISION' + Phase='WORKFLOW_CLEANUP_CONTROLLER' + Callsite='CONTROLLER_RESULT_FIELD'; Field='CONTROLLER_STATUS' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_RESOURCE_COLLISION_REPLACEMENT_SURVIVAL_READ' + Test='PRE_EXISTING_CLEANUP_OWNERSHIP' + Scenario='RESOURCE_COLLISION' + Phase='RESOURCE_ASSERTION' + Callsite='REPLACEMENT_SURVIVAL_READ'; Field='REGISTRY_VALUE' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_RESOURCE_COLLISION_MANIFEST_PRESERVATION' + Test='PRE_EXISTING_CLEANUP_OWNERSHIP' + Scenario='RESOURCE_COLLISION' + Phase='MANIFEST_ASSERTION' + Callsite='MANIFEST_PRESERVATION'; Field='MANIFEST_PATH' + }, [PSCustomObject]@{ CaseId='CALLSITE_EARLY_PROCESS_STATE_PATH' Test='PRE_EXISTING_CLEANUP_OWNERSHIP' @@ -4730,16 +4779,94 @@ function Test-PreExistingCleanupOwnership { -Name 'ProPRInstalledAppOwner' -Value 'foreign-owner' $failedWorkflowCleanup = Invoke-WorkflowCleanupController ` 'RESOURCE_COLLISION' $workflowManifest $workflowRunId $workflowStateDirectory - Assert-True ($failedWorkflowCleanup.ExitCode -eq 21 -and - $failedWorkflowCleanup.ReportedExitCode -eq 21 -and - $failedWorkflowCleanup.Result -ceq 'FAILED' -and - $failedWorkflowCleanup.ControllerStatus -ceq 'OWNED_RESOURCE_CLEANUP_FAILURE') ` - 'workflow cleanup did not report a fixed replacement-collision failure' - Assert-True ((Get-ItemPropertyValue -LiteralPath $workflowOwned.RegistryPath ` - -Name 'ProPRInstalledAppOwner') -ceq 'foreign-owner') ` - 'workflow cleanup removed a replacement registry object' - Assert-True (Test-Path -LiteralPath $workflowManifest -PathType Leaf) ` - 'failed workflow cleanup discarded authenticated recovery authority' + Invoke-SupervisorAttributedOperation ` + -Scenario 'RESOURCE_COLLISION' ` + -Phase 'WORKFLOW_CLEANUP_CONTROLLER' ` + -Callsite 'CONTROLLER_RESULT_FIELD' ` + -Field 'EXIT_CODE' ` + -Action { + Assert-True ($failedWorkflowCleanup.ExitCode -eq 21) ` + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + 'RESOURCE_COLLISION' ` + 'WORKFLOW_CLEANUP_CONTROLLER' ` + 'CONTROLLER_RESULT_FIELD' ` + 'EXIT_CODE') + } + Invoke-SupervisorAttributedOperation ` + -Scenario 'RESOURCE_COLLISION' ` + -Phase 'WORKFLOW_CLEANUP_CONTROLLER' ` + -Callsite 'CONTROLLER_RESULT_FIELD' ` + -Field 'REPORTED_EXIT_CODE' ` + -Action { + Assert-True ($failedWorkflowCleanup.ReportedExitCode -eq 21) ` + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + 'RESOURCE_COLLISION' ` + 'WORKFLOW_CLEANUP_CONTROLLER' ` + 'CONTROLLER_RESULT_FIELD' ` + 'REPORTED_EXIT_CODE') + } + Invoke-SupervisorAttributedOperation ` + -Scenario 'RESOURCE_COLLISION' ` + -Phase 'WORKFLOW_CLEANUP_CONTROLLER' ` + -Callsite 'CONTROLLER_RESULT_FIELD' ` + -Field 'RESULT' ` + -Action { + Assert-True ($failedWorkflowCleanup.Result -ceq 'FAILED') ` + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + 'RESOURCE_COLLISION' ` + 'WORKFLOW_CLEANUP_CONTROLLER' ` + 'CONTROLLER_RESULT_FIELD' ` + 'RESULT') + } + Invoke-SupervisorAttributedOperation ` + -Scenario 'RESOURCE_COLLISION' ` + -Phase 'WORKFLOW_CLEANUP_CONTROLLER' ` + -Callsite 'CONTROLLER_RESULT_FIELD' ` + -Field 'CONTROLLER_STATUS' ` + -Action { + Assert-True ($failedWorkflowCleanup.ControllerStatus -ceq + 'OWNED_RESOURCE_CLEANUP_FAILURE') ` + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + 'RESOURCE_COLLISION' ` + 'WORKFLOW_CLEANUP_CONTROLLER' ` + 'CONTROLLER_RESULT_FIELD' ` + 'CONTROLLER_STATUS') + } + Invoke-SupervisorAttributedOperation ` + -Scenario 'RESOURCE_COLLISION' ` + -Phase 'RESOURCE_ASSERTION' ` + -Callsite 'REPLACEMENT_SURVIVAL_READ' ` + -Field 'REGISTRY_VALUE' ` + -Action { + Assert-True ((Get-ItemPropertyValue -LiteralPath $workflowOwned.RegistryPath ` + -Name 'ProPRInstalledAppOwner') -ceq 'foreign-owner') ` + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + 'RESOURCE_COLLISION' ` + 'RESOURCE_ASSERTION' ` + 'REPLACEMENT_SURVIVAL_READ' ` + 'REGISTRY_VALUE') + } + Invoke-SupervisorAttributedOperation ` + -Scenario 'RESOURCE_COLLISION' ` + -Phase 'MANIFEST_ASSERTION' ` + -Callsite 'MANIFEST_PRESERVATION' ` + -Field 'MANIFEST_PATH' ` + -Action { + $collisionAuthority = Get-Content -LiteralPath $workflowManifest -Raw -Encoding UTF8 | + ConvertFrom-Json -ErrorAction Stop + Assert-True ($collisionAuthority.State -ceq 'ACTIVE') ` + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + 'RESOURCE_COLLISION' ` + 'MANIFEST_ASSERTION' ` + 'MANIFEST_PRESERVATION' ` + 'MANIFEST_PATH') + } Set-ItemProperty -LiteralPath $workflowOwned.RegistryPath ` -Name 'ProPRInstalledAppOwner' -Value ([string]$workflowOwned.Token) diff --git a/apps/desktop/src/release-workflow.test.ts b/apps/desktop/src/release-workflow.test.ts index 1431ffea6..ae10b16fe 100644 --- a/apps/desktop/src/release-workflow.test.ts +++ b/apps/desktop/src/release-workflow.test.ts @@ -626,6 +626,23 @@ describe('desktop trusted release workflow', () => { installedWindowsAppSupervisorBehaviorTest, /foreach \(\$callsiteName in Get-SupervisorInvocationCallsites\)[\s\S]*foreach \(\$fieldName in Get-SupervisorInvocationFields\)/, ); + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /Get-SupervisorInvocationFields[\s\S]*'CONTROLLER_STATUS'/, + ); + for (const caseId of [ + 'CALLSITE_RESOURCE_COLLISION_CONTROLLER_RESULT_EXIT_CODE', + 'CALLSITE_RESOURCE_COLLISION_CONTROLLER_RESULT_REPORTED_EXIT_CODE', + 'CALLSITE_RESOURCE_COLLISION_CONTROLLER_RESULT_RESULT', + 'CALLSITE_RESOURCE_COLLISION_CONTROLLER_RESULT_CONTROLLER_STATUS', + 'CALLSITE_RESOURCE_COLLISION_REPLACEMENT_SURVIVAL_READ', + 'CALLSITE_RESOURCE_COLLISION_MANIFEST_PRESERVATION', + ]) { + assert.match( + installedWindowsAppSupervisorBehaviorTest, + new RegExp(`CaseId='${caseId}'`), + ); + } assert.match( installedWindowsAppSupervisorBehaviorTest, /Test-CriticalGatePublisherPowerShellCompatibility/, @@ -647,6 +664,38 @@ describe('desktop trusted release workflow', () => { installedWindowsAppSupervisorBehaviorTest, /Assert-SupervisorInvocationDiagnosticBounded[\s\S]*Cannot bind argument[\s\S]*LiteralPath[\s\S]*Registry::[\s\S]*stdout[\s\S]*stderr/, ); + const resourceCollisionAssertions = installedWindowsAppSupervisorBehaviorTest.slice( + installedWindowsAppSupervisorBehaviorTest.indexOf( + "$failedWorkflowCleanup = Invoke-WorkflowCleanupController `\n 'RESOURCE_COLLISION'", + ), + installedWindowsAppSupervisorBehaviorTest.indexOf( + "$workflowCleanup = Invoke-WorkflowCleanupController `\n 'WORKFLOW_RETRY'", + ), + ); + for (const field of [ + 'EXIT_CODE', + 'REPORTED_EXIT_CODE', + 'RESULT', + 'CONTROLLER_STATUS', + ]) { + assert.match( + resourceCollisionAssertions, + new RegExp( + "-Scenario 'RESOURCE_COLLISION'[\\s\\S]*" + + "-Phase 'WORKFLOW_CLEANUP_CONTROLLER'[\\s\\S]*" + + "-Callsite 'CONTROLLER_RESULT_FIELD'[\\s\\S]*" + + `-Field '${field}'`, + ), + ); + } + assert.match( + resourceCollisionAssertions, + /-Scenario 'RESOURCE_COLLISION'[\s\S]*-Phase 'RESOURCE_ASSERTION'[\s\S]*-Callsite 'REPLACEMENT_SURVIVAL_READ'[\s\S]*-Field 'REGISTRY_VALUE'/, + ); + assert.match( + resourceCollisionAssertions, + /-Scenario 'RESOURCE_COLLISION'[\s\S]*-Phase 'MANIFEST_ASSERTION'[\s\S]*-Callsite 'MANIFEST_PRESERVATION'[\s\S]*-Field 'MANIFEST_PATH'[\s\S]*\$collisionAuthority\.State -ceq 'ACTIVE'/, + ); const fixtureProcessStateReader = installedWindowsAppSupervisorBehaviorTest.slice( installedWindowsAppSupervisorBehaviorTest.indexOf('function Read-FixtureProcessState'), installedWindowsAppSupervisorBehaviorTest.indexOf('function Read-FixtureResourceState'), @@ -1434,7 +1483,7 @@ describe('desktop trusted release workflow', () => { ); assert.match( installedWindowsAppSupervisorBehaviorTest, - /timed-out workflow cleanup discarded authenticated recovery authority[\s\S]*failed workflow cleanup discarded authenticated recovery authority[\s\S]*retry to fixed cleanup success/, + /timed-out workflow cleanup discarded authenticated recovery authority[\s\S]*\$collisionAuthority\.State -ceq 'ACTIVE'[\s\S]*retry to fixed cleanup success/, ); const fixedResultWrite = installedWindowsAppWorkflowCleanup.indexOf( 'Write-FixedResult $fixedResult', From 260908b8d310a474769398ba74c702ae4c52b6e5 Mon Sep 17 00:00:00 2001 From: "propr-dev[bot]" <1316198+propr-dev[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 15:25:55 +0000 Subject: [PATCH 20/33] feat(ai): Implemented the RESOURCE_COLLISION follow-up on exact head `7e8851a7b5df0a53b30b44973a21cebbc26f4f2e`. Implemented the RESOURCE_COLLISION follow-up on exact head `7e8851a7b5df0a53b30b44973a21cebbc26f4f2e`. Changed: - [cleanup-installed-windows-app.ps1](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T15-11-46/apps/desktop/scripts/cleanup-installed-windows-app.ps1:1574): fixture registry owner mismatch now marks `$cleanupFailed` as an ownership collision instead of throwing as manifest validation. - [cleanup-installed-windows-app.ps1](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T15-11-46/apps/desktop/scripts/cleanup-installed-windows-app.ps1:1739): committed MSI file-system authority still runs first, then any preflight collision exits through the existing manifest-validated exit `21` path before MSI uninstall or manual cleanup mutation. - [test-installed-windows-app-supervisor.ps1](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T15-11-46/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1:1329): RESOURCE_COLLISION assertions now include bounded sanitized workflow-cleanup result tuple evidence for controller-result mismatches. - [test-installed-windows-app-supervisor.ps1](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T15-11-46/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1:1495): regression now snapshots owned resources with fixed hashes/tokens, proves the foreign registry value, other owned resources, and ACTIVE manifest remain unchanged, restores the exact owner token, and verifies retry cleanup success. - [release-workflow.test.ts](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T15-11-46/apps/desktop/src/release-workflow.test.ts:695): static contract updated to assert the new fail-closed ordering. Validation: - `git diff --check` passed. - `npx tsx --test apps/desktop/src/release-workflow.test.ts` passed, 23/23. I could not run native `win32-x64` or `win32-arm64` jobs from this Linux worktree because there is no Windows/PowerShell host here and these changes are not committed/pushed by me. No new native job IDs are available from this local run. PR: #2057 Comment by: @integry (ID: 5511790556) Model: gpt-5.5 --- .../scripts/cleanup-installed-windows-app.ps1 | 10 +- .../test-installed-windows-app-supervisor.ps1 | 308 ++++++++++++++++-- apps/desktop/src/release-workflow.test.ts | 27 +- 3 files changed, 310 insertions(+), 35 deletions(-) diff --git a/apps/desktop/scripts/cleanup-installed-windows-app.ps1 b/apps/desktop/scripts/cleanup-installed-windows-app.ps1 index 83e781eab..d69034c7d 100644 --- a/apps/desktop/scripts/cleanup-installed-windows-app.ps1 +++ b/apps/desktop/scripts/cleanup-installed-windows-app.ps1 @@ -1571,9 +1571,10 @@ try { throw 'registry manifest scope is invalid' } if (!(Test-Path -LiteralPath $path)) { continue } - if ([string](Get-ItemPropertyValue -LiteralPath $path -Name $ownerRegistryValue ` - -ErrorAction Stop) -cne [string]$record.Token) { - throw 'registry manifest token is invalid' + $currentOwnerToken = Get-ItemPropertyValue -LiteralPath $path ` + -Name $ownerRegistryValue -ErrorAction SilentlyContinue + if ([string]$currentOwnerToken -cne [string]$record.Token) { + $cleanupFailed = $true } } else { $expectedPath = if ($kind -eq 'PROTOCOL') { @@ -1738,6 +1739,9 @@ try { if ([string]$manifest.MsiTransactionState -ceq 'COMMITTED') { Assert-MsiManagedFileSystemAuthority $manifest } + if ($cleanupFailed) { + throw 'owned resource authority collision' + } if ($allowAuthenticatedMsiUninstall -and !$cleanupFailed) { $msiExitCode = 1618 for ($attempt = 0; $attempt -lt 12 -and $msiExitCode -eq 1618; $attempt += 1) { diff --git a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 index 5282813c5..f72b366f0 100644 --- a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 +++ b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 @@ -1326,6 +1326,224 @@ function Get-SanitizedWorkflowCleanupResultDiagnostic($Result) { return $diagnostic } +function Get-SanitizedResourceCollisionControllerDiagnostic($Result, [string]$Field) { + return ( + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + 'RESOURCE_COLLISION' ` + 'WORKFLOW_CLEANUP_CONTROLLER' ` + 'CONTROLLER_RESULT_FIELD' ` + $Field) + ':' + (Get-SanitizedWorkflowCleanupResultDiagnostic $Result) + ) +} + +function Get-SupervisorFixtureSha256Hex([byte[]]$Bytes) { + $sha256 = [Security.Cryptography.SHA256]::Create() + try { + return [BitConverter]::ToString($sha256.ComputeHash($Bytes)).Replace( + '-', '').ToLowerInvariant() + } finally { + $sha256.Dispose() + } +} + +function Get-SupervisorFixtureStringDigest([string]$Text) { + $bytes = [Text.Encoding]::UTF8.GetBytes([string]$Text) + return Get-SupervisorFixtureSha256Hex $bytes +} + +function Get-SupervisorFixtureFileDigest([string]$Path) { + try { + if (!(Test-Path -LiteralPath $Path -PathType Leaf)) { return 'MISSING' } + $stream = [IO.File]::Open( + $Path, [IO.FileMode]::Open, [IO.FileAccess]::Read, [IO.FileShare]::ReadWrite) + try { + $sha256 = [Security.Cryptography.SHA256]::Create() + try { + return [BitConverter]::ToString($sha256.ComputeHash($stream)).Replace( + '-', '').ToLowerInvariant() + } finally { + $sha256.Dispose() + } + } finally { + $stream.Dispose() + } + } catch { + return 'INVALID' + } +} + +function Get-SupervisorFixtureDirectoryDigest([string]$Path) { + try { + if (!(Test-Path -LiteralPath $Path -PathType Container)) { return 'MISSING' } + $root = Get-Item -LiteralPath $Path -Force -ErrorAction Stop + if (!$root.PSIsContainer -or + ($root.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) { + return 'INVALID' + } + $rootPath = $root.FullName.TrimEnd('\') + $records = [Collections.Generic.List[string]]::new() + $records.Add('D||') + foreach ($entry in @(Get-ChildItem -LiteralPath $rootPath -Recurse -Force ` + -ErrorAction Stop | Sort-Object -Property FullName -CaseSensitive)) { + if (($entry.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) { + return 'INVALID' + } + $relativePath = $entry.FullName.Substring($rootPath.Length).TrimStart('\') + if (!$relativePath -or [IO.Path]::IsPathRooted($relativePath)) { + return 'INVALID' + } + $relative = [Convert]::ToBase64String( + [Text.Encoding]::UTF8.GetBytes($relativePath)) + if ($entry.PSIsContainer) { + $records.Add(('D|{0}|' -f $relative)) + } else { + $records.Add(('F|{0}|{1}' -f + $relative, (Get-SupervisorFixtureFileDigest $entry.FullName))) + } + } + return Get-SupervisorFixtureStringDigest ($records.ToArray() -join "`n") + } catch { + return 'INVALID' + } +} + +function Get-SupervisorFixtureRegistryDigest([string]$Path) { + try { + if (!(Test-Path -LiteralPath $Path)) { return 'MISSING' } + $root = Get-Item -LiteralPath $Path -ErrorAction Stop + $records = [Collections.Generic.List[string]]::new() + $pending = [Collections.Generic.Queue[object]]::new() + $pending.Enqueue([PSCustomObject]@{ Key = $root; Relative = '' }) + while ($pending.Count -ne 0) { + $entry = $pending.Dequeue() + $records.Add(('K|{0}' -f [Convert]::ToBase64String( + [Text.Encoding]::UTF8.GetBytes([string]$entry.Relative)))) + foreach ($valueName in @($entry.Key.GetValueNames() | Sort-Object -CaseSensitive)) { + $value = $entry.Key.GetValue( + $valueName, + $null, + [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames + ) + $valueBytes = if ($value -is [byte[]]) { + $value + } elseif ($value -is [string[]]) { + [Text.Encoding]::UTF8.GetBytes(($value | ConvertTo-Json -Compress)) + } else { + [Text.Encoding]::UTF8.GetBytes([Convert]::ToString( + $value, + [Globalization.CultureInfo]::InvariantCulture + )) + } + $records.Add(('V|{0}|{1}|{2}' -f + [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes([string]$valueName)), + $entry.Key.GetValueKind($valueName).ToString(), + [Convert]::ToBase64String($valueBytes))) + } + foreach ($child in @(Get-ChildItem -LiteralPath $entry.Key.PSPath -ErrorAction Stop | + Sort-Object -Property PSChildName -CaseSensitive)) { + $relative = if ($entry.Relative) { + '{0}\{1}' -f $entry.Relative, $child.PSChildName + } else { [string]$child.PSChildName } + $pending.Enqueue([PSCustomObject]@{ Key = $child; Relative = $relative }) + } + } + return Get-SupervisorFixtureStringDigest ($records.ToArray() -join "`n") + } catch { + return 'INVALID' + } +} + +function Get-SupervisorFixtureRegistryValueDigest([string]$Path, [string]$Name) { + if (!(Test-Path -LiteralPath $Path)) { return 'MISSING' } + try { + return Get-SupervisorFixtureStringDigest ([string]( + Get-ItemPropertyValue -LiteralPath $Path -Name $Name -ErrorAction Stop)) + } catch { + return 'MISSING' + } +} + +function Get-OwnedResourcePreservationSnapshot($Owned) { + $user = Get-LocalUser -Name $Owned.UserName -ErrorAction SilentlyContinue + $profileMatches = try { + @(Get-CimInstance -ClassName Win32_UserProfile -ErrorAction Stop | + Where-Object { + $_.SID -ceq [string]$Owned.UserSid -and + [string]::Equals( + ([string]$_.LocalPath).TrimEnd('\'), + ([string]$Owned.ProfilePath).TrimEnd('\'), + [StringComparison]::OrdinalIgnoreCase) + }) + } catch { + @() + } + $shortcutFolderDigest = + Get-SupervisorFixtureDirectoryDigest ([string]$Owned.ShortcutFolder) + $smokeDirectoryDigest = + Get-SupervisorFixtureDirectoryDigest ([string]$Owned.SmokeDirectory) + $ownedRootDigest = + Get-SupervisorFixtureDirectoryDigest ([string]$Owned.OwnedRoot) + $installRootDigest = + Get-SupervisorFixtureDirectoryDigest ([string]$Owned.InstallRoot) + $executableDigest = Get-SupervisorFixtureFileDigest ([string]$Owned.Executable) + $shortcutDigest = Get-SupervisorFixtureFileDigest ([string]$Owned.Shortcut) + $registryPathDigest = + Get-SupervisorFixtureRegistryDigest ([string]$Owned.RegistryPath) + $registryValueDigest = Get-SupervisorFixtureRegistryValueDigest ` + ([string]$Owned.RegistryPath) 'ProPRInstalledAppOwner' + $userDigest = if ($null -eq $user) { + 'MISSING' + } elseif ([string]$user.SID.Value -ceq [string]$Owned.UserSid) { + 'MATCH' + } else { + 'CHANGED' + } + $profileDigest = if ($profileMatches.Count -eq 1 -and + (Test-Path -LiteralPath $Owned.ProfilePath -PathType Container)) { + 'MATCH' + } else { + 'CHANGED' + } + return [PSCustomObject][ordered]@{ + OWNED_ROOT = $ownedRootDigest + INSTALL_ROOT = $installRootDigest + EXECUTABLE = $executableDigest + SHORTCUT_FOLDER = $shortcutFolderDigest + SHORTCUT = $shortcutDigest + SMOKE_DIRECTORY = $smokeDirectoryDigest + REGISTRY_PATH = $registryPathDigest + REGISTRY_VALUE = $registryValueDigest + USER_NAME = $userDigest + PROFILE_PATH = $profileDigest + } +} + +function Assert-OwnedResourcePreservationSnapshot( + $Before, + $After, + [string]$Field +) { + $preservationBefore = $Before + $preservationAfter = $After + $preservationField = [string]$Field + Invoke-SupervisorAttributedOperation ` + -Scenario 'RESOURCE_COLLISION' ` + -Phase 'RESOURCE_ASSERTION' ` + -Callsite 'REPLACEMENT_SURVIVAL_READ' ` + -Field $preservationField ` + -Action { + Assert-True ([string]$preservationBefore.$preservationField -ceq + [string]$preservationAfter.$preservationField) ` + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + 'RESOURCE_COLLISION' ` + 'RESOURCE_ASSERTION' ` + 'REPLACEMENT_SURVIVAL_READ' ` + $preservationField) + } +} + function Get-WorkflowCleanupControllerStatusMatch([string]$TerminalLine) { return [regex]::Match( $TerminalLine, @@ -4777,6 +4995,10 @@ function Test-PreExistingCleanupOwnership { Set-ItemProperty -LiteralPath $workflowOwned.RegistryPath ` -Name 'ProPRInstalledAppOwner' -Value 'foreign-owner' + $collisionManifestBefore = Get-Content -LiteralPath $workflowManifest -Raw ` + -Encoding UTF8 + $collisionResourcesBefore = + Get-OwnedResourcePreservationSnapshot $workflowOwned $failedWorkflowCleanup = Invoke-WorkflowCleanupController ` 'RESOURCE_COLLISION' $workflowManifest $workflowRunId $workflowStateDirectory Invoke-SupervisorAttributedOperation ` @@ -4786,12 +5008,8 @@ function Test-PreExistingCleanupOwnership { -Field 'EXIT_CODE' ` -Action { Assert-True ($failedWorkflowCleanup.ExitCode -eq 21) ` - (Get-SanitizedSupervisorInvocationDiagnostic ` - $script:currentSupervisorInvocationTest ` - 'RESOURCE_COLLISION' ` - 'WORKFLOW_CLEANUP_CONTROLLER' ` - 'CONTROLLER_RESULT_FIELD' ` - 'EXIT_CODE') + (Get-SanitizedResourceCollisionControllerDiagnostic ` + $failedWorkflowCleanup 'EXIT_CODE') } Invoke-SupervisorAttributedOperation ` -Scenario 'RESOURCE_COLLISION' ` @@ -4800,12 +5018,8 @@ function Test-PreExistingCleanupOwnership { -Field 'REPORTED_EXIT_CODE' ` -Action { Assert-True ($failedWorkflowCleanup.ReportedExitCode -eq 21) ` - (Get-SanitizedSupervisorInvocationDiagnostic ` - $script:currentSupervisorInvocationTest ` - 'RESOURCE_COLLISION' ` - 'WORKFLOW_CLEANUP_CONTROLLER' ` - 'CONTROLLER_RESULT_FIELD' ` - 'REPORTED_EXIT_CODE') + (Get-SanitizedResourceCollisionControllerDiagnostic ` + $failedWorkflowCleanup 'REPORTED_EXIT_CODE') } Invoke-SupervisorAttributedOperation ` -Scenario 'RESOURCE_COLLISION' ` @@ -4814,12 +5028,8 @@ function Test-PreExistingCleanupOwnership { -Field 'RESULT' ` -Action { Assert-True ($failedWorkflowCleanup.Result -ceq 'FAILED') ` - (Get-SanitizedSupervisorInvocationDiagnostic ` - $script:currentSupervisorInvocationTest ` - 'RESOURCE_COLLISION' ` - 'WORKFLOW_CLEANUP_CONTROLLER' ` - 'CONTROLLER_RESULT_FIELD' ` - 'RESULT') + (Get-SanitizedResourceCollisionControllerDiagnostic ` + $failedWorkflowCleanup 'RESULT') } Invoke-SupervisorAttributedOperation ` -Scenario 'RESOURCE_COLLISION' ` @@ -4829,21 +5039,28 @@ function Test-PreExistingCleanupOwnership { -Action { Assert-True ($failedWorkflowCleanup.ControllerStatus -ceq 'OWNED_RESOURCE_CLEANUP_FAILURE') ` - (Get-SanitizedSupervisorInvocationDiagnostic ` - $script:currentSupervisorInvocationTest ` - 'RESOURCE_COLLISION' ` - 'WORKFLOW_CLEANUP_CONTROLLER' ` - 'CONTROLLER_RESULT_FIELD' ` - 'CONTROLLER_STATUS') + (Get-SanitizedResourceCollisionControllerDiagnostic ` + $failedWorkflowCleanup 'CONTROLLER_STATUS') } + $collisionResourcesAfter = + Get-OwnedResourcePreservationSnapshot $workflowOwned + foreach ($collisionResourceField in @( + 'OWNED_ROOT','INSTALL_ROOT','EXECUTABLE','SHORTCUT_FOLDER','SHORTCUT', + 'SMOKE_DIRECTORY','REGISTRY_PATH','REGISTRY_VALUE','USER_NAME', + 'PROFILE_PATH' + )) { + Assert-OwnedResourcePreservationSnapshot ` + $collisionResourcesBefore $collisionResourcesAfter $collisionResourceField + } Invoke-SupervisorAttributedOperation ` -Scenario 'RESOURCE_COLLISION' ` -Phase 'RESOURCE_ASSERTION' ` -Callsite 'REPLACEMENT_SURVIVAL_READ' ` -Field 'REGISTRY_VALUE' ` -Action { - Assert-True ((Get-ItemPropertyValue -LiteralPath $workflowOwned.RegistryPath ` - -Name 'ProPRInstalledAppOwner') -ceq 'foreign-owner') ` + Assert-True ((Get-SupervisorFixtureRegistryValueDigest ` + ([string]$workflowOwned.RegistryPath) 'ProPRInstalledAppOwner') -ceq + (Get-SupervisorFixtureStringDigest 'foreign-owner')) ` (Get-SanitizedSupervisorInvocationDiagnostic ` $script:currentSupervisorInvocationTest ` 'RESOURCE_COLLISION' ` @@ -4857,9 +5074,29 @@ function Test-PreExistingCleanupOwnership { -Callsite 'MANIFEST_PRESERVATION' ` -Field 'MANIFEST_PATH' ` -Action { - $collisionAuthority = Get-Content -LiteralPath $workflowManifest -Raw -Encoding UTF8 | - ConvertFrom-Json -ErrorAction Stop - Assert-True ($collisionAuthority.State -ceq 'ACTIVE') ` + $collisionManifestAfter = if (Test-Path -LiteralPath $workflowManifest ` + -PathType Leaf) { + try { + Get-Content -LiteralPath $workflowManifest -Raw -Encoding UTF8 + } catch { + '' + } + } else { + '' + } + Assert-True ($collisionManifestAfter -ceq $collisionManifestBefore) ` + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + 'RESOURCE_COLLISION' ` + 'MANIFEST_ASSERTION' ` + 'MANIFEST_PRESERVATION' ` + 'MANIFEST_PATH') + $collisionManifestState = try { + [string](($collisionManifestAfter | ConvertFrom-Json -ErrorAction Stop).State) + } catch { + 'INVALID' + } + Assert-True ($collisionManifestState -ceq 'ACTIVE') ` (Get-SanitizedSupervisorInvocationDiagnostic ` $script:currentSupervisorInvocationTest ` 'RESOURCE_COLLISION' ` @@ -4870,13 +5107,24 @@ function Test-PreExistingCleanupOwnership { Set-ItemProperty -LiteralPath $workflowOwned.RegistryPath ` -Name 'ProPRInstalledAppOwner' -Value ([string]$workflowOwned.Token) + Assert-True ((Get-SupervisorFixtureRegistryValueDigest ` + ([string]$workflowOwned.RegistryPath) 'ProPRInstalledAppOwner') -ceq + (Get-SupervisorFixtureStringDigest ([string]$workflowOwned.Token))) ` + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + 'RESOURCE_COLLISION' ` + 'AUTHORITY_RESTORE' ` + 'AUTHORITY_RESTORE_WRITE' ` + 'REGISTRY_VALUE') $workflowCleanup = Invoke-WorkflowCleanupController ` 'WORKFLOW_RETRY' $workflowManifest $workflowRunId $workflowStateDirectory Assert-True ($workflowCleanup.ExitCode -eq 0 -and $workflowCleanup.ReportedExitCode -eq 0 -and + $workflowCleanup.Result -ceq 'COMPLETE' -and $workflowCleanup.ControllerStatus -ceq 'EMPTY_OR_CLEANED' -and $workflowCleanup.InvocationIdentifier -ceq 'WORKFLOW_RETRY') ` - 'workflow cleanup controller did not retry to fixed cleanup success' + ("workflow cleanup controller did not retry to fixed cleanup success:" + + (Get-SanitizedWorkflowCleanupResultDiagnostic $workflowCleanup)) Assert-OwnedResourcesGone $workflowOwned Assert-True (!(Test-Path -LiteralPath $workflowManifest)) ` 'workflow cleanup did not consume the ownership manifest' diff --git a/apps/desktop/src/release-workflow.test.ts b/apps/desktop/src/release-workflow.test.ts index ae10b16fe..099dc092e 100644 --- a/apps/desktop/src/release-workflow.test.ts +++ b/apps/desktop/src/release-workflow.test.ts @@ -694,7 +694,30 @@ describe('desktop trusted release workflow', () => { ); assert.match( resourceCollisionAssertions, - /-Scenario 'RESOURCE_COLLISION'[\s\S]*-Phase 'MANIFEST_ASSERTION'[\s\S]*-Callsite 'MANIFEST_PRESERVATION'[\s\S]*-Field 'MANIFEST_PATH'[\s\S]*\$collisionAuthority\.State -ceq 'ACTIVE'/, + /-Scenario 'RESOURCE_COLLISION'[\s\S]*-Phase 'MANIFEST_ASSERTION'[\s\S]*-Callsite 'MANIFEST_PRESERVATION'[\s\S]*-Field 'MANIFEST_PATH'[\s\S]*\$collisionManifestState -ceq 'ACTIVE'/, + ); + assert.match( + installedWindowsAppCleanup, + /\$currentOwnerToken = Get-ItemPropertyValue[\s\S]*\$cleanupFailed = \$true/, + ); + assert.ok( + installedWindowsAppCleanup.indexOf('$currentOwnerToken = Get-ItemPropertyValue') + < installedWindowsAppCleanup.indexOf( + "if ([string]$manifest.MsiTransactionState -ceq 'COMMITTED') {\n Assert-MsiManagedFileSystemAuthority $manifest\n }", + ), + 'registry ownership collision preflight must precede committed MSI file-system authority', + ); + assert.ok( + installedWindowsAppCleanup.indexOf( + "if ([string]$manifest.MsiTransactionState -ceq 'COMMITTED') {\n Assert-MsiManagedFileSystemAuthority $manifest\n }", + ) + < installedWindowsAppCleanup.indexOf("if ($cleanupFailed) {\n throw 'owned resource authority collision'\n }"), + 'committed MSI file-system authority must complete before collision exit', + ); + assert.ok( + installedWindowsAppCleanup.indexOf("if ($cleanupFailed) {\n throw 'owned resource authority collision'\n }") + < installedWindowsAppCleanup.indexOf('if ($allowAuthenticatedMsiUninstall'), + 'ownership collision must stop before cleanup mutation', ); const fixtureProcessStateReader = installedWindowsAppSupervisorBehaviorTest.slice( installedWindowsAppSupervisorBehaviorTest.indexOf('function Read-FixtureProcessState'), @@ -1483,7 +1506,7 @@ describe('desktop trusted release workflow', () => { ); assert.match( installedWindowsAppSupervisorBehaviorTest, - /timed-out workflow cleanup discarded authenticated recovery authority[\s\S]*\$collisionAuthority\.State -ceq 'ACTIVE'[\s\S]*retry to fixed cleanup success/, + /timed-out workflow cleanup discarded authenticated recovery authority[\s\S]*\$collisionManifestState -ceq 'ACTIVE'[\s\S]*retry to fixed cleanup success/, ); const fixedResultWrite = installedWindowsAppWorkflowCleanup.indexOf( 'Write-FixedResult $fixedResult', From ce10c08564b815ec3fe20763a61fb0502d04de87 Mon Sep 17 00:00:00 2001 From: "propr-dev[bot]" <1316198+propr-dev[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 15:36:52 +0000 Subject: [PATCH 21/33] feat(ai): Implemented the follow-up on head `260908b8d310a474769398ba74c702ae4c52b6e5`. Implemented the follow-up on head `260908b8d310a474769398ba74c702ae4c52b6e5`. Changed: - Moved provisional user/profile promotion and `Write-DurableOwnershipManifest` after RegistryValues preflight, committed MSI filesystem authority, and the cleanupFailed collision guard in [cleanup-installed-windows-app.ps1](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T15-30-51/apps/desktop/scripts/cleanup-installed-windows-app.ps1:1716). - Removed the appended resource-collision controller diagnostic helper and switched RESOURCE_COLLISION result assertions back to exact `CONTROLLER_RESULT_FIELD` supervisor diagnostics in [test-installed-windows-app-supervisor.ps1](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T15-30-51/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1:4959). - Strengthened static regressions for byte-identical ACTIVE manifest preservation, all resource preservation fields, exact-token retry cleanup, and promotion-after-guard ordering in [release-workflow.test.ts](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T15-30-51/apps/desktop/src/release-workflow.test.ts:633). Validation: - `npx tsx --test apps/desktop/src/release-workflow.test.ts` passed. - `git diff --check` passed. Native win32-x64 / win32-ARM64 were not run here. This worker is Linux and has no `pwsh`; although `gh` is authenticated, these changes are uncommitted per instructions, so any GitHub Actions run started now would test the old head rather than this working tree. No native job IDs or remaining predicates are available from this local run. PR: #2057 Comment by: @integry (ID: 5512061818) Model: gpt-5.5 --- .../scripts/cleanup-installed-windows-app.ps1 | 20 ++--- .../test-installed-windows-app-supervisor.ps1 | 75 ++++++------------- apps/desktop/src/release-workflow.test.ts | 44 +++++++++-- 3 files changed, 73 insertions(+), 66 deletions(-) diff --git a/apps/desktop/scripts/cleanup-installed-windows-app.ps1 b/apps/desktop/scripts/cleanup-installed-windows-app.ps1 index d69034c7d..fb268b35a 100644 --- a/apps/desktop/scripts/cleanup-installed-windows-app.ps1 +++ b/apps/desktop/scripts/cleanup-installed-windows-app.ps1 @@ -1713,16 +1713,6 @@ try { Assert-MsiRolledBackCleanBaseline $manifest } } - $ownershipPromoted = $false - foreach ($record in @($manifest.Users)) { - if (Resolve-ProvisionalOwnedUser $record) { $ownershipPromoted = $true } - if (Promote-UncapturedOwnedProfiles $record $manifest) { - $ownershipPromoted = $true - } - } - if ($ownershipPromoted) { - Write-DurableOwnershipManifest $manifestPath $manifest - } foreach ($record in @($manifest.RegistryValues)) { if (!$record.Owned) { continue } $current = Get-RegistryValueSnapshot ([string]$record.Path) ([string]$record.Name) @@ -1742,6 +1732,16 @@ try { if ($cleanupFailed) { throw 'owned resource authority collision' } + $ownershipPromoted = $false + foreach ($record in @($manifest.Users)) { + if (Resolve-ProvisionalOwnedUser $record) { $ownershipPromoted = $true } + if (Promote-UncapturedOwnedProfiles $record $manifest) { + $ownershipPromoted = $true + } + } + if ($ownershipPromoted) { + Write-DurableOwnershipManifest $manifestPath $manifest + } if ($allowAuthenticatedMsiUninstall -and !$cleanupFailed) { $msiExitCode = 1618 for ($attempt = 0; $attempt -lt 12 -and $msiExitCode -eq 1618; $attempt += 1) { diff --git a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 index f72b366f0..c1b665276 100644 --- a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 +++ b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 @@ -1326,17 +1326,6 @@ function Get-SanitizedWorkflowCleanupResultDiagnostic($Result) { return $diagnostic } -function Get-SanitizedResourceCollisionControllerDiagnostic($Result, [string]$Field) { - return ( - (Get-SanitizedSupervisorInvocationDiagnostic ` - $script:currentSupervisorInvocationTest ` - 'RESOURCE_COLLISION' ` - 'WORKFLOW_CLEANUP_CONTROLLER' ` - 'CONTROLLER_RESULT_FIELD' ` - $Field) + ':' + (Get-SanitizedWorkflowCleanupResultDiagnostic $Result) - ) -} - function Get-SupervisorFixtureSha256Hex([byte[]]$Bytes) { $sha256 = [Security.Cryptography.SHA256]::Create() try { @@ -2219,10 +2208,6 @@ function Get-SupervisorAttributionTotalityCases { 'CALLSITE_CONTROLLER_RESULT_EXIT_CODE', 'CALLSITE_CONTROLLER_RESULT_REPORTED_EXIT_CODE', 'CALLSITE_CONTROLLER_RESULT_RESULT', - 'CALLSITE_RESOURCE_COLLISION_CONTROLLER_RESULT_EXIT_CODE', - 'CALLSITE_RESOURCE_COLLISION_CONTROLLER_RESULT_REPORTED_EXIT_CODE', - 'CALLSITE_RESOURCE_COLLISION_CONTROLLER_RESULT_RESULT', - 'CALLSITE_RESOURCE_COLLISION_CONTROLLER_RESULT_CONTROLLER_STATUS', 'CALLSITE_RESOURCE_COLLISION_REPLACEMENT_SURVIVAL_READ', 'CALLSITE_RESOURCE_COLLISION_MANIFEST_PRESERVATION', 'CALLSITE_EARLY_PROCESS_STATE_PATH', @@ -3349,34 +3334,6 @@ function Test-SupervisorInvocationAttributionTotality { Phase='WORKFLOW_CLEANUP_CONTROLLER' Callsite='CONTROLLER_RESULT_FIELD'; Field='RESULT' }, - [PSCustomObject]@{ - CaseId='CALLSITE_RESOURCE_COLLISION_CONTROLLER_RESULT_EXIT_CODE' - Test='PRE_EXISTING_CLEANUP_OWNERSHIP' - Scenario='RESOURCE_COLLISION' - Phase='WORKFLOW_CLEANUP_CONTROLLER' - Callsite='CONTROLLER_RESULT_FIELD'; Field='EXIT_CODE' - }, - [PSCustomObject]@{ - CaseId='CALLSITE_RESOURCE_COLLISION_CONTROLLER_RESULT_REPORTED_EXIT_CODE' - Test='PRE_EXISTING_CLEANUP_OWNERSHIP' - Scenario='RESOURCE_COLLISION' - Phase='WORKFLOW_CLEANUP_CONTROLLER' - Callsite='CONTROLLER_RESULT_FIELD'; Field='REPORTED_EXIT_CODE' - }, - [PSCustomObject]@{ - CaseId='CALLSITE_RESOURCE_COLLISION_CONTROLLER_RESULT_RESULT' - Test='PRE_EXISTING_CLEANUP_OWNERSHIP' - Scenario='RESOURCE_COLLISION' - Phase='WORKFLOW_CLEANUP_CONTROLLER' - Callsite='CONTROLLER_RESULT_FIELD'; Field='RESULT' - }, - [PSCustomObject]@{ - CaseId='CALLSITE_RESOURCE_COLLISION_CONTROLLER_RESULT_CONTROLLER_STATUS' - Test='PRE_EXISTING_CLEANUP_OWNERSHIP' - Scenario='RESOURCE_COLLISION' - Phase='WORKFLOW_CLEANUP_CONTROLLER' - Callsite='CONTROLLER_RESULT_FIELD'; Field='CONTROLLER_STATUS' - }, [PSCustomObject]@{ CaseId='CALLSITE_RESOURCE_COLLISION_REPLACEMENT_SURVIVAL_READ' Test='PRE_EXISTING_CLEANUP_OWNERSHIP' @@ -5008,8 +4965,12 @@ function Test-PreExistingCleanupOwnership { -Field 'EXIT_CODE' ` -Action { Assert-True ($failedWorkflowCleanup.ExitCode -eq 21) ` - (Get-SanitizedResourceCollisionControllerDiagnostic ` - $failedWorkflowCleanup 'EXIT_CODE') + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + 'RESOURCE_COLLISION' ` + 'WORKFLOW_CLEANUP_CONTROLLER' ` + 'CONTROLLER_RESULT_FIELD' ` + 'EXIT_CODE') } Invoke-SupervisorAttributedOperation ` -Scenario 'RESOURCE_COLLISION' ` @@ -5018,8 +4979,12 @@ function Test-PreExistingCleanupOwnership { -Field 'REPORTED_EXIT_CODE' ` -Action { Assert-True ($failedWorkflowCleanup.ReportedExitCode -eq 21) ` - (Get-SanitizedResourceCollisionControllerDiagnostic ` - $failedWorkflowCleanup 'REPORTED_EXIT_CODE') + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + 'RESOURCE_COLLISION' ` + 'WORKFLOW_CLEANUP_CONTROLLER' ` + 'CONTROLLER_RESULT_FIELD' ` + 'REPORTED_EXIT_CODE') } Invoke-SupervisorAttributedOperation ` -Scenario 'RESOURCE_COLLISION' ` @@ -5028,8 +4993,12 @@ function Test-PreExistingCleanupOwnership { -Field 'RESULT' ` -Action { Assert-True ($failedWorkflowCleanup.Result -ceq 'FAILED') ` - (Get-SanitizedResourceCollisionControllerDiagnostic ` - $failedWorkflowCleanup 'RESULT') + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + 'RESOURCE_COLLISION' ` + 'WORKFLOW_CLEANUP_CONTROLLER' ` + 'CONTROLLER_RESULT_FIELD' ` + 'RESULT') } Invoke-SupervisorAttributedOperation ` -Scenario 'RESOURCE_COLLISION' ` @@ -5039,8 +5008,12 @@ function Test-PreExistingCleanupOwnership { -Action { Assert-True ($failedWorkflowCleanup.ControllerStatus -ceq 'OWNED_RESOURCE_CLEANUP_FAILURE') ` - (Get-SanitizedResourceCollisionControllerDiagnostic ` - $failedWorkflowCleanup 'CONTROLLER_STATUS') + (Get-SanitizedSupervisorInvocationDiagnostic ` + $script:currentSupervisorInvocationTest ` + 'RESOURCE_COLLISION' ` + 'WORKFLOW_CLEANUP_CONTROLLER' ` + 'CONTROLLER_RESULT_FIELD' ` + 'CONTROLLER_STATUS') } $collisionResourcesAfter = Get-OwnedResourcePreservationSnapshot $workflowOwned diff --git a/apps/desktop/src/release-workflow.test.ts b/apps/desktop/src/release-workflow.test.ts index 099dc092e..4b5248033 100644 --- a/apps/desktop/src/release-workflow.test.ts +++ b/apps/desktop/src/release-workflow.test.ts @@ -630,11 +630,11 @@ describe('desktop trusted release workflow', () => { installedWindowsAppSupervisorBehaviorTest, /Get-SupervisorInvocationFields[\s\S]*'CONTROLLER_STATUS'/, ); + assert.doesNotMatch( + installedWindowsAppSupervisorBehaviorTest, + /Get-SanitizedResourceCollisionControllerDiagnostic/, + ); for (const caseId of [ - 'CALLSITE_RESOURCE_COLLISION_CONTROLLER_RESULT_EXIT_CODE', - 'CALLSITE_RESOURCE_COLLISION_CONTROLLER_RESULT_REPORTED_EXIT_CODE', - 'CALLSITE_RESOURCE_COLLISION_CONTROLLER_RESULT_RESULT', - 'CALLSITE_RESOURCE_COLLISION_CONTROLLER_RESULT_CONTROLLER_STATUS', 'CALLSITE_RESOURCE_COLLISION_REPLACEMENT_SURVIVAL_READ', 'CALLSITE_RESOURCE_COLLISION_MANIFEST_PRESERVATION', ]) { @@ -692,9 +692,27 @@ describe('desktop trusted release workflow', () => { resourceCollisionAssertions, /-Scenario 'RESOURCE_COLLISION'[\s\S]*-Phase 'RESOURCE_ASSERTION'[\s\S]*-Callsite 'REPLACEMENT_SURVIVAL_READ'[\s\S]*-Field 'REGISTRY_VALUE'/, ); + for (const resourceField of [ + 'OWNED_ROOT', + 'INSTALL_ROOT', + 'EXECUTABLE', + 'SHORTCUT_FOLDER', + 'SHORTCUT', + 'SMOKE_DIRECTORY', + 'REGISTRY_PATH', + 'REGISTRY_VALUE', + 'USER_NAME', + 'PROFILE_PATH', + ]) { + assert.match(resourceCollisionAssertions, new RegExp(`'${resourceField}'`)); + } assert.match( resourceCollisionAssertions, - /-Scenario 'RESOURCE_COLLISION'[\s\S]*-Phase 'MANIFEST_ASSERTION'[\s\S]*-Callsite 'MANIFEST_PRESERVATION'[\s\S]*-Field 'MANIFEST_PATH'[\s\S]*\$collisionManifestState -ceq 'ACTIVE'/, + /-Scenario 'RESOURCE_COLLISION'[\s\S]*-Phase 'MANIFEST_ASSERTION'[\s\S]*-Callsite 'MANIFEST_PRESERVATION'[\s\S]*-Field 'MANIFEST_PATH'[\s\S]*\$collisionManifestAfter -ceq \$collisionManifestBefore[\s\S]*\$collisionManifestState -ceq 'ACTIVE'/, + ); + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /\$collisionManifestBefore = Get-Content[\s\S]*\$collisionResourcesBefore =[\s\S]*Get-OwnedResourcePreservationSnapshot[\s\S]*'RESOURCE_COLLISION' \$workflowManifest[\s\S]*\$collisionResourcesAfter =[\s\S]*Get-OwnedResourcePreservationSnapshot[\s\S]*Set-ItemProperty[\s\S]*-Name 'ProPRInstalledAppOwner' -Value \(\[string\]\$workflowOwned\.Token\)[\s\S]*'WORKFLOW_RETRY' \$workflowManifest[\s\S]*Assert-OwnedResourcesGone \$workflowOwned/, ); assert.match( installedWindowsAppCleanup, @@ -719,6 +737,22 @@ describe('desktop trusted release workflow', () => { < installedWindowsAppCleanup.indexOf('if ($allowAuthenticatedMsiUninstall'), 'ownership collision must stop before cleanup mutation', ); + for (const promotionProducer of [ + 'Resolve-ProvisionalOwnedUser $record', + 'Promote-UncapturedOwnedProfiles $record $manifest', + 'Write-DurableOwnershipManifest $manifestPath $manifest', + ]) { + assert.ok( + installedWindowsAppCleanup.indexOf("if ($cleanupFailed) {\n throw 'owned resource authority collision'\n }") + < installedWindowsAppCleanup.indexOf(promotionProducer), + `${promotionProducer} must occur after ownership collision guard`, + ); + assert.ok( + installedWindowsAppCleanup.indexOf(promotionProducer) + < installedWindowsAppCleanup.indexOf('if ($allowAuthenticatedMsiUninstall'), + `${promotionProducer} must still occur before cleanup mutation on the no-collision path`, + ); + } const fixtureProcessStateReader = installedWindowsAppSupervisorBehaviorTest.slice( installedWindowsAppSupervisorBehaviorTest.indexOf('function Read-FixtureProcessState'), installedWindowsAppSupervisorBehaviorTest.indexOf('function Read-FixtureResourceState'), From bb3cc0d307f7fd16bdb95f09b78a304fc6bee8a1 Mon Sep 17 00:00:00 2001 From: "propr-dev[bot]" <1316198+propr-dev[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 15:45:30 +0000 Subject: [PATCH 22/33] feat(ai): Implemented the requested attribution-only follow-up on head `ce10c08564b815ec3fe20763a61fb0502d04de87`. Implemented the requested attribution-only follow-up on head `ce10c08564b815ec3fe20763a61fb0502d04de87`. Changed: - Wrapped the initial HKCU baseline `Test-Path` predicate in [test-installed-windows-app-supervisor.ps1](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T15-39-22/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1:5494) with: `HKCU_INSTALLED_VALUE_OWNERSHIP / HKCU_BASELINE_RESTORE / FIXTURE_SETUP / HKCU_BASELINE_STATE / REGISTRY_PATH` - Added `HKCU_BASELINE_STATE` to the fixed callsite allowlist and added hostile-data attribution totality coverage. - Set fixed HKCU scenario setup context at entry for all six HKCU matrix cases. - Added static release-workflow coverage in [release-workflow.test.ts](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T15-39-22/apps/desktop/src/release-workflow.test.ts:1018). Validation: - `npx tsx --test apps/desktop/src/release-workflow.test.ts` passed. - `git diff --check` passed. - Could not run native `win32-x64` / `win32-ARM64` here because this container is Linux and `pwsh` is not installed, so no next native fixed predicate was observed locally. PR: #2057 Comment by: @integry (ID: 5512182307) Model: gpt-5.5 --- .../test-installed-windows-app-supervisor.ps1 | 56 ++++++++++++++++++- apps/desktop/src/release-workflow.test.ts | 27 +++++++++ 2 files changed, 81 insertions(+), 2 deletions(-) diff --git a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 index c1b665276..18292523f 100644 --- a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 +++ b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 @@ -2008,6 +2008,7 @@ function Get-SupervisorInvocationCallsites { 'CONTROLLER_RESULT_FIELD', 'EARLY_PROCESS_STATE_PATH', 'EARLY_PROCESS_STATE_READ', + 'HKCU_BASELINE_STATE', 'MANIFEST_PRESERVATION', 'RESOURCE_FIELD_VALIDATION', 'REPLACEMENT_SURVIVAL_READ', @@ -2216,6 +2217,7 @@ function Get-SupervisorAttributionTotalityCases { 'CALLSITE_EARLY_DESCENDANT_PID', 'CALLSITE_EARLY_PROCESS_TREE_ASSERTION', 'CALLSITE_EARLY_MANIFEST_PRESERVATION', + 'CALLSITE_HKCU_BASELINE_STATE', 'CALLSITE_REPLACEMENT_SURVIVAL_READ', 'FIELD_EXECUTABLE_BACKUP', 'FIELD_MANIFEST_PATH', @@ -3390,6 +3392,13 @@ function Test-SupervisorInvocationAttributionTotality { Phase='MANIFEST_ASSERTION' Callsite='MANIFEST_PRESERVATION'; Field='MANIFEST_PATH' }, + [PSCustomObject]@{ + CaseId='CALLSITE_HKCU_BASELINE_STATE' + Test='HKCU_INSTALLED_VALUE_OWNERSHIP' + Scenario='HKCU_BASELINE_RESTORE' + Phase='FIXTURE_SETUP' + Callsite='HKCU_BASELINE_STATE'; Field='REGISTRY_PATH' + }, [PSCustomObject]@{ CaseId='CALLSITE_REPLACEMENT_SURVIVAL_READ' Test='PRE_EXISTING_CLEANUP_OWNERSHIP' @@ -5484,8 +5493,15 @@ function Test-HkcuInstalledValueOwnership { $installedName = 'installed' $sentinelInstalled = 'pre-existing-installed' $sentinelUnrelated = 'preserve-unrelated' - Assert-True (!(Test-Path -LiteralPath $desktopKey)) ` - 'HKCU installed-value fixture baseline was not clean' + Invoke-SupervisorAttributedOperation ` + -Scenario 'HKCU_BASELINE_RESTORE' ` + -Phase 'FIXTURE_SETUP' ` + -Callsite 'HKCU_BASELINE_STATE' ` + -Field 'REGISTRY_PATH' ` + -Action { + Assert-True (!(Test-Path -LiteralPath $desktopKey)) ` + 'HKCU installed-value fixture baseline was not clean' + } function New-HkcuManifest( [bool]$BaselineKeyExisted, @@ -5544,6 +5560,12 @@ function Test-HkcuInstalledValueOwnership { } try { + Set-SupervisorInvocationContext ` + 'HKCU_INSTALLED_VALUE_OWNERSHIP' ` + 'HKCU_BASELINE_RESTORE' ` + 'FIXTURE_SETUP' ` + 'HKCU_BASELINE_STATE' ` + 'REGISTRY_PATH' [void](New-Item -Path $desktopKey -Force -ErrorAction Stop) (Get-Item -LiteralPath $desktopKey).SetValue( $installedName, $sentinelInstalled, [Microsoft.Win32.RegistryValueKind]::String) @@ -5566,6 +5588,12 @@ function Test-HkcuInstalledValueOwnership { Assert-True ([string]$restoredKey.GetValue('Unrelated') -ceq $sentinelUnrelated) ` 'unrelated HKCU value was changed during baseline restoration' + Set-SupervisorInvocationContext ` + 'HKCU_INSTALLED_VALUE_OWNERSHIP' ` + 'HKCU_PENDING_RECEIPT' ` + 'FIXTURE_SETUP' ` + 'HKCU_BASELINE_STATE' ` + 'REGISTRY_PATH' $unchangedManifest = New-HkcuManifest ` $true $true 'String' $baselineData $false $false $true $unchanged = Invoke-WorkflowCleanupController ` @@ -5581,6 +5609,12 @@ function Test-HkcuInstalledValueOwnership { 'rejected pending MSI receipt discarded authenticated recovery authority' Remove-Item -LiteralPath $unchangedManifest.Path -Force -ErrorAction Stop + Set-SupervisorInvocationContext ` + 'HKCU_INSTALLED_VALUE_OWNERSHIP' ` + 'HKCU_NONEMPTY' ` + 'FIXTURE_SETUP' ` + 'HKCU_BASELINE_STATE' ` + 'REGISTRY_PATH' Remove-Item -LiteralPath $desktopKey -Recurse -Force -ErrorAction Stop [void](New-Item -Path $desktopKey -Force -ErrorAction Stop) (Get-Item -LiteralPath $desktopKey).SetValue( @@ -5597,6 +5631,12 @@ function Test-HkcuInstalledValueOwnership { [string]$nonemptyKey.GetValue('Unrelated') -ceq $sentinelUnrelated) ` 'run-owned HKCU cleanup removed its nonempty key or unrelated value' + Set-SupervisorInvocationContext ` + 'HKCU_INSTALLED_VALUE_OWNERSHIP' ` + 'HKCU_EMPTY' ` + 'FIXTURE_SETUP' ` + 'HKCU_BASELINE_STATE' ` + 'REGISTRY_PATH' Remove-Item -LiteralPath $desktopKey -Recurse -Force -ErrorAction Stop [void](New-Item -Path $desktopKey -Force -ErrorAction Stop) (Get-Item -LiteralPath $desktopKey).SetValue( @@ -5607,6 +5647,12 @@ function Test-HkcuInstalledValueOwnership { Assert-True ($empty.ExitCode -eq 0 -and !(Test-Path -LiteralPath $desktopKey)) ` 'run-created empty HKCU key was not removed' + Set-SupervisorInvocationContext ` + 'HKCU_INSTALLED_VALUE_OWNERSHIP' ` + 'HKCU_CONFLICT' ` + 'FIXTURE_SETUP' ` + 'HKCU_BASELINE_STATE' ` + 'REGISTRY_PATH' [void](New-Item -Path $desktopKey -Force -ErrorAction Stop) (Get-Item -LiteralPath $desktopKey).SetValue( $installedName, 'foreign-conflict', [Microsoft.Win32.RegistryValueKind]::String) @@ -5624,6 +5670,12 @@ function Test-HkcuInstalledValueOwnership { 'conflicting HKCU cleanup discarded authenticated recovery authority' Remove-Item -LiteralPath $conflictManifest.Path -Force -ErrorAction Stop + Set-SupervisorInvocationContext ` + 'HKCU_INSTALLED_VALUE_OWNERSHIP' ` + 'HKCU_PROVISIONAL' ` + 'FIXTURE_SETUP' ` + 'HKCU_BASELINE_STATE' ` + 'REGISTRY_PATH' Remove-Item -LiteralPath $desktopKey -Recurse -Force -ErrorAction Stop [void](New-Item -Path $desktopKey -Force -ErrorAction Stop) (Get-Item -LiteralPath $desktopKey).SetValue( diff --git a/apps/desktop/src/release-workflow.test.ts b/apps/desktop/src/release-workflow.test.ts index 4b5248033..9afa2ced8 100644 --- a/apps/desktop/src/release-workflow.test.ts +++ b/apps/desktop/src/release-workflow.test.ts @@ -637,6 +637,7 @@ describe('desktop trusted release workflow', () => { for (const caseId of [ 'CALLSITE_RESOURCE_COLLISION_REPLACEMENT_SURVIVAL_READ', 'CALLSITE_RESOURCE_COLLISION_MANIFEST_PRESERVATION', + 'CALLSITE_HKCU_BASELINE_STATE', ]) { assert.match( installedWindowsAppSupervisorBehaviorTest, @@ -1017,6 +1018,32 @@ describe('desktop trusted release workflow', () => { assert.match(installedWindowsAppTest, /HKCU_INSTALLED_ABSENCE_ASSERTION/); assert.match(installedWindowsAppTest, /HKCU_INSTALLED_FALLBACK/); assert.match(installedWindowsAppSupervisorBehaviorTest, /Test-HkcuInstalledValueOwnership/); + const hkcuInstalledValueOwnership = installedWindowsAppSupervisorBehaviorTest.slice( + installedWindowsAppSupervisorBehaviorTest.indexOf('function Test-HkcuInstalledValueOwnership'), + installedWindowsAppSupervisorBehaviorTest.indexOf('function Test-ProvisionalUserMarkerOwnership'), + ); + assert.match( + hkcuInstalledValueOwnership, + /Invoke-SupervisorAttributedOperation\s+`\n\s+-Scenario 'HKCU_BASELINE_RESTORE'\s+`\n\s+-Phase 'FIXTURE_SETUP'\s+`\n\s+-Callsite 'HKCU_BASELINE_STATE'\s+`\n\s+-Field 'REGISTRY_PATH'\s+`\n\s+-Action \{\n\s+Assert-True \(!\(Test-Path -LiteralPath \$desktopKey\)\)/, + ); + for (const scenario of [ + 'HKCU_BASELINE_RESTORE', + 'HKCU_PENDING_RECEIPT', + 'HKCU_NONEMPTY', + 'HKCU_EMPTY', + 'HKCU_CONFLICT', + 'HKCU_PROVISIONAL', + ]) { + assert.match( + hkcuInstalledValueOwnership, + new RegExp( + "Set-SupervisorInvocationContext\\s+`\\n\\s+'HKCU_INSTALLED_VALUE_OWNERSHIP'\\s+`\\n\\s+" + + "'" + scenario + "'\\s+`\\n\\s+'FIXTURE_SETUP'\\s+`\\n\\s+'HKCU_BASELINE_STATE'\\s+`\\n\\s+'REGISTRY_PATH'", + ), + ); + } + assert.match(installedWindowsAppSupervisorBehaviorTest, /Get-SupervisorInvocationCallsites[\s\S]*'HKCU_BASELINE_STATE'/); + assert.match(installedWindowsAppSupervisorBehaviorTest, /Get-SupervisorInvocationFields[\s\S]*'REGISTRY_PATH'/); assert.match(installedWindowsAppSupervisorBehaviorTest, /OWNED_RESOURCES_NORMAL_SUCCESS/); assert.match(installedWindowsAppSupervisorBehaviorTest, /typed authenticated empty-state receipt/); assert.match( From 15cff463a737fee3e8543a96e4a8c84d65975f89 Mon Sep 17 00:00:00 2001 From: "propr-dev[bot]" <1316198+propr-dev[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 16:09:22 +0000 Subject: [PATCH 23/33] feat(ai): Implemented the HKCU fixture-only boundary on head `bb3cc0d307f7fd16bdb95f09b78a304fc6bee8a1`. Implemented the HKCU fixture-only boundary on head `bb3cc0d307f7fd16bdb95f09b78a304fc6bee8a1`. Changed: - [test-installed-windows-app-supervisor.ps1](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T15-56-42/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1): added raw-byte recursive HKCU registry digesting, GUID sibling rename/restore boundary, fail-closed restore handling, and deterministic present/absent/nested/value-kind/restoration-failure regressions. - [release-workflow.test.ts](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T15-56-42/apps/desktop/src/release-workflow.test.ts): updated static contracts for the new boundary. Validation: - `npx tsx --test apps/desktop/src/release-workflow.test.ts` passed: 23/23. - `npm run test -w @propr/desktop -- src/release-workflow.test.ts` passed: 177 passed, 6 skipped. - `git diff --check` passed. I could not run new native `win32-x64` / `win32-ARM64` jobs from this container: no `pwsh`/Windows host is available, and the PR instructions require leaving changes uncommitted, so there is no pushed new head for CI to execute yet. No new job IDs or next fixed predicate are available from this local run. Reference consulted for the REG_NONE raw-byte edge: Microsoft `RegistryKey.GetValue` docs note `REG_NONE` data is not returned by `GetValue()`: https://learn.microsoft.com/en-us/dotnet/api/microsoft.win32.registrykey.getvalue. PR: #2057 Comment by: @integry (ID: 5512421741) Model: gpt-5.5 --- .../test-installed-windows-app-supervisor.ps1 | 388 ++++++++++++++++-- apps/desktop/src/release-workflow.test.ts | 34 +- 2 files changed, 394 insertions(+), 28 deletions(-) diff --git a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 index 18292523f..a8b6f48f8 100644 --- a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 +++ b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 @@ -1362,6 +1362,63 @@ function Get-SupervisorFixtureFileDigest([string]$Path) { } } +function Add-SupervisorFixtureRegistryNativeApi { + if ('ProprSupervisorFixtureRegistryNative' -as [type]) { return } + Add-Type -TypeDefinition @' +using System; +using System.Runtime.InteropServices; +using Microsoft.Win32.SafeHandles; + +public static class ProprSupervisorFixtureRegistryNative +{ + [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern int RegQueryValueEx( + SafeRegistryHandle hKey, + string lpValueName, + IntPtr lpReserved, + out int lpType, + byte[] lpData, + ref int lpcbData); +} +'@ +} + +function Get-SupervisorFixtureRegistryValueNativeBytes($Key, [string]$Name) { + Add-SupervisorFixtureRegistryNativeApi + $valueType = 0 + $valueByteCount = 0 + $result = [ProprSupervisorFixtureRegistryNative]::RegQueryValueEx( + $Key.Handle, + $Name, + [IntPtr]::Zero, + [ref]$valueType, + $null, + [ref]$valueByteCount + ) + if ($result -ne 0 -and $result -ne 234) { throw 'registry value query failed' } + if ($valueByteCount -lt 0 -or $valueByteCount -gt (16 * 1024 * 1024)) { + throw 'registry value query failed' + } + $valueBytes = if ($valueByteCount -eq 0) { + [byte[]]@() + } else { + New-Object byte[] $valueByteCount + } + $result = [ProprSupervisorFixtureRegistryNative]::RegQueryValueEx( + $Key.Handle, + $Name, + [IntPtr]::Zero, + [ref]$valueType, + $valueBytes, + [ref]$valueByteCount + ) + if ($result -ne 0) { throw 'registry value query failed' } + return [PSCustomObject]@{ + Type = $valueType + Bytes = $valueBytes + } +} + function Get-SupervisorFixtureDirectoryDigest([string]$Path) { try { if (!(Test-Path -LiteralPath $Path -PathType Container)) { return 'MISSING' } @@ -1409,25 +1466,12 @@ function Get-SupervisorFixtureRegistryDigest([string]$Path) { $records.Add(('K|{0}' -f [Convert]::ToBase64String( [Text.Encoding]::UTF8.GetBytes([string]$entry.Relative)))) foreach ($valueName in @($entry.Key.GetValueNames() | Sort-Object -CaseSensitive)) { - $value = $entry.Key.GetValue( - $valueName, - $null, - [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames - ) - $valueBytes = if ($value -is [byte[]]) { - $value - } elseif ($value -is [string[]]) { - [Text.Encoding]::UTF8.GetBytes(($value | ConvertTo-Json -Compress)) - } else { - [Text.Encoding]::UTF8.GetBytes([Convert]::ToString( - $value, - [Globalization.CultureInfo]::InvariantCulture - )) - } + $nativeValue = + Get-SupervisorFixtureRegistryValueNativeBytes $entry.Key ([string]$valueName) $records.Add(('V|{0}|{1}|{2}' -f [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes([string]$valueName)), - $entry.Key.GetValueKind($valueName).ToString(), - [Convert]::ToBase64String($valueBytes))) + ('{0}:{1}' -f $entry.Key.GetValueKind($valueName).ToString(), $nativeValue.Type), + [Convert]::ToBase64String([byte[]]$nativeValue.Bytes))) } foreach ($child in @(Get-ChildItem -LiteralPath $entry.Key.PSPath -ErrorAction Stop | Sort-Object -Property PSChildName -CaseSensitive)) { @@ -1453,6 +1497,141 @@ function Get-SupervisorFixtureRegistryValueDigest([string]$Path, [string]$Name) } } +function Test-HkcuFixtureRegistryDigest([string]$Digest) { + return [string]$Digest -cmatch '^[0-9a-f]{64}$' +} + +function Get-HkcuFixtureBoundaryDiagnostic { + return Get-SanitizedSupervisorInvocationDiagnostic ` + 'HKCU_INSTALLED_VALUE_OWNERSHIP' ` + 'HKCU_BASELINE_RESTORE' ` + 'FIXTURE_SETUP' ` + 'HKCU_BASELINE_STATE' ` + 'REGISTRY_PATH' +} + +function Split-HkcuFixtureRegistryPath([string]$Path) { + $normalizedPath = ([string]$Path).TrimEnd('\') + Assert-True ( + $normalizedPath.StartsWith( + 'Registry::HKEY_CURRENT_USER\', + [StringComparison]::OrdinalIgnoreCase + ) + ) (Get-HkcuFixtureBoundaryDiagnostic) + $separatorIndex = $normalizedPath.LastIndexOf('\') + Assert-True ($separatorIndex -gt 'Registry::HKEY_CURRENT_USER'.Length) ` + (Get-HkcuFixtureBoundaryDiagnostic) + $leaf = $normalizedPath.Substring($separatorIndex + 1) + Assert-True (![string]::IsNullOrWhiteSpace($leaf)) ` + (Get-HkcuFixtureBoundaryDiagnostic) + return [PSCustomObject]@{ + Parent = $normalizedPath.Substring(0, $separatorIndex) + Leaf = $leaf + } +} + +function New-HkcuDesktopFixtureBoundaryState([string]$DesktopKey) { + return [PSCustomObject]@{ + DesktopKey = $DesktopKey + DesktopLeaf = $null + ParentKey = $null + BackupLeaf = $null + BackupPath = $null + BaselinePresent = $false + BaselineDigest = $null + Relocated = $false + OriginalAbsentProven = $false + } +} + +function Initialize-HkcuDesktopFixtureBoundary($Boundary) { + Invoke-SupervisorAttributedOperation ` + -Scenario 'HKCU_BASELINE_RESTORE' ` + -Phase 'FIXTURE_SETUP' ` + -Callsite 'HKCU_BASELINE_STATE' ` + -Field 'REGISTRY_PATH' ` + -Action { + $parts = Split-HkcuFixtureRegistryPath ([string]$Boundary.DesktopKey) + $Boundary.ParentKey = [string]$parts.Parent + $Boundary.DesktopLeaf = [string]$parts.Leaf + if (!(Test-Path -LiteralPath $Boundary.DesktopKey)) { + $Boundary.OriginalAbsentProven = $true + return + } + $Boundary.BaselinePresent = $true + $baselineDigest = + Get-SupervisorFixtureRegistryDigest ([string]$Boundary.DesktopKey) + Assert-True (Test-HkcuFixtureRegistryDigest $baselineDigest) ` + (Get-HkcuFixtureBoundaryDiagnostic) + $Boundary.BaselineDigest = $baselineDigest + $Boundary.BackupLeaf = [Guid]::NewGuid().ToString('D') + $Boundary.BackupPath = Join-Path ` + ([string]$Boundary.ParentKey) ([string]$Boundary.BackupLeaf) + Assert-True (Test-Path -LiteralPath $Boundary.ParentKey) ` + (Get-HkcuFixtureBoundaryDiagnostic) + Assert-True (!(Test-Path -LiteralPath $Boundary.BackupPath)) ` + (Get-HkcuFixtureBoundaryDiagnostic) + Rename-Item -LiteralPath $Boundary.DesktopKey ` + -NewName ([string]$Boundary.BackupLeaf) -ErrorAction Stop + $Boundary.Relocated = $true + Assert-True (!(Test-Path -LiteralPath $Boundary.DesktopKey)) ` + (Get-HkcuFixtureBoundaryDiagnostic) + $Boundary.OriginalAbsentProven = $true + } +} + +function Restore-HkcuDesktopFixtureBoundary( + $Boundary, + [bool]$TargetOwnedByFixture +) { + if ($null -eq $Boundary) { return } + Invoke-SupervisorAttributedOperation ` + -Scenario 'HKCU_BASELINE_RESTORE' ` + -Phase 'FIXTURE_SETUP' ` + -Callsite 'HKCU_BASELINE_STATE' ` + -Field 'REGISTRY_PATH' ` + -Action { + if (!$Boundary.BaselinePresent) { + if (Test-Path -LiteralPath $Boundary.DesktopKey) { + Remove-Item -LiteralPath $Boundary.DesktopKey -Recurse -Force ` + -ErrorAction Stop + } + return + } + if (!$Boundary.Relocated) { return } + Assert-True (Test-HkcuFixtureRegistryDigest ([string]$Boundary.BaselineDigest)) ` + (Get-HkcuFixtureBoundaryDiagnostic) + Assert-True (Test-Path -LiteralPath $Boundary.BackupPath) ` + (Get-HkcuFixtureBoundaryDiagnostic) + $backupDigest = + Get-SupervisorFixtureRegistryDigest ([string]$Boundary.BackupPath) + Assert-True ($backupDigest -ceq [string]$Boundary.BaselineDigest) ` + (Get-HkcuFixtureBoundaryDiagnostic) + if (Test-Path -LiteralPath $Boundary.DesktopKey) { + Assert-True $TargetOwnedByFixture (Get-HkcuFixtureBoundaryDiagnostic) + Remove-Item -LiteralPath $Boundary.DesktopKey -Recurse -Force ` + -ErrorAction Stop + } + Assert-True (!(Test-Path -LiteralPath $Boundary.DesktopKey)) ` + (Get-HkcuFixtureBoundaryDiagnostic) + Rename-Item -LiteralPath $Boundary.BackupPath ` + -NewName ([string]$Boundary.DesktopLeaf) -ErrorAction Stop + $restoredDigest = + Get-SupervisorFixtureRegistryDigest ([string]$Boundary.DesktopKey) + if ($restoredDigest -cne [string]$Boundary.BaselineDigest) { + if ((Test-Path -LiteralPath $Boundary.DesktopKey) -and + !(Test-Path -LiteralPath $Boundary.BackupPath)) { + Rename-Item -LiteralPath $Boundary.DesktopKey ` + -NewName ([string]$Boundary.BackupLeaf) -ErrorAction SilentlyContinue + } + } + Assert-True ($restoredDigest -ceq [string]$Boundary.BaselineDigest) ` + (Get-HkcuFixtureBoundaryDiagnostic) + Assert-True (!(Test-Path -LiteralPath $Boundary.BackupPath)) ` + (Get-HkcuFixtureBoundaryDiagnostic) + } +} + function Get-OwnedResourcePreservationSnapshot($Owned) { $user = Get-LocalUser -Name $Owned.UserName -ErrorAction SilentlyContinue $profileMatches = try { @@ -5488,20 +5667,164 @@ function Test-PreExistingAppPathsAuthority { [Console]::Out.Flush() } -function Test-HkcuInstalledValueOwnership { - $desktopKey = 'Registry::HKEY_CURRENT_USER\Software\ProPR\Desktop' - $installedName = 'installed' - $sentinelInstalled = 'pre-existing-installed' - $sentinelUnrelated = 'preserve-unrelated' +function Set-HkcuFixtureBoundaryValueKinds([string]$Path) { + [void](New-Item -Path $Path -Force -ErrorAction Stop) + $key = Get-Item -LiteralPath $Path -ErrorAction Stop + $key.SetValue('', 'default-string', [Microsoft.Win32.RegistryValueKind]::String) + $key.SetValue('StringValue', 'plain-string', [Microsoft.Win32.RegistryValueKind]::String) + $key.SetValue( + 'ExpandStringValue', + '%TEMP%\propr-fixture', + [Microsoft.Win32.RegistryValueKind]::ExpandString + ) + $key.SetValue( + 'BinaryValue', + [byte[]]@(0, 1, 2, 127, 128, 255), + [Microsoft.Win32.RegistryValueKind]::Binary + ) + $key.SetValue('DWordValue', [int]305419896, [Microsoft.Win32.RegistryValueKind]::DWord) + $key.SetValue( + 'QWordValue', + [long]1311768467463790320, + [Microsoft.Win32.RegistryValueKind]::QWord + ) + $key.SetValue( + 'MultiStringValue', + [string[]]@('alpha', '', 'omega'), + [Microsoft.Win32.RegistryValueKind]::MultiString + ) + $key.SetValue( + 'NoneValue', + [byte[]]@(9, 8, 7), + [Microsoft.Win32.RegistryValueKind]::None + ) + $nested = Join-Path $Path 'Nested' + $child = Join-Path $nested 'Child' + [void](New-Item -Path $child -Force -ErrorAction Stop) + $childKey = Get-Item -LiteralPath $child -ErrorAction Stop + $childKey.SetValue('NestedValue', 'nested-string', [Microsoft.Win32.RegistryValueKind]::String) +} + +function Assert-HkcuFixtureBoundaryValueKinds([string]$Path) { + $key = Get-Item -LiteralPath $Path -ErrorAction Stop + Assert-True ($key.GetValueKind('').ToString() -ceq 'String' -and + [string]$key.GetValue('') -ceq 'default-string') ` + (Get-HkcuFixtureBoundaryDiagnostic) + Assert-True ($key.GetValueKind('StringValue').ToString() -ceq 'String' -and + [string]$key.GetValue('StringValue') -ceq 'plain-string') ` + (Get-HkcuFixtureBoundaryDiagnostic) + Assert-True ($key.GetValueKind('ExpandStringValue').ToString() -ceq 'ExpandString' -and + [string]$key.GetValue( + 'ExpandStringValue', + $null, + [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames + ) -ceq '%TEMP%\propr-fixture') (Get-HkcuFixtureBoundaryDiagnostic) + $binary = [byte[]]$key.GetValue('BinaryValue') + Assert-True ($key.GetValueKind('BinaryValue').ToString() -ceq 'Binary' -and + $binary.Length -eq 6 -and $binary[0] -eq 0 -and $binary[3] -eq 127 -and + $binary[4] -eq 128 -and $binary[5] -eq 255) ` + (Get-HkcuFixtureBoundaryDiagnostic) + Assert-True ($key.GetValueKind('DWordValue').ToString() -ceq 'DWord' -and + [int]$key.GetValue('DWordValue') -eq 305419896) ` + (Get-HkcuFixtureBoundaryDiagnostic) + Assert-True ($key.GetValueKind('QWordValue').ToString() -ceq 'QWord' -and + [long]$key.GetValue('QWordValue') -eq 1311768467463790320) ` + (Get-HkcuFixtureBoundaryDiagnostic) + $multi = [string[]]$key.GetValue('MultiStringValue') + Assert-True ($key.GetValueKind('MultiStringValue').ToString() -ceq 'MultiString' -and + $multi.Length -eq 3 -and $multi[0] -ceq 'alpha' -and + $multi[1] -ceq '' -and $multi[2] -ceq 'omega') ` + (Get-HkcuFixtureBoundaryDiagnostic) + $none = Get-SupervisorFixtureRegistryValueNativeBytes $key 'NoneValue' + Assert-True ($key.GetValueKind('NoneValue').ToString() -ceq 'None' -and + $none.Type -eq 0 -and $none.Bytes.Length -eq 3 -and + $none.Bytes[0] -eq 9 -and $none.Bytes[2] -eq 7) ` + (Get-HkcuFixtureBoundaryDiagnostic) + $child = Join-Path (Join-Path $Path 'Nested') 'Child' + $childKey = Get-Item -LiteralPath $child -ErrorAction Stop + Assert-True ([string]$childKey.GetValue('NestedValue') -ceq 'nested-string') ` + (Get-HkcuFixtureBoundaryDiagnostic) +} + +function Test-HkcuDesktopFixtureBoundaryRegression { + $root = 'Registry::HKEY_CURRENT_USER\Software\ProPRSupervisorFixture' + $parent = Join-Path $root ([Guid]::NewGuid().ToString('N')) + $presentDesktop = Join-Path $parent 'Desktop' + $absentParent = Join-Path $root ([Guid]::NewGuid().ToString('N')) + $absentDesktop = Join-Path $absentParent 'Desktop' + $failureParent = Join-Path $root ([Guid]::NewGuid().ToString('N')) + $failureDesktop = Join-Path $failureParent 'Desktop' Invoke-SupervisorAttributedOperation ` -Scenario 'HKCU_BASELINE_RESTORE' ` -Phase 'FIXTURE_SETUP' ` -Callsite 'HKCU_BASELINE_STATE' ` -Field 'REGISTRY_PATH' ` -Action { - Assert-True (!(Test-Path -LiteralPath $desktopKey)) ` - 'HKCU installed-value fixture baseline was not clean' + try { + Set-HkcuFixtureBoundaryValueKinds $presentDesktop + $presentDigest = Get-SupervisorFixtureRegistryDigest $presentDesktop + Assert-True (Test-HkcuFixtureRegistryDigest $presentDigest) ` + (Get-HkcuFixtureBoundaryDiagnostic) + $presentBoundary = New-HkcuDesktopFixtureBoundaryState $presentDesktop + Initialize-HkcuDesktopFixtureBoundary $presentBoundary + Assert-True ($presentBoundary.BaselinePresent -and + $presentBoundary.Relocated -and + !(Test-Path -LiteralPath $presentDesktop) -and + (Test-Path -LiteralPath $presentBoundary.BackupPath) -and + (Get-SupervisorFixtureRegistryDigest $presentBoundary.BackupPath) -ceq + $presentDigest) (Get-HkcuFixtureBoundaryDiagnostic) + [void](New-Item -Path $presentDesktop -Force -ErrorAction Stop) + $presentFixtureOwned = $true + (Get-Item -LiteralPath $presentDesktop).SetValue( + 'installed', [int]1, [Microsoft.Win32.RegistryValueKind]::DWord) + Restore-HkcuDesktopFixtureBoundary $presentBoundary $presentFixtureOwned + Assert-True ((Get-SupervisorFixtureRegistryDigest $presentDesktop) -ceq + $presentDigest) (Get-HkcuFixtureBoundaryDiagnostic) + Assert-True (!(Test-Path -LiteralPath $presentBoundary.BackupPath)) ` + (Get-HkcuFixtureBoundaryDiagnostic) + Assert-HkcuFixtureBoundaryValueKinds $presentDesktop + + [void](New-Item -Path $absentParent -Force -ErrorAction Stop) + $absentBoundary = New-HkcuDesktopFixtureBoundaryState $absentDesktop + Initialize-HkcuDesktopFixtureBoundary $absentBoundary + Assert-True (!$absentBoundary.BaselinePresent -and + [string]::IsNullOrWhiteSpace([string]$absentBoundary.BackupPath)) ` + (Get-HkcuFixtureBoundaryDiagnostic) + [void](New-Item -Path $absentDesktop -Force -ErrorAction Stop) + Restore-HkcuDesktopFixtureBoundary $absentBoundary $true + Assert-True (!(Test-Path -LiteralPath $absentDesktop)) ` + (Get-HkcuFixtureBoundaryDiagnostic) + + Set-HkcuFixtureBoundaryValueKinds $failureDesktop + $failureBoundary = New-HkcuDesktopFixtureBoundaryState $failureDesktop + Initialize-HkcuDesktopFixtureBoundary $failureBoundary + [void](New-Item -Path $failureDesktop -Force -ErrorAction Stop) + $expectedFailure = Get-HkcuFixtureBoundaryDiagnostic + try { + Restore-HkcuDesktopFixtureBoundary $failureBoundary $false + Assert-True $false (Get-HkcuFixtureBoundaryDiagnostic) + } catch { + Assert-True ([string]$_.Exception.Message -ceq $expectedFailure) ` + (Get-HkcuFixtureBoundaryDiagnostic) + } + Assert-True ((Test-Path -LiteralPath $failureBoundary.BackupPath) -and + (Test-Path -LiteralPath $failureDesktop)) ` + (Get-HkcuFixtureBoundaryDiagnostic) + } finally { + foreach ($path in @($parent, $absentParent, $failureParent)) { + if (Test-Path -LiteralPath $path) { + Remove-Item -LiteralPath $path -Recurse -Force -ErrorAction SilentlyContinue + } + } + } } +} + +function Test-HkcuInstalledValueOwnership { + $desktopKey = 'Registry::HKEY_CURRENT_USER\Software\ProPR\Desktop' + $installedName = 'installed' + $sentinelInstalled = 'pre-existing-installed' + $sentinelUnrelated = 'preserve-unrelated' function New-HkcuManifest( [bool]$BaselineKeyExisted, @@ -5559,7 +5882,11 @@ function Test-HkcuInstalledValueOwnership { return [PSCustomObject]@{ RunId = $runId; Path = $path } } + Test-HkcuDesktopFixtureBoundaryRegression + $hkcuBoundary = New-HkcuDesktopFixtureBoundaryState $desktopKey + $desktopKeyFixtureOwned = $false try { + Initialize-HkcuDesktopFixtureBoundary $hkcuBoundary Set-SupervisorInvocationContext ` 'HKCU_INSTALLED_VALUE_OWNERSHIP' ` 'HKCU_BASELINE_RESTORE' ` @@ -5567,6 +5894,7 @@ function Test-HkcuInstalledValueOwnership { 'HKCU_BASELINE_STATE' ` 'REGISTRY_PATH' [void](New-Item -Path $desktopKey -Force -ErrorAction Stop) + $desktopKeyFixtureOwned = $true (Get-Item -LiteralPath $desktopKey).SetValue( $installedName, $sentinelInstalled, [Microsoft.Win32.RegistryValueKind]::String) (Get-Item -LiteralPath $desktopKey).SetValue( @@ -5616,7 +5944,9 @@ function Test-HkcuInstalledValueOwnership { 'HKCU_BASELINE_STATE' ` 'REGISTRY_PATH' Remove-Item -LiteralPath $desktopKey -Recurse -Force -ErrorAction Stop + $desktopKeyFixtureOwned = $false [void](New-Item -Path $desktopKey -Force -ErrorAction Stop) + $desktopKeyFixtureOwned = $true (Get-Item -LiteralPath $desktopKey).SetValue( $installedName, [int]1, [Microsoft.Win32.RegistryValueKind]::DWord) (Get-Item -LiteralPath $desktopKey).SetValue( @@ -5638,7 +5968,9 @@ function Test-HkcuInstalledValueOwnership { 'HKCU_BASELINE_STATE' ` 'REGISTRY_PATH' Remove-Item -LiteralPath $desktopKey -Recurse -Force -ErrorAction Stop + $desktopKeyFixtureOwned = $false [void](New-Item -Path $desktopKey -Force -ErrorAction Stop) + $desktopKeyFixtureOwned = $true (Get-Item -LiteralPath $desktopKey).SetValue( $installedName, [int]1, [Microsoft.Win32.RegistryValueKind]::DWord) $emptyManifest = New-HkcuManifest $false $false $null $null $true @@ -5646,6 +5978,7 @@ function Test-HkcuInstalledValueOwnership { 'HKCU_EMPTY' $emptyManifest.Path $emptyManifest.RunId '' Assert-True ($empty.ExitCode -eq 0 -and !(Test-Path -LiteralPath $desktopKey)) ` 'run-created empty HKCU key was not removed' + $desktopKeyFixtureOwned = $false Set-SupervisorInvocationContext ` 'HKCU_INSTALLED_VALUE_OWNERSHIP' ` @@ -5654,6 +5987,7 @@ function Test-HkcuInstalledValueOwnership { 'HKCU_BASELINE_STATE' ` 'REGISTRY_PATH' [void](New-Item -Path $desktopKey -Force -ErrorAction Stop) + $desktopKeyFixtureOwned = $true (Get-Item -LiteralPath $desktopKey).SetValue( $installedName, 'foreign-conflict', [Microsoft.Win32.RegistryValueKind]::String) $conflictManifest = New-HkcuManifest $false $false $null $null $true @@ -5677,7 +6011,9 @@ function Test-HkcuInstalledValueOwnership { 'HKCU_BASELINE_STATE' ` 'REGISTRY_PATH' Remove-Item -LiteralPath $desktopKey -Recurse -Force -ErrorAction Stop + $desktopKeyFixtureOwned = $false [void](New-Item -Path $desktopKey -Force -ErrorAction Stop) + $desktopKeyFixtureOwned = $true (Get-Item -LiteralPath $desktopKey).SetValue( $installedName, [int]1, [Microsoft.Win32.RegistryValueKind]::DWord) $provisionalManifest = New-HkcuManifest $false $false $null $null $true $true @@ -5694,9 +6030,7 @@ function Test-HkcuInstalledValueOwnership { 'provisional HKCU failure discarded authenticated recovery authority' Remove-Item -LiteralPath $provisionalManifest.Path -Force -ErrorAction Stop } finally { - if (Test-Path -LiteralPath $desktopKey) { - Remove-Item -LiteralPath $desktopKey -Recurse -Force -ErrorAction SilentlyContinue - } + Restore-HkcuDesktopFixtureBoundary $hkcuBoundary $desktopKeyFixtureOwned } Write-Host 'PROPR_WINDOWS_SUPERVISOR_OWNERSHIP:HKCU_INSTALLED_VALUE:PRESERVED' [Console]::Out.Flush() diff --git a/apps/desktop/src/release-workflow.test.ts b/apps/desktop/src/release-workflow.test.ts index 9afa2ced8..c751aa91f 100644 --- a/apps/desktop/src/release-workflow.test.ts +++ b/apps/desktop/src/release-workflow.test.ts @@ -902,6 +902,14 @@ describe('desktop trusted release workflow', () => { installedWindowsAppSupervisorBehaviorTest, /PROPR_WINDOWS_SUPERVISOR_OWNERSHIP:PRE_EXISTING_AUTHORITIES:PRESERVED/, ); + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /function Initialize-HkcuDesktopFixtureBoundary[\s\S]*Get-SupervisorFixtureRegistryDigest \(\[string\]\$Boundary\.DesktopKey\)[\s\S]*\[Guid\]::NewGuid\(\)\.ToString\('D'\)[\s\S]*Assert-True \(!\(Test-Path -LiteralPath \$Boundary\.BackupPath\)\)[\s\S]*Rename-Item -LiteralPath \$Boundary\.DesktopKey[\s\S]*Assert-True \(!\(Test-Path -LiteralPath \$Boundary\.DesktopKey\)\)/, + ); + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /function Restore-HkcuDesktopFixtureBoundary[\s\S]*Get-SupervisorFixtureRegistryDigest \(\[string\]\$Boundary\.BackupPath\)[\s\S]*Assert-True \$TargetOwnedByFixture[\s\S]*Remove-Item -LiteralPath \$Boundary\.DesktopKey[\s\S]*Assert-True \(!\(Test-Path -LiteralPath \$Boundary\.DesktopKey\)\)[\s\S]*Rename-Item -LiteralPath \$Boundary\.BackupPath[\s\S]*Assert-True \(\$restoredDigest -ceq \[string\]\$Boundary\.BaselineDigest\)/, + ); assert.doesNotMatch( installedWindowsAppSupervisorBehaviorTest, /CreateProfile|DeleteProfile|userenv\.dll/, @@ -1024,7 +1032,31 @@ describe('desktop trusted release workflow', () => { ); assert.match( hkcuInstalledValueOwnership, - /Invoke-SupervisorAttributedOperation\s+`\n\s+-Scenario 'HKCU_BASELINE_RESTORE'\s+`\n\s+-Phase 'FIXTURE_SETUP'\s+`\n\s+-Callsite 'HKCU_BASELINE_STATE'\s+`\n\s+-Field 'REGISTRY_PATH'\s+`\n\s+-Action \{\n\s+Assert-True \(!\(Test-Path -LiteralPath \$desktopKey\)\)/, + /Test-HkcuDesktopFixtureBoundaryRegression[\s\S]*\$hkcuBoundary = New-HkcuDesktopFixtureBoundaryState \$desktopKey[\s\S]*\$desktopKeyFixtureOwned = \$false[\s\S]*try \{[\s\S]*Initialize-HkcuDesktopFixtureBoundary \$hkcuBoundary[\s\S]*\} finally \{[\s\S]*Restore-HkcuDesktopFixtureBoundary \$hkcuBoundary \$desktopKeyFixtureOwned/, + ); + assert.doesNotMatch( + hkcuInstalledValueOwnership, + /HKCU installed-value fixture baseline was not clean/, + ); + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /function Test-HkcuDesktopFixtureBoundaryRegression[\s\S]*Set-HkcuFixtureBoundaryValueKinds \$presentDesktop[\s\S]*\(Get-SupervisorFixtureRegistryDigest \$presentBoundary\.BackupPath\) -ceq[\s\S]*Assert-HkcuFixtureBoundaryValueKinds \$presentDesktop[\s\S]*!\$absentBoundary\.BaselinePresent[\s\S]*\[string\]::IsNullOrWhiteSpace\(\[string\]\$absentBoundary\.BackupPath\)[\s\S]*Restore-HkcuDesktopFixtureBoundary \$failureBoundary \$false[\s\S]*Test-Path -LiteralPath \$failureBoundary\.BackupPath/, + ); + for (const valueKind of [ + 'StringValue', + 'ExpandStringValue', + 'BinaryValue', + 'DWordValue', + 'QWordValue', + 'MultiStringValue', + 'NoneValue', + 'NestedValue', + ]) { + assert.match(installedWindowsAppSupervisorBehaviorTest, new RegExp(valueKind)); + } + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /Get-HkcuFixtureBoundaryDiagnostic[\s\S]*HKCU_INSTALLED_VALUE_OWNERSHIP[\s\S]*HKCU_BASELINE_RESTORE[\s\S]*FIXTURE_SETUP[\s\S]*HKCU_BASELINE_STATE[\s\S]*REGISTRY_PATH/, ); for (const scenario of [ 'HKCU_BASELINE_RESTORE', From e0eee3f94b2362da907335c6aad94f33a0f60179 Mon Sep 17 00:00:00 2001 From: "propr-dev[bot]" <1316198+propr-dev[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 16:29:17 +0000 Subject: [PATCH 24/33] feat(ai): Applied only the requested `package-lock.json` qs metadata update: Applied only the requested `package-lock.json` qs metadata update: - `qs` `6.15.3` -> `6.16.0` - canonical registry tarball URL updated - matching npm integrity updated - no manifests, source, workflows, overrides, allowlists, audit settings, or unrelated lock entries changed Validation completed: - `git diff --name-only`: only `package-lock.json` - `git diff --numstat`: `3 3 package-lock.json` - `git diff --check`: passed - saved diff comparison after install/regeneration: unchanged - `npm audit --package-lock-only --omit=dev --audit-level=low`: `found 0 vulnerabilities` - `npm ci`: passed; lock diff unchanged - `npm install --package-lock-only --ignore-scripts`: passed; lock diff unchanged - `npm run audit:runtime`: passed - `npm run desktop:audit:packaging`: passed - `npm run test:prepare`: passed - JS syntax/type gates checked with `node --check` and `tsc --noEmit`: passed I also ran `npm run test:full:prepared`. It completed the matrix except for `test/llmMetrics.test.ts`, which timed out after 180s because this environment has no Redis available. The final UI/Vitest block passed: `70` files, `501` tests. I could not run the native Windows `win32-x64` / `win32-ARM64` HKCU supervisor matrix here because the host is Linux x86_64 and `pwsh` is not installed. I did not commit, create a PR, or merge to main. PR: #2057 Comment by: @integry (ID: 5512616689) Model: gpt-5.5 --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index cc0fb8e58..3d584b0d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12013,9 +12013,9 @@ } }, "node_modules/qs": { - "version": "6.15.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", - "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", "license": "BSD-3-Clause", "dependencies": { "es-define-property": "^1.0.1", From 8a4b0d3b1e59c1e9ebbb64f5f968efa2e7ab86fc Mon Sep 17 00:00:00 2001 From: "propr-dev[bot]" <1316198+propr-dev[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 16:41:25 +0000 Subject: [PATCH 25/33] feat(ai): Implemented the follow-up changes without committing. Implemented the follow-up changes without committing. Changed: - [test-installed-windows-app-supervisor.ps1](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T16-29-34/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1:1469) - Added fixed HKCU fixture operation attribution. - Made initially absent Desktop restore fail closed before removal unless fixture-owned. - Replaced silent digest-mismatch recovery with terminating recovery relocation and recoverable-original verification. - Added absent-baseline foreign-target survival coverage and forced recovery-rename-failure coverage. - [release-workflow.test.ts](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T16-29-34/apps/desktop/src/release-workflow.test.ts:637) - Updated static assertions for the new attribution and fixture safety contract. Notes: - `package-lock.json` already contains the requested `qs` `6.16.0` canonical tarball/integrity from the prior qs-only head, so I preserved it unchanged. - I did not modify `cleanup-installed-windows-app.ps1` or production cleanup behavior. Validation: - `npm ci --ignore-scripts` passed. - `npm install --package-lock-only --ignore-scripts` passed with no lockfile diff. - `npm --workspace @propr/desktop test -- src/release-workflow.test.ts` passed: 177 passed, 6 skipped. - `git diff --check` passed. - `npm run audit:runtime` still fails, but now on unrelated `fast-uri` advisories, not `qs`; I left that untouched due the explicit scope exclusions. - Could not run native Windows x64/ARM64 jobs locally because this container is Linux and has no `pwsh`. Sources checked: GitHub CI x64/ARM64 pages and PR comment `5512779552` via GitHub API. PR: #2057 Comment by: @propr-dev[bot] (ID: 5512698201) Comment by: @propr-dev[bot] (ID: 5512711878) Comment by: @integry (ID: 5512819313) Model: gpt-5.5 --- .../test-installed-windows-app-supervisor.ps1 | 400 ++++++++++++++---- apps/desktop/src/release-workflow.test.ts | 41 +- 2 files changed, 353 insertions(+), 88 deletions(-) diff --git a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 index a8b6f48f8..fe2a822f1 100644 --- a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 +++ b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 @@ -1466,8 +1466,12 @@ function Get-SupervisorFixtureRegistryDigest([string]$Path) { $records.Add(('K|{0}' -f [Convert]::ToBase64String( [Text.Encoding]::UTF8.GetBytes([string]$entry.Relative)))) foreach ($valueName in @($entry.Key.GetValueNames() | Sort-Object -CaseSensitive)) { - $nativeValue = - Get-SupervisorFixtureRegistryValueNativeBytes $entry.Key ([string]$valueName) + $nativeValue = Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'NATIVE_VALUE_READ' ` + -Field 'REGISTRY_VALUE' ` + -Action { + Get-SupervisorFixtureRegistryValueNativeBytes $entry.Key ([string]$valueName) + } $records.Add(('V|{0}|{1}|{2}' -f [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes([string]$valueName)), ('{0}:{1}' -f $entry.Key.GetValueKind($valueName).ToString(), $nativeValue.Type), @@ -1483,6 +1487,8 @@ function Get-SupervisorFixtureRegistryDigest([string]$Path) { } return Get-SupervisorFixtureStringDigest ($records.ToArray() -join "`n") } catch { + $diagnosticMessage = [string]$_.Exception.Message + if (Test-SupervisorInvocationDiagnosticExact $diagnosticMessage) { throw } return 'INVALID' } } @@ -1510,20 +1516,23 @@ function Get-HkcuFixtureBoundaryDiagnostic { 'REGISTRY_PATH' } +function Assert-HkcuDesktopFixtureOperation([bool]$Condition) { + if (!$Condition) { throw 'hkcu desktop fixture operation failed' } +} + function Split-HkcuFixtureRegistryPath([string]$Path) { $normalizedPath = ([string]$Path).TrimEnd('\') - Assert-True ( + Assert-HkcuDesktopFixtureOperation ( $normalizedPath.StartsWith( 'Registry::HKEY_CURRENT_USER\', [StringComparison]::OrdinalIgnoreCase ) - ) (Get-HkcuFixtureBoundaryDiagnostic) + ) $separatorIndex = $normalizedPath.LastIndexOf('\') - Assert-True ($separatorIndex -gt 'Registry::HKEY_CURRENT_USER'.Length) ` - (Get-HkcuFixtureBoundaryDiagnostic) + Assert-HkcuDesktopFixtureOperation ` + ($separatorIndex -gt 'Registry::HKEY_CURRENT_USER'.Length) $leaf = $normalizedPath.Substring($separatorIndex + 1) - Assert-True (![string]::IsNullOrWhiteSpace($leaf)) ` - (Get-HkcuFixtureBoundaryDiagnostic) + Assert-HkcuDesktopFixtureOperation (![string]::IsNullOrWhiteSpace($leaf)) return [PSCustomObject]@{ Parent = $normalizedPath.Substring(0, $separatorIndex) Leaf = $leaf @@ -1541,14 +1550,27 @@ function New-HkcuDesktopFixtureBoundaryState([string]$DesktopKey) { BaselineDigest = $null Relocated = $false OriginalAbsentProven = $false + ForcePostRestoreDigestMismatch = $false + ForceRecoveryRenameFailure = $false } } -function Initialize-HkcuDesktopFixtureBoundary($Boundary) { +function Invoke-HkcuDesktopFixtureOperation( + [string]$Callsite, + [string]$Field, + [scriptblock]$Action +) { Invoke-SupervisorAttributedOperation ` -Scenario 'HKCU_BASELINE_RESTORE' ` -Phase 'FIXTURE_SETUP' ` - -Callsite 'HKCU_BASELINE_STATE' ` + -Callsite $Callsite ` + -Field $Field ` + -Action $Action +} + +function Initialize-HkcuDesktopFixtureBoundary($Boundary) { + Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'BASELINE_RELOCATE' ` -Field 'REGISTRY_PATH' ` -Action { $parts = Split-HkcuFixtureRegistryPath ([string]$Boundary.DesktopKey) @@ -1559,23 +1581,23 @@ function Initialize-HkcuDesktopFixtureBoundary($Boundary) { return } $Boundary.BaselinePresent = $true - $baselineDigest = - Get-SupervisorFixtureRegistryDigest ([string]$Boundary.DesktopKey) - Assert-True (Test-HkcuFixtureRegistryDigest $baselineDigest) ` - (Get-HkcuFixtureBoundaryDiagnostic) + $baselineDigest = Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'BASELINE_DIGEST' ` + -Field 'REGISTRY_ROOT' ` + -Action { + Get-SupervisorFixtureRegistryDigest ([string]$Boundary.DesktopKey) + } + Assert-HkcuDesktopFixtureOperation (Test-HkcuFixtureRegistryDigest $baselineDigest) $Boundary.BaselineDigest = $baselineDigest $Boundary.BackupLeaf = [Guid]::NewGuid().ToString('D') $Boundary.BackupPath = Join-Path ` ([string]$Boundary.ParentKey) ([string]$Boundary.BackupLeaf) - Assert-True (Test-Path -LiteralPath $Boundary.ParentKey) ` - (Get-HkcuFixtureBoundaryDiagnostic) - Assert-True (!(Test-Path -LiteralPath $Boundary.BackupPath)) ` - (Get-HkcuFixtureBoundaryDiagnostic) + Assert-HkcuDesktopFixtureOperation (Test-Path -LiteralPath $Boundary.ParentKey) + Assert-HkcuDesktopFixtureOperation (!(Test-Path -LiteralPath $Boundary.BackupPath)) Rename-Item -LiteralPath $Boundary.DesktopKey ` -NewName ([string]$Boundary.BackupLeaf) -ErrorAction Stop $Boundary.Relocated = $true - Assert-True (!(Test-Path -LiteralPath $Boundary.DesktopKey)) ` - (Get-HkcuFixtureBoundaryDiagnostic) + Assert-HkcuDesktopFixtureOperation (!(Test-Path -LiteralPath $Boundary.DesktopKey)) $Boundary.OriginalAbsentProven = $true } } @@ -1585,50 +1607,102 @@ function Restore-HkcuDesktopFixtureBoundary( [bool]$TargetOwnedByFixture ) { if ($null -eq $Boundary) { return } - Invoke-SupervisorAttributedOperation ` - -Scenario 'HKCU_BASELINE_RESTORE' ` - -Phase 'FIXTURE_SETUP' ` - -Callsite 'HKCU_BASELINE_STATE' ` + Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'BASELINE_RESTORE' ` -Field 'REGISTRY_PATH' ` -Action { if (!$Boundary.BaselinePresent) { if (Test-Path -LiteralPath $Boundary.DesktopKey) { + Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'TARGET_OWNERSHIP' ` + -Field 'REGISTRY_PATH' ` + -Action { + Assert-HkcuDesktopFixtureOperation $TargetOwnedByFixture + } Remove-Item -LiteralPath $Boundary.DesktopKey -Recurse -Force ` -ErrorAction Stop } return } if (!$Boundary.Relocated) { return } - Assert-True (Test-HkcuFixtureRegistryDigest ([string]$Boundary.BaselineDigest)) ` - (Get-HkcuFixtureBoundaryDiagnostic) - Assert-True (Test-Path -LiteralPath $Boundary.BackupPath) ` - (Get-HkcuFixtureBoundaryDiagnostic) - $backupDigest = - Get-SupervisorFixtureRegistryDigest ([string]$Boundary.BackupPath) - Assert-True ($backupDigest -ceq [string]$Boundary.BaselineDigest) ` - (Get-HkcuFixtureBoundaryDiagnostic) + Assert-HkcuDesktopFixtureOperation ` + (Test-HkcuFixtureRegistryDigest ([string]$Boundary.BaselineDigest)) + Assert-HkcuDesktopFixtureOperation (Test-Path -LiteralPath $Boundary.BackupPath) + $backupDigest = Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'BASELINE_DIGEST' ` + -Field 'REGISTRY_ROOT' ` + -Action { + Get-SupervisorFixtureRegistryDigest ([string]$Boundary.BackupPath) + } + Assert-HkcuDesktopFixtureOperation ($backupDigest -ceq [string]$Boundary.BaselineDigest) if (Test-Path -LiteralPath $Boundary.DesktopKey) { - Assert-True $TargetOwnedByFixture (Get-HkcuFixtureBoundaryDiagnostic) + Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'TARGET_OWNERSHIP' ` + -Field 'REGISTRY_PATH' ` + -Action { + Assert-HkcuDesktopFixtureOperation $TargetOwnedByFixture + } Remove-Item -LiteralPath $Boundary.DesktopKey -Recurse -Force ` -ErrorAction Stop } - Assert-True (!(Test-Path -LiteralPath $Boundary.DesktopKey)) ` - (Get-HkcuFixtureBoundaryDiagnostic) + Assert-HkcuDesktopFixtureOperation (!(Test-Path -LiteralPath $Boundary.DesktopKey)) Rename-Item -LiteralPath $Boundary.BackupPath ` -NewName ([string]$Boundary.DesktopLeaf) -ErrorAction Stop - $restoredDigest = - Get-SupervisorFixtureRegistryDigest ([string]$Boundary.DesktopKey) - if ($restoredDigest -cne [string]$Boundary.BaselineDigest) { - if ((Test-Path -LiteralPath $Boundary.DesktopKey) -and - !(Test-Path -LiteralPath $Boundary.BackupPath)) { - Rename-Item -LiteralPath $Boundary.DesktopKey ` - -NewName ([string]$Boundary.BackupLeaf) -ErrorAction SilentlyContinue + $restoredDigest = Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'BASELINE_DIGEST' ` + -Field 'REGISTRY_ROOT' ` + -Action { + Get-SupervisorFixtureRegistryDigest ([string]$Boundary.DesktopKey) } + if ($Boundary.ForcePostRestoreDigestMismatch) { + $restoredDigest = 'INVALID' } - Assert-True ($restoredDigest -ceq [string]$Boundary.BaselineDigest) ` - (Get-HkcuFixtureBoundaryDiagnostic) - Assert-True (!(Test-Path -LiteralPath $Boundary.BackupPath)) ` - (Get-HkcuFixtureBoundaryDiagnostic) + if ($restoredDigest -cne [string]$Boundary.BaselineDigest) { + Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'RECOVERY_RELOCATE' ` + -Field 'REGISTRY_PATH' ` + -Action { + try { + if ($Boundary.ForceRecoveryRenameFailure -and + !(Test-Path -LiteralPath $Boundary.BackupPath)) { + [void](New-Item -Path $Boundary.BackupPath -Force -ErrorAction Stop) + } + if ((Test-Path -LiteralPath $Boundary.DesktopKey) -and + !(Test-Path -LiteralPath $Boundary.BackupPath)) { + Rename-Item -LiteralPath $Boundary.DesktopKey ` + -NewName ([string]$Boundary.BackupLeaf) -ErrorAction Stop + } + Assert-HkcuDesktopFixtureOperation (Test-Path -LiteralPath $Boundary.BackupPath) + Assert-HkcuDesktopFixtureOperation (!(Test-Path -LiteralPath $Boundary.DesktopKey)) + throw 'post-restore digest mismatch' + } catch { + $desktopDigest = if (Test-Path -LiteralPath $Boundary.DesktopKey) { + Get-SupervisorFixtureRegistryDigest ([string]$Boundary.DesktopKey) + } else { 'MISSING' } + $backupDigestAfterRecovery = if (Test-Path -LiteralPath $Boundary.BackupPath) { + Get-SupervisorFixtureRegistryDigest ([string]$Boundary.BackupPath) + } else { 'MISSING' } + Assert-HkcuDesktopFixtureOperation ( + $desktopDigest -ceq [string]$Boundary.BaselineDigest -or + $backupDigestAfterRecovery -ceq [string]$Boundary.BaselineDigest + ) + throw + } + } + } + Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'FINAL_BASELINE_DIGEST' ` + -Field 'REGISTRY_ROOT' ` + -Action { + Assert-HkcuDesktopFixtureOperation ` + ($restoredDigest -ceq [string]$Boundary.BaselineDigest) + } + Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'FINAL_BACKUP_ABSENCE' ` + -Field 'REGISTRY_PATH' ` + -Action { + Assert-HkcuDesktopFixtureOperation (!(Test-Path -LiteralPath $Boundary.BackupPath)) + } } } @@ -2188,6 +2262,15 @@ function Get-SupervisorInvocationCallsites { 'EARLY_PROCESS_STATE_PATH', 'EARLY_PROCESS_STATE_READ', 'HKCU_BASELINE_STATE', + 'REGRESSION_VALUE_SETUP', + 'NATIVE_VALUE_READ', + 'BASELINE_DIGEST', + 'BASELINE_RELOCATE', + 'TARGET_OWNERSHIP', + 'BASELINE_RESTORE', + 'RECOVERY_RELOCATE', + 'FINAL_BASELINE_DIGEST', + 'FINAL_BACKUP_ABSENCE', 'MANIFEST_PRESERVATION', 'RESOURCE_FIELD_VALIDATION', 'REPLACEMENT_SURVIVAL_READ', @@ -2397,6 +2480,15 @@ function Get-SupervisorAttributionTotalityCases { 'CALLSITE_EARLY_PROCESS_TREE_ASSERTION', 'CALLSITE_EARLY_MANIFEST_PRESERVATION', 'CALLSITE_HKCU_BASELINE_STATE', + 'CALLSITE_HKCU_REGRESSION_VALUE_SETUP', + 'CALLSITE_HKCU_NATIVE_VALUE_READ', + 'CALLSITE_HKCU_BASELINE_DIGEST', + 'CALLSITE_HKCU_BASELINE_RELOCATE', + 'CALLSITE_HKCU_TARGET_OWNERSHIP', + 'CALLSITE_HKCU_BASELINE_RESTORE', + 'CALLSITE_HKCU_RECOVERY_RELOCATE', + 'CALLSITE_HKCU_FINAL_BASELINE_DIGEST', + 'CALLSITE_HKCU_FINAL_BACKUP_ABSENCE', 'CALLSITE_REPLACEMENT_SURVIVAL_READ', 'FIELD_EXECUTABLE_BACKUP', 'FIELD_MANIFEST_PATH', @@ -3578,6 +3670,69 @@ function Test-SupervisorInvocationAttributionTotality { Phase='FIXTURE_SETUP' Callsite='HKCU_BASELINE_STATE'; Field='REGISTRY_PATH' }, + [PSCustomObject]@{ + CaseId='CALLSITE_HKCU_REGRESSION_VALUE_SETUP' + Test='HKCU_INSTALLED_VALUE_OWNERSHIP' + Scenario='HKCU_BASELINE_RESTORE' + Phase='FIXTURE_SETUP' + Callsite='REGRESSION_VALUE_SETUP'; Field='REGISTRY_VALUE' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_HKCU_NATIVE_VALUE_READ' + Test='HKCU_INSTALLED_VALUE_OWNERSHIP' + Scenario='HKCU_BASELINE_RESTORE' + Phase='FIXTURE_SETUP' + Callsite='NATIVE_VALUE_READ'; Field='REGISTRY_VALUE' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_HKCU_BASELINE_DIGEST' + Test='HKCU_INSTALLED_VALUE_OWNERSHIP' + Scenario='HKCU_BASELINE_RESTORE' + Phase='FIXTURE_SETUP' + Callsite='BASELINE_DIGEST'; Field='REGISTRY_ROOT' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_HKCU_BASELINE_RELOCATE' + Test='HKCU_INSTALLED_VALUE_OWNERSHIP' + Scenario='HKCU_BASELINE_RESTORE' + Phase='FIXTURE_SETUP' + Callsite='BASELINE_RELOCATE'; Field='REGISTRY_PATH' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_HKCU_TARGET_OWNERSHIP' + Test='HKCU_INSTALLED_VALUE_OWNERSHIP' + Scenario='HKCU_BASELINE_RESTORE' + Phase='FIXTURE_SETUP' + Callsite='TARGET_OWNERSHIP'; Field='REGISTRY_PATH' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_HKCU_BASELINE_RESTORE' + Test='HKCU_INSTALLED_VALUE_OWNERSHIP' + Scenario='HKCU_BASELINE_RESTORE' + Phase='FIXTURE_SETUP' + Callsite='BASELINE_RESTORE'; Field='REGISTRY_PATH' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_HKCU_RECOVERY_RELOCATE' + Test='HKCU_INSTALLED_VALUE_OWNERSHIP' + Scenario='HKCU_BASELINE_RESTORE' + Phase='FIXTURE_SETUP' + Callsite='RECOVERY_RELOCATE'; Field='REGISTRY_PATH' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_HKCU_FINAL_BASELINE_DIGEST' + Test='HKCU_INSTALLED_VALUE_OWNERSHIP' + Scenario='HKCU_BASELINE_RESTORE' + Phase='FIXTURE_SETUP' + Callsite='FINAL_BASELINE_DIGEST'; Field='REGISTRY_ROOT' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_HKCU_FINAL_BACKUP_ABSENCE' + Test='HKCU_INSTALLED_VALUE_OWNERSHIP' + Scenario='HKCU_BASELINE_RESTORE' + Phase='FIXTURE_SETUP' + Callsite='FINAL_BACKUP_ABSENCE'; Field='REGISTRY_PATH' + }, [PSCustomObject]@{ CaseId='CALLSITE_REPLACEMENT_SURVIVAL_READ' Test='PRE_EXISTING_CLEANUP_OWNERSHIP' @@ -5668,41 +5823,46 @@ function Test-PreExistingAppPathsAuthority { } function Set-HkcuFixtureBoundaryValueKinds([string]$Path) { - [void](New-Item -Path $Path -Force -ErrorAction Stop) - $key = Get-Item -LiteralPath $Path -ErrorAction Stop - $key.SetValue('', 'default-string', [Microsoft.Win32.RegistryValueKind]::String) - $key.SetValue('StringValue', 'plain-string', [Microsoft.Win32.RegistryValueKind]::String) - $key.SetValue( - 'ExpandStringValue', - '%TEMP%\propr-fixture', - [Microsoft.Win32.RegistryValueKind]::ExpandString - ) - $key.SetValue( - 'BinaryValue', - [byte[]]@(0, 1, 2, 127, 128, 255), - [Microsoft.Win32.RegistryValueKind]::Binary - ) - $key.SetValue('DWordValue', [int]305419896, [Microsoft.Win32.RegistryValueKind]::DWord) - $key.SetValue( - 'QWordValue', - [long]1311768467463790320, - [Microsoft.Win32.RegistryValueKind]::QWord - ) - $key.SetValue( - 'MultiStringValue', - [string[]]@('alpha', '', 'omega'), - [Microsoft.Win32.RegistryValueKind]::MultiString - ) - $key.SetValue( - 'NoneValue', - [byte[]]@(9, 8, 7), - [Microsoft.Win32.RegistryValueKind]::None - ) - $nested = Join-Path $Path 'Nested' - $child = Join-Path $nested 'Child' - [void](New-Item -Path $child -Force -ErrorAction Stop) - $childKey = Get-Item -LiteralPath $child -ErrorAction Stop - $childKey.SetValue('NestedValue', 'nested-string', [Microsoft.Win32.RegistryValueKind]::String) + Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'REGRESSION_VALUE_SETUP' ` + -Field 'REGISTRY_VALUE' ` + -Action { + [void](New-Item -Path $Path -Force -ErrorAction Stop) + $key = Get-Item -LiteralPath $Path -ErrorAction Stop + $key.SetValue('', 'default-string', [Microsoft.Win32.RegistryValueKind]::String) + $key.SetValue('StringValue', 'plain-string', [Microsoft.Win32.RegistryValueKind]::String) + $key.SetValue( + 'ExpandStringValue', + '%TEMP%\propr-fixture', + [Microsoft.Win32.RegistryValueKind]::ExpandString + ) + $key.SetValue( + 'BinaryValue', + [byte[]]@(0, 1, 2, 127, 128, 255), + [Microsoft.Win32.RegistryValueKind]::Binary + ) + $key.SetValue('DWordValue', [int]305419896, [Microsoft.Win32.RegistryValueKind]::DWord) + $key.SetValue( + 'QWordValue', + [long]1311768467463790320, + [Microsoft.Win32.RegistryValueKind]::QWord + ) + $key.SetValue( + 'MultiStringValue', + [string[]]@('alpha', '', 'omega'), + [Microsoft.Win32.RegistryValueKind]::MultiString + ) + $key.SetValue( + 'NoneValue', + [byte[]]@(9, 8, 7), + [Microsoft.Win32.RegistryValueKind]::None + ) + $nested = Join-Path $Path 'Nested' + $child = Join-Path $nested 'Child' + [void](New-Item -Path $child -Force -ErrorAction Stop) + $childKey = Get-Item -LiteralPath $child -ErrorAction Stop + $childKey.SetValue('NestedValue', 'nested-string', [Microsoft.Win32.RegistryValueKind]::String) + } } function Assert-HkcuFixtureBoundaryValueKinds([string]$Path) { @@ -5735,7 +5895,12 @@ function Assert-HkcuFixtureBoundaryValueKinds([string]$Path) { $multi.Length -eq 3 -and $multi[0] -ceq 'alpha' -and $multi[1] -ceq '' -and $multi[2] -ceq 'omega') ` (Get-HkcuFixtureBoundaryDiagnostic) - $none = Get-SupervisorFixtureRegistryValueNativeBytes $key 'NoneValue' + $none = Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'NATIVE_VALUE_READ' ` + -Field 'REGISTRY_VALUE' ` + -Action { + Get-SupervisorFixtureRegistryValueNativeBytes $key 'NoneValue' + } Assert-True ($key.GetValueKind('NoneValue').ToString() -ceq 'None' -and $none.Type -eq 0 -and $none.Bytes.Length -eq 3 -and $none.Bytes[0] -eq 9 -and $none.Bytes[2] -eq 7) ` @@ -5752,8 +5917,12 @@ function Test-HkcuDesktopFixtureBoundaryRegression { $presentDesktop = Join-Path $parent 'Desktop' $absentParent = Join-Path $root ([Guid]::NewGuid().ToString('N')) $absentDesktop = Join-Path $absentParent 'Desktop' + $absentForeignParent = Join-Path $root ([Guid]::NewGuid().ToString('N')) + $absentForeignDesktop = Join-Path $absentForeignParent 'Desktop' $failureParent = Join-Path $root ([Guid]::NewGuid().ToString('N')) $failureDesktop = Join-Path $failureParent 'Desktop' + $recoveryFailureParent = Join-Path $root ([Guid]::NewGuid().ToString('N')) + $recoveryFailureDesktop = Join-Path $recoveryFailureParent 'Desktop' Invoke-SupervisorAttributedOperation ` -Scenario 'HKCU_BASELINE_RESTORE' ` -Phase 'FIXTURE_SETUP' ` @@ -5795,11 +5964,43 @@ function Test-HkcuDesktopFixtureBoundaryRegression { Assert-True (!(Test-Path -LiteralPath $absentDesktop)) ` (Get-HkcuFixtureBoundaryDiagnostic) + [void](New-Item -Path $absentForeignParent -Force -ErrorAction Stop) + $absentForeignBoundary = + New-HkcuDesktopFixtureBoundaryState $absentForeignDesktop + Initialize-HkcuDesktopFixtureBoundary $absentForeignBoundary + Assert-True (!$absentForeignBoundary.BaselinePresent) ` + (Get-HkcuFixtureBoundaryDiagnostic) + [void](New-Item -Path $absentForeignDesktop -Force -ErrorAction Stop) + (Get-Item -LiteralPath $absentForeignDesktop).SetValue( + 'foreign', 'preserve', [Microsoft.Win32.RegistryValueKind]::String) + $expectedOwnershipFailure = Get-SanitizedSupervisorInvocationDiagnostic ` + 'HKCU_INSTALLED_VALUE_OWNERSHIP' ` + 'HKCU_BASELINE_RESTORE' ` + 'FIXTURE_SETUP' ` + 'TARGET_OWNERSHIP' ` + 'REGISTRY_PATH' + try { + Restore-HkcuDesktopFixtureBoundary $absentForeignBoundary $false + Assert-True $false (Get-HkcuFixtureBoundaryDiagnostic) + } catch { + Assert-True ([string]$_.Exception.Message -ceq $expectedOwnershipFailure) ` + (Get-HkcuFixtureBoundaryDiagnostic) + } + $absentForeignKey = Get-Item -LiteralPath $absentForeignDesktop -ErrorAction Stop + Assert-True ((Test-Path -LiteralPath $absentForeignDesktop) -and + [string]($absentForeignKey.GetValue('foreign')) -ceq 'preserve') ` + (Get-HkcuFixtureBoundaryDiagnostic) + Set-HkcuFixtureBoundaryValueKinds $failureDesktop $failureBoundary = New-HkcuDesktopFixtureBoundaryState $failureDesktop Initialize-HkcuDesktopFixtureBoundary $failureBoundary [void](New-Item -Path $failureDesktop -Force -ErrorAction Stop) - $expectedFailure = Get-HkcuFixtureBoundaryDiagnostic + $expectedFailure = Get-SanitizedSupervisorInvocationDiagnostic ` + 'HKCU_INSTALLED_VALUE_OWNERSHIP' ` + 'HKCU_BASELINE_RESTORE' ` + 'FIXTURE_SETUP' ` + 'TARGET_OWNERSHIP' ` + 'REGISTRY_PATH' try { Restore-HkcuDesktopFixtureBoundary $failureBoundary $false Assert-True $false (Get-HkcuFixtureBoundaryDiagnostic) @@ -5810,8 +6011,39 @@ function Test-HkcuDesktopFixtureBoundaryRegression { Assert-True ((Test-Path -LiteralPath $failureBoundary.BackupPath) -and (Test-Path -LiteralPath $failureDesktop)) ` (Get-HkcuFixtureBoundaryDiagnostic) + + Set-HkcuFixtureBoundaryValueKinds $recoveryFailureDesktop + $recoveryFailureDigest = + Get-SupervisorFixtureRegistryDigest $recoveryFailureDesktop + $recoveryFailureBoundary = + New-HkcuDesktopFixtureBoundaryState $recoveryFailureDesktop + Initialize-HkcuDesktopFixtureBoundary $recoveryFailureBoundary + $recoveryFailureBoundary.ForcePostRestoreDigestMismatch = $true + $recoveryFailureBoundary.ForceRecoveryRenameFailure = $true + $expectedRecoveryFailure = Get-SanitizedSupervisorInvocationDiagnostic ` + 'HKCU_INSTALLED_VALUE_OWNERSHIP' ` + 'HKCU_BASELINE_RESTORE' ` + 'FIXTURE_SETUP' ` + 'RECOVERY_RELOCATE' ` + 'REGISTRY_PATH' + try { + Restore-HkcuDesktopFixtureBoundary $recoveryFailureBoundary $false + Assert-True $false (Get-HkcuFixtureBoundaryDiagnostic) + } catch { + Assert-True ([string]$_.Exception.Message -ceq $expectedRecoveryFailure) ` + (Get-HkcuFixtureBoundaryDiagnostic) + } + Assert-True ((Test-Path -LiteralPath $recoveryFailureDesktop) -and + (Get-SupervisorFixtureRegistryDigest $recoveryFailureDesktop) -ceq + $recoveryFailureDigest) (Get-HkcuFixtureBoundaryDiagnostic) } finally { - foreach ($path in @($parent, $absentParent, $failureParent)) { + foreach ($path in @( + $parent, + $absentParent, + $absentForeignParent, + $failureParent, + $recoveryFailureParent + )) { if (Test-Path -LiteralPath $path) { Remove-Item -LiteralPath $path -Recurse -Force -ErrorAction SilentlyContinue } diff --git a/apps/desktop/src/release-workflow.test.ts b/apps/desktop/src/release-workflow.test.ts index c751aa91f..c9e4c6bd2 100644 --- a/apps/desktop/src/release-workflow.test.ts +++ b/apps/desktop/src/release-workflow.test.ts @@ -638,6 +638,15 @@ describe('desktop trusted release workflow', () => { 'CALLSITE_RESOURCE_COLLISION_REPLACEMENT_SURVIVAL_READ', 'CALLSITE_RESOURCE_COLLISION_MANIFEST_PRESERVATION', 'CALLSITE_HKCU_BASELINE_STATE', + 'CALLSITE_HKCU_REGRESSION_VALUE_SETUP', + 'CALLSITE_HKCU_NATIVE_VALUE_READ', + 'CALLSITE_HKCU_BASELINE_DIGEST', + 'CALLSITE_HKCU_BASELINE_RELOCATE', + 'CALLSITE_HKCU_TARGET_OWNERSHIP', + 'CALLSITE_HKCU_BASELINE_RESTORE', + 'CALLSITE_HKCU_RECOVERY_RELOCATE', + 'CALLSITE_HKCU_FINAL_BASELINE_DIGEST', + 'CALLSITE_HKCU_FINAL_BACKUP_ABSENCE', ]) { assert.match( installedWindowsAppSupervisorBehaviorTest, @@ -904,11 +913,19 @@ describe('desktop trusted release workflow', () => { ); assert.match( installedWindowsAppSupervisorBehaviorTest, - /function Initialize-HkcuDesktopFixtureBoundary[\s\S]*Get-SupervisorFixtureRegistryDigest \(\[string\]\$Boundary\.DesktopKey\)[\s\S]*\[Guid\]::NewGuid\(\)\.ToString\('D'\)[\s\S]*Assert-True \(!\(Test-Path -LiteralPath \$Boundary\.BackupPath\)\)[\s\S]*Rename-Item -LiteralPath \$Boundary\.DesktopKey[\s\S]*Assert-True \(!\(Test-Path -LiteralPath \$Boundary\.DesktopKey\)\)/, + /function Initialize-HkcuDesktopFixtureBoundary[\s\S]*Callsite 'BASELINE_RELOCATE'[\s\S]*Callsite 'BASELINE_DIGEST'[\s\S]*Get-SupervisorFixtureRegistryDigest \(\[string\]\$Boundary\.DesktopKey\)[\s\S]*\[Guid\]::NewGuid\(\)\.ToString\('D'\)[\s\S]*Assert-HkcuDesktopFixtureOperation \(!\(Test-Path -LiteralPath \$Boundary\.BackupPath\)\)[\s\S]*Rename-Item -LiteralPath \$Boundary\.DesktopKey[\s\S]*Assert-HkcuDesktopFixtureOperation \(!\(Test-Path -LiteralPath \$Boundary\.DesktopKey\)\)/, ); assert.match( installedWindowsAppSupervisorBehaviorTest, - /function Restore-HkcuDesktopFixtureBoundary[\s\S]*Get-SupervisorFixtureRegistryDigest \(\[string\]\$Boundary\.BackupPath\)[\s\S]*Assert-True \$TargetOwnedByFixture[\s\S]*Remove-Item -LiteralPath \$Boundary\.DesktopKey[\s\S]*Assert-True \(!\(Test-Path -LiteralPath \$Boundary\.DesktopKey\)\)[\s\S]*Rename-Item -LiteralPath \$Boundary\.BackupPath[\s\S]*Assert-True \(\$restoredDigest -ceq \[string\]\$Boundary\.BaselineDigest\)/, + /function Restore-HkcuDesktopFixtureBoundary[\s\S]*if \(!\$Boundary\.BaselinePresent\)[\s\S]*Callsite 'TARGET_OWNERSHIP'[\s\S]*Assert-HkcuDesktopFixtureOperation \$TargetOwnedByFixture[\s\S]*Remove-Item -LiteralPath \$Boundary\.DesktopKey[\s\S]*Callsite 'BASELINE_DIGEST'[\s\S]*Get-SupervisorFixtureRegistryDigest \(\[string\]\$Boundary\.BackupPath\)[\s\S]*Callsite 'TARGET_OWNERSHIP'[\s\S]*Assert-HkcuDesktopFixtureOperation \$TargetOwnedByFixture[\s\S]*Remove-Item -LiteralPath \$Boundary\.DesktopKey[\s\S]*Assert-HkcuDesktopFixtureOperation \(!\(Test-Path -LiteralPath \$Boundary\.DesktopKey\)\)[\s\S]*Rename-Item -LiteralPath \$Boundary\.BackupPath[\s\S]*Callsite 'RECOVERY_RELOCATE'[\s\S]*Rename-Item -LiteralPath \$Boundary\.DesktopKey[\s\S]*-ErrorAction Stop[\s\S]*Callsite 'FINAL_BASELINE_DIGEST'[\s\S]*Callsite 'FINAL_BACKUP_ABSENCE'/, + ); + const hkcuDesktopBoundaryRestore = installedWindowsAppSupervisorBehaviorTest.slice( + installedWindowsAppSupervisorBehaviorTest.indexOf('function Restore-HkcuDesktopFixtureBoundary'), + installedWindowsAppSupervisorBehaviorTest.indexOf('function Get-OwnedResourcePreservationSnapshot'), + ); + assert.doesNotMatch( + hkcuDesktopBoundaryRestore, + /ErrorAction SilentlyContinue/, ); assert.doesNotMatch( installedWindowsAppSupervisorBehaviorTest, @@ -1040,7 +1057,7 @@ describe('desktop trusted release workflow', () => { ); assert.match( installedWindowsAppSupervisorBehaviorTest, - /function Test-HkcuDesktopFixtureBoundaryRegression[\s\S]*Set-HkcuFixtureBoundaryValueKinds \$presentDesktop[\s\S]*\(Get-SupervisorFixtureRegistryDigest \$presentBoundary\.BackupPath\) -ceq[\s\S]*Assert-HkcuFixtureBoundaryValueKinds \$presentDesktop[\s\S]*!\$absentBoundary\.BaselinePresent[\s\S]*\[string\]::IsNullOrWhiteSpace\(\[string\]\$absentBoundary\.BackupPath\)[\s\S]*Restore-HkcuDesktopFixtureBoundary \$failureBoundary \$false[\s\S]*Test-Path -LiteralPath \$failureBoundary\.BackupPath/, + /function Test-HkcuDesktopFixtureBoundaryRegression[\s\S]*Set-HkcuFixtureBoundaryValueKinds \$presentDesktop[\s\S]*\(Get-SupervisorFixtureRegistryDigest \$presentBoundary\.BackupPath\) -ceq[\s\S]*Assert-HkcuFixtureBoundaryValueKinds \$presentDesktop[\s\S]*!\$absentBoundary\.BaselinePresent[\s\S]*\[string\]::IsNullOrWhiteSpace\(\[string\]\$absentBoundary\.BackupPath\)[\s\S]*Restore-HkcuDesktopFixtureBoundary \$absentForeignBoundary \$false[\s\S]*Test-Path -LiteralPath \$absentForeignDesktop[\s\S]*Restore-HkcuDesktopFixtureBoundary \$failureBoundary \$false[\s\S]*Test-Path -LiteralPath \$failureBoundary\.BackupPath[\s\S]*ForcePostRestoreDigestMismatch = \$true[\s\S]*ForceRecoveryRenameFailure = \$true[\s\S]*Restore-HkcuDesktopFixtureBoundary \$recoveryFailureBoundary \$false[\s\S]*Get-SupervisorFixtureRegistryDigest \$recoveryFailureDesktop/, ); for (const valueKind of [ 'StringValue', @@ -1074,7 +1091,23 @@ describe('desktop trusted release workflow', () => { ), ); } - assert.match(installedWindowsAppSupervisorBehaviorTest, /Get-SupervisorInvocationCallsites[\s\S]*'HKCU_BASELINE_STATE'/); + for (const callsite of [ + 'HKCU_BASELINE_STATE', + 'REGRESSION_VALUE_SETUP', + 'NATIVE_VALUE_READ', + 'BASELINE_DIGEST', + 'BASELINE_RELOCATE', + 'TARGET_OWNERSHIP', + 'BASELINE_RESTORE', + 'RECOVERY_RELOCATE', + 'FINAL_BASELINE_DIGEST', + 'FINAL_BACKUP_ABSENCE', + ]) { + assert.match( + installedWindowsAppSupervisorBehaviorTest, + new RegExp(`Get-SupervisorInvocationCallsites[\\s\\S]*'${callsite}'`), + ); + } assert.match(installedWindowsAppSupervisorBehaviorTest, /Get-SupervisorInvocationFields[\s\S]*'REGISTRY_PATH'/); assert.match(installedWindowsAppSupervisorBehaviorTest, /OWNED_RESOURCES_NORMAL_SUCCESS/); assert.match(installedWindowsAppSupervisorBehaviorTest, /typed authenticated empty-state receipt/); From 4ee06b62beb46a9797806223918d445d3df7877f Mon Sep 17 00:00:00 2001 From: "propr-dev[bot]" <1316198+propr-dev[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 16:46:50 +0000 Subject: [PATCH 26/33] feat(ai): Applied the lockfile-only `fast-uri` correction on the requested head. Applied the lockfile-only `fast-uri` correction on the requested head. Changed only [package-lock.json](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T16-42-52/package-lock.json:7424): - `fast-uri` `3.1.5` -> `3.1.6` - canonical tarball set to `https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.6.tgz` - integrity set to `sha512-7Ical1vFEMr0onbVzEDIreM22I4khW+fzyQPwvAFWBp1iwdshSZRsL4jjRvPG9JP1uiqMHRto+YU6R2/CzDz5Q==` Preserved `qs` at `6.16.0`. No manifests, source, workflows, or unrelated lock entries changed. Validation passed: - `npm install --package-lock-only --ignore-scripts --audit=false --fund=false` - `npm ci --ignore-scripts --audit=false --fund=false` - `npm run audit:runtime` -> `found 0 vulnerabilities` - `npm run desktop:audit:packaging` -> `found 0 vulnerabilities` - `npm ls fast-uri qs` - `git diff --check` Final status: only `package-lock.json` is modified. No commit or merge performed. PR: #2057 Comment by: @integry (ID: 5513037833) Model: gpt-5.5 --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3d584b0d6..427f433d5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7421,9 +7421,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", - "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.6.tgz", + "integrity": "sha512-7Ical1vFEMr0onbVzEDIreM22I4khW+fzyQPwvAFWBp1iwdshSZRsL4jjRvPG9JP1uiqMHRto+YU6R2/CzDz5Q==", "funding": [ { "type": "github", From 5fcb54eacf531fca7ca445edbfd80a6a8cc25b33 Mon Sep 17 00:00:00 2001 From: "propr-dev[bot]" <1316198+propr-dev[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 17:02:15 +0000 Subject: [PATCH 27/33] feat(ai): Implemented the queued fixture-only correction on exact head `4ee06b62beb46a9797806223918d445d3df7877f`. No commit or merge performed. Implemented the queued fixture-only correction on exact head `4ee06b62beb46a9797806223918d445d3df7877f`. No commit or merge performed. Changed only: - [test-installed-windows-app-supervisor.ps1](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T16-51-25/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1:1384) - [release-workflow.test.ts](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T16-51-25/apps/desktop/src/release-workflow.test.ts:922) What changed: - Made `Get-SupervisorFixtureRegistryDigest` keep native value reads unattributed by default, with HKCU attribution opt-in through `Get-HkcuFixtureRegistryDigest`. - Kept non-HKCU owned-resource snapshots on the shared digest path. - Added focused cross-scenario attribution regression. - Added native `RegSetValueEx` only in the fixture and used it only for the REG_NONE fixture writer. - Moved REG_NONE read, type, length, and byte validation inside `NATIVE_VALUE_READ / REGISTRY_VALUE`. - Added direct REG_NONE negative regressions for wrong type, wrong length, and wrong bytes. - Renamed the recovery path coverage to `ForceRecoveryBackupCollision`; it no longer claims to prove a terminating `Rename-Item` failure. Validation run: - `git diff --check` passed. - `npx tsx --test apps/desktop/src/release-workflow.test.ts` passed: 23/23. - `npm run test -w @propr/desktop -- src/release-workflow.test.ts` passed: 177 passed, 6 skipped. - `npm run audit:runtime` passed: 0 vulnerabilities. - `npm run desktop:audit:packaging` passed: 0 vulnerabilities. Not run: - Native Windows x64/ARM64 jobs could not be rerun from this Linux container, and no `pwsh`/`powershell` binary is installed locally. Confirmed: - `package-lock.json` diff is `0` bytes; audited `qs@6.16.0` and `fast-uri@3.1.6` entries remain unchanged. - Production cleanup/controller/runtime scripts have `0` bytes of diff. PR: #2057 Comment by: @integry (ID: 5513147875) Model: gpt-5.5 --- .../test-installed-windows-app-supervisor.ps1 | 244 ++++++++++++++---- apps/desktop/src/release-workflow.test.ts | 51 +++- 2 files changed, 241 insertions(+), 54 deletions(-) diff --git a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 index fe2a822f1..b8e6ed238 100644 --- a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 +++ b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 @@ -1379,6 +1379,15 @@ public static class ProprSupervisorFixtureRegistryNative out int lpType, byte[] lpData, ref int lpcbData); + + [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern int RegSetValueEx( + SafeRegistryHandle hKey, + string lpValueName, + int Reserved, + int dwType, + byte[] lpData, + int cbData); } '@ } @@ -1454,7 +1463,29 @@ function Get-SupervisorFixtureDirectoryDigest([string]$Path) { } } -function Get-SupervisorFixtureRegistryDigest([string]$Path) { +function Set-SupervisorFixtureRegistryValueNativeBytes( + $Key, + [string]$Name, + [int]$Type, + [byte[]]$Bytes +) { + Add-SupervisorFixtureRegistryNativeApi + $valueBytes = if ($null -eq $Bytes) { [byte[]]@() } else { [byte[]]$Bytes } + $result = [ProprSupervisorFixtureRegistryNative]::RegSetValueEx( + $Key.Handle, + $Name, + 0, + $Type, + $valueBytes, + $valueBytes.Length + ) + if ($result -ne 0) { throw 'registry value set failed' } +} + +function Get-SupervisorFixtureRegistryDigest( + [string]$Path, + [switch]$AttributeHkcuNativeValueRead +) { try { if (!(Test-Path -LiteralPath $Path)) { return 'MISSING' } $root = Get-Item -LiteralPath $Path -ErrorAction Stop @@ -1466,15 +1497,21 @@ function Get-SupervisorFixtureRegistryDigest([string]$Path) { $records.Add(('K|{0}' -f [Convert]::ToBase64String( [Text.Encoding]::UTF8.GetBytes([string]$entry.Relative)))) foreach ($valueName in @($entry.Key.GetValueNames() | Sort-Object -CaseSensitive)) { - $nativeValue = Invoke-HkcuDesktopFixtureOperation ` - -Callsite 'NATIVE_VALUE_READ' ` - -Field 'REGISTRY_VALUE' ` - -Action { - Get-SupervisorFixtureRegistryValueNativeBytes $entry.Key ([string]$valueName) - } + $digestKey = $entry.Key + $digestValueName = [string]$valueName + $nativeValue = if ($AttributeHkcuNativeValueRead) { + Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'NATIVE_VALUE_READ' ` + -Field 'REGISTRY_VALUE' ` + -Action { + Get-SupervisorFixtureRegistryValueNativeBytes $digestKey $digestValueName + } + } else { + Get-SupervisorFixtureRegistryValueNativeBytes $digestKey $digestValueName + } $records.Add(('V|{0}|{1}|{2}' -f - [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes([string]$valueName)), - ('{0}:{1}' -f $entry.Key.GetValueKind($valueName).ToString(), $nativeValue.Type), + [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($digestValueName)), + ('{0}:{1}' -f $entry.Key.GetValueKind($digestValueName).ToString(), $nativeValue.Type), [Convert]::ToBase64String([byte[]]$nativeValue.Bytes))) } foreach ($child in @(Get-ChildItem -LiteralPath $entry.Key.PSPath -ErrorAction Stop | @@ -1493,6 +1530,12 @@ function Get-SupervisorFixtureRegistryDigest([string]$Path) { } } +function Get-HkcuFixtureRegistryDigest([string]$Path) { + Get-SupervisorFixtureRegistryDigest ` + -Path $Path ` + -AttributeHkcuNativeValueRead +} + function Get-SupervisorFixtureRegistryValueDigest([string]$Path, [string]$Name) { if (!(Test-Path -LiteralPath $Path)) { return 'MISSING' } try { @@ -1551,7 +1594,7 @@ function New-HkcuDesktopFixtureBoundaryState([string]$DesktopKey) { Relocated = $false OriginalAbsentProven = $false ForcePostRestoreDigestMismatch = $false - ForceRecoveryRenameFailure = $false + ForceRecoveryBackupCollision = $false } } @@ -1585,7 +1628,7 @@ function Initialize-HkcuDesktopFixtureBoundary($Boundary) { -Callsite 'BASELINE_DIGEST' ` -Field 'REGISTRY_ROOT' ` -Action { - Get-SupervisorFixtureRegistryDigest ([string]$Boundary.DesktopKey) + Get-HkcuFixtureRegistryDigest ([string]$Boundary.DesktopKey) } Assert-HkcuDesktopFixtureOperation (Test-HkcuFixtureRegistryDigest $baselineDigest) $Boundary.BaselineDigest = $baselineDigest @@ -1632,7 +1675,7 @@ function Restore-HkcuDesktopFixtureBoundary( -Callsite 'BASELINE_DIGEST' ` -Field 'REGISTRY_ROOT' ` -Action { - Get-SupervisorFixtureRegistryDigest ([string]$Boundary.BackupPath) + Get-HkcuFixtureRegistryDigest ([string]$Boundary.BackupPath) } Assert-HkcuDesktopFixtureOperation ($backupDigest -ceq [string]$Boundary.BaselineDigest) if (Test-Path -LiteralPath $Boundary.DesktopKey) { @@ -1652,7 +1695,7 @@ function Restore-HkcuDesktopFixtureBoundary( -Callsite 'BASELINE_DIGEST' ` -Field 'REGISTRY_ROOT' ` -Action { - Get-SupervisorFixtureRegistryDigest ([string]$Boundary.DesktopKey) + Get-HkcuFixtureRegistryDigest ([string]$Boundary.DesktopKey) } if ($Boundary.ForcePostRestoreDigestMismatch) { $restoredDigest = 'INVALID' @@ -1663,7 +1706,7 @@ function Restore-HkcuDesktopFixtureBoundary( -Field 'REGISTRY_PATH' ` -Action { try { - if ($Boundary.ForceRecoveryRenameFailure -and + if ($Boundary.ForceRecoveryBackupCollision -and !(Test-Path -LiteralPath $Boundary.BackupPath)) { [void](New-Item -Path $Boundary.BackupPath -Force -ErrorAction Stop) } @@ -1677,10 +1720,10 @@ function Restore-HkcuDesktopFixtureBoundary( throw 'post-restore digest mismatch' } catch { $desktopDigest = if (Test-Path -LiteralPath $Boundary.DesktopKey) { - Get-SupervisorFixtureRegistryDigest ([string]$Boundary.DesktopKey) + Get-HkcuFixtureRegistryDigest ([string]$Boundary.DesktopKey) } else { 'MISSING' } $backupDigestAfterRecovery = if (Test-Path -LiteralPath $Boundary.BackupPath) { - Get-SupervisorFixtureRegistryDigest ([string]$Boundary.BackupPath) + Get-HkcuFixtureRegistryDigest ([string]$Boundary.BackupPath) } else { 'MISSING' } Assert-HkcuDesktopFixtureOperation ( $desktopDigest -ceq [string]$Boundary.BaselineDigest -or @@ -5852,11 +5895,8 @@ function Set-HkcuFixtureBoundaryValueKinds([string]$Path) { [string[]]@('alpha', '', 'omega'), [Microsoft.Win32.RegistryValueKind]::MultiString ) - $key.SetValue( - 'NoneValue', - [byte[]]@(9, 8, 7), - [Microsoft.Win32.RegistryValueKind]::None - ) + Set-SupervisorFixtureRegistryValueNativeBytes ` + $key 'NoneValue' 0 ([byte[]]@(9, 8, 7)) $nested = Join-Path $Path 'Nested' $child = Join-Path $nested 'Child' [void](New-Item -Path $child -Force -ErrorAction Stop) @@ -5895,22 +5935,111 @@ function Assert-HkcuFixtureBoundaryValueKinds([string]$Path) { $multi.Length -eq 3 -and $multi[0] -ceq 'alpha' -and $multi[1] -ceq '' -and $multi[2] -ceq 'omega') ` (Get-HkcuFixtureBoundaryDiagnostic) - $none = Invoke-HkcuDesktopFixtureOperation ` - -Callsite 'NATIVE_VALUE_READ' ` - -Field 'REGISTRY_VALUE' ` - -Action { - Get-SupervisorFixtureRegistryValueNativeBytes $key 'NoneValue' - } - Assert-True ($key.GetValueKind('NoneValue').ToString() -ceq 'None' -and - $none.Type -eq 0 -and $none.Bytes.Length -eq 3 -and - $none.Bytes[0] -eq 9 -and $none.Bytes[2] -eq 7) ` - (Get-HkcuFixtureBoundaryDiagnostic) + Assert-HkcuFixtureNativeNoneValue $key ([byte[]]@(9, 8, 7)) $child = Join-Path (Join-Path $Path 'Nested') 'Child' $childKey = Get-Item -LiteralPath $child -ErrorAction Stop Assert-True ([string]$childKey.GetValue('NestedValue') -ceq 'nested-string') ` (Get-HkcuFixtureBoundaryDiagnostic) } +function Assert-HkcuFixtureNativeNoneValue($Key, [byte[]]$ExpectedBytes) { + Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'NATIVE_VALUE_READ' ` + -Field 'REGISTRY_VALUE' ` + -Action { + $none = Get-SupervisorFixtureRegistryValueNativeBytes $Key 'NoneValue' + $actualBytes = [byte[]]$none.Bytes + Assert-HkcuDesktopFixtureOperation ` + ($Key.GetValueKind('NoneValue').ToString() -ceq 'None') + Assert-HkcuDesktopFixtureOperation ($none.Type -eq 0) + Assert-HkcuDesktopFixtureOperation ($actualBytes.Length -eq $ExpectedBytes.Length) + for ($index = 0; $index -lt $ExpectedBytes.Length; $index++) { + Assert-HkcuDesktopFixtureOperation ` + ($actualBytes[$index] -eq $ExpectedBytes[$index]) + } + } +} + +function Assert-HkcuFixtureNativeNoneValueFailure($Key) { + $expectedNativeReadFailure = Get-SanitizedSupervisorInvocationDiagnostic ` + 'HKCU_INSTALLED_VALUE_OWNERSHIP' ` + 'HKCU_BASELINE_RESTORE' ` + 'FIXTURE_SETUP' ` + 'NATIVE_VALUE_READ' ` + 'REGISTRY_VALUE' + try { + Assert-HkcuFixtureNativeNoneValue $Key ([byte[]]@(9, 8, 7)) + Assert-True $false (Get-HkcuFixtureBoundaryDiagnostic) + } catch { + Assert-True ([string]$_.Exception.Message -ceq $expectedNativeReadFailure) ` + (Get-HkcuFixtureBoundaryDiagnostic) + } +} + +function Test-HkcuFixtureNativeNoneValueReadRegression( + [string]$TypePath, + [string]$LengthPath, + [string]$BytePath +) { + Set-HkcuFixtureBoundaryValueKinds $TypePath + $typeKey = Get-Item -LiteralPath $TypePath -ErrorAction Stop + $typeKey.SetValue( + 'NoneValue', + [byte[]]@(9, 8, 7), + [Microsoft.Win32.RegistryValueKind]::Binary + ) + Assert-HkcuFixtureNativeNoneValueFailure $typeKey + + Set-HkcuFixtureBoundaryValueKinds $LengthPath + $lengthKey = Get-Item -LiteralPath $LengthPath -ErrorAction Stop + Set-SupervisorFixtureRegistryValueNativeBytes ` + $lengthKey 'NoneValue' 0 ([byte[]]@(9, 8)) + Assert-HkcuFixtureNativeNoneValueFailure $lengthKey + + Set-HkcuFixtureBoundaryValueKinds $BytePath + $byteKey = Get-Item -LiteralPath $BytePath -ErrorAction Stop + Set-SupervisorFixtureRegistryValueNativeBytes ` + $byteKey 'NoneValue' 0 ([byte[]]@(9, 8, 6)) + Assert-HkcuFixtureNativeNoneValueFailure $byteKey +} + +function Test-SupervisorFixtureRegistryDigestAttributionRegression([string]$Path) { + [void](New-Item -Path $Path -Force -ErrorAction Stop) + $key = Get-Item -LiteralPath $Path -ErrorAction Stop + $key.SetValue('SyntheticValue', 'value', [Microsoft.Win32.RegistryValueKind]::String) + $originalNativeReader = + (Get-Command Get-SupervisorFixtureRegistryValueNativeBytes -CommandType Function).ScriptBlock + function Get-SupervisorFixtureRegistryValueNativeBytes { throw 'synthetic registry read failure' } + try { + Invoke-SupervisorAttributedBoundary ` + -Test 'PRE_EXISTING_CLEANUP_OWNERSHIP' ` + -Scenario 'RESOURCE_COLLISION' ` + -Phase 'RESOURCE_ASSERTION' ` + -Callsite 'REPLACEMENT_SURVIVAL_READ' ` + -Field 'REGISTRY_VALUE' ` + -Action { + Assert-True ((Get-SupervisorFixtureRegistryDigest $Path) -ceq 'INVALID') ` + 'shared registry digest rewrote non-HKCU read attribution' + } + $expectedHkcuNativeReadFailure = Get-SanitizedSupervisorInvocationDiagnostic ` + 'HKCU_INSTALLED_VALUE_OWNERSHIP' ` + 'HKCU_BASELINE_RESTORE' ` + 'FIXTURE_SETUP' ` + 'NATIVE_VALUE_READ' ` + 'REGISTRY_VALUE' + try { + Get-HkcuFixtureRegistryDigest $Path | Out-Null + Assert-True $false (Get-HkcuFixtureBoundaryDiagnostic) + } catch { + Assert-True ([string]$_.Exception.Message -ceq $expectedHkcuNativeReadFailure) ` + (Get-HkcuFixtureBoundaryDiagnostic) + } + } finally { + Set-Item -Path Function:\Get-SupervisorFixtureRegistryValueNativeBytes ` + -Value $originalNativeReader + } +} + function Test-HkcuDesktopFixtureBoundaryRegression { $root = 'Registry::HKEY_CURRENT_USER\Software\ProPRSupervisorFixture' $parent = Join-Path $root ([Guid]::NewGuid().ToString('N')) @@ -5921,8 +6050,12 @@ function Test-HkcuDesktopFixtureBoundaryRegression { $absentForeignDesktop = Join-Path $absentForeignParent 'Desktop' $failureParent = Join-Path $root ([Guid]::NewGuid().ToString('N')) $failureDesktop = Join-Path $failureParent 'Desktop' - $recoveryFailureParent = Join-Path $root ([Guid]::NewGuid().ToString('N')) - $recoveryFailureDesktop = Join-Path $recoveryFailureParent 'Desktop' + $recoveryCollisionParent = Join-Path $root ([Guid]::NewGuid().ToString('N')) + $recoveryCollisionDesktop = Join-Path $recoveryCollisionParent 'Desktop' + $noneTypePath = Join-Path $root ([Guid]::NewGuid().ToString('N')) + $noneLengthPath = Join-Path $root ([Guid]::NewGuid().ToString('N')) + $noneBytePath = Join-Path $root ([Guid]::NewGuid().ToString('N')) + $digestAttributionPath = Join-Path $root ([Guid]::NewGuid().ToString('N')) Invoke-SupervisorAttributedOperation ` -Scenario 'HKCU_BASELINE_RESTORE' ` -Phase 'FIXTURE_SETUP' ` @@ -5930,8 +6063,11 @@ function Test-HkcuDesktopFixtureBoundaryRegression { -Field 'REGISTRY_PATH' ` -Action { try { + Test-HkcuFixtureNativeNoneValueReadRegression ` + $noneTypePath $noneLengthPath $noneBytePath + Test-SupervisorFixtureRegistryDigestAttributionRegression $digestAttributionPath Set-HkcuFixtureBoundaryValueKinds $presentDesktop - $presentDigest = Get-SupervisorFixtureRegistryDigest $presentDesktop + $presentDigest = Get-HkcuFixtureRegistryDigest $presentDesktop Assert-True (Test-HkcuFixtureRegistryDigest $presentDigest) ` (Get-HkcuFixtureBoundaryDiagnostic) $presentBoundary = New-HkcuDesktopFixtureBoundaryState $presentDesktop @@ -5940,18 +6076,20 @@ function Test-HkcuDesktopFixtureBoundaryRegression { $presentBoundary.Relocated -and !(Test-Path -LiteralPath $presentDesktop) -and (Test-Path -LiteralPath $presentBoundary.BackupPath) -and - (Get-SupervisorFixtureRegistryDigest $presentBoundary.BackupPath) -ceq + (Get-HkcuFixtureRegistryDigest $presentBoundary.BackupPath) -ceq $presentDigest) (Get-HkcuFixtureBoundaryDiagnostic) [void](New-Item -Path $presentDesktop -Force -ErrorAction Stop) $presentFixtureOwned = $true (Get-Item -LiteralPath $presentDesktop).SetValue( 'installed', [int]1, [Microsoft.Win32.RegistryValueKind]::DWord) Restore-HkcuDesktopFixtureBoundary $presentBoundary $presentFixtureOwned - Assert-True ((Get-SupervisorFixtureRegistryDigest $presentDesktop) -ceq + Assert-True ((Get-HkcuFixtureRegistryDigest $presentDesktop) -ceq $presentDigest) (Get-HkcuFixtureBoundaryDiagnostic) Assert-True (!(Test-Path -LiteralPath $presentBoundary.BackupPath)) ` (Get-HkcuFixtureBoundaryDiagnostic) Assert-HkcuFixtureBoundaryValueKinds $presentDesktop + Assert-True ((Get-HkcuFixtureRegistryDigest $presentDesktop) -ceq + $presentDigest) (Get-HkcuFixtureBoundaryDiagnostic) [void](New-Item -Path $absentParent -Force -ErrorAction Stop) $absentBoundary = New-HkcuDesktopFixtureBoundaryState $absentDesktop @@ -6012,37 +6150,41 @@ function Test-HkcuDesktopFixtureBoundaryRegression { (Test-Path -LiteralPath $failureDesktop)) ` (Get-HkcuFixtureBoundaryDiagnostic) - Set-HkcuFixtureBoundaryValueKinds $recoveryFailureDesktop - $recoveryFailureDigest = - Get-SupervisorFixtureRegistryDigest $recoveryFailureDesktop - $recoveryFailureBoundary = - New-HkcuDesktopFixtureBoundaryState $recoveryFailureDesktop - Initialize-HkcuDesktopFixtureBoundary $recoveryFailureBoundary - $recoveryFailureBoundary.ForcePostRestoreDigestMismatch = $true - $recoveryFailureBoundary.ForceRecoveryRenameFailure = $true - $expectedRecoveryFailure = Get-SanitizedSupervisorInvocationDiagnostic ` + Set-HkcuFixtureBoundaryValueKinds $recoveryCollisionDesktop + $recoveryCollisionDigest = + Get-HkcuFixtureRegistryDigest $recoveryCollisionDesktop + $recoveryCollisionBoundary = + New-HkcuDesktopFixtureBoundaryState $recoveryCollisionDesktop + Initialize-HkcuDesktopFixtureBoundary $recoveryCollisionBoundary + $recoveryCollisionBoundary.ForcePostRestoreDigestMismatch = $true + $recoveryCollisionBoundary.ForceRecoveryBackupCollision = $true + $expectedRecoveryCollision = Get-SanitizedSupervisorInvocationDiagnostic ` 'HKCU_INSTALLED_VALUE_OWNERSHIP' ` 'HKCU_BASELINE_RESTORE' ` 'FIXTURE_SETUP' ` 'RECOVERY_RELOCATE' ` 'REGISTRY_PATH' try { - Restore-HkcuDesktopFixtureBoundary $recoveryFailureBoundary $false + Restore-HkcuDesktopFixtureBoundary $recoveryCollisionBoundary $false Assert-True $false (Get-HkcuFixtureBoundaryDiagnostic) } catch { - Assert-True ([string]$_.Exception.Message -ceq $expectedRecoveryFailure) ` + Assert-True ([string]$_.Exception.Message -ceq $expectedRecoveryCollision) ` (Get-HkcuFixtureBoundaryDiagnostic) } - Assert-True ((Test-Path -LiteralPath $recoveryFailureDesktop) -and - (Get-SupervisorFixtureRegistryDigest $recoveryFailureDesktop) -ceq - $recoveryFailureDigest) (Get-HkcuFixtureBoundaryDiagnostic) + Assert-True ((Test-Path -LiteralPath $recoveryCollisionDesktop) -and + (Get-HkcuFixtureRegistryDigest $recoveryCollisionDesktop) -ceq + $recoveryCollisionDigest) (Get-HkcuFixtureBoundaryDiagnostic) } finally { foreach ($path in @( $parent, $absentParent, $absentForeignParent, $failureParent, - $recoveryFailureParent + $recoveryCollisionParent, + $noneTypePath, + $noneLengthPath, + $noneBytePath, + $digestAttributionPath )) { if (Test-Path -LiteralPath $path) { Remove-Item -LiteralPath $path -Recurse -Force -ErrorAction SilentlyContinue diff --git a/apps/desktop/src/release-workflow.test.ts b/apps/desktop/src/release-workflow.test.ts index c9e4c6bd2..2c054af72 100644 --- a/apps/desktop/src/release-workflow.test.ts +++ b/apps/desktop/src/release-workflow.test.ts @@ -913,12 +913,47 @@ describe('desktop trusted release workflow', () => { ); assert.match( installedWindowsAppSupervisorBehaviorTest, - /function Initialize-HkcuDesktopFixtureBoundary[\s\S]*Callsite 'BASELINE_RELOCATE'[\s\S]*Callsite 'BASELINE_DIGEST'[\s\S]*Get-SupervisorFixtureRegistryDigest \(\[string\]\$Boundary\.DesktopKey\)[\s\S]*\[Guid\]::NewGuid\(\)\.ToString\('D'\)[\s\S]*Assert-HkcuDesktopFixtureOperation \(!\(Test-Path -LiteralPath \$Boundary\.BackupPath\)\)[\s\S]*Rename-Item -LiteralPath \$Boundary\.DesktopKey[\s\S]*Assert-HkcuDesktopFixtureOperation \(!\(Test-Path -LiteralPath \$Boundary\.DesktopKey\)\)/, + /function Initialize-HkcuDesktopFixtureBoundary[\s\S]*Callsite 'BASELINE_RELOCATE'[\s\S]*Callsite 'BASELINE_DIGEST'[\s\S]*Get-HkcuFixtureRegistryDigest \(\[string\]\$Boundary\.DesktopKey\)[\s\S]*\[Guid\]::NewGuid\(\)\.ToString\('D'\)[\s\S]*Assert-HkcuDesktopFixtureOperation \(!\(Test-Path -LiteralPath \$Boundary\.BackupPath\)\)[\s\S]*Rename-Item -LiteralPath \$Boundary\.DesktopKey[\s\S]*Assert-HkcuDesktopFixtureOperation \(!\(Test-Path -LiteralPath \$Boundary\.DesktopKey\)\)/, ); assert.match( installedWindowsAppSupervisorBehaviorTest, - /function Restore-HkcuDesktopFixtureBoundary[\s\S]*if \(!\$Boundary\.BaselinePresent\)[\s\S]*Callsite 'TARGET_OWNERSHIP'[\s\S]*Assert-HkcuDesktopFixtureOperation \$TargetOwnedByFixture[\s\S]*Remove-Item -LiteralPath \$Boundary\.DesktopKey[\s\S]*Callsite 'BASELINE_DIGEST'[\s\S]*Get-SupervisorFixtureRegistryDigest \(\[string\]\$Boundary\.BackupPath\)[\s\S]*Callsite 'TARGET_OWNERSHIP'[\s\S]*Assert-HkcuDesktopFixtureOperation \$TargetOwnedByFixture[\s\S]*Remove-Item -LiteralPath \$Boundary\.DesktopKey[\s\S]*Assert-HkcuDesktopFixtureOperation \(!\(Test-Path -LiteralPath \$Boundary\.DesktopKey\)\)[\s\S]*Rename-Item -LiteralPath \$Boundary\.BackupPath[\s\S]*Callsite 'RECOVERY_RELOCATE'[\s\S]*Rename-Item -LiteralPath \$Boundary\.DesktopKey[\s\S]*-ErrorAction Stop[\s\S]*Callsite 'FINAL_BASELINE_DIGEST'[\s\S]*Callsite 'FINAL_BACKUP_ABSENCE'/, + /function Restore-HkcuDesktopFixtureBoundary[\s\S]*if \(!\$Boundary\.BaselinePresent\)[\s\S]*Callsite 'TARGET_OWNERSHIP'[\s\S]*Assert-HkcuDesktopFixtureOperation \$TargetOwnedByFixture[\s\S]*Remove-Item -LiteralPath \$Boundary\.DesktopKey[\s\S]*Callsite 'BASELINE_DIGEST'[\s\S]*Get-HkcuFixtureRegistryDigest \(\[string\]\$Boundary\.BackupPath\)[\s\S]*Callsite 'TARGET_OWNERSHIP'[\s\S]*Assert-HkcuDesktopFixtureOperation \$TargetOwnedByFixture[\s\S]*Remove-Item -LiteralPath \$Boundary\.DesktopKey[\s\S]*Assert-HkcuDesktopFixtureOperation \(!\(Test-Path -LiteralPath \$Boundary\.DesktopKey\)\)[\s\S]*Rename-Item -LiteralPath \$Boundary\.BackupPath[\s\S]*Callsite 'RECOVERY_RELOCATE'[\s\S]*Rename-Item -LiteralPath \$Boundary\.DesktopKey[\s\S]*-ErrorAction Stop[\s\S]*Callsite 'FINAL_BASELINE_DIGEST'[\s\S]*Callsite 'FINAL_BACKUP_ABSENCE'/, ); + const registryDigestFunction = installedWindowsAppSupervisorBehaviorTest.slice( + installedWindowsAppSupervisorBehaviorTest.indexOf( + 'function Get-SupervisorFixtureRegistryDigest', + ), + installedWindowsAppSupervisorBehaviorTest.indexOf( + 'function Get-HkcuFixtureRegistryDigest', + ), + ); + assert.match(registryDigestFunction, /\[switch\]\$AttributeHkcuNativeValueRead/); + assert.match( + registryDigestFunction, + /if \(\$AttributeHkcuNativeValueRead\)[\s\S]*Invoke-HkcuDesktopFixtureOperation[\s\S]*else \{[\s\S]*Get-SupervisorFixtureRegistryValueNativeBytes/, + ); + const hkcuDigestFunction = installedWindowsAppSupervisorBehaviorTest.slice( + installedWindowsAppSupervisorBehaviorTest.indexOf( + 'function Get-HkcuFixtureRegistryDigest', + ), + installedWindowsAppSupervisorBehaviorTest.indexOf( + 'function Get-SupervisorFixtureRegistryValueDigest', + ), + ); + assert.match(hkcuDigestFunction, /-AttributeHkcuNativeValueRead/); + const ownedResourceSnapshot = installedWindowsAppSupervisorBehaviorTest.slice( + installedWindowsAppSupervisorBehaviorTest.indexOf( + 'function Get-OwnedResourcePreservationSnapshot', + ), + installedWindowsAppSupervisorBehaviorTest.indexOf( + 'function Assert-OwnedResourcePreservationSnapshot', + ), + ); + assert.match( + ownedResourceSnapshot, + /Get-SupervisorFixtureRegistryDigest \(\[string\]\$Owned\.RegistryPath\)/, + ); + assert.doesNotMatch(ownedResourceSnapshot, /Get-HkcuFixtureRegistryDigest/); const hkcuDesktopBoundaryRestore = installedWindowsAppSupervisorBehaviorTest.slice( installedWindowsAppSupervisorBehaviorTest.indexOf('function Restore-HkcuDesktopFixtureBoundary'), installedWindowsAppSupervisorBehaviorTest.indexOf('function Get-OwnedResourcePreservationSnapshot'), @@ -1057,7 +1092,17 @@ describe('desktop trusted release workflow', () => { ); assert.match( installedWindowsAppSupervisorBehaviorTest, - /function Test-HkcuDesktopFixtureBoundaryRegression[\s\S]*Set-HkcuFixtureBoundaryValueKinds \$presentDesktop[\s\S]*\(Get-SupervisorFixtureRegistryDigest \$presentBoundary\.BackupPath\) -ceq[\s\S]*Assert-HkcuFixtureBoundaryValueKinds \$presentDesktop[\s\S]*!\$absentBoundary\.BaselinePresent[\s\S]*\[string\]::IsNullOrWhiteSpace\(\[string\]\$absentBoundary\.BackupPath\)[\s\S]*Restore-HkcuDesktopFixtureBoundary \$absentForeignBoundary \$false[\s\S]*Test-Path -LiteralPath \$absentForeignDesktop[\s\S]*Restore-HkcuDesktopFixtureBoundary \$failureBoundary \$false[\s\S]*Test-Path -LiteralPath \$failureBoundary\.BackupPath[\s\S]*ForcePostRestoreDigestMismatch = \$true[\s\S]*ForceRecoveryRenameFailure = \$true[\s\S]*Restore-HkcuDesktopFixtureBoundary \$recoveryFailureBoundary \$false[\s\S]*Get-SupervisorFixtureRegistryDigest \$recoveryFailureDesktop/, + /function Test-HkcuDesktopFixtureBoundaryRegression[\s\S]*Test-HkcuFixtureNativeNoneValueReadRegression[\s\S]*Test-SupervisorFixtureRegistryDigestAttributionRegression[\s\S]*Set-HkcuFixtureBoundaryValueKinds \$presentDesktop[\s\S]*\(Get-HkcuFixtureRegistryDigest \$presentBoundary\.BackupPath\) -ceq[\s\S]*Assert-HkcuFixtureBoundaryValueKinds \$presentDesktop[\s\S]*!\$absentBoundary\.BaselinePresent[\s\S]*\[string\]::IsNullOrWhiteSpace\(\[string\]\$absentBoundary\.BackupPath\)[\s\S]*Restore-HkcuDesktopFixtureBoundary \$absentForeignBoundary \$false[\s\S]*Test-Path -LiteralPath \$absentForeignDesktop[\s\S]*Restore-HkcuDesktopFixtureBoundary \$failureBoundary \$false[\s\S]*Test-Path -LiteralPath \$failureBoundary\.BackupPath[\s\S]*ForcePostRestoreDigestMismatch = \$true[\s\S]*ForceRecoveryBackupCollision = \$true[\s\S]*Restore-HkcuDesktopFixtureBoundary \$recoveryCollisionBoundary \$false[\s\S]*Get-HkcuFixtureRegistryDigest \$recoveryCollisionDesktop/, + ); + assert.doesNotMatch(installedWindowsAppSupervisorBehaviorTest, /ForceRecoveryRenameFailure/); + assert.match(installedWindowsAppSupervisorBehaviorTest, /RegSetValueEx/); + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /Set-SupervisorFixtureRegistryValueNativeBytes\s+`\n\s+\$key 'NoneValue' 0 \(\[byte\[\]\]@\(9, 8, 7\)\)/, + ); + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /function Assert-HkcuFixtureNativeNoneValue[\s\S]*Callsite 'NATIVE_VALUE_READ'[\s\S]*Field 'REGISTRY_VALUE'[\s\S]*GetValueKind\('NoneValue'\)[\s\S]*\$none\.Type -eq 0[\s\S]*\$actualBytes\.Length -eq \$ExpectedBytes\.Length[\s\S]*\$actualBytes\[\$index\] -eq \$ExpectedBytes\[\$index\]/, ); for (const valueKind of [ 'StringValue', From 30edcce07e2ca415aef6b30a450bfc44ab53b1d2 Mon Sep 17 00:00:00 2001 From: "propr-dev[bot]" <1316198+propr-dev[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 17:10:09 +0000 Subject: [PATCH 28/33] feat(ai): Implemented the two requested follow-up gaps without touching production code or lockfiles. Implemented the two requested follow-up gaps without touching production code or lockfiles. Changed: - [test-installed-windows-app-supervisor.ps1](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T17-04-09/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1:6077): wrapped the direct HKCU regression digest reads/assertions in `BASELINE_DIGEST` or `FINAL_BASELINE_DIGEST`. - [test-installed-windows-app-supervisor.ps1](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T17-04-09/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1:1717): added separate deterministic recovery `Rename-Item -ErrorAction Stop` failure coverage, distinct from backup collision. - [release-workflow.test.ts](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T17-04-09/apps/desktop/src/release-workflow.test.ts:953): added static regressions for fixed HKCU digest boundaries and non-HKCU digest attribution. Validation: - Passed: `npm --workspace apps/desktop exec -- tsx --test src/release-workflow.test.ts` - Passed: `git diff --check` - Confirmed no manifest or `package-lock.json` diff, preserving qs/fast-uri lock entries byte-for-byte. Not run: real native Windows x64/ARM64 supervisor/package jobs. This local worktree is Linux and the edits are uncommitted, so Actions cannot validate this exact changed state until the system creates the follow-up commit. PR: #2057 Comment by: @integry (ID: 5513310475) Model: gpt-5.5 --- .../test-installed-windows-app-supervisor.ps1 | 120 +++++++++++++++--- apps/desktop/src/release-workflow.test.ts | 45 ++++++- 2 files changed, 148 insertions(+), 17 deletions(-) diff --git a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 index b8e6ed238..6366f0d2c 100644 --- a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 +++ b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 @@ -1595,6 +1595,7 @@ function New-HkcuDesktopFixtureBoundaryState([string]$DesktopKey) { OriginalAbsentProven = $false ForcePostRestoreDigestMismatch = $false ForceRecoveryBackupCollision = $false + ForceRecoveryRenameFailure = $false } } @@ -1712,8 +1713,12 @@ function Restore-HkcuDesktopFixtureBoundary( } if ((Test-Path -LiteralPath $Boundary.DesktopKey) -and !(Test-Path -LiteralPath $Boundary.BackupPath)) { + $recoveryBackupLeaf = [string]$Boundary.BackupLeaf + if ($Boundary.ForceRecoveryRenameFailure) { + $recoveryBackupLeaf = 'ProPRInvalid\RecoveryBackup' + } Rename-Item -LiteralPath $Boundary.DesktopKey ` - -NewName ([string]$Boundary.BackupLeaf) -ErrorAction Stop + -NewName $recoveryBackupLeaf -ErrorAction Stop } Assert-HkcuDesktopFixtureOperation (Test-Path -LiteralPath $Boundary.BackupPath) Assert-HkcuDesktopFixtureOperation (!(Test-Path -LiteralPath $Boundary.DesktopKey)) @@ -6052,6 +6057,8 @@ function Test-HkcuDesktopFixtureBoundaryRegression { $failureDesktop = Join-Path $failureParent 'Desktop' $recoveryCollisionParent = Join-Path $root ([Guid]::NewGuid().ToString('N')) $recoveryCollisionDesktop = Join-Path $recoveryCollisionParent 'Desktop' + $recoveryRenameFailureParent = Join-Path $root ([Guid]::NewGuid().ToString('N')) + $recoveryRenameFailureDesktop = Join-Path $recoveryRenameFailureParent 'Desktop' $noneTypePath = Join-Path $root ([Guid]::NewGuid().ToString('N')) $noneLengthPath = Join-Path $root ([Guid]::NewGuid().ToString('N')) $noneBytePath = Join-Path $root ([Guid]::NewGuid().ToString('N')) @@ -6067,29 +6074,53 @@ function Test-HkcuDesktopFixtureBoundaryRegression { $noneTypePath $noneLengthPath $noneBytePath Test-SupervisorFixtureRegistryDigestAttributionRegression $digestAttributionPath Set-HkcuFixtureBoundaryValueKinds $presentDesktop - $presentDigest = Get-HkcuFixtureRegistryDigest $presentDesktop - Assert-True (Test-HkcuFixtureRegistryDigest $presentDigest) ` - (Get-HkcuFixtureBoundaryDiagnostic) + $presentDigest = Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'BASELINE_DIGEST' ` + -Field 'REGISTRY_ROOT' ` + -Action { + $digest = Get-HkcuFixtureRegistryDigest $presentDesktop + Assert-True (Test-HkcuFixtureRegistryDigest $digest) ` + (Get-HkcuFixtureBoundaryDiagnostic) + $digest + } $presentBoundary = New-HkcuDesktopFixtureBoundaryState $presentDesktop Initialize-HkcuDesktopFixtureBoundary $presentBoundary Assert-True ($presentBoundary.BaselinePresent -and $presentBoundary.Relocated -and !(Test-Path -LiteralPath $presentDesktop) -and - (Test-Path -LiteralPath $presentBoundary.BackupPath) -and - (Get-HkcuFixtureRegistryDigest $presentBoundary.BackupPath) -ceq - $presentDigest) (Get-HkcuFixtureBoundaryDiagnostic) + (Test-Path -LiteralPath $presentBoundary.BackupPath)) ` + (Get-HkcuFixtureBoundaryDiagnostic) + Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'BASELINE_DIGEST' ` + -Field 'REGISTRY_ROOT' ` + -Action { + Assert-True ( + (Get-HkcuFixtureRegistryDigest $presentBoundary.BackupPath) -ceq + $presentDigest + ) (Get-HkcuFixtureBoundaryDiagnostic) + } [void](New-Item -Path $presentDesktop -Force -ErrorAction Stop) $presentFixtureOwned = $true (Get-Item -LiteralPath $presentDesktop).SetValue( 'installed', [int]1, [Microsoft.Win32.RegistryValueKind]::DWord) Restore-HkcuDesktopFixtureBoundary $presentBoundary $presentFixtureOwned - Assert-True ((Get-HkcuFixtureRegistryDigest $presentDesktop) -ceq - $presentDigest) (Get-HkcuFixtureBoundaryDiagnostic) + Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'FINAL_BASELINE_DIGEST' ` + -Field 'REGISTRY_ROOT' ` + -Action { + Assert-True ((Get-HkcuFixtureRegistryDigest $presentDesktop) -ceq + $presentDigest) (Get-HkcuFixtureBoundaryDiagnostic) + } Assert-True (!(Test-Path -LiteralPath $presentBoundary.BackupPath)) ` (Get-HkcuFixtureBoundaryDiagnostic) Assert-HkcuFixtureBoundaryValueKinds $presentDesktop - Assert-True ((Get-HkcuFixtureRegistryDigest $presentDesktop) -ceq - $presentDigest) (Get-HkcuFixtureBoundaryDiagnostic) + Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'FINAL_BASELINE_DIGEST' ` + -Field 'REGISTRY_ROOT' ` + -Action { + Assert-True ((Get-HkcuFixtureRegistryDigest $presentDesktop) -ceq + $presentDigest) (Get-HkcuFixtureBoundaryDiagnostic) + } [void](New-Item -Path $absentParent -Force -ErrorAction Stop) $absentBoundary = New-HkcuDesktopFixtureBoundaryState $absentDesktop @@ -6151,8 +6182,15 @@ function Test-HkcuDesktopFixtureBoundaryRegression { (Get-HkcuFixtureBoundaryDiagnostic) Set-HkcuFixtureBoundaryValueKinds $recoveryCollisionDesktop - $recoveryCollisionDigest = - Get-HkcuFixtureRegistryDigest $recoveryCollisionDesktop + $recoveryCollisionDigest = Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'BASELINE_DIGEST' ` + -Field 'REGISTRY_ROOT' ` + -Action { + $digest = Get-HkcuFixtureRegistryDigest $recoveryCollisionDesktop + Assert-True (Test-HkcuFixtureRegistryDigest $digest) ` + (Get-HkcuFixtureBoundaryDiagnostic) + $digest + } $recoveryCollisionBoundary = New-HkcuDesktopFixtureBoundaryState $recoveryCollisionDesktop Initialize-HkcuDesktopFixtureBoundary $recoveryCollisionBoundary @@ -6171,9 +6209,58 @@ function Test-HkcuDesktopFixtureBoundaryRegression { Assert-True ([string]$_.Exception.Message -ceq $expectedRecoveryCollision) ` (Get-HkcuFixtureBoundaryDiagnostic) } - Assert-True ((Test-Path -LiteralPath $recoveryCollisionDesktop) -and - (Get-HkcuFixtureRegistryDigest $recoveryCollisionDesktop) -ceq - $recoveryCollisionDigest) (Get-HkcuFixtureBoundaryDiagnostic) + Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'FINAL_BASELINE_DIGEST' ` + -Field 'REGISTRY_ROOT' ` + -Action { + Assert-True ((Test-Path -LiteralPath $recoveryCollisionDesktop) -and + (Get-HkcuFixtureRegistryDigest $recoveryCollisionDesktop) -ceq + $recoveryCollisionDigest) (Get-HkcuFixtureBoundaryDiagnostic) + } + + Set-HkcuFixtureBoundaryValueKinds $recoveryRenameFailureDesktop + $recoveryRenameFailureDigest = Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'BASELINE_DIGEST' ` + -Field 'REGISTRY_ROOT' ` + -Action { + $digest = Get-HkcuFixtureRegistryDigest $recoveryRenameFailureDesktop + Assert-True (Test-HkcuFixtureRegistryDigest $digest) ` + (Get-HkcuFixtureBoundaryDiagnostic) + $digest + } + $recoveryRenameFailureBoundary = + New-HkcuDesktopFixtureBoundaryState $recoveryRenameFailureDesktop + Initialize-HkcuDesktopFixtureBoundary $recoveryRenameFailureBoundary + $recoveryRenameFailureBoundary.ForcePostRestoreDigestMismatch = $true + $recoveryRenameFailureBoundary.ForceRecoveryRenameFailure = $true + $expectedRecoveryRenameFailure = Get-SanitizedSupervisorInvocationDiagnostic ` + 'HKCU_INSTALLED_VALUE_OWNERSHIP' ` + 'HKCU_BASELINE_RESTORE' ` + 'FIXTURE_SETUP' ` + 'RECOVERY_RELOCATE' ` + 'REGISTRY_PATH' + try { + Restore-HkcuDesktopFixtureBoundary $recoveryRenameFailureBoundary $false + Assert-True $false (Get-HkcuFixtureBoundaryDiagnostic) + } catch { + Assert-True ([string]$_.Exception.Message -ceq $expectedRecoveryRenameFailure) ` + (Get-HkcuFixtureBoundaryDiagnostic) + } + Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'FINAL_BASELINE_DIGEST' ` + -Field 'REGISTRY_ROOT' ` + -Action { + Assert-True ((Test-Path -LiteralPath $recoveryRenameFailureDesktop) -and + (Get-HkcuFixtureRegistryDigest $recoveryRenameFailureDesktop) -ceq + $recoveryRenameFailureDigest) (Get-HkcuFixtureBoundaryDiagnostic) + } + Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'FINAL_BACKUP_ABSENCE' ` + -Field 'REGISTRY_PATH' ` + -Action { + Assert-True (!(Test-Path -LiteralPath $recoveryRenameFailureBoundary.BackupPath)) ` + (Get-HkcuFixtureBoundaryDiagnostic) + } } finally { foreach ($path in @( $parent, @@ -6181,6 +6268,7 @@ function Test-HkcuDesktopFixtureBoundaryRegression { $absentForeignParent, $failureParent, $recoveryCollisionParent, + $recoveryRenameFailureParent, $noneTypePath, $noneLengthPath, $noneBytePath, diff --git a/apps/desktop/src/release-workflow.test.ts b/apps/desktop/src/release-workflow.test.ts index 2c054af72..f42737cf1 100644 --- a/apps/desktop/src/release-workflow.test.ts +++ b/apps/desktop/src/release-workflow.test.ts @@ -953,6 +953,8 @@ describe('desktop trusted release workflow', () => { ownedResourceSnapshot, /Get-SupervisorFixtureRegistryDigest \(\[string\]\$Owned\.RegistryPath\)/, ); + assert.doesNotMatch(ownedResourceSnapshot, /AttributeHkcuNativeValueRead/); + assert.doesNotMatch(ownedResourceSnapshot, /Invoke-HkcuDesktopFixtureOperation/); assert.doesNotMatch(ownedResourceSnapshot, /Get-HkcuFixtureRegistryDigest/); const hkcuDesktopBoundaryRestore = installedWindowsAppSupervisorBehaviorTest.slice( installedWindowsAppSupervisorBehaviorTest.indexOf('function Restore-HkcuDesktopFixtureBoundary'), @@ -1094,7 +1096,48 @@ describe('desktop trusted release workflow', () => { installedWindowsAppSupervisorBehaviorTest, /function Test-HkcuDesktopFixtureBoundaryRegression[\s\S]*Test-HkcuFixtureNativeNoneValueReadRegression[\s\S]*Test-SupervisorFixtureRegistryDigestAttributionRegression[\s\S]*Set-HkcuFixtureBoundaryValueKinds \$presentDesktop[\s\S]*\(Get-HkcuFixtureRegistryDigest \$presentBoundary\.BackupPath\) -ceq[\s\S]*Assert-HkcuFixtureBoundaryValueKinds \$presentDesktop[\s\S]*!\$absentBoundary\.BaselinePresent[\s\S]*\[string\]::IsNullOrWhiteSpace\(\[string\]\$absentBoundary\.BackupPath\)[\s\S]*Restore-HkcuDesktopFixtureBoundary \$absentForeignBoundary \$false[\s\S]*Test-Path -LiteralPath \$absentForeignDesktop[\s\S]*Restore-HkcuDesktopFixtureBoundary \$failureBoundary \$false[\s\S]*Test-Path -LiteralPath \$failureBoundary\.BackupPath[\s\S]*ForcePostRestoreDigestMismatch = \$true[\s\S]*ForceRecoveryBackupCollision = \$true[\s\S]*Restore-HkcuDesktopFixtureBoundary \$recoveryCollisionBoundary \$false[\s\S]*Get-HkcuFixtureRegistryDigest \$recoveryCollisionDesktop/, ); - assert.doesNotMatch(installedWindowsAppSupervisorBehaviorTest, /ForceRecoveryRenameFailure/); + const hkcuBoundaryRegression = installedWindowsAppSupervisorBehaviorTest.slice( + installedWindowsAppSupervisorBehaviorTest.indexOf( + 'function Test-HkcuDesktopFixtureBoundaryRegression', + ), + installedWindowsAppSupervisorBehaviorTest.indexOf( + 'function Test-HkcuInstalledValueOwnership', + ), + ); + assert.match( + hkcuBoundaryRegression, + /\$presentDigest = Invoke-HkcuDesktopFixtureOperation\s+`\n\s+-Callsite 'BASELINE_DIGEST'\s+`\n\s+-Field 'REGISTRY_ROOT'[\s\S]*Get-HkcuFixtureRegistryDigest \$presentDesktop/, + ); + assert.match( + hkcuBoundaryRegression, + /Invoke-HkcuDesktopFixtureOperation\s+`\n\s+-Callsite 'BASELINE_DIGEST'\s+`\n\s+-Field 'REGISTRY_ROOT'[\s\S]*Get-HkcuFixtureRegistryDigest \$presentBoundary\.BackupPath[\s\S]*\$presentDigest/, + ); + assert.equal( + (hkcuBoundaryRegression.match( + /Invoke-HkcuDesktopFixtureOperation\s+`\n\s+-Callsite 'FINAL_BASELINE_DIGEST'\s+`\n\s+-Field 'REGISTRY_ROOT'\s+`\n\s+-Action \{\n\s+Assert-True \(\(Get-HkcuFixtureRegistryDigest \$presentDesktop\) -ceq/g, + ) ?? []).length, + 2, + ); + assert.match( + hkcuBoundaryRegression, + /\$recoveryCollisionDigest = Invoke-HkcuDesktopFixtureOperation\s+`\n\s+-Callsite 'BASELINE_DIGEST'\s+`\n\s+-Field 'REGISTRY_ROOT'[\s\S]*Get-HkcuFixtureRegistryDigest \$recoveryCollisionDesktop/, + ); + assert.match( + hkcuBoundaryRegression, + /Invoke-HkcuDesktopFixtureOperation\s+`\n\s+-Callsite 'FINAL_BASELINE_DIGEST'\s+`\n\s+-Field 'REGISTRY_ROOT'[\s\S]*Get-HkcuFixtureRegistryDigest \$recoveryCollisionDesktop[\s\S]*\$recoveryCollisionDigest/, + ); + assert.match( + hkcuBoundaryRegression, + /ForceRecoveryBackupCollision = \$true[\s\S]*Restore-HkcuDesktopFixtureBoundary \$recoveryCollisionBoundary \$false/, + ); + assert.match( + hkcuBoundaryRegression, + /ForceRecoveryRenameFailure = \$true[\s\S]*Restore-HkcuDesktopFixtureBoundary \$recoveryRenameFailureBoundary \$false[\s\S]*Get-HkcuFixtureRegistryDigest \$recoveryRenameFailureDesktop/, + ); + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /ForceRecoveryRenameFailure[\s\S]*\$recoveryBackupLeaf = 'ProPRInvalid\\RecoveryBackup'[\s\S]*Rename-Item -LiteralPath \$Boundary\.DesktopKey\s+`\n\s+-NewName \$recoveryBackupLeaf -ErrorAction Stop/, + ); assert.match(installedWindowsAppSupervisorBehaviorTest, /RegSetValueEx/); assert.match( installedWindowsAppSupervisorBehaviorTest, From 4af5b0609b1a53653aace43280ac9201081390cf Mon Sep 17 00:00:00 2001 From: "propr-dev[bot]" <1316198+propr-dev[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 17:23:01 +0000 Subject: [PATCH 29/33] feat(ai): Implemented the narrow semantic attribution fix on head `30edcce07e2ca415aef6b30a450bfc44ab53b1d2`. No commit made. Implemented the narrow semantic attribution fix on head `30edcce07e2ca415aef6b30a450bfc44ab53b1d2`. No commit made. Changed: - [test-installed-windows-app-supervisor.ps1](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T17-11-35/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1): moved Initialize digest validity inside `BASELINE_DIGEST`; changed direct `BASELINE_DIGEST` / `FINAL_BASELINE_DIGEST` assertions to generic fixture throws; preserved distinct recovery collision and rename-failure coverage. - Added executable focused regressions for digest-invalid and equality-failure paths, asserting exact emitted tokens are `BASELINE_DIGEST` / `FINAL_BASELINE_DIGEST` and not broad `HKCU_BASELINE_STATE`. - [release-workflow.test.ts](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T17-11-35/apps/desktop/src/release-workflow.test.ts): updated static contract checks for the corrected semantics. Preserved: - Production cleanup/controller/runtime code unchanged. - Native `RegSetValueExW` fixture producer unchanged. - `package-lock.json` unchanged; `qs` / `fast-uri` untouched. Validation run: - `npx tsx --test apps/desktop/src/release-workflow.test.ts` passed. - `npm run test -w @propr/desktop` passed: 177 passed, 6 skipped. - `npm run audit:runtime` passed, 0 vulnerabilities. - `npm run desktop:audit:packaging` passed, 0 vulnerabilities. - `git diff --check` passed. - Digest-wrapper scan passed: 13 digest wrapper candidates checked, none contained `Get-HkcuFixtureBoundaryDiagnostic`. Not run: - Real native Windows x64/ARM64 supervisor/package jobs. This container is Linux and has no `pwsh`; those jobs require the committed remote ref in `desktop-release-guard.yml`, and the task explicitly says not to commit. PR: #2057 Comment by: @integry (ID: 5513411134) Model: gpt-5.5 --- .../test-installed-windows-app-supervisor.ps1 | 189 +++++++++++++++--- apps/desktop/src/release-workflow.test.ts | 16 +- 2 files changed, 174 insertions(+), 31 deletions(-) diff --git a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 index 6366f0d2c..be0652c52 100644 --- a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 +++ b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 @@ -1559,6 +1559,15 @@ function Get-HkcuFixtureBoundaryDiagnostic { 'REGISTRY_PATH' } +function Get-HkcuFixtureDigestDiagnostic([string]$Callsite) { + return Get-SanitizedSupervisorInvocationDiagnostic ` + 'HKCU_INSTALLED_VALUE_OWNERSHIP' ` + 'HKCU_BASELINE_RESTORE' ` + 'FIXTURE_SETUP' ` + $Callsite ` + 'REGISTRY_ROOT' +} + function Assert-HkcuDesktopFixtureOperation([bool]$Condition) { if (!$Condition) { throw 'hkcu desktop fixture operation failed' } } @@ -1629,9 +1638,10 @@ function Initialize-HkcuDesktopFixtureBoundary($Boundary) { -Callsite 'BASELINE_DIGEST' ` -Field 'REGISTRY_ROOT' ` -Action { - Get-HkcuFixtureRegistryDigest ([string]$Boundary.DesktopKey) + $digest = Get-HkcuFixtureRegistryDigest ([string]$Boundary.DesktopKey) + Assert-HkcuDesktopFixtureOperation (Test-HkcuFixtureRegistryDigest $digest) + $digest } - Assert-HkcuDesktopFixtureOperation (Test-HkcuFixtureRegistryDigest $baselineDigest) $Boundary.BaselineDigest = $baselineDigest $Boundary.BackupLeaf = [Guid]::NewGuid().ToString('D') $Boundary.BackupPath = Join-Path ` @@ -1676,9 +1686,11 @@ function Restore-HkcuDesktopFixtureBoundary( -Callsite 'BASELINE_DIGEST' ` -Field 'REGISTRY_ROOT' ` -Action { - Get-HkcuFixtureRegistryDigest ([string]$Boundary.BackupPath) + $digest = Get-HkcuFixtureRegistryDigest ([string]$Boundary.BackupPath) + Assert-HkcuDesktopFixtureOperation ` + ($digest -ceq [string]$Boundary.BaselineDigest) + $digest } - Assert-HkcuDesktopFixtureOperation ($backupDigest -ceq [string]$Boundary.BaselineDigest) if (Test-Path -LiteralPath $Boundary.DesktopKey) { Invoke-HkcuDesktopFixtureOperation ` -Callsite 'TARGET_OWNERSHIP' ` @@ -1692,13 +1704,24 @@ function Restore-HkcuDesktopFixtureBoundary( Assert-HkcuDesktopFixtureOperation (!(Test-Path -LiteralPath $Boundary.DesktopKey)) Rename-Item -LiteralPath $Boundary.BackupPath ` -NewName ([string]$Boundary.DesktopLeaf) -ErrorAction Stop - $restoredDigest = Invoke-HkcuDesktopFixtureOperation ` - -Callsite 'BASELINE_DIGEST' ` - -Field 'REGISTRY_ROOT' ` - -Action { - Get-HkcuFixtureRegistryDigest ([string]$Boundary.DesktopKey) - } + $restoredDigestFailure = $null + try { + $restoredDigest = Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'BASELINE_DIGEST' ` + -Field 'REGISTRY_ROOT' ` + -Action { + $digest = Get-HkcuFixtureRegistryDigest ([string]$Boundary.DesktopKey) + Assert-HkcuDesktopFixtureOperation (Test-HkcuFixtureRegistryDigest $digest) + Assert-HkcuDesktopFixtureOperation ` + ($digest -ceq [string]$Boundary.BaselineDigest) + $digest + } + } catch { + $restoredDigestFailure = [string]$_.Exception.Message + $restoredDigest = 'INVALID' + } if ($Boundary.ForcePostRestoreDigestMismatch) { + $restoredDigestFailure = Get-HkcuFixtureDigestDiagnostic 'BASELINE_DIGEST' $restoredDigest = 'INVALID' } if ($restoredDigest -cne [string]$Boundary.BaselineDigest) { @@ -1722,7 +1745,6 @@ function Restore-HkcuDesktopFixtureBoundary( } Assert-HkcuDesktopFixtureOperation (Test-Path -LiteralPath $Boundary.BackupPath) Assert-HkcuDesktopFixtureOperation (!(Test-Path -LiteralPath $Boundary.DesktopKey)) - throw 'post-restore digest mismatch' } catch { $desktopDigest = if (Test-Path -LiteralPath $Boundary.DesktopKey) { Get-HkcuFixtureRegistryDigest ([string]$Boundary.DesktopKey) @@ -1735,13 +1757,15 @@ function Restore-HkcuDesktopFixtureBoundary( $backupDigestAfterRecovery -ceq [string]$Boundary.BaselineDigest ) throw - } } } + throw $restoredDigestFailure + } Invoke-HkcuDesktopFixtureOperation ` -Callsite 'FINAL_BASELINE_DIGEST' ` -Field 'REGISTRY_ROOT' ` -Action { + Assert-HkcuDesktopFixtureOperation (Test-HkcuFixtureRegistryDigest $restoredDigest) Assert-HkcuDesktopFixtureOperation ` ($restoredDigest -ceq [string]$Boundary.BaselineDigest) } @@ -6045,6 +6069,93 @@ function Test-SupervisorFixtureRegistryDigestAttributionRegression([string]$Path } } +function Assert-HkcuFixtureDigestFailureAttribution( + [scriptblock]$Action, + [string]$ExpectedCallsite +) { + $expected = Get-HkcuFixtureDigestDiagnostic $ExpectedCallsite + $broadBaseline = Get-HkcuFixtureBoundaryDiagnostic + $actual = $null + try { + & $Action + Assert-HkcuDesktopFixtureOperation $false + } catch { + $actual = [string]$_.Exception.Message + } + Assert-HkcuDesktopFixtureOperation ($actual -ceq $expected) + Assert-HkcuDesktopFixtureOperation ($actual -cne $broadBaseline) +} + +function Get-HkcuFixtureDifferentDigest([string]$Digest) { + $zeroDigest = '0' * 64 + if ([string]$Digest -cne $zeroDigest) { return $zeroDigest } + return 'f' * 64 +} + +function Test-HkcuFixtureDigestFailureAttributionRegression( + [string]$InitializeInvalidPath, + [string]$BackupEqualityPath, + [string]$PostRestoreEqualityPath, + [string]$FinalEqualityPath +) { + Set-HkcuFixtureBoundaryValueKinds $InitializeInvalidPath + $originalHkcuDigest = + (Get-Command Get-HkcuFixtureRegistryDigest -CommandType Function).ScriptBlock + function Get-HkcuFixtureRegistryDigest { return 'INVALID' } + try { + $initializeInvalidBoundary = + New-HkcuDesktopFixtureBoundaryState $InitializeInvalidPath + Assert-HkcuFixtureDigestFailureAttribution ` + { Initialize-HkcuDesktopFixtureBoundary $initializeInvalidBoundary } ` + 'BASELINE_DIGEST' + Assert-HkcuDesktopFixtureOperation (Test-Path -LiteralPath $InitializeInvalidPath) + } finally { + Set-Item -Path Function:\Get-HkcuFixtureRegistryDigest ` + -Value $originalHkcuDigest + } + + Set-HkcuFixtureBoundaryValueKinds $BackupEqualityPath + $backupEqualityBoundary = New-HkcuDesktopFixtureBoundaryState $BackupEqualityPath + Initialize-HkcuDesktopFixtureBoundary $backupEqualityBoundary + $backupEqualityBoundary.BaselineDigest = + Get-HkcuFixtureDifferentDigest ([string]$backupEqualityBoundary.BaselineDigest) + Assert-HkcuFixtureDigestFailureAttribution ` + { Restore-HkcuDesktopFixtureBoundary $backupEqualityBoundary $false } ` + 'BASELINE_DIGEST' + Assert-HkcuDesktopFixtureOperation ` + ((Test-Path -LiteralPath $backupEqualityBoundary.BackupPath) -and + !(Test-Path -LiteralPath $BackupEqualityPath)) + + Set-HkcuFixtureBoundaryValueKinds $PostRestoreEqualityPath + $postRestoreEqualityBoundary = + New-HkcuDesktopFixtureBoundaryState $PostRestoreEqualityPath + Initialize-HkcuDesktopFixtureBoundary $postRestoreEqualityBoundary + $postRestoreEqualityBoundary.ForcePostRestoreDigestMismatch = $true + Assert-HkcuFixtureDigestFailureAttribution ` + { Restore-HkcuDesktopFixtureBoundary $postRestoreEqualityBoundary $false } ` + 'BASELINE_DIGEST' + Assert-HkcuDesktopFixtureOperation ` + ((Test-Path -LiteralPath $postRestoreEqualityBoundary.BackupPath) -and + !(Test-Path -LiteralPath $PostRestoreEqualityPath)) + + Set-HkcuFixtureBoundaryValueKinds $FinalEqualityPath + $finalDigest = Get-HkcuFixtureRegistryDigest $FinalEqualityPath + $differentFinalDigest = Get-HkcuFixtureDifferentDigest $finalDigest + Assert-HkcuFixtureDigestFailureAttribution ` + { + Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'FINAL_BASELINE_DIGEST' ` + -Field 'REGISTRY_ROOT' ` + -Action { + Assert-HkcuDesktopFixtureOperation ( + (Get-HkcuFixtureRegistryDigest $FinalEqualityPath) -ceq + $differentFinalDigest + ) + } + } ` + 'FINAL_BASELINE_DIGEST' +} + function Test-HkcuDesktopFixtureBoundaryRegression { $root = 'Registry::HKEY_CURRENT_USER\Software\ProPRSupervisorFixture' $parent = Join-Path $root ([Guid]::NewGuid().ToString('N')) @@ -6063,6 +6174,14 @@ function Test-HkcuDesktopFixtureBoundaryRegression { $noneLengthPath = Join-Path $root ([Guid]::NewGuid().ToString('N')) $noneBytePath = Join-Path $root ([Guid]::NewGuid().ToString('N')) $digestAttributionPath = Join-Path $root ([Guid]::NewGuid().ToString('N')) + $initializeInvalidParent = Join-Path $root ([Guid]::NewGuid().ToString('N')) + $initializeInvalidDesktop = Join-Path $initializeInvalidParent 'Desktop' + $backupEqualityParent = Join-Path $root ([Guid]::NewGuid().ToString('N')) + $backupEqualityDesktop = Join-Path $backupEqualityParent 'Desktop' + $postRestoreEqualityParent = Join-Path $root ([Guid]::NewGuid().ToString('N')) + $postRestoreEqualityDesktop = Join-Path $postRestoreEqualityParent 'Desktop' + $finalEqualityParent = Join-Path $root ([Guid]::NewGuid().ToString('N')) + $finalEqualityDesktop = Join-Path $finalEqualityParent 'Desktop' Invoke-SupervisorAttributedOperation ` -Scenario 'HKCU_BASELINE_RESTORE' ` -Phase 'FIXTURE_SETUP' ` @@ -6073,14 +6192,18 @@ function Test-HkcuDesktopFixtureBoundaryRegression { Test-HkcuFixtureNativeNoneValueReadRegression ` $noneTypePath $noneLengthPath $noneBytePath Test-SupervisorFixtureRegistryDigestAttributionRegression $digestAttributionPath + Test-HkcuFixtureDigestFailureAttributionRegression ` + $initializeInvalidDesktop ` + $backupEqualityDesktop ` + $postRestoreEqualityDesktop ` + $finalEqualityDesktop Set-HkcuFixtureBoundaryValueKinds $presentDesktop $presentDigest = Invoke-HkcuDesktopFixtureOperation ` -Callsite 'BASELINE_DIGEST' ` -Field 'REGISTRY_ROOT' ` -Action { $digest = Get-HkcuFixtureRegistryDigest $presentDesktop - Assert-True (Test-HkcuFixtureRegistryDigest $digest) ` - (Get-HkcuFixtureBoundaryDiagnostic) + Assert-HkcuDesktopFixtureOperation (Test-HkcuFixtureRegistryDigest $digest) $digest } $presentBoundary = New-HkcuDesktopFixtureBoundaryState $presentDesktop @@ -6094,10 +6217,10 @@ function Test-HkcuDesktopFixtureBoundaryRegression { -Callsite 'BASELINE_DIGEST' ` -Field 'REGISTRY_ROOT' ` -Action { - Assert-True ( + Assert-HkcuDesktopFixtureOperation ( (Get-HkcuFixtureRegistryDigest $presentBoundary.BackupPath) -ceq $presentDigest - ) (Get-HkcuFixtureBoundaryDiagnostic) + ) } [void](New-Item -Path $presentDesktop -Force -ErrorAction Stop) $presentFixtureOwned = $true @@ -6108,8 +6231,9 @@ function Test-HkcuDesktopFixtureBoundaryRegression { -Callsite 'FINAL_BASELINE_DIGEST' ` -Field 'REGISTRY_ROOT' ` -Action { - Assert-True ((Get-HkcuFixtureRegistryDigest $presentDesktop) -ceq - $presentDigest) (Get-HkcuFixtureBoundaryDiagnostic) + Assert-HkcuDesktopFixtureOperation ( + (Get-HkcuFixtureRegistryDigest $presentDesktop) -ceq $presentDigest + ) } Assert-True (!(Test-Path -LiteralPath $presentBoundary.BackupPath)) ` (Get-HkcuFixtureBoundaryDiagnostic) @@ -6118,8 +6242,9 @@ function Test-HkcuDesktopFixtureBoundaryRegression { -Callsite 'FINAL_BASELINE_DIGEST' ` -Field 'REGISTRY_ROOT' ` -Action { - Assert-True ((Get-HkcuFixtureRegistryDigest $presentDesktop) -ceq - $presentDigest) (Get-HkcuFixtureBoundaryDiagnostic) + Assert-HkcuDesktopFixtureOperation ( + (Get-HkcuFixtureRegistryDigest $presentDesktop) -ceq $presentDigest + ) } [void](New-Item -Path $absentParent -Force -ErrorAction Stop) @@ -6187,8 +6312,7 @@ function Test-HkcuDesktopFixtureBoundaryRegression { -Field 'REGISTRY_ROOT' ` -Action { $digest = Get-HkcuFixtureRegistryDigest $recoveryCollisionDesktop - Assert-True (Test-HkcuFixtureRegistryDigest $digest) ` - (Get-HkcuFixtureBoundaryDiagnostic) + Assert-HkcuDesktopFixtureOperation (Test-HkcuFixtureRegistryDigest $digest) $digest } $recoveryCollisionBoundary = @@ -6213,9 +6337,11 @@ function Test-HkcuDesktopFixtureBoundaryRegression { -Callsite 'FINAL_BASELINE_DIGEST' ` -Field 'REGISTRY_ROOT' ` -Action { - Assert-True ((Test-Path -LiteralPath $recoveryCollisionDesktop) -and + Assert-HkcuDesktopFixtureOperation ( + (Test-Path -LiteralPath $recoveryCollisionDesktop) -and (Get-HkcuFixtureRegistryDigest $recoveryCollisionDesktop) -ceq - $recoveryCollisionDigest) (Get-HkcuFixtureBoundaryDiagnostic) + $recoveryCollisionDigest + ) } Set-HkcuFixtureBoundaryValueKinds $recoveryRenameFailureDesktop @@ -6224,8 +6350,7 @@ function Test-HkcuDesktopFixtureBoundaryRegression { -Field 'REGISTRY_ROOT' ` -Action { $digest = Get-HkcuFixtureRegistryDigest $recoveryRenameFailureDesktop - Assert-True (Test-HkcuFixtureRegistryDigest $digest) ` - (Get-HkcuFixtureBoundaryDiagnostic) + Assert-HkcuDesktopFixtureOperation (Test-HkcuFixtureRegistryDigest $digest) $digest } $recoveryRenameFailureBoundary = @@ -6250,9 +6375,11 @@ function Test-HkcuDesktopFixtureBoundaryRegression { -Callsite 'FINAL_BASELINE_DIGEST' ` -Field 'REGISTRY_ROOT' ` -Action { - Assert-True ((Test-Path -LiteralPath $recoveryRenameFailureDesktop) -and + Assert-HkcuDesktopFixtureOperation ( + (Test-Path -LiteralPath $recoveryRenameFailureDesktop) -and (Get-HkcuFixtureRegistryDigest $recoveryRenameFailureDesktop) -ceq - $recoveryRenameFailureDigest) (Get-HkcuFixtureBoundaryDiagnostic) + $recoveryRenameFailureDigest + ) } Invoke-HkcuDesktopFixtureOperation ` -Callsite 'FINAL_BACKUP_ABSENCE' ` @@ -6272,7 +6399,11 @@ function Test-HkcuDesktopFixtureBoundaryRegression { $noneTypePath, $noneLengthPath, $noneBytePath, - $digestAttributionPath + $digestAttributionPath, + $initializeInvalidParent, + $backupEqualityParent, + $postRestoreEqualityParent, + $finalEqualityParent )) { if (Test-Path -LiteralPath $path) { Remove-Item -LiteralPath $path -Recurse -Force -ErrorAction SilentlyContinue diff --git a/apps/desktop/src/release-workflow.test.ts b/apps/desktop/src/release-workflow.test.ts index f42737cf1..3444009cd 100644 --- a/apps/desktop/src/release-workflow.test.ts +++ b/apps/desktop/src/release-workflow.test.ts @@ -1108,16 +1108,28 @@ describe('desktop trusted release workflow', () => { hkcuBoundaryRegression, /\$presentDigest = Invoke-HkcuDesktopFixtureOperation\s+`\n\s+-Callsite 'BASELINE_DIGEST'\s+`\n\s+-Field 'REGISTRY_ROOT'[\s\S]*Get-HkcuFixtureRegistryDigest \$presentDesktop/, ); + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /function Initialize-HkcuDesktopFixtureBoundary[\s\S]*-Callsite 'BASELINE_DIGEST'\s+`\n\s+-Field 'REGISTRY_ROOT'\s+`\n\s+-Action \{\n\s+\$digest = Get-HkcuFixtureRegistryDigest \(\[string\]\$Boundary\.DesktopKey\)\n\s+Assert-HkcuDesktopFixtureOperation \(Test-HkcuFixtureRegistryDigest \$digest\)\n\s+\$digest\n\s+\}\n\s+\$Boundary\.BaselineDigest = \$baselineDigest/, + ); assert.match( hkcuBoundaryRegression, - /Invoke-HkcuDesktopFixtureOperation\s+`\n\s+-Callsite 'BASELINE_DIGEST'\s+`\n\s+-Field 'REGISTRY_ROOT'[\s\S]*Get-HkcuFixtureRegistryDigest \$presentBoundary\.BackupPath[\s\S]*\$presentDigest/, + /Invoke-HkcuDesktopFixtureOperation\s+`\n\s+-Callsite 'BASELINE_DIGEST'\s+`\n\s+-Field 'REGISTRY_ROOT'[\s\S]*Assert-HkcuDesktopFixtureOperation \(\n\s+\(Get-HkcuFixtureRegistryDigest \$presentBoundary\.BackupPath\) -ceq\n\s+\$presentDigest\n\s+\)/, ); assert.equal( (hkcuBoundaryRegression.match( - /Invoke-HkcuDesktopFixtureOperation\s+`\n\s+-Callsite 'FINAL_BASELINE_DIGEST'\s+`\n\s+-Field 'REGISTRY_ROOT'\s+`\n\s+-Action \{\n\s+Assert-True \(\(Get-HkcuFixtureRegistryDigest \$presentDesktop\) -ceq/g, + /Invoke-HkcuDesktopFixtureOperation\s+`\n\s+-Callsite 'FINAL_BASELINE_DIGEST'\s+`\n\s+-Field 'REGISTRY_ROOT'\s+`\n\s+-Action \{\n\s+Assert-HkcuDesktopFixtureOperation \(\n\s+\(Get-HkcuFixtureRegistryDigest \$presentDesktop\) -ceq \$presentDigest/g, ) ?? []).length, 2, ); + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /function Assert-HkcuFixtureDigestFailureAttribution[\s\S]*\$actual = \[string\]\$_\.Exception\.Message[\s\S]*Assert-HkcuDesktopFixtureOperation \(\$actual -ceq \$expected\)[\s\S]*Assert-HkcuDesktopFixtureOperation \(\$actual -cne \$broadBaseline\)/, + ); + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /function Test-HkcuFixtureDigestFailureAttributionRegression[\s\S]*Initialize-HkcuDesktopFixtureBoundary \$initializeInvalidBoundary[\s\S]*'BASELINE_DIGEST'[\s\S]*Restore-HkcuDesktopFixtureBoundary \$backupEqualityBoundary \$false[\s\S]*'BASELINE_DIGEST'[\s\S]*ForcePostRestoreDigestMismatch = \$true[\s\S]*Restore-HkcuDesktopFixtureBoundary \$postRestoreEqualityBoundary \$false[\s\S]*'BASELINE_DIGEST'[\s\S]*-Callsite 'FINAL_BASELINE_DIGEST'[\s\S]*'FINAL_BASELINE_DIGEST'/, + ); assert.match( hkcuBoundaryRegression, /\$recoveryCollisionDigest = Invoke-HkcuDesktopFixtureOperation\s+`\n\s+-Callsite 'BASELINE_DIGEST'\s+`\n\s+-Field 'REGISTRY_ROOT'[\s\S]*Get-HkcuFixtureRegistryDigest \$recoveryCollisionDesktop/, From 54dddc835e8847195f1c7d97716d5d2cadc6bf6e Mon Sep 17 00:00:00 2001 From: "propr-dev[bot]" <1316198+propr-dev[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 17:37:30 +0000 Subject: [PATCH 30/33] feat(ai): Implemented the two requested fixture-only follow-ups on exact head `4af5b0609b1a53653aace43280ac9201081390cf`. Implemented the two requested fixture-only follow-ups on exact head `4af5b0609b1a53653aace43280ac9201081390cf`. Changed: - [test-installed-windows-app-supervisor.ps1](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T17-27-40/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1:1727): recovery relocation now computes the moved-back `BackupPath` digest through `Get-HkcuFixtureRegistryDigest` and requires exact `BaselineDigest` equality before rethrowing the saved baseline failure. If proof fails, it reports `RECOVERY_RELOCATE`. - [test-installed-windows-app-supervisor.ps1](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T17-27-40/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1:5951): split `REGRESSION_VALUE_SETUP / REGISTRY_VALUE` into fixed sub-operations for root key setup, value-kind setup, native `REG_NONE` write with `NATIVE_RETURN_CODE`, nested key setup, and nested value setup. - [release-workflow.test.ts](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T17-27-40/apps/desktop/src/release-workflow.test.ts:1133): updated static regressions to require the new digest proof and reject the old broad setup attribution. Validation: - `npx tsx --test apps/desktop/src/release-workflow.test.ts` passed. - `npm run test -w @propr/desktop` passed: 177 pass, 6 platform skips. - `git diff --check` passed. Native x64/ARM64 gates were not run locally: this container is Linux and has no `pwsh`, so there is no native Windows failing token to report from this environment. `package-lock.json` was not changed. PR: #2057 Comment by: @integry (ID: 5513619014) Model: gpt-5.5 --- .../test-installed-windows-app-supervisor.ps1 | 164 +++++++++++++++++- apps/desktop/src/release-workflow.test.ts | 27 ++- 2 files changed, 178 insertions(+), 13 deletions(-) diff --git a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 index be0652c52..1fc0836a4 100644 --- a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 +++ b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 @@ -1745,12 +1745,29 @@ function Restore-HkcuDesktopFixtureBoundary( } Assert-HkcuDesktopFixtureOperation (Test-Path -LiteralPath $Boundary.BackupPath) Assert-HkcuDesktopFixtureOperation (!(Test-Path -LiteralPath $Boundary.DesktopKey)) + $recoveredBackupDigest = try { + Get-HkcuFixtureRegistryDigest ([string]$Boundary.BackupPath) + } catch { + 'INVALID' + } + Assert-HkcuDesktopFixtureOperation ( + Test-HkcuFixtureRegistryDigest $recoveredBackupDigest) + Assert-HkcuDesktopFixtureOperation ( + $recoveredBackupDigest -ceq [string]$Boundary.BaselineDigest) } catch { $desktopDigest = if (Test-Path -LiteralPath $Boundary.DesktopKey) { - Get-HkcuFixtureRegistryDigest ([string]$Boundary.DesktopKey) + try { + Get-HkcuFixtureRegistryDigest ([string]$Boundary.DesktopKey) + } catch { + 'INVALID' + } } else { 'MISSING' } $backupDigestAfterRecovery = if (Test-Path -LiteralPath $Boundary.BackupPath) { - Get-HkcuFixtureRegistryDigest ([string]$Boundary.BackupPath) + try { + Get-HkcuFixtureRegistryDigest ([string]$Boundary.BackupPath) + } catch { + 'INVALID' + } } else { 'MISSING' } Assert-HkcuDesktopFixtureOperation ( $desktopDigest -ceq [string]$Boundary.BaselineDigest -or @@ -2334,7 +2351,11 @@ function Get-SupervisorInvocationCallsites { 'EARLY_PROCESS_STATE_PATH', 'EARLY_PROCESS_STATE_READ', 'HKCU_BASELINE_STATE', - 'REGRESSION_VALUE_SETUP', + 'REGRESSION_ROOT_KEY_SETUP', + 'REGRESSION_VALUE_KIND_SETUP', + 'REGRESSION_NATIVE_NONE_WRITE', + 'REGRESSION_NESTED_KEY_SETUP', + 'REGRESSION_NESTED_VALUE_SETUP', 'NATIVE_VALUE_READ', 'BASELINE_DIGEST', 'BASELINE_RELOCATE', @@ -2388,6 +2409,7 @@ function Get-SupervisorInvocationFields { 'REGISTRY_PATH', 'REGISTRY_ROOT', 'REGISTRY_VALUE', + 'NATIVE_RETURN_CODE', 'USER_NAME', 'USER_SID', 'PROFILE_PATH', @@ -2552,7 +2574,11 @@ function Get-SupervisorAttributionTotalityCases { 'CALLSITE_EARLY_PROCESS_TREE_ASSERTION', 'CALLSITE_EARLY_MANIFEST_PRESERVATION', 'CALLSITE_HKCU_BASELINE_STATE', - 'CALLSITE_HKCU_REGRESSION_VALUE_SETUP', + 'CALLSITE_HKCU_REGRESSION_ROOT_KEY_SETUP', + 'CALLSITE_HKCU_REGRESSION_VALUE_KIND_SETUP', + 'CALLSITE_HKCU_REGRESSION_NATIVE_NONE_WRITE', + 'CALLSITE_HKCU_REGRESSION_NESTED_KEY_SETUP', + 'CALLSITE_HKCU_REGRESSION_NESTED_VALUE_SETUP', 'CALLSITE_HKCU_NATIVE_VALUE_READ', 'CALLSITE_HKCU_BASELINE_DIGEST', 'CALLSITE_HKCU_BASELINE_RELOCATE', @@ -3743,11 +3769,39 @@ function Test-SupervisorInvocationAttributionTotality { Callsite='HKCU_BASELINE_STATE'; Field='REGISTRY_PATH' }, [PSCustomObject]@{ - CaseId='CALLSITE_HKCU_REGRESSION_VALUE_SETUP' + CaseId='CALLSITE_HKCU_REGRESSION_ROOT_KEY_SETUP' + Test='HKCU_INSTALLED_VALUE_OWNERSHIP' + Scenario='HKCU_BASELINE_RESTORE' + Phase='FIXTURE_SETUP' + Callsite='REGRESSION_ROOT_KEY_SETUP'; Field='REGISTRY_PATH' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_HKCU_REGRESSION_VALUE_KIND_SETUP' + Test='HKCU_INSTALLED_VALUE_OWNERSHIP' + Scenario='HKCU_BASELINE_RESTORE' + Phase='FIXTURE_SETUP' + Callsite='REGRESSION_VALUE_KIND_SETUP'; Field='REGISTRY_VALUE' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_HKCU_REGRESSION_NATIVE_NONE_WRITE' Test='HKCU_INSTALLED_VALUE_OWNERSHIP' Scenario='HKCU_BASELINE_RESTORE' Phase='FIXTURE_SETUP' - Callsite='REGRESSION_VALUE_SETUP'; Field='REGISTRY_VALUE' + Callsite='REGRESSION_NATIVE_NONE_WRITE'; Field='NATIVE_RETURN_CODE' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_HKCU_REGRESSION_NESTED_KEY_SETUP' + Test='HKCU_INSTALLED_VALUE_OWNERSHIP' + Scenario='HKCU_BASELINE_RESTORE' + Phase='FIXTURE_SETUP' + Callsite='REGRESSION_NESTED_KEY_SETUP'; Field='REGISTRY_PATH' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_HKCU_REGRESSION_NESTED_VALUE_SETUP' + Test='HKCU_INSTALLED_VALUE_OWNERSHIP' + Scenario='HKCU_BASELINE_RESTORE' + Phase='FIXTURE_SETUP' + Callsite='REGRESSION_NESTED_VALUE_SETUP'; Field='REGISTRY_VALUE' }, [PSCustomObject]@{ CaseId='CALLSITE_HKCU_NATIVE_VALUE_READ' @@ -5896,10 +5950,16 @@ function Test-PreExistingAppPathsAuthority { function Set-HkcuFixtureBoundaryValueKinds([string]$Path) { Invoke-HkcuDesktopFixtureOperation ` - -Callsite 'REGRESSION_VALUE_SETUP' ` - -Field 'REGISTRY_VALUE' ` + -Callsite 'REGRESSION_ROOT_KEY_SETUP' ` + -Field 'REGISTRY_PATH' ` -Action { [void](New-Item -Path $Path -Force -ErrorAction Stop) + Assert-HkcuDesktopFixtureOperation (Test-Path -LiteralPath $Path) + } + Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'REGRESSION_VALUE_KIND_SETUP' ` + -Field 'REGISTRY_VALUE' ` + -Action { $key = Get-Item -LiteralPath $Path -ErrorAction Stop $key.SetValue('', 'default-string', [Microsoft.Win32.RegistryValueKind]::String) $key.SetValue('StringValue', 'plain-string', [Microsoft.Win32.RegistryValueKind]::String) @@ -5924,11 +5984,30 @@ function Set-HkcuFixtureBoundaryValueKinds([string]$Path) { [string[]]@('alpha', '', 'omega'), [Microsoft.Win32.RegistryValueKind]::MultiString ) + } + Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'REGRESSION_NATIVE_NONE_WRITE' ` + -Field 'NATIVE_RETURN_CODE' ` + -Action { + $key = Get-Item -LiteralPath $Path -ErrorAction Stop Set-SupervisorFixtureRegistryValueNativeBytes ` $key 'NoneValue' 0 ([byte[]]@(9, 8, 7)) + } + Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'REGRESSION_NESTED_KEY_SETUP' ` + -Field 'REGISTRY_PATH' ` + -Action { $nested = Join-Path $Path 'Nested' $child = Join-Path $nested 'Child' [void](New-Item -Path $child -Force -ErrorAction Stop) + Assert-HkcuDesktopFixtureOperation (Test-Path -LiteralPath $child) + } + Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'REGRESSION_NESTED_VALUE_SETUP' ` + -Field 'REGISTRY_VALUE' ` + -Action { + $nested = Join-Path $Path 'Nested' + $child = Join-Path $nested 'Child' $childKey = Get-Item -LiteralPath $child -ErrorAction Stop $childKey.SetValue('NestedValue', 'nested-string', [Microsoft.Win32.RegistryValueKind]::String) } @@ -6096,6 +6175,7 @@ function Test-HkcuFixtureDigestFailureAttributionRegression( [string]$InitializeInvalidPath, [string]$BackupEqualityPath, [string]$PostRestoreEqualityPath, + [string]$RecoveryProofPath, [string]$FinalEqualityPath ) { Set-HkcuFixtureBoundaryValueKinds $InitializeInvalidPath @@ -6137,6 +6217,70 @@ function Test-HkcuFixtureDigestFailureAttributionRegression( Assert-HkcuDesktopFixtureOperation ` ((Test-Path -LiteralPath $postRestoreEqualityBoundary.BackupPath) -and !(Test-Path -LiteralPath $PostRestoreEqualityPath)) + Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'FINAL_BASELINE_DIGEST' ` + -Field 'REGISTRY_ROOT' ` + -Action { + Assert-HkcuDesktopFixtureOperation ( + (Get-HkcuFixtureRegistryDigest $postRestoreEqualityBoundary.BackupPath) -ceq + [string]$postRestoreEqualityBoundary.BaselineDigest + ) + } + + Set-HkcuFixtureBoundaryValueKinds $RecoveryProofPath + $recoveryProofDigest = Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'BASELINE_DIGEST' ` + -Field 'REGISTRY_ROOT' ` + -Action { + $digest = Get-HkcuFixtureRegistryDigest $RecoveryProofPath + Assert-HkcuDesktopFixtureOperation (Test-HkcuFixtureRegistryDigest $digest) + $digest + } + $recoveryProofBoundary = New-HkcuDesktopFixtureBoundaryState $RecoveryProofPath + Initialize-HkcuDesktopFixtureBoundary $recoveryProofBoundary + $recoveryProofBoundary.ForcePostRestoreDigestMismatch = $true + $recoveryProofBackupPath = [string]$recoveryProofBoundary.BackupPath + $recoveryProofBadDigest = Get-HkcuFixtureDifferentDigest $recoveryProofDigest + $originalHkcuDigestForRecoveryProof = + (Get-Command Get-HkcuFixtureRegistryDigest -CommandType Function).ScriptBlock + function Get-HkcuFixtureRegistryDigest([string]$Path) { + if ([string]::Equals( + [string]$Path, + [string]$recoveryProofBackupPath, + [StringComparison]::OrdinalIgnoreCase + )) { + return $recoveryProofBadDigest + } + & $originalHkcuDigestForRecoveryProof $Path + } + $expectedRecoveryProofFailure = Get-SanitizedSupervisorInvocationDiagnostic ` + 'HKCU_INSTALLED_VALUE_OWNERSHIP' ` + 'HKCU_BASELINE_RESTORE' ` + 'FIXTURE_SETUP' ` + 'RECOVERY_RELOCATE' ` + 'REGISTRY_PATH' + try { + Restore-HkcuDesktopFixtureBoundary $recoveryProofBoundary $false + Assert-True $false (Get-HkcuFixtureBoundaryDiagnostic) + } catch { + Assert-True ([string]$_.Exception.Message -ceq $expectedRecoveryProofFailure) ` + (Get-HkcuFixtureBoundaryDiagnostic) + } finally { + Set-Item -Path Function:\Get-HkcuFixtureRegistryDigest ` + -Value $originalHkcuDigestForRecoveryProof + } + Assert-HkcuDesktopFixtureOperation ` + ((Test-Path -LiteralPath $recoveryProofBoundary.BackupPath) -and + !(Test-Path -LiteralPath $RecoveryProofPath)) + Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'FINAL_BASELINE_DIGEST' ` + -Field 'REGISTRY_ROOT' ` + -Action { + Assert-HkcuDesktopFixtureOperation ( + (Get-HkcuFixtureRegistryDigest $recoveryProofBoundary.BackupPath) -ceq + $recoveryProofDigest + ) + } Set-HkcuFixtureBoundaryValueKinds $FinalEqualityPath $finalDigest = Get-HkcuFixtureRegistryDigest $FinalEqualityPath @@ -6180,6 +6324,8 @@ function Test-HkcuDesktopFixtureBoundaryRegression { $backupEqualityDesktop = Join-Path $backupEqualityParent 'Desktop' $postRestoreEqualityParent = Join-Path $root ([Guid]::NewGuid().ToString('N')) $postRestoreEqualityDesktop = Join-Path $postRestoreEqualityParent 'Desktop' + $recoveryProofParent = Join-Path $root ([Guid]::NewGuid().ToString('N')) + $recoveryProofDesktop = Join-Path $recoveryProofParent 'Desktop' $finalEqualityParent = Join-Path $root ([Guid]::NewGuid().ToString('N')) $finalEqualityDesktop = Join-Path $finalEqualityParent 'Desktop' Invoke-SupervisorAttributedOperation ` @@ -6196,6 +6342,7 @@ function Test-HkcuDesktopFixtureBoundaryRegression { $initializeInvalidDesktop ` $backupEqualityDesktop ` $postRestoreEqualityDesktop ` + $recoveryProofDesktop ` $finalEqualityDesktop Set-HkcuFixtureBoundaryValueKinds $presentDesktop $presentDigest = Invoke-HkcuDesktopFixtureOperation ` @@ -6403,6 +6550,7 @@ function Test-HkcuDesktopFixtureBoundaryRegression { $initializeInvalidParent, $backupEqualityParent, $postRestoreEqualityParent, + $recoveryProofParent, $finalEqualityParent )) { if (Test-Path -LiteralPath $path) { diff --git a/apps/desktop/src/release-workflow.test.ts b/apps/desktop/src/release-workflow.test.ts index 3444009cd..26b134efe 100644 --- a/apps/desktop/src/release-workflow.test.ts +++ b/apps/desktop/src/release-workflow.test.ts @@ -638,7 +638,11 @@ describe('desktop trusted release workflow', () => { 'CALLSITE_RESOURCE_COLLISION_REPLACEMENT_SURVIVAL_READ', 'CALLSITE_RESOURCE_COLLISION_MANIFEST_PRESERVATION', 'CALLSITE_HKCU_BASELINE_STATE', - 'CALLSITE_HKCU_REGRESSION_VALUE_SETUP', + 'CALLSITE_HKCU_REGRESSION_ROOT_KEY_SETUP', + 'CALLSITE_HKCU_REGRESSION_VALUE_KIND_SETUP', + 'CALLSITE_HKCU_REGRESSION_NATIVE_NONE_WRITE', + 'CALLSITE_HKCU_REGRESSION_NESTED_KEY_SETUP', + 'CALLSITE_HKCU_REGRESSION_NESTED_VALUE_SETUP', 'CALLSITE_HKCU_NATIVE_VALUE_READ', 'CALLSITE_HKCU_BASELINE_DIGEST', 'CALLSITE_HKCU_BASELINE_RELOCATE', @@ -917,7 +921,7 @@ describe('desktop trusted release workflow', () => { ); assert.match( installedWindowsAppSupervisorBehaviorTest, - /function Restore-HkcuDesktopFixtureBoundary[\s\S]*if \(!\$Boundary\.BaselinePresent\)[\s\S]*Callsite 'TARGET_OWNERSHIP'[\s\S]*Assert-HkcuDesktopFixtureOperation \$TargetOwnedByFixture[\s\S]*Remove-Item -LiteralPath \$Boundary\.DesktopKey[\s\S]*Callsite 'BASELINE_DIGEST'[\s\S]*Get-HkcuFixtureRegistryDigest \(\[string\]\$Boundary\.BackupPath\)[\s\S]*Callsite 'TARGET_OWNERSHIP'[\s\S]*Assert-HkcuDesktopFixtureOperation \$TargetOwnedByFixture[\s\S]*Remove-Item -LiteralPath \$Boundary\.DesktopKey[\s\S]*Assert-HkcuDesktopFixtureOperation \(!\(Test-Path -LiteralPath \$Boundary\.DesktopKey\)\)[\s\S]*Rename-Item -LiteralPath \$Boundary\.BackupPath[\s\S]*Callsite 'RECOVERY_RELOCATE'[\s\S]*Rename-Item -LiteralPath \$Boundary\.DesktopKey[\s\S]*-ErrorAction Stop[\s\S]*Callsite 'FINAL_BASELINE_DIGEST'[\s\S]*Callsite 'FINAL_BACKUP_ABSENCE'/, + /function Restore-HkcuDesktopFixtureBoundary[\s\S]*if \(!\$Boundary\.BaselinePresent\)[\s\S]*Callsite 'TARGET_OWNERSHIP'[\s\S]*Assert-HkcuDesktopFixtureOperation \$TargetOwnedByFixture[\s\S]*Remove-Item -LiteralPath \$Boundary\.DesktopKey[\s\S]*Callsite 'BASELINE_DIGEST'[\s\S]*Get-HkcuFixtureRegistryDigest \(\[string\]\$Boundary\.BackupPath\)[\s\S]*Callsite 'TARGET_OWNERSHIP'[\s\S]*Assert-HkcuDesktopFixtureOperation \$TargetOwnedByFixture[\s\S]*Remove-Item -LiteralPath \$Boundary\.DesktopKey[\s\S]*Assert-HkcuDesktopFixtureOperation \(!\(Test-Path -LiteralPath \$Boundary\.DesktopKey\)\)[\s\S]*Rename-Item -LiteralPath \$Boundary\.BackupPath[\s\S]*Callsite 'RECOVERY_RELOCATE'[\s\S]*Rename-Item -LiteralPath \$Boundary\.DesktopKey[\s\S]*-ErrorAction Stop[\s\S]*\$recoveredBackupDigest = try \{[\s\S]*Get-HkcuFixtureRegistryDigest \(\[string\]\$Boundary\.BackupPath\)[\s\S]*\$recoveredBackupDigest -ceq \[string\]\$Boundary\.BaselineDigest[\s\S]*Callsite 'FINAL_BASELINE_DIGEST'[\s\S]*Callsite 'FINAL_BACKUP_ABSENCE'/, ); const registryDigestFunction = installedWindowsAppSupervisorBehaviorTest.slice( installedWindowsAppSupervisorBehaviorTest.indexOf( @@ -1128,7 +1132,7 @@ describe('desktop trusted release workflow', () => { ); assert.match( installedWindowsAppSupervisorBehaviorTest, - /function Test-HkcuFixtureDigestFailureAttributionRegression[\s\S]*Initialize-HkcuDesktopFixtureBoundary \$initializeInvalidBoundary[\s\S]*'BASELINE_DIGEST'[\s\S]*Restore-HkcuDesktopFixtureBoundary \$backupEqualityBoundary \$false[\s\S]*'BASELINE_DIGEST'[\s\S]*ForcePostRestoreDigestMismatch = \$true[\s\S]*Restore-HkcuDesktopFixtureBoundary \$postRestoreEqualityBoundary \$false[\s\S]*'BASELINE_DIGEST'[\s\S]*-Callsite 'FINAL_BASELINE_DIGEST'[\s\S]*'FINAL_BASELINE_DIGEST'/, + /function Test-HkcuFixtureDigestFailureAttributionRegression[\s\S]*Initialize-HkcuDesktopFixtureBoundary \$initializeInvalidBoundary[\s\S]*'BASELINE_DIGEST'[\s\S]*Restore-HkcuDesktopFixtureBoundary \$backupEqualityBoundary \$false[\s\S]*'BASELINE_DIGEST'[\s\S]*ForcePostRestoreDigestMismatch = \$true[\s\S]*Restore-HkcuDesktopFixtureBoundary \$postRestoreEqualityBoundary \$false[\s\S]*'BASELINE_DIGEST'[\s\S]*Get-HkcuFixtureRegistryDigest \$postRestoreEqualityBoundary\.BackupPath[\s\S]*ForcePostRestoreDigestMismatch = \$true[\s\S]*\$recoveryProofBadDigest[\s\S]*'RECOVERY_RELOCATE'[\s\S]*Restore-HkcuDesktopFixtureBoundary \$recoveryProofBoundary \$false[\s\S]*\$expectedRecoveryProofFailure[\s\S]*-Callsite 'FINAL_BASELINE_DIGEST'[\s\S]*Get-HkcuFixtureRegistryDigest \$recoveryProofBoundary\.BackupPath[\s\S]*'FINAL_BASELINE_DIGEST'/, ); assert.match( hkcuBoundaryRegression, @@ -1153,7 +1157,15 @@ describe('desktop trusted release workflow', () => { assert.match(installedWindowsAppSupervisorBehaviorTest, /RegSetValueEx/); assert.match( installedWindowsAppSupervisorBehaviorTest, - /Set-SupervisorFixtureRegistryValueNativeBytes\s+`\n\s+\$key 'NoneValue' 0 \(\[byte\[\]\]@\(9, 8, 7\)\)/, + /Callsite 'REGRESSION_NATIVE_NONE_WRITE'[\s\S]*Field 'NATIVE_RETURN_CODE'[\s\S]*Set-SupervisorFixtureRegistryValueNativeBytes\s+`\n\s+\$key 'NoneValue' 0 \(\[byte\[\]\]@\(9, 8, 7\)\)/, + ); + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /function Set-HkcuFixtureBoundaryValueKinds[\s\S]*Callsite 'REGRESSION_ROOT_KEY_SETUP'[\s\S]*Field 'REGISTRY_PATH'[\s\S]*Callsite 'REGRESSION_VALUE_KIND_SETUP'[\s\S]*Field 'REGISTRY_VALUE'[\s\S]*Callsite 'REGRESSION_NATIVE_NONE_WRITE'[\s\S]*Field 'NATIVE_RETURN_CODE'[\s\S]*Callsite 'REGRESSION_NESTED_KEY_SETUP'[\s\S]*Field 'REGISTRY_PATH'[\s\S]*Callsite 'REGRESSION_NESTED_VALUE_SETUP'[\s\S]*Field 'REGISTRY_VALUE'/, + ); + assert.doesNotMatch( + installedWindowsAppSupervisorBehaviorTest, + /Callsite 'REGRESSION_VALUE_SETUP'[\s\S]*Field 'REGISTRY_VALUE'/, ); assert.match( installedWindowsAppSupervisorBehaviorTest, @@ -1193,7 +1205,11 @@ describe('desktop trusted release workflow', () => { } for (const callsite of [ 'HKCU_BASELINE_STATE', - 'REGRESSION_VALUE_SETUP', + 'REGRESSION_ROOT_KEY_SETUP', + 'REGRESSION_VALUE_KIND_SETUP', + 'REGRESSION_NATIVE_NONE_WRITE', + 'REGRESSION_NESTED_KEY_SETUP', + 'REGRESSION_NESTED_VALUE_SETUP', 'NATIVE_VALUE_READ', 'BASELINE_DIGEST', 'BASELINE_RELOCATE', @@ -1208,6 +1224,7 @@ describe('desktop trusted release workflow', () => { new RegExp(`Get-SupervisorInvocationCallsites[\\s\\S]*'${callsite}'`), ); } + assert.match(installedWindowsAppSupervisorBehaviorTest, /Get-SupervisorInvocationFields[\s\S]*'NATIVE_RETURN_CODE'/); assert.match(installedWindowsAppSupervisorBehaviorTest, /Get-SupervisorInvocationFields[\s\S]*'REGISTRY_PATH'/); assert.match(installedWindowsAppSupervisorBehaviorTest, /OWNED_RESOURCES_NORMAL_SUCCESS/); assert.match(installedWindowsAppSupervisorBehaviorTest, /typed authenticated empty-state receipt/); From b911a90f460e5b3dd29ed7d6d03cb8e9aea54611 Mon Sep 17 00:00:00 2001 From: "propr-dev[bot]" <1316198+propr-dev[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 17:43:27 +0000 Subject: [PATCH 31/33] feat(ai): Implemented the follow-up fixture fix on exact head `54dddc835e8847195f1c7d97716d5d2cadc6bf6e`. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implemented the follow-up fixture fix on exact head `54dddc835e8847195f1c7d97716d5d2cadc6bf6e`. Changed: - [test-installed-windows-app-supervisor.ps1](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T17-39-17/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1): recovery proof mock now delegates the first `BackupPath` digest read, returns the bad digest only on the second `BackupPath` read, then delegates later reads. It asserts exact call count/order and the `RECOVERY_RELOCATE` diagnostic, restores the original function, then proves the retained backup’s real digest equals the original baseline. - [release-workflow.test.ts](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T17-39-17/apps/desktop/src/release-workflow.test.ts): tightened static coverage for that exact fixture behavior. Validation: - `git diff --check` passed. - `npx tsx --test apps/desktop/src/release-workflow.test.ts` passed: 23/23. - `npm --workspace apps/desktop test -- src/release-workflow.test.ts` passed the desktop suite: 177 passed, 6 skipped. - Native Windows x64/ARM64 were not run locally because `pwsh` is not installed in this Linux environment. PR: #2057 Comment by: @integry (ID: 5513775588) Model: gpt-5.5 --- .../test-installed-windows-app-supervisor.ps1 | 40 +++++++++++++++++-- apps/desktop/src/release-workflow.test.ts | 12 ++++++ 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 index 1fc0836a4..dff0ce089 100644 --- a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 +++ b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 @@ -6241,6 +6241,9 @@ function Test-HkcuFixtureDigestFailureAttributionRegression( $recoveryProofBoundary.ForcePostRestoreDigestMismatch = $true $recoveryProofBackupPath = [string]$recoveryProofBoundary.BackupPath $recoveryProofBadDigest = Get-HkcuFixtureDifferentDigest $recoveryProofDigest + $recoveryProofDigestCallOrder = + [System.Collections.Generic.List[string]]::new() + $recoveryProofBackupDigestReads = @{ Value = 0 } $originalHkcuDigestForRecoveryProof = (Get-Command Get-HkcuFixtureRegistryDigest -CommandType Function).ScriptBlock function Get-HkcuFixtureRegistryDigest([string]$Path) { @@ -6249,7 +6252,22 @@ function Test-HkcuFixtureDigestFailureAttributionRegression( [string]$recoveryProofBackupPath, [StringComparison]::OrdinalIgnoreCase )) { - return $recoveryProofBadDigest + $recoveryProofBackupDigestReads.Value = + [int]$recoveryProofBackupDigestReads.Value + 1 + [void]$recoveryProofDigestCallOrder.Add('BackupPath') + if ([int]$recoveryProofBackupDigestReads.Value -eq 2) { + return $recoveryProofBadDigest + } + return (& $originalHkcuDigestForRecoveryProof $Path) + } + if ([string]::Equals( + [string]$Path, + [string]$recoveryProofBoundary.DesktopKey, + [StringComparison]::OrdinalIgnoreCase + )) { + [void]$recoveryProofDigestCallOrder.Add('DesktopKey') + } else { + [void]$recoveryProofDigestCallOrder.Add('Other') } & $originalHkcuDigestForRecoveryProof $Path } @@ -6259,16 +6277,28 @@ function Test-HkcuFixtureDigestFailureAttributionRegression( 'FIXTURE_SETUP' ` 'RECOVERY_RELOCATE' ` 'REGISTRY_PATH' + $actualRecoveryProofFailure = $null try { Restore-HkcuDesktopFixtureBoundary $recoveryProofBoundary $false Assert-True $false (Get-HkcuFixtureBoundaryDiagnostic) } catch { - Assert-True ([string]$_.Exception.Message -ceq $expectedRecoveryProofFailure) ` - (Get-HkcuFixtureBoundaryDiagnostic) + $actualRecoveryProofFailure = [string]$_.Exception.Message } finally { Set-Item -Path Function:\Get-HkcuFixtureRegistryDigest ` -Value $originalHkcuDigestForRecoveryProof } + Assert-True ($actualRecoveryProofFailure -ceq $expectedRecoveryProofFailure) ` + (Get-HkcuFixtureBoundaryDiagnostic) + Assert-HkcuDesktopFixtureOperation ( + [int]$recoveryProofBackupDigestReads.Value -eq 3 + ) + Assert-HkcuDesktopFixtureOperation ( + $recoveryProofDigestCallOrder.Count -eq 4 + ) + Assert-HkcuDesktopFixtureOperation ( + ([string]::Join(',', $recoveryProofDigestCallOrder.ToArray())) -ceq + 'BackupPath,DesktopKey,BackupPath,BackupPath' + ) Assert-HkcuDesktopFixtureOperation ` ((Test-Path -LiteralPath $recoveryProofBoundary.BackupPath) -and !(Test-Path -LiteralPath $RecoveryProofPath)) @@ -6280,6 +6310,10 @@ function Test-HkcuFixtureDigestFailureAttributionRegression( (Get-HkcuFixtureRegistryDigest $recoveryProofBoundary.BackupPath) -ceq $recoveryProofDigest ) + Assert-HkcuDesktopFixtureOperation ( + (Get-HkcuFixtureRegistryDigest $recoveryProofBoundary.BackupPath) -ceq + [string]$recoveryProofBoundary.BaselineDigest + ) } Set-HkcuFixtureBoundaryValueKinds $FinalEqualityPath diff --git a/apps/desktop/src/release-workflow.test.ts b/apps/desktop/src/release-workflow.test.ts index 26b134efe..59ca071c1 100644 --- a/apps/desktop/src/release-workflow.test.ts +++ b/apps/desktop/src/release-workflow.test.ts @@ -1134,6 +1134,18 @@ describe('desktop trusted release workflow', () => { installedWindowsAppSupervisorBehaviorTest, /function Test-HkcuFixtureDigestFailureAttributionRegression[\s\S]*Initialize-HkcuDesktopFixtureBoundary \$initializeInvalidBoundary[\s\S]*'BASELINE_DIGEST'[\s\S]*Restore-HkcuDesktopFixtureBoundary \$backupEqualityBoundary \$false[\s\S]*'BASELINE_DIGEST'[\s\S]*ForcePostRestoreDigestMismatch = \$true[\s\S]*Restore-HkcuDesktopFixtureBoundary \$postRestoreEqualityBoundary \$false[\s\S]*'BASELINE_DIGEST'[\s\S]*Get-HkcuFixtureRegistryDigest \$postRestoreEqualityBoundary\.BackupPath[\s\S]*ForcePostRestoreDigestMismatch = \$true[\s\S]*\$recoveryProofBadDigest[\s\S]*'RECOVERY_RELOCATE'[\s\S]*Restore-HkcuDesktopFixtureBoundary \$recoveryProofBoundary \$false[\s\S]*\$expectedRecoveryProofFailure[\s\S]*-Callsite 'FINAL_BASELINE_DIGEST'[\s\S]*Get-HkcuFixtureRegistryDigest \$recoveryProofBoundary\.BackupPath[\s\S]*'FINAL_BASELINE_DIGEST'/, ); + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /\$recoveryProofBackupDigestReads = @\{ Value = 0 \}[\s\S]*\$recoveryProofBackupDigestReads\.Value =[\s\S]*\[int\]\$recoveryProofBackupDigestReads\.Value \+ 1[\s\S]*if \(\[int\]\$recoveryProofBackupDigestReads\.Value -eq 2\) \{[\s\S]*return \$recoveryProofBadDigest[\s\S]*return \(& \$originalHkcuDigestForRecoveryProof \$Path\)/, + ); + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /finally \{[\s\S]*Set-Item -Path Function:\\Get-HkcuFixtureRegistryDigest[\s\S]*-Value \$originalHkcuDigestForRecoveryProof[\s\S]*Assert-True \(\$actualRecoveryProofFailure -ceq \$expectedRecoveryProofFailure\)[\s\S]*\[int\]\$recoveryProofBackupDigestReads\.Value -eq 3[\s\S]*\$recoveryProofDigestCallOrder\.Count -eq 4[\s\S]*'BackupPath,DesktopKey,BackupPath,BackupPath'/, + ); + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /-Callsite 'FINAL_BASELINE_DIGEST'\s+`\n\s+-Field 'REGISTRY_ROOT'[\s\S]*Get-HkcuFixtureRegistryDigest \$recoveryProofBoundary\.BackupPath\) -ceq[\s\S]*\$recoveryProofDigest[\s\S]*Get-HkcuFixtureRegistryDigest \$recoveryProofBoundary\.BackupPath\) -ceq[\s\S]*\[string\]\$recoveryProofBoundary\.BaselineDigest/, + ); assert.match( hkcuBoundaryRegression, /\$recoveryCollisionDigest = Invoke-HkcuDesktopFixtureOperation\s+`\n\s+-Callsite 'BASELINE_DIGEST'\s+`\n\s+-Field 'REGISTRY_ROOT'[\s\S]*Get-HkcuFixtureRegistryDigest \$recoveryCollisionDesktop/, From 7b8dcfccc3426d2cb1dd6cc941908979ffe6ff2b Mon Sep 17 00:00:00 2001 From: "propr-dev[bot]" <1316198+propr-dev[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 18:00:28 +0000 Subject: [PATCH 32/33] feat(ai): Implemented the attribution-only split on exact head `b911a90f...`. Implemented the attribution-only split on exact head `b911a90f...`. Changed: - [test-installed-windows-app-supervisor.ps1](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T17-53-02/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1:2355): replaced broad `REGRESSION_VALUE_KIND_SETUP` allowlist entry with fixed tokens for `KEY_OPEN`, `DEFAULT_STRING`, `STRING`, `EXPAND_STRING`, `BINARY`, `DWORD`, `QWORD`, `MULTI_STRING`. - [test-installed-windows-app-supervisor.ps1](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T17-53-02/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1:3793): updated attribution-totality behavioral cases for every exact token. - [test-installed-windows-app-supervisor.ps1](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T17-53-02/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1:6022): split the setup so `Get-Item` is under `REGRESSION_VALUE_KIND_KEY_OPEN / REGISTRY_PATH`, and each existing `.SetValue(...)` call is under its own fixed `REGISTRY_VALUE` token. No `SetValue` arguments/types/values or native `REG_NONE` code changed. - [release-workflow.test.ts](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T17-53-02/apps/desktop/src/release-workflow.test.ts:642): updated static regressions to require the exact tokens and reject the old broad `REGRESSION_VALUE_KIND_SETUP`. Validation: - `npm --workspace @propr/desktop exec -- tsx --test src/release-workflow.test.ts` passed. - `npm --workspace @propr/desktop test` passed: 177 passed, 6 skipped. - `git diff --check` passed. Native x64/ARM64 were not rerun: this Linux host has no `pwsh`, and hosted native Actions cannot exercise these uncommitted changes without committing/pushing, which the request explicitly forbids. New first converged native token is therefore not available from this run. PR: #2057 Comment by: @integry (ID: 5513954655) Model: gpt-5.5 --- .../test-installed-windows-app-supervisor.ps1 | 110 +++++++++++++++++- apps/desktop/src/release-workflow.test.ts | 56 ++++++++- 2 files changed, 157 insertions(+), 9 deletions(-) diff --git a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 index dff0ce089..d1d377d14 100644 --- a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 +++ b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 @@ -2352,7 +2352,14 @@ function Get-SupervisorInvocationCallsites { 'EARLY_PROCESS_STATE_READ', 'HKCU_BASELINE_STATE', 'REGRESSION_ROOT_KEY_SETUP', - 'REGRESSION_VALUE_KIND_SETUP', + 'REGRESSION_VALUE_KIND_KEY_OPEN', + 'REGRESSION_VALUE_KIND_DEFAULT_STRING', + 'REGRESSION_VALUE_KIND_STRING', + 'REGRESSION_VALUE_KIND_EXPAND_STRING', + 'REGRESSION_VALUE_KIND_BINARY', + 'REGRESSION_VALUE_KIND_DWORD', + 'REGRESSION_VALUE_KIND_QWORD', + 'REGRESSION_VALUE_KIND_MULTI_STRING', 'REGRESSION_NATIVE_NONE_WRITE', 'REGRESSION_NESTED_KEY_SETUP', 'REGRESSION_NESTED_VALUE_SETUP', @@ -2575,7 +2582,14 @@ function Get-SupervisorAttributionTotalityCases { 'CALLSITE_EARLY_MANIFEST_PRESERVATION', 'CALLSITE_HKCU_BASELINE_STATE', 'CALLSITE_HKCU_REGRESSION_ROOT_KEY_SETUP', - 'CALLSITE_HKCU_REGRESSION_VALUE_KIND_SETUP', + 'CALLSITE_HKCU_REGRESSION_VALUE_KIND_KEY_OPEN', + 'CALLSITE_HKCU_REGRESSION_VALUE_KIND_DEFAULT_STRING', + 'CALLSITE_HKCU_REGRESSION_VALUE_KIND_STRING', + 'CALLSITE_HKCU_REGRESSION_VALUE_KIND_EXPAND_STRING', + 'CALLSITE_HKCU_REGRESSION_VALUE_KIND_BINARY', + 'CALLSITE_HKCU_REGRESSION_VALUE_KIND_DWORD', + 'CALLSITE_HKCU_REGRESSION_VALUE_KIND_QWORD', + 'CALLSITE_HKCU_REGRESSION_VALUE_KIND_MULTI_STRING', 'CALLSITE_HKCU_REGRESSION_NATIVE_NONE_WRITE', 'CALLSITE_HKCU_REGRESSION_NESTED_KEY_SETUP', 'CALLSITE_HKCU_REGRESSION_NESTED_VALUE_SETUP', @@ -3776,11 +3790,60 @@ function Test-SupervisorInvocationAttributionTotality { Callsite='REGRESSION_ROOT_KEY_SETUP'; Field='REGISTRY_PATH' }, [PSCustomObject]@{ - CaseId='CALLSITE_HKCU_REGRESSION_VALUE_KIND_SETUP' + CaseId='CALLSITE_HKCU_REGRESSION_VALUE_KIND_KEY_OPEN' Test='HKCU_INSTALLED_VALUE_OWNERSHIP' Scenario='HKCU_BASELINE_RESTORE' Phase='FIXTURE_SETUP' - Callsite='REGRESSION_VALUE_KIND_SETUP'; Field='REGISTRY_VALUE' + Callsite='REGRESSION_VALUE_KIND_KEY_OPEN'; Field='REGISTRY_PATH' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_HKCU_REGRESSION_VALUE_KIND_DEFAULT_STRING' + Test='HKCU_INSTALLED_VALUE_OWNERSHIP' + Scenario='HKCU_BASELINE_RESTORE' + Phase='FIXTURE_SETUP' + Callsite='REGRESSION_VALUE_KIND_DEFAULT_STRING'; Field='REGISTRY_VALUE' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_HKCU_REGRESSION_VALUE_KIND_STRING' + Test='HKCU_INSTALLED_VALUE_OWNERSHIP' + Scenario='HKCU_BASELINE_RESTORE' + Phase='FIXTURE_SETUP' + Callsite='REGRESSION_VALUE_KIND_STRING'; Field='REGISTRY_VALUE' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_HKCU_REGRESSION_VALUE_KIND_EXPAND_STRING' + Test='HKCU_INSTALLED_VALUE_OWNERSHIP' + Scenario='HKCU_BASELINE_RESTORE' + Phase='FIXTURE_SETUP' + Callsite='REGRESSION_VALUE_KIND_EXPAND_STRING'; Field='REGISTRY_VALUE' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_HKCU_REGRESSION_VALUE_KIND_BINARY' + Test='HKCU_INSTALLED_VALUE_OWNERSHIP' + Scenario='HKCU_BASELINE_RESTORE' + Phase='FIXTURE_SETUP' + Callsite='REGRESSION_VALUE_KIND_BINARY'; Field='REGISTRY_VALUE' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_HKCU_REGRESSION_VALUE_KIND_DWORD' + Test='HKCU_INSTALLED_VALUE_OWNERSHIP' + Scenario='HKCU_BASELINE_RESTORE' + Phase='FIXTURE_SETUP' + Callsite='REGRESSION_VALUE_KIND_DWORD'; Field='REGISTRY_VALUE' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_HKCU_REGRESSION_VALUE_KIND_QWORD' + Test='HKCU_INSTALLED_VALUE_OWNERSHIP' + Scenario='HKCU_BASELINE_RESTORE' + Phase='FIXTURE_SETUP' + Callsite='REGRESSION_VALUE_KIND_QWORD'; Field='REGISTRY_VALUE' + }, + [PSCustomObject]@{ + CaseId='CALLSITE_HKCU_REGRESSION_VALUE_KIND_MULTI_STRING' + Test='HKCU_INSTALLED_VALUE_OWNERSHIP' + Scenario='HKCU_BASELINE_RESTORE' + Phase='FIXTURE_SETUP' + Callsite='REGRESSION_VALUE_KIND_MULTI_STRING'; Field='REGISTRY_VALUE' }, [PSCustomObject]@{ CaseId='CALLSITE_HKCU_REGRESSION_NATIVE_NONE_WRITE' @@ -5956,29 +6019,64 @@ function Set-HkcuFixtureBoundaryValueKinds([string]$Path) { [void](New-Item -Path $Path -Force -ErrorAction Stop) Assert-HkcuDesktopFixtureOperation (Test-Path -LiteralPath $Path) } + $key = Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'REGRESSION_VALUE_KIND_KEY_OPEN' ` + -Field 'REGISTRY_PATH' ` + -Action { + Get-Item -LiteralPath $Path -ErrorAction Stop + } Invoke-HkcuDesktopFixtureOperation ` - -Callsite 'REGRESSION_VALUE_KIND_SETUP' ` + -Callsite 'REGRESSION_VALUE_KIND_DEFAULT_STRING' ` -Field 'REGISTRY_VALUE' ` -Action { - $key = Get-Item -LiteralPath $Path -ErrorAction Stop $key.SetValue('', 'default-string', [Microsoft.Win32.RegistryValueKind]::String) + } + Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'REGRESSION_VALUE_KIND_STRING' ` + -Field 'REGISTRY_VALUE' ` + -Action { $key.SetValue('StringValue', 'plain-string', [Microsoft.Win32.RegistryValueKind]::String) + } + Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'REGRESSION_VALUE_KIND_EXPAND_STRING' ` + -Field 'REGISTRY_VALUE' ` + -Action { $key.SetValue( 'ExpandStringValue', '%TEMP%\propr-fixture', [Microsoft.Win32.RegistryValueKind]::ExpandString ) + } + Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'REGRESSION_VALUE_KIND_BINARY' ` + -Field 'REGISTRY_VALUE' ` + -Action { $key.SetValue( 'BinaryValue', [byte[]]@(0, 1, 2, 127, 128, 255), [Microsoft.Win32.RegistryValueKind]::Binary ) + } + Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'REGRESSION_VALUE_KIND_DWORD' ` + -Field 'REGISTRY_VALUE' ` + -Action { $key.SetValue('DWordValue', [int]305419896, [Microsoft.Win32.RegistryValueKind]::DWord) + } + Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'REGRESSION_VALUE_KIND_QWORD' ` + -Field 'REGISTRY_VALUE' ` + -Action { $key.SetValue( 'QWordValue', [long]1311768467463790320, [Microsoft.Win32.RegistryValueKind]::QWord ) + } + Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'REGRESSION_VALUE_KIND_MULTI_STRING' ` + -Field 'REGISTRY_VALUE' ` + -Action { $key.SetValue( 'MultiStringValue', [string[]]@('alpha', '', 'omega'), diff --git a/apps/desktop/src/release-workflow.test.ts b/apps/desktop/src/release-workflow.test.ts index 59ca071c1..3e97b2d4a 100644 --- a/apps/desktop/src/release-workflow.test.ts +++ b/apps/desktop/src/release-workflow.test.ts @@ -639,7 +639,14 @@ describe('desktop trusted release workflow', () => { 'CALLSITE_RESOURCE_COLLISION_MANIFEST_PRESERVATION', 'CALLSITE_HKCU_BASELINE_STATE', 'CALLSITE_HKCU_REGRESSION_ROOT_KEY_SETUP', - 'CALLSITE_HKCU_REGRESSION_VALUE_KIND_SETUP', + 'CALLSITE_HKCU_REGRESSION_VALUE_KIND_KEY_OPEN', + 'CALLSITE_HKCU_REGRESSION_VALUE_KIND_DEFAULT_STRING', + 'CALLSITE_HKCU_REGRESSION_VALUE_KIND_STRING', + 'CALLSITE_HKCU_REGRESSION_VALUE_KIND_EXPAND_STRING', + 'CALLSITE_HKCU_REGRESSION_VALUE_KIND_BINARY', + 'CALLSITE_HKCU_REGRESSION_VALUE_KIND_DWORD', + 'CALLSITE_HKCU_REGRESSION_VALUE_KIND_QWORD', + 'CALLSITE_HKCU_REGRESSION_VALUE_KIND_MULTI_STRING', 'CALLSITE_HKCU_REGRESSION_NATIVE_NONE_WRITE', 'CALLSITE_HKCU_REGRESSION_NESTED_KEY_SETUP', 'CALLSITE_HKCU_REGRESSION_NESTED_VALUE_SETUP', @@ -1173,12 +1180,48 @@ describe('desktop trusted release workflow', () => { ); assert.match( installedWindowsAppSupervisorBehaviorTest, - /function Set-HkcuFixtureBoundaryValueKinds[\s\S]*Callsite 'REGRESSION_ROOT_KEY_SETUP'[\s\S]*Field 'REGISTRY_PATH'[\s\S]*Callsite 'REGRESSION_VALUE_KIND_SETUP'[\s\S]*Field 'REGISTRY_VALUE'[\s\S]*Callsite 'REGRESSION_NATIVE_NONE_WRITE'[\s\S]*Field 'NATIVE_RETURN_CODE'[\s\S]*Callsite 'REGRESSION_NESTED_KEY_SETUP'[\s\S]*Field 'REGISTRY_PATH'[\s\S]*Callsite 'REGRESSION_NESTED_VALUE_SETUP'[\s\S]*Field 'REGISTRY_VALUE'/, + /function Set-HkcuFixtureBoundaryValueKinds[\s\S]*Callsite 'REGRESSION_ROOT_KEY_SETUP'[\s\S]*Field 'REGISTRY_PATH'[\s\S]*Callsite 'REGRESSION_VALUE_KIND_KEY_OPEN'[\s\S]*Field 'REGISTRY_PATH'[\s\S]*Callsite 'REGRESSION_VALUE_KIND_DEFAULT_STRING'[\s\S]*Field 'REGISTRY_VALUE'[\s\S]*Callsite 'REGRESSION_VALUE_KIND_STRING'[\s\S]*Field 'REGISTRY_VALUE'[\s\S]*Callsite 'REGRESSION_VALUE_KIND_EXPAND_STRING'[\s\S]*Field 'REGISTRY_VALUE'[\s\S]*Callsite 'REGRESSION_VALUE_KIND_BINARY'[\s\S]*Field 'REGISTRY_VALUE'[\s\S]*Callsite 'REGRESSION_VALUE_KIND_DWORD'[\s\S]*Field 'REGISTRY_VALUE'[\s\S]*Callsite 'REGRESSION_VALUE_KIND_QWORD'[\s\S]*Field 'REGISTRY_VALUE'[\s\S]*Callsite 'REGRESSION_VALUE_KIND_MULTI_STRING'[\s\S]*Field 'REGISTRY_VALUE'[\s\S]*Callsite 'REGRESSION_NATIVE_NONE_WRITE'[\s\S]*Field 'NATIVE_RETURN_CODE'[\s\S]*Callsite 'REGRESSION_NESTED_KEY_SETUP'[\s\S]*Field 'REGISTRY_PATH'[\s\S]*Callsite 'REGRESSION_NESTED_VALUE_SETUP'[\s\S]*Field 'REGISTRY_VALUE'/, + ); + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /Callsite 'REGRESSION_VALUE_KIND_KEY_OPEN'[\s\S]*Field 'REGISTRY_PATH'[\s\S]*Get-Item -LiteralPath \$Path -ErrorAction Stop/, + ); + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /Callsite 'REGRESSION_VALUE_KIND_DEFAULT_STRING'[\s\S]*Field 'REGISTRY_VALUE'[\s\S]*\$key\.SetValue\('', 'default-string', \[Microsoft\.Win32\.RegistryValueKind\]::String\)/, + ); + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /Callsite 'REGRESSION_VALUE_KIND_STRING'[\s\S]*Field 'REGISTRY_VALUE'[\s\S]*\$key\.SetValue\('StringValue', 'plain-string', \[Microsoft\.Win32\.RegistryValueKind\]::String\)/, + ); + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /Callsite 'REGRESSION_VALUE_KIND_EXPAND_STRING'[\s\S]*Field 'REGISTRY_VALUE'[\s\S]*\$key\.SetValue\(\s*'ExpandStringValue',\s*'%TEMP%\\propr-fixture',\s*\[Microsoft\.Win32\.RegistryValueKind\]::ExpandString/, + ); + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /Callsite 'REGRESSION_VALUE_KIND_BINARY'[\s\S]*Field 'REGISTRY_VALUE'[\s\S]*\$key\.SetValue\(\s*'BinaryValue',\s*\[byte\[\]\]@\(0, 1, 2, 127, 128, 255\),\s*\[Microsoft\.Win32\.RegistryValueKind\]::Binary/, + ); + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /Callsite 'REGRESSION_VALUE_KIND_DWORD'[\s\S]*Field 'REGISTRY_VALUE'[\s\S]*\$key\.SetValue\('DWordValue', \[int\]305419896, \[Microsoft\.Win32\.RegistryValueKind\]::DWord\)/, + ); + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /Callsite 'REGRESSION_VALUE_KIND_QWORD'[\s\S]*Field 'REGISTRY_VALUE'[\s\S]*\$key\.SetValue\(\s*'QWordValue',\s*\[long\]1311768467463790320,\s*\[Microsoft\.Win32\.RegistryValueKind\]::QWord/, + ); + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /Callsite 'REGRESSION_VALUE_KIND_MULTI_STRING'[\s\S]*Field 'REGISTRY_VALUE'[\s\S]*\$key\.SetValue\(\s*'MultiStringValue',\s*\[string\[\]\]@\('alpha', '', 'omega'\),\s*\[Microsoft\.Win32\.RegistryValueKind\]::MultiString/, ); assert.doesNotMatch( installedWindowsAppSupervisorBehaviorTest, /Callsite 'REGRESSION_VALUE_SETUP'[\s\S]*Field 'REGISTRY_VALUE'/, ); + assert.doesNotMatch( + installedWindowsAppSupervisorBehaviorTest, + /Callsite 'REGRESSION_VALUE_KIND_SETUP'[\s\S]*Field 'REGISTRY_VALUE'/, + ); assert.match( installedWindowsAppSupervisorBehaviorTest, /function Assert-HkcuFixtureNativeNoneValue[\s\S]*Callsite 'NATIVE_VALUE_READ'[\s\S]*Field 'REGISTRY_VALUE'[\s\S]*GetValueKind\('NoneValue'\)[\s\S]*\$none\.Type -eq 0[\s\S]*\$actualBytes\.Length -eq \$ExpectedBytes\.Length[\s\S]*\$actualBytes\[\$index\] -eq \$ExpectedBytes\[\$index\]/, @@ -1218,7 +1261,14 @@ describe('desktop trusted release workflow', () => { for (const callsite of [ 'HKCU_BASELINE_STATE', 'REGRESSION_ROOT_KEY_SETUP', - 'REGRESSION_VALUE_KIND_SETUP', + 'REGRESSION_VALUE_KIND_KEY_OPEN', + 'REGRESSION_VALUE_KIND_DEFAULT_STRING', + 'REGRESSION_VALUE_KIND_STRING', + 'REGRESSION_VALUE_KIND_EXPAND_STRING', + 'REGRESSION_VALUE_KIND_BINARY', + 'REGRESSION_VALUE_KIND_DWORD', + 'REGRESSION_VALUE_KIND_QWORD', + 'REGRESSION_VALUE_KIND_MULTI_STRING', 'REGRESSION_NATIVE_NONE_WRITE', 'REGRESSION_NESTED_KEY_SETUP', 'REGRESSION_NESTED_VALUE_SETUP', From fd333cc161d46f08c54fe7812b52e505eeea1e3c Mon Sep 17 00:00:00 2001 From: "propr-dev[bot]" <1316198+propr-dev[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 18:23:06 +0000 Subject: [PATCH 33/33] feat(ai): Implemented the narrow fixture-only writable-handle correction. Implemented the narrow fixture-only writable-handle correction. Changed: - Added bounded HKCU supervisor fixture writable opener in [test-installed-windows-app-supervisor.ps1](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T18-13-40/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1:1594), restricted to canonical `Registry::HKEY_CURRENT_USER\Software\ProPRSupervisorFixture\...` paths. - Updated `Set-HkcuFixtureBoundaryValueKinds` to use the explicit writable key for all existing value-kind writes and the native REG_NONE writer, with deterministic `Dispose()` in `finally`. - Routed later HKCU fixture setup `.SetValue` sites through the helper where paths are under `ProPRSupervisorFixture`; for `Software\ProPR\Desktop`, switched to `New-ItemProperty` with explicit `String`/`DWord` property types. - Added focused static coverage in [release-workflow.test.ts](/tmp/git-processor/worktrees/integry/propr/pr-2057-followup-2026-09-02T18-13-40/apps/desktop/src/release-workflow.test.ts:1183) for accepted/rejected fixture paths, writable `OpenSubKey(..., $true)`, disposal, and prevention of the old read-view `.SetValue` pattern. Validation run: - `npx tsx --test apps/desktop/src/release-workflow.test.ts` passed. - `npx tsc --noEmit --project apps/desktop/tsconfig.json` passed. - `git diff --check` passed. I could not run native PowerShell/x64/ARM64 validation here because `pwsh` is not installed in this Linux container. Per instruction, I did not commit; current checked-out HEAD remains `7b8dcfccc3426d2cb1dd6cc941908979ffe6ff2b` with the two intended files modified. PR: #2057 Comment by: @integry (ID: 5514216622) Model: gpt-5.5 --- .../test-installed-windows-app-supervisor.ps1 | 324 +++++++++++------- apps/desktop/src/release-workflow.test.ts | 69 +++- 2 files changed, 274 insertions(+), 119 deletions(-) diff --git a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 index d1d377d14..e31749991 100644 --- a/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 +++ b/apps/desktop/scripts/test-installed-windows-app-supervisor.ps1 @@ -1591,6 +1591,38 @@ function Split-HkcuFixtureRegistryPath([string]$Path) { } } +function Get-HkcuSupervisorFixtureRelativeSubKeyPath([string]$Path) { + $providerPrefix = 'Registry::HKEY_CURRENT_USER\' + $fixturePrefix = 'Software\ProPRSupervisorFixture\' + $pathText = [string]$Path + Assert-HkcuDesktopFixtureOperation (![string]::IsNullOrWhiteSpace($pathText)) + Assert-HkcuDesktopFixtureOperation (!$pathText.Contains('/')) + Assert-HkcuDesktopFixtureOperation ( + $pathText.StartsWith($providerPrefix, [StringComparison]::Ordinal) + ) + $relativePath = $pathText.Substring($providerPrefix.Length) + Assert-HkcuDesktopFixtureOperation ( + $relativePath.StartsWith($fixturePrefix, [StringComparison]::Ordinal) + ) + $fixtureRelativePath = $relativePath.Substring($fixturePrefix.Length) + Assert-HkcuDesktopFixtureOperation (![string]::IsNullOrWhiteSpace($fixtureRelativePath)) + Assert-HkcuDesktopFixtureOperation ($fixtureRelativePath -ceq $fixtureRelativePath.Trim()) + foreach ($segment in $fixtureRelativePath.Split('\')) { + Assert-HkcuDesktopFixtureOperation (![string]::IsNullOrWhiteSpace($segment)) + Assert-HkcuDesktopFixtureOperation ($segment -ceq $segment.Trim()) + Assert-HkcuDesktopFixtureOperation ($segment -cne '.' -and $segment -cne '..') + Assert-HkcuDesktopFixtureOperation (!$segment.Contains(':')) + } + return $relativePath +} + +function Open-HkcuSupervisorFixtureWritableSubKey([string]$Path) { + $relativePath = Get-HkcuSupervisorFixtureRelativeSubKeyPath $Path + $key = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey($relativePath, $true) + Assert-HkcuDesktopFixtureOperation ($null -ne $key) + return $key +} + function New-HkcuDesktopFixtureBoundaryState([string]$DesktopKey) { return [PSCustomObject]@{ DesktopKey = $DesktopKey @@ -6016,99 +6048,114 @@ function Set-HkcuFixtureBoundaryValueKinds([string]$Path) { -Callsite 'REGRESSION_ROOT_KEY_SETUP' ` -Field 'REGISTRY_PATH' ` -Action { + [void](Get-HkcuSupervisorFixtureRelativeSubKeyPath $Path) [void](New-Item -Path $Path -Force -ErrorAction Stop) Assert-HkcuDesktopFixtureOperation (Test-Path -LiteralPath $Path) } - $key = Invoke-HkcuDesktopFixtureOperation ` - -Callsite 'REGRESSION_VALUE_KIND_KEY_OPEN' ` - -Field 'REGISTRY_PATH' ` - -Action { - Get-Item -LiteralPath $Path -ErrorAction Stop - } - Invoke-HkcuDesktopFixtureOperation ` - -Callsite 'REGRESSION_VALUE_KIND_DEFAULT_STRING' ` - -Field 'REGISTRY_VALUE' ` - -Action { - $key.SetValue('', 'default-string', [Microsoft.Win32.RegistryValueKind]::String) - } - Invoke-HkcuDesktopFixtureOperation ` - -Callsite 'REGRESSION_VALUE_KIND_STRING' ` - -Field 'REGISTRY_VALUE' ` - -Action { - $key.SetValue('StringValue', 'plain-string', [Microsoft.Win32.RegistryValueKind]::String) - } - Invoke-HkcuDesktopFixtureOperation ` - -Callsite 'REGRESSION_VALUE_KIND_EXPAND_STRING' ` - -Field 'REGISTRY_VALUE' ` - -Action { - $key.SetValue( - 'ExpandStringValue', - '%TEMP%\propr-fixture', - [Microsoft.Win32.RegistryValueKind]::ExpandString - ) - } - Invoke-HkcuDesktopFixtureOperation ` - -Callsite 'REGRESSION_VALUE_KIND_BINARY' ` - -Field 'REGISTRY_VALUE' ` - -Action { - $key.SetValue( - 'BinaryValue', - [byte[]]@(0, 1, 2, 127, 128, 255), - [Microsoft.Win32.RegistryValueKind]::Binary - ) - } - Invoke-HkcuDesktopFixtureOperation ` - -Callsite 'REGRESSION_VALUE_KIND_DWORD' ` - -Field 'REGISTRY_VALUE' ` - -Action { - $key.SetValue('DWordValue', [int]305419896, [Microsoft.Win32.RegistryValueKind]::DWord) - } - Invoke-HkcuDesktopFixtureOperation ` - -Callsite 'REGRESSION_VALUE_KIND_QWORD' ` - -Field 'REGISTRY_VALUE' ` - -Action { - $key.SetValue( - 'QWordValue', - [long]1311768467463790320, - [Microsoft.Win32.RegistryValueKind]::QWord - ) - } - Invoke-HkcuDesktopFixtureOperation ` - -Callsite 'REGRESSION_VALUE_KIND_MULTI_STRING' ` - -Field 'REGISTRY_VALUE' ` - -Action { - $key.SetValue( - 'MultiStringValue', - [string[]]@('alpha', '', 'omega'), - [Microsoft.Win32.RegistryValueKind]::MultiString - ) - } - Invoke-HkcuDesktopFixtureOperation ` - -Callsite 'REGRESSION_NATIVE_NONE_WRITE' ` - -Field 'NATIVE_RETURN_CODE' ` - -Action { - $key = Get-Item -LiteralPath $Path -ErrorAction Stop - Set-SupervisorFixtureRegistryValueNativeBytes ` - $key 'NoneValue' 0 ([byte[]]@(9, 8, 7)) - } + $key = $null + try { + $key = Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'REGRESSION_VALUE_KIND_KEY_OPEN' ` + -Field 'REGISTRY_PATH' ` + -Action { + Open-HkcuSupervisorFixtureWritableSubKey $Path + } + Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'REGRESSION_VALUE_KIND_DEFAULT_STRING' ` + -Field 'REGISTRY_VALUE' ` + -Action { + $key.SetValue('', 'default-string', [Microsoft.Win32.RegistryValueKind]::String) + } + Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'REGRESSION_VALUE_KIND_STRING' ` + -Field 'REGISTRY_VALUE' ` + -Action { + $key.SetValue('StringValue', 'plain-string', [Microsoft.Win32.RegistryValueKind]::String) + } + Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'REGRESSION_VALUE_KIND_EXPAND_STRING' ` + -Field 'REGISTRY_VALUE' ` + -Action { + $key.SetValue( + 'ExpandStringValue', + '%TEMP%\propr-fixture', + [Microsoft.Win32.RegistryValueKind]::ExpandString + ) + } + Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'REGRESSION_VALUE_KIND_BINARY' ` + -Field 'REGISTRY_VALUE' ` + -Action { + $key.SetValue( + 'BinaryValue', + [byte[]]@(0, 1, 2, 127, 128, 255), + [Microsoft.Win32.RegistryValueKind]::Binary + ) + } + Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'REGRESSION_VALUE_KIND_DWORD' ` + -Field 'REGISTRY_VALUE' ` + -Action { + $key.SetValue('DWordValue', [int]305419896, [Microsoft.Win32.RegistryValueKind]::DWord) + } + Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'REGRESSION_VALUE_KIND_QWORD' ` + -Field 'REGISTRY_VALUE' ` + -Action { + $key.SetValue( + 'QWordValue', + [long]1311768467463790320, + [Microsoft.Win32.RegistryValueKind]::QWord + ) + } + Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'REGRESSION_VALUE_KIND_MULTI_STRING' ` + -Field 'REGISTRY_VALUE' ` + -Action { + $key.SetValue( + 'MultiStringValue', + [string[]]@('alpha', '', 'omega'), + [Microsoft.Win32.RegistryValueKind]::MultiString + ) + } + Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'REGRESSION_NATIVE_NONE_WRITE' ` + -Field 'NATIVE_RETURN_CODE' ` + -Action { + Set-SupervisorFixtureRegistryValueNativeBytes ` + $key 'NoneValue' 0 ([byte[]]@(9, 8, 7)) + } + } finally { + if ($null -ne $key) { $key.Dispose() } + } Invoke-HkcuDesktopFixtureOperation ` -Callsite 'REGRESSION_NESTED_KEY_SETUP' ` -Field 'REGISTRY_PATH' ` -Action { $nested = Join-Path $Path 'Nested' $child = Join-Path $nested 'Child' + [void](Get-HkcuSupervisorFixtureRelativeSubKeyPath $child) [void](New-Item -Path $child -Force -ErrorAction Stop) Assert-HkcuDesktopFixtureOperation (Test-Path -LiteralPath $child) } - Invoke-HkcuDesktopFixtureOperation ` - -Callsite 'REGRESSION_NESTED_VALUE_SETUP' ` - -Field 'REGISTRY_VALUE' ` - -Action { - $nested = Join-Path $Path 'Nested' - $child = Join-Path $nested 'Child' - $childKey = Get-Item -LiteralPath $child -ErrorAction Stop - $childKey.SetValue('NestedValue', 'nested-string', [Microsoft.Win32.RegistryValueKind]::String) - } + $childKeyRef = @{ Value = $null } + try { + Invoke-HkcuDesktopFixtureOperation ` + -Callsite 'REGRESSION_NESTED_VALUE_SETUP' ` + -Field 'REGISTRY_VALUE' ` + -Action { + $nested = Join-Path $Path 'Nested' + $child = Join-Path $nested 'Child' + $childKeyRef.Value = Open-HkcuSupervisorFixtureWritableSubKey $child + $childKeyRef.Value.SetValue( + 'NestedValue', + 'nested-string', + [Microsoft.Win32.RegistryValueKind]::String + ) + } + } finally { + if ($null -ne $childKeyRef.Value) { $childKeyRef.Value.Dispose() } + } } function Assert-HkcuFixtureBoundaryValueKinds([string]$Path) { @@ -6188,31 +6235,51 @@ function Test-HkcuFixtureNativeNoneValueReadRegression( [string]$BytePath ) { Set-HkcuFixtureBoundaryValueKinds $TypePath - $typeKey = Get-Item -LiteralPath $TypePath -ErrorAction Stop - $typeKey.SetValue( - 'NoneValue', - [byte[]]@(9, 8, 7), - [Microsoft.Win32.RegistryValueKind]::Binary - ) - Assert-HkcuFixtureNativeNoneValueFailure $typeKey + $typeKey = $null + try { + $typeKey = Open-HkcuSupervisorFixtureWritableSubKey $TypePath + $typeKey.SetValue( + 'NoneValue', + [byte[]]@(9, 8, 7), + [Microsoft.Win32.RegistryValueKind]::Binary + ) + Assert-HkcuFixtureNativeNoneValueFailure $typeKey + } finally { + if ($null -ne $typeKey) { $typeKey.Dispose() } + } Set-HkcuFixtureBoundaryValueKinds $LengthPath - $lengthKey = Get-Item -LiteralPath $LengthPath -ErrorAction Stop - Set-SupervisorFixtureRegistryValueNativeBytes ` - $lengthKey 'NoneValue' 0 ([byte[]]@(9, 8)) - Assert-HkcuFixtureNativeNoneValueFailure $lengthKey + $lengthKey = $null + try { + $lengthKey = Open-HkcuSupervisorFixtureWritableSubKey $LengthPath + Set-SupervisorFixtureRegistryValueNativeBytes ` + $lengthKey 'NoneValue' 0 ([byte[]]@(9, 8)) + Assert-HkcuFixtureNativeNoneValueFailure $lengthKey + } finally { + if ($null -ne $lengthKey) { $lengthKey.Dispose() } + } Set-HkcuFixtureBoundaryValueKinds $BytePath - $byteKey = Get-Item -LiteralPath $BytePath -ErrorAction Stop - Set-SupervisorFixtureRegistryValueNativeBytes ` - $byteKey 'NoneValue' 0 ([byte[]]@(9, 8, 6)) - Assert-HkcuFixtureNativeNoneValueFailure $byteKey + $byteKey = $null + try { + $byteKey = Open-HkcuSupervisorFixtureWritableSubKey $BytePath + Set-SupervisorFixtureRegistryValueNativeBytes ` + $byteKey 'NoneValue' 0 ([byte[]]@(9, 8, 6)) + Assert-HkcuFixtureNativeNoneValueFailure $byteKey + } finally { + if ($null -ne $byteKey) { $byteKey.Dispose() } + } } function Test-SupervisorFixtureRegistryDigestAttributionRegression([string]$Path) { [void](New-Item -Path $Path -Force -ErrorAction Stop) - $key = Get-Item -LiteralPath $Path -ErrorAction Stop - $key.SetValue('SyntheticValue', 'value', [Microsoft.Win32.RegistryValueKind]::String) + $key = $null + try { + $key = Open-HkcuSupervisorFixtureWritableSubKey $Path + $key.SetValue('SyntheticValue', 'value', [Microsoft.Win32.RegistryValueKind]::String) + } finally { + if ($null -ne $key) { $key.Dispose() } + } $originalNativeReader = (Get-Command Get-SupervisorFixtureRegistryValueNativeBytes -CommandType Function).ScriptBlock function Get-SupervisorFixtureRegistryValueNativeBytes { throw 'synthetic registry read failure' } @@ -6503,8 +6570,17 @@ function Test-HkcuDesktopFixtureBoundaryRegression { } [void](New-Item -Path $presentDesktop -Force -ErrorAction Stop) $presentFixtureOwned = $true - (Get-Item -LiteralPath $presentDesktop).SetValue( - 'installed', [int]1, [Microsoft.Win32.RegistryValueKind]::DWord) + $presentWritableKey = $null + try { + $presentWritableKey = Open-HkcuSupervisorFixtureWritableSubKey $presentDesktop + $presentWritableKey.SetValue( + 'installed', + [int]1, + [Microsoft.Win32.RegistryValueKind]::DWord + ) + } finally { + if ($null -ne $presentWritableKey) { $presentWritableKey.Dispose() } + } Restore-HkcuDesktopFixtureBoundary $presentBoundary $presentFixtureOwned Invoke-HkcuDesktopFixtureOperation ` -Callsite 'FINAL_BASELINE_DIGEST' ` @@ -6544,8 +6620,20 @@ function Test-HkcuDesktopFixtureBoundaryRegression { Assert-True (!$absentForeignBoundary.BaselinePresent) ` (Get-HkcuFixtureBoundaryDiagnostic) [void](New-Item -Path $absentForeignDesktop -Force -ErrorAction Stop) - (Get-Item -LiteralPath $absentForeignDesktop).SetValue( - 'foreign', 'preserve', [Microsoft.Win32.RegistryValueKind]::String) + $absentForeignWritableKey = $null + try { + $absentForeignWritableKey = + Open-HkcuSupervisorFixtureWritableSubKey $absentForeignDesktop + $absentForeignWritableKey.SetValue( + 'foreign', + 'preserve', + [Microsoft.Win32.RegistryValueKind]::String + ) + } finally { + if ($null -ne $absentForeignWritableKey) { + $absentForeignWritableKey.Dispose() + } + } $expectedOwnershipFailure = Get-SanitizedSupervisorInvocationDiagnostic ` 'HKCU_INSTALLED_VALUE_OWNERSHIP' ` 'HKCU_BASELINE_RESTORE' ` @@ -6768,14 +6856,14 @@ function Test-HkcuInstalledValueOwnership { 'REGISTRY_PATH' [void](New-Item -Path $desktopKey -Force -ErrorAction Stop) $desktopKeyFixtureOwned = $true - (Get-Item -LiteralPath $desktopKey).SetValue( - $installedName, $sentinelInstalled, [Microsoft.Win32.RegistryValueKind]::String) - (Get-Item -LiteralPath $desktopKey).SetValue( - 'Unrelated', $sentinelUnrelated, [Microsoft.Win32.RegistryValueKind]::String) + [void](New-ItemProperty -LiteralPath $desktopKey -Name $installedName ` + -Value $sentinelInstalled -PropertyType String -Force -ErrorAction Stop) + [void](New-ItemProperty -LiteralPath $desktopKey -Name 'Unrelated' ` + -Value $sentinelUnrelated -PropertyType String -Force -ErrorAction Stop) $baselineData = [Convert]::ToBase64String( [Text.Encoding]::UTF8.GetBytes($sentinelInstalled)) - (Get-Item -LiteralPath $desktopKey).SetValue( - $installedName, [int]1, [Microsoft.Win32.RegistryValueKind]::DWord) + [void](New-ItemProperty -LiteralPath $desktopKey -Name $installedName ` + -Value ([int]1) -PropertyType DWord -Force -ErrorAction Stop) $restoreManifest = New-HkcuManifest $true $true 'String' $baselineData $false $restore = Invoke-WorkflowCleanupController ` 'HKCU_BASELINE_RESTORE' $restoreManifest.Path $restoreManifest.RunId '' @@ -6820,10 +6908,10 @@ function Test-HkcuInstalledValueOwnership { $desktopKeyFixtureOwned = $false [void](New-Item -Path $desktopKey -Force -ErrorAction Stop) $desktopKeyFixtureOwned = $true - (Get-Item -LiteralPath $desktopKey).SetValue( - $installedName, [int]1, [Microsoft.Win32.RegistryValueKind]::DWord) - (Get-Item -LiteralPath $desktopKey).SetValue( - 'Unrelated', $sentinelUnrelated, [Microsoft.Win32.RegistryValueKind]::String) + [void](New-ItemProperty -LiteralPath $desktopKey -Name $installedName ` + -Value ([int]1) -PropertyType DWord -Force -ErrorAction Stop) + [void](New-ItemProperty -LiteralPath $desktopKey -Name 'Unrelated' ` + -Value $sentinelUnrelated -PropertyType String -Force -ErrorAction Stop) $nonemptyManifest = New-HkcuManifest $false $false $null $null $true $nonempty = Invoke-WorkflowCleanupController ` 'HKCU_NONEMPTY' $nonemptyManifest.Path $nonemptyManifest.RunId '' @@ -6844,8 +6932,8 @@ function Test-HkcuInstalledValueOwnership { $desktopKeyFixtureOwned = $false [void](New-Item -Path $desktopKey -Force -ErrorAction Stop) $desktopKeyFixtureOwned = $true - (Get-Item -LiteralPath $desktopKey).SetValue( - $installedName, [int]1, [Microsoft.Win32.RegistryValueKind]::DWord) + [void](New-ItemProperty -LiteralPath $desktopKey -Name $installedName ` + -Value ([int]1) -PropertyType DWord -Force -ErrorAction Stop) $emptyManifest = New-HkcuManifest $false $false $null $null $true $empty = Invoke-WorkflowCleanupController ` 'HKCU_EMPTY' $emptyManifest.Path $emptyManifest.RunId '' @@ -6861,8 +6949,8 @@ function Test-HkcuInstalledValueOwnership { 'REGISTRY_PATH' [void](New-Item -Path $desktopKey -Force -ErrorAction Stop) $desktopKeyFixtureOwned = $true - (Get-Item -LiteralPath $desktopKey).SetValue( - $installedName, 'foreign-conflict', [Microsoft.Win32.RegistryValueKind]::String) + [void](New-ItemProperty -LiteralPath $desktopKey -Name $installedName ` + -Value 'foreign-conflict' -PropertyType String -Force -ErrorAction Stop) $conflictManifest = New-HkcuManifest $false $false $null $null $true $conflict = Invoke-WorkflowCleanupController ` 'HKCU_CONFLICT' $conflictManifest.Path $conflictManifest.RunId '' @@ -6887,8 +6975,8 @@ function Test-HkcuInstalledValueOwnership { $desktopKeyFixtureOwned = $false [void](New-Item -Path $desktopKey -Force -ErrorAction Stop) $desktopKeyFixtureOwned = $true - (Get-Item -LiteralPath $desktopKey).SetValue( - $installedName, [int]1, [Microsoft.Win32.RegistryValueKind]::DWord) + [void](New-ItemProperty -LiteralPath $desktopKey -Name $installedName ` + -Value ([int]1) -PropertyType DWord -Force -ErrorAction Stop) $provisionalManifest = New-HkcuManifest $false $false $null $null $true $true $provisional = Invoke-WorkflowCleanupController ` 'HKCU_PROVISIONAL' $provisionalManifest.Path $provisionalManifest.RunId '' diff --git a/apps/desktop/src/release-workflow.test.ts b/apps/desktop/src/release-workflow.test.ts index 3e97b2d4a..a9c11dcc7 100644 --- a/apps/desktop/src/release-workflow.test.ts +++ b/apps/desktop/src/release-workflow.test.ts @@ -1178,13 +1178,80 @@ describe('desktop trusted release workflow', () => { installedWindowsAppSupervisorBehaviorTest, /Callsite 'REGRESSION_NATIVE_NONE_WRITE'[\s\S]*Field 'NATIVE_RETURN_CODE'[\s\S]*Set-SupervisorFixtureRegistryValueNativeBytes\s+`\n\s+\$key 'NoneValue' 0 \(\[byte\[\]\]@\(9, 8, 7\)\)/, ); + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /function Get-HkcuSupervisorFixtureRelativeSubKeyPath[\s\S]*\$providerPrefix = 'Registry::HKEY_CURRENT_USER\\'[\s\S]*\$fixturePrefix = 'Software\\ProPRSupervisorFixture\\'[\s\S]*!\$pathText\.Contains\('\/'\)[\s\S]*StartsWith\(\$providerPrefix, \[StringComparison\]::Ordinal\)[\s\S]*StartsWith\(\$fixturePrefix, \[StringComparison\]::Ordinal\)[\s\S]*!\[string\]::IsNullOrWhiteSpace\(\$fixtureRelativePath\)[\s\S]*\$fixtureRelativePath -ceq \$fixtureRelativePath\.Trim\(\)[\s\S]*\$segment -ceq \$segment\.Trim\(\)[\s\S]*\$segment -cne '\.' -and \$segment -cne '\.\.'[\s\S]*!\$segment\.Contains\(':'\)/, + ); + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /function Open-HkcuSupervisorFixtureWritableSubKey[\s\S]*Get-HkcuSupervisorFixtureRelativeSubKeyPath \$Path[\s\S]*\[Microsoft\.Win32\.Registry\]::CurrentUser\.OpenSubKey\(\$relativePath, \$true\)[\s\S]*Assert-HkcuDesktopFixtureOperation \(\$null -ne \$key\)[\s\S]*return \$key/, + ); + const acceptsHkcuSupervisorFixturePath = (path: string): boolean => { + const providerPrefix = 'Registry::HKEY_CURRENT_USER\\'; + const fixturePrefix = 'Software\\ProPRSupervisorFixture\\'; + if (!path || path.trim() === '' || path.includes('/')) return false; + if (!path.startsWith(providerPrefix)) return false; + const relativePath = path.slice(providerPrefix.length); + if (!relativePath.startsWith(fixturePrefix)) return false; + const fixtureRelativePath = relativePath.slice(fixturePrefix.length); + if (!fixtureRelativePath || fixtureRelativePath.trim() !== fixtureRelativePath) { + return false; + } + return fixtureRelativePath.split('\\').every(segment => + segment !== '' && + segment === segment.trim() && + segment !== '.' && + segment !== '..' && + !segment.includes(':'), + ); + }; + assert.equal( + acceptsHkcuSupervisorFixturePath( + 'Registry::HKEY_CURRENT_USER\\Software\\ProPRSupervisorFixture\\accepted\\Nested', + ), + true, + ); + for (const rejectedPath of [ + 'Registry::HKEY_CURRENT_USER', + 'Registry::HKEY_CURRENT_USER\\Software\\ProPRSupervisorFixture', + 'Registry::HKEY_CURRENT_USER\\Software\\ProPRSupervisorFixture\\', + 'Registry::HKEY_LOCAL_MACHINE\\Software\\ProPRSupervisorFixture\\foreign', + 'Registry::HKEY_CURRENT_USER\\Software\\ProPR\\Desktop', + 'Registry::HKEY_CURRENT_USER\\Software\\ProPRSupervisorFixture\\..\\Desktop', + 'Registry::HKEY_CURRENT_USER\\Software\\ProPRSupervisorFixture\\malformed/child', + 'Registry::HKEY_CURRENT_USER\\Software\\ProPRSupervisorFixture\\segment\\ child', + 'registry::HKEY_CURRENT_USER\\Software\\ProPRSupervisorFixture\\lowercase', + '', + ]) { + assert.equal(acceptsHkcuSupervisorFixturePath(rejectedPath), false); + } assert.match( installedWindowsAppSupervisorBehaviorTest, /function Set-HkcuFixtureBoundaryValueKinds[\s\S]*Callsite 'REGRESSION_ROOT_KEY_SETUP'[\s\S]*Field 'REGISTRY_PATH'[\s\S]*Callsite 'REGRESSION_VALUE_KIND_KEY_OPEN'[\s\S]*Field 'REGISTRY_PATH'[\s\S]*Callsite 'REGRESSION_VALUE_KIND_DEFAULT_STRING'[\s\S]*Field 'REGISTRY_VALUE'[\s\S]*Callsite 'REGRESSION_VALUE_KIND_STRING'[\s\S]*Field 'REGISTRY_VALUE'[\s\S]*Callsite 'REGRESSION_VALUE_KIND_EXPAND_STRING'[\s\S]*Field 'REGISTRY_VALUE'[\s\S]*Callsite 'REGRESSION_VALUE_KIND_BINARY'[\s\S]*Field 'REGISTRY_VALUE'[\s\S]*Callsite 'REGRESSION_VALUE_KIND_DWORD'[\s\S]*Field 'REGISTRY_VALUE'[\s\S]*Callsite 'REGRESSION_VALUE_KIND_QWORD'[\s\S]*Field 'REGISTRY_VALUE'[\s\S]*Callsite 'REGRESSION_VALUE_KIND_MULTI_STRING'[\s\S]*Field 'REGISTRY_VALUE'[\s\S]*Callsite 'REGRESSION_NATIVE_NONE_WRITE'[\s\S]*Field 'NATIVE_RETURN_CODE'[\s\S]*Callsite 'REGRESSION_NESTED_KEY_SETUP'[\s\S]*Field 'REGISTRY_PATH'[\s\S]*Callsite 'REGRESSION_NESTED_VALUE_SETUP'[\s\S]*Field 'REGISTRY_VALUE'/, ); assert.match( installedWindowsAppSupervisorBehaviorTest, - /Callsite 'REGRESSION_VALUE_KIND_KEY_OPEN'[\s\S]*Field 'REGISTRY_PATH'[\s\S]*Get-Item -LiteralPath \$Path -ErrorAction Stop/, + /Callsite 'REGRESSION_VALUE_KIND_KEY_OPEN'[\s\S]*Field 'REGISTRY_PATH'[\s\S]*Open-HkcuSupervisorFixtureWritableSubKey \$Path/, + ); + assert.match( + installedWindowsAppSupervisorBehaviorTest, + /function Set-HkcuFixtureBoundaryValueKinds[\s\S]*finally \{\n\s+if \(\$null -ne \$key\) \{ \$key\.Dispose\(\) \}[\s\S]*finally \{\n\s+if \(\$null -ne \$childKeyRef\.Value\) \{ \$childKeyRef\.Value\.Dispose\(\) \}/, + ); + assert.doesNotMatch( + hkcuBoundaryRegression, + /\(Get-Item -LiteralPath \$[A-Za-z]+\)\.SetValue\(/, + ); + assert.doesNotMatch( + hkcuInstalledValueOwnership, + /\(Get-Item -LiteralPath \$desktopKey\)\.SetValue\(/, + ); + assert.match( + hkcuInstalledValueOwnership, + /New-ItemProperty -LiteralPath \$desktopKey -Name \$installedName\s+`\n\s+-Value \$sentinelInstalled -PropertyType String -Force -ErrorAction Stop/, + ); + assert.match( + hkcuInstalledValueOwnership, + /New-ItemProperty -LiteralPath \$desktopKey -Name \$installedName\s+`\n\s+-Value \(\[int\]1\) -PropertyType DWord -Force -ErrorAction Stop/, ); assert.match( installedWindowsAppSupervisorBehaviorTest,