diff --git a/probes/windows/09-elevation-uipi.ps1 b/probes/windows/09-elevation-uipi.ps1
new file mode 100644
index 0000000..67dece0
--- /dev/null
+++ b/probes/windows/09-elevation-uipi.ps1
@@ -0,0 +1,465 @@
+<#
+.SYNOPSIS
+ Probe 09 (sub-phase 2.0, unit U8): UIPI measured across a real integrity boundary.
+
+.DESCRIPTION
+ Engineering Invariant #6 is about a Medium-integrity automation process driving a
+ High-integrity target. This box cannot produce that pair by ordinary means: the
+ built-in Administrator (RID 500) runs a full token at S-1-16-12288 and
+ FilterAdministratorToken is unset, so Admin Approval Mode is off and
+ "Start-Process -Verb RunAs" yields High-vs-High. runas.exe /trustlevel:0x20000 was
+ also ruled out - it restricts the token but leaves the mandatory label at High.
+ The boundary is therefore manufactured with common.ps1's
+ Start-MediumIntegrityProcess, which lowers a duplicated token to S-1-16-8192 and
+ asserts the label on read-back.
+
+ Two arms run the SAME worker code against the SAME probe-launched High Notepad;
+ only the integrity level of the worker differs:
+
+ arm "high" control - worker at S-1-16-12288, injection expected to land
+ arm "medium" test - worker at S-1-16-8192, injection expected to be dropped
+
+ Effect is never read from SendInput's return value (which is documented to report
+ success when UIPI drops the input). The parent re-reads the target's edit text over
+ WM_GETTEXT before and after each arm, High-to-High, as independent observation.
+#>
+[CmdletBinding()]
+param(
+ [switch]$Worker,
+ [string]$WorkerOut = '',
+ [string]$HelperDir = '',
+ [int]$TargetProcessId = 0,
+ [string]$TargetHwnd = '0',
+ [string]$EditHwnd = '0',
+ [string]$Marker = 'ZZZ',
+ [string]$Arm = 'unknown'
+)
+
+$ErrorActionPreference = 'Stop'
+. "$PSScriptRoot\common.ps1"
+
+$Probe = '09-elevation-uipi'
+$NativeShimSource = @'
+using System;
+using System.Runtime.InteropServices;
+
+namespace AgentDesktopProbe {
+ public static class Native {
+ [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
+ [DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid);
+ [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr h, int cmd);
+ [DllImport("kernel32.dll", SetLastError = true)] public static extern IntPtr OpenProcess(uint access, bool inherit, int pid);
+ [DllImport("advapi32.dll", SetLastError = true)] public static extern bool OpenProcessToken(IntPtr proc, uint access, out IntPtr tok);
+ [DllImport("advapi32.dll", SetLastError = true)] public static extern bool GetTokenInformation(IntPtr tok, int cls, IntPtr buf, int len, out int ret);
+ [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] public static extern bool ConvertSidToStringSid(IntPtr sid, out IntPtr str);
+ [DllImport("kernel32.dll", SetLastError = true)] public static extern bool CloseHandle(IntPtr h);
+ [DllImport("kernel32.dll")] public static extern IntPtr LocalFree(IntPtr h);
+
+ public static int GetForegroundProcessId() {
+ IntPtr h = GetForegroundWindow();
+ if (h == IntPtr.Zero) { return 0; }
+ uint p = 0;
+ GetWindowThreadProcessId(h, out p);
+ return (int)p;
+ }
+
+ public static string GetIntegritySid(int processId) {
+ IntPtr proc = OpenProcess(0x1000, false, processId);
+ if (proc == IntPtr.Zero) { throw new InvalidOperationException("OpenProcess failed for pid " + processId + ", error " + Marshal.GetLastWin32Error()); }
+ IntPtr tok = IntPtr.Zero;
+ try {
+ if (!OpenProcessToken(proc, 0x0008, out tok)) { throw new InvalidOperationException("OpenProcessToken failed, error " + Marshal.GetLastWin32Error()); }
+ int len = 0;
+ GetTokenInformation(tok, 25, IntPtr.Zero, 0, out len);
+ if (len <= 0) { throw new InvalidOperationException("GetTokenInformation sizing failed, error " + Marshal.GetLastWin32Error()); }
+ IntPtr buf = Marshal.AllocHGlobal(len);
+ try {
+ if (!GetTokenInformation(tok, 25, buf, len, out len)) { throw new InvalidOperationException("GetTokenInformation failed, error " + Marshal.GetLastWin32Error()); }
+ IntPtr str = IntPtr.Zero;
+ if (!ConvertSidToStringSid(Marshal.ReadIntPtr(buf), out str)) { throw new InvalidOperationException("ConvertSidToStringSid failed, error " + Marshal.GetLastWin32Error()); }
+ string s = Marshal.PtrToStringUni(str);
+ LocalFree(str);
+ return s;
+ } finally { Marshal.FreeHGlobal(buf); }
+ } finally {
+ if (tok != IntPtr.Zero) { CloseHandle(tok); }
+ CloseHandle(proc);
+ }
+ }
+ }
+}
+'@
+
+$HelperSource = @'
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace AgentDesktopProbe {
+ [StructLayout(LayoutKind.Sequential)]
+ public struct KeybdInput { public ushort wVk; public ushort wScan; public uint dwFlags; public uint time; public IntPtr dwExtraInfo; }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct Input { public uint type; public KeybdInput ki; public int padA; public int padB; }
+
+ public static class U8 {
+ private delegate bool EnumProc(IntPtr h, IntPtr l);
+
+ [DllImport("user32.dll", SetLastError = true)] private static extern uint SendInput(uint n, Input[] inputs, int size);
+ [DllImport("user32.dll")] private static extern bool EnumChildWindows(IntPtr parent, EnumProc cb, IntPtr l);
+ [DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern int GetClassNameW(IntPtr h, StringBuilder b, int max);
+ [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "SendMessageTimeoutW")]
+ private static extern IntPtr SendMessageTimeoutText(IntPtr h, uint msg, IntPtr w, StringBuilder l, uint flags, uint ms, out IntPtr res);
+ [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "SendMessageTimeoutW")]
+ private static extern IntPtr SendMessageTimeoutStr(IntPtr h, uint msg, IntPtr w, string l, uint flags, uint ms, out IntPtr res);
+ [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)] private static extern bool PostMessageW(IntPtr h, uint msg, IntPtr w, IntPtr l);
+ [DllImport("kernel32.dll")] public static extern IntPtr GetConsoleWindow();
+ [DllImport("kernel32.dll")] private static extern void SetLastError(uint code);
+
+ public static int InputStructSize() { return Marshal.SizeOf(typeof(Input)); }
+
+ public static IntPtr FindChildByClass(IntPtr parent, string wanted) {
+ IntPtr hit = IntPtr.Zero;
+ EnumChildWindows(parent, delegate(IntPtr h, IntPtr l) {
+ StringBuilder b = new StringBuilder(256);
+ GetClassNameW(h, b, 256);
+ if (String.Equals(b.ToString(), wanted, StringComparison.OrdinalIgnoreCase)) { hit = h; return false; }
+ return true;
+ }, IntPtr.Zero);
+ return hit;
+ }
+
+ public static string ReadText(IntPtr h, out long sendResult, out int lastError) {
+ StringBuilder b = new StringBuilder(8192);
+ IntPtr res;
+ IntPtr ok = SendMessageTimeoutText(h, 0x000D, (IntPtr)8192, b, 0x0002, 4000, out res);
+ lastError = Marshal.GetLastWin32Error();
+ sendResult = res.ToInt64();
+ if (ok == IntPtr.Zero) { return null; }
+ return b.ToString();
+ }
+
+ public static bool WriteText(IntPtr h, string text, out int lastError) {
+ IntPtr res;
+ IntPtr ok = SendMessageTimeoutStr(h, 0x000C, IntPtr.Zero, text, 0x0002, 4000, out res);
+ lastError = Marshal.GetLastWin32Error();
+ return ok != IntPtr.Zero;
+ }
+
+ public static bool PostChar(IntPtr h, char c, out int lastError) {
+ bool ok = PostMessageW(h, 0x0102, (IntPtr)c, IntPtr.Zero);
+ lastError = Marshal.GetLastWin32Error();
+ return ok;
+ }
+
+ public static uint SendUnicode(string text, out int lastError) {
+ List batch = new List();
+ for (int i = 0; i < text.Length; i++) {
+ Input down = new Input();
+ down.type = 1;
+ down.ki.wScan = (ushort)text[i];
+ down.ki.dwFlags = 0x0004;
+ batch.Add(down);
+ Input up = new Input();
+ up.type = 1;
+ up.ki.wScan = (ushort)text[i];
+ up.ki.dwFlags = 0x0004 | 0x0002;
+ batch.Add(up);
+ }
+ Input[] arr = batch.ToArray();
+ SetLastError(0);
+ uint sent = SendInput((uint)arr.Length, arr, Marshal.SizeOf(typeof(Input)));
+ lastError = Marshal.GetLastWin32Error();
+ return sent;
+ }
+ }
+}
+'@
+
+function Get-UiaSnapshot {
+ param([IntPtr]$WindowHandle)
+ $row = [ordered]@{}
+ try {
+ Add-Type -AssemblyName UIAutomationClient -ErrorAction Stop
+ Add-Type -AssemblyName UIAutomationTypes -ErrorAction Stop
+ $root = [System.Windows.Automation.AutomationElement]::FromHandle($WindowHandle)
+ $row['fromHandle'] = 'ok'
+ $row['name'] = $root.Current.Name
+ $row['className'] = $root.Current.ClassName
+ $row['controlType'] = ($root.Current.ControlType.ProgrammaticName -replace '^ControlType\.', '')
+ $row['ownerProcessId'] = $root.Current.ProcessId
+ $r = $root.Current.BoundingRectangle
+ if ($r.IsEmpty -or [double]::IsInfinity($r.Left)) {
+ $row['boundingRectangle'] = 'empty (window minimized or off-screen)'
+ } else {
+ $row['boundingRectangle'] = [ordered]@{ Left = [int]$r.Left; Top = [int]$r.Top; Width = [int]$r.Width; Height = [int]$r.Height }
+ }
+ $walker = [System.Windows.Automation.TreeWalker]::RawViewWalker
+ $stack = New-Object System.Collections.Stack
+ $stack.Push($root)
+ $count = 0
+ $editValue = $null
+ $editPatterns = @()
+ while ($stack.Count -gt 0 -and $count -lt 200) {
+ $node = $stack.Pop()
+ $count++
+ if ($node.Current.ClassName -eq 'Edit' -and $null -eq $editValue) {
+ $editPatterns = @($node.GetSupportedPatterns() | ForEach-Object { $_.ProgrammaticName -replace '^(\w+)PatternIdentifiers\.Pattern$', '$1' })
+ try {
+ $vp = $node.GetCurrentPattern([System.Windows.Automation.ValuePattern]::Pattern)
+ $editValue = $vp.Current.Value
+ } catch {
+ try {
+ $tp = $node.GetCurrentPattern([System.Windows.Automation.TextPattern]::Pattern)
+ $editValue = $tp.DocumentRange.GetText(-1)
+ } catch { $editValue = '' }
+ }
+ }
+ $child = $walker.GetFirstChild($node)
+ while ($null -ne $child) {
+ $stack.Push($child)
+ $child = $walker.GetNextSibling($child)
+ }
+ }
+ $row['rawViewNodes'] = $count
+ $row['editPatterns'] = $editPatterns
+ $row['editValueSeenByThisProcess'] = $editValue
+ } catch {
+ $row['fromHandle'] = 'failed'
+ $row['error'] = $_.Exception.Message
+ $row['hresult'] = ('0x' + ('{0:x8}' -f $_.Exception.HResult))
+ }
+ return $row
+}
+
+function Invoke-WorkerArm {
+ $result = [ordered]@{ arm = $Arm; processId = $PID }
+ Add-Type -Path (Join-Path $HelperDir 'u8native.dll')
+ Add-Type -Path (Join-Path $HelperDir 'u8helper.dll')
+ $console = [AgentDesktopProbe.U8]::GetConsoleWindow()
+ [void][AgentDesktopProbe.Native]::ShowWindow($console, 6)
+ [void][AgentDesktopProbe.Native]::ShowWindow($console, 0)
+ $result['addTypeCompilationAvailable'] = $null
+ try {
+ Add-Type -TypeDefinition 'public static class U8Compile { public static int V() { return 1; } }' -Language CSharp -ErrorAction Stop
+ $result['addTypeCompilationAvailable'] = $true
+ } catch {
+ $result['addTypeCompilationAvailable'] = $false
+ $result['addTypeCompilationError'] = $_.Exception.Message
+ }
+ $target = [IntPtr][int64]$TargetHwnd
+ $edit = [IntPtr][int64]$EditHwnd
+ $result['selfIntegritySid'] = [AgentDesktopProbe.Native]::GetIntegritySid($PID)
+ try { $result['targetIntegritySid'] = [AgentDesktopProbe.Native]::GetIntegritySid($TargetProcessId) }
+ catch { $result['targetIntegritySid'] = 'ERROR ' + $_.Exception.Message }
+ $result['uiaRead'] = Get-UiaSnapshot -WindowHandle $target
+
+ $err = 0
+ $raw = [int64]0
+ $textBefore = [AgentDesktopProbe.U8]::ReadText($edit, [ref]$raw, [ref]$err)
+ $result['wmGetTextFromWorker'] = [ordered]@{ returned = $textBefore; sendMessageResult = $raw; lastError = $err }
+
+ [IO.File]::WriteAllText(($WorkerOut + '.ready'), 'ready')
+ $deadline = (Get-Date).AddSeconds(120)
+ while ((Get-Date) -lt $deadline -and -not (Test-Path -LiteralPath ($WorkerOut + '.go'))) { Start-Sleep -Milliseconds 200 }
+ $deadline = (Get-Date).AddSeconds(20)
+ while ((Get-Date) -lt $deadline -and [AgentDesktopProbe.Native]::GetForegroundProcessId() -ne $TargetProcessId) {
+ Start-Sleep -Milliseconds 200
+ }
+ $result['foregroundOwnerAtAssert'] = [ordered]@{ processId = [AgentDesktopProbe.Native]::GetForegroundProcessId() }
+ try {
+ Assert-Foreground -ExpectedProcessId $TargetProcessId -Stage ('sendinput-pre-' + $Arm)
+ $result['foregroundAssertPre'] = 'ok'
+ $sent = [AgentDesktopProbe.U8]::SendUnicode($Marker, [ref]$err)
+ $result['sendInputEventsAccepted'] = [int]$sent
+ $result['sendInputLastError'] = $err
+ $result['sendInputStructSize'] = [AgentDesktopProbe.U8]::InputStructSize()
+ Start-Sleep -Milliseconds 700
+ Assert-Foreground -ExpectedProcessId $TargetProcessId -Stage ('sendinput-post-' + $Arm)
+ $result['foregroundAssertPost'] = 'ok'
+ } catch {
+ $result['foregroundAssertPre'] = 'interference'
+ $result['interference'] = $_.Exception.Message
+ }
+
+ $postOk = [AgentDesktopProbe.U8]::PostChar($edit, 'P', [ref]$err)
+ $result['postMessageWmChar'] = [ordered]@{ returned = $postOk; lastError = $err }
+ Start-Sleep -Milliseconds 400
+ $textAfter = [AgentDesktopProbe.U8]::ReadText($edit, [ref]$raw, [ref]$err)
+ $result['wmGetTextAfterFromWorker'] = [ordered]@{ returned = $textAfter; sendMessageResult = $raw; lastError = $err }
+ $result['uiaReadAfter'] = Get-UiaSnapshot -WindowHandle $target
+ [IO.File]::WriteAllText($WorkerOut, (ConvertTo-Json -InputObject $result -Depth 12), (New-Object System.Text.UTF8Encoding $false))
+}
+
+if ($Worker) {
+ try { Invoke-WorkerArm } catch {
+ [IO.File]::WriteAllText($WorkerOut, (ConvertTo-Json -InputObject ([ordered]@{ arm = $Arm; fatal = $_.Exception.Message }) -Depth 6), (New-Object System.Text.UTF8Encoding $false))
+ }
+ exit 0
+}
+
+$status = 'ok'
+$message = ''
+$summary = [ordered]@{}
+$script:Spawned = New-Object System.Collections.ArrayList
+
+try {
+ Initialize-ProbeNative
+ $work = Join-Path $env:TEMP 'agent-desktop-u8'
+ if (-not (Test-Path -LiteralPath $work)) { New-Item -ItemType Directory -Path $work -Force | Out-Null }
+ Add-Type -TypeDefinition $NativeShimSource -Language CSharp -OutputAssembly (Join-Path $work 'u8native.dll') -OutputType Library
+ Add-Type -TypeDefinition $HelperSource -Language CSharp -OutputAssembly (Join-Path $work 'u8helper.dll') -OutputType Library
+ Add-Type -Path (Join-Path $work 'u8helper.dll')
+ Write-ProbeLog -Message 'compiled u8native.dll + u8helper.dll at High integrity for the Medium worker to load'
+
+ $policy = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System'
+ $uac = [ordered]@{}
+ foreach ($n in @('EnableLUA', 'FilterAdministratorToken', 'ConsentPromptBehaviorAdmin', 'EnableInstallerDetection')) {
+ $v = (Get-ItemProperty -Path $policy -Name $n -ErrorAction SilentlyContinue).$n
+ if ($null -eq $v) { $uac[$n] = '' } else { $uac[$n] = [int]$v }
+ }
+ $uac['sessionIntegritySid'] = [AgentDesktopProbe.Native]::GetIntegritySid($PID)
+ $uac['adminApprovalModeActive'] = ($uac['EnableLUA'] -eq 1 -and $uac['FilterAdministratorToken'] -eq 1)
+
+ $notepad = Start-ScratchProcess -FilePath (Join-Path $env:WINDIR 'System32\notepad.exe') -TimeoutSec 20
+ [void]$script:Spawned.Add($notepad.ProcessId)
+ if ($notepad.MainWindowHandle -eq [IntPtr]::Zero) { throw 'target notepad window never appeared' }
+ $targetSid = [AgentDesktopProbe.Native]::GetIntegritySid($notepad.ProcessId)
+ if ($targetSid -ne 'S-1-16-12288') { throw ('target notepad is not High integrity: ' + $targetSid) }
+ $editHandle = [AgentDesktopProbe.U8]::FindChildByClass($notepad.MainWindowHandle, 'Edit')
+ if ($editHandle -eq [IntPtr]::Zero) { throw 'notepad Edit child window not found' }
+
+ $arms = @()
+ $lastErr = 0
+ $rawRes = [int64]0
+ foreach ($armSpec in @(
+ [pscustomobject]@{ Name = 'high'; Marker = 'HHH'; Medium = $false },
+ [pscustomobject]@{ Name = 'medium'; Marker = 'MMM'; Medium = $true })) {
+ [void][AgentDesktopProbe.U8]::WriteText($editHandle, 'U8-BASE', [ref]$lastErr)
+ Start-Sleep -Milliseconds 300
+ $before = [AgentDesktopProbe.U8]::ReadText($editHandle, [ref]$rawRes, [ref]$lastErr)
+ $outFile = Join-Path $work ('arm-' + $armSpec.Name + '.json')
+ foreach ($stale in @($outFile, ($outFile + '.ready'), ($outFile + '.go'))) {
+ if (Test-Path -LiteralPath $stale) { Remove-Item -LiteralPath $stale -Force }
+ }
+ $argv = @('-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass',
+ '-File', $PSCommandPath, '-Worker', '-WorkerOut', $outFile, '-HelperDir', $work,
+ '-TargetProcessId', ([string]$notepad.ProcessId),
+ '-TargetHwnd', ([string]$notepad.MainWindowHandle.ToInt64()),
+ '-EditHwnd', ([string]$editHandle.ToInt64()),
+ '-Marker', $armSpec.Marker, '-Arm', $armSpec.Name)
+ $shell = Join-Path $env:WINDIR 'System32\WindowsPowerShell\v1.0\powershell.exe'
+ $launchError = ''
+ $workerPid = 0
+ $workerSid = ''
+ try {
+ if ($armSpec.Medium) {
+ $w = Start-MediumIntegrityProcess -FilePath $shell -ArgumentList $argv
+ $workerPid = $w.ProcessId
+ $workerSid = $w.IntegritySid
+ } else {
+ $w = Start-Process -FilePath $shell -ArgumentList $argv -PassThru
+ $workerPid = $w.Id
+ Register-ScratchProcessId -ProcessId $workerPid
+ $workerSid = [AgentDesktopProbe.Native]::GetIntegritySid($workerPid)
+ }
+ [void]$script:Spawned.Add($workerPid)
+ } catch {
+ $launchError = $_.Exception.Message
+ }
+ if ($armSpec.Medium -and $workerSid -ne 'S-1-16-8192') {
+ throw ('PROBE-HARNESS: refusing to record a UIPI verdict without a real boundary - medium arm token label is ' + $workerSid + ' ' + $launchError)
+ }
+ $handoff = 'worker never signalled ready'
+ $deadline = (Get-Date).AddSeconds(120)
+ while ((Get-Date) -lt $deadline -and -not (Test-Path -LiteralPath ($outFile + '.ready'))) { Start-Sleep -Milliseconds 300 }
+ if (Test-Path -LiteralPath ($outFile + '.ready')) {
+ [void][AgentDesktopProbe.Native]::ShowWindow($notepad.MainWindowHandle, 6)
+ Start-Sleep -Milliseconds 500
+ [void][AgentDesktopProbe.Native]::ShowWindow($notepad.MainWindowHandle, 9)
+ $fgDeadline = (Get-Date).AddSeconds(15)
+ while ((Get-Date) -lt $fgDeadline -and [AgentDesktopProbe.Native]::GetForegroundProcessId() -ne $notepad.ProcessId) { Start-Sleep -Milliseconds 250 }
+ $observedFg = [AgentDesktopProbe.Native]::GetForegroundProcessId()
+ $handoff = if ($observedFg -eq $notepad.ProcessId) { 'target foreground restored by parent ShowWindow(SW_MINIMIZE->SW_RESTORE) on the probe-launched window; SetForegroundWindow never called' }
+ else { 'FAILED - foreground pid ' + $observedFg + ' is not the probe-launched target' }
+ [IO.File]::WriteAllText(($outFile + '.go'), 'go')
+ }
+ $deadline = (Get-Date).AddSeconds(180)
+ while ((Get-Date) -lt $deadline -and -not (Test-Path -LiteralPath $outFile)) { Start-Sleep -Milliseconds 500 }
+ $workerData = $null
+ if (Test-Path -LiteralPath $outFile) { $workerData = (Get-Content -LiteralPath $outFile -Raw | ConvertFrom-Json) }
+ Start-Sleep -Milliseconds 500
+ $after = [AgentDesktopProbe.U8]::ReadText($editHandle, [ref]$rawRes, [ref]$lastErr)
+ $arms += [ordered]@{
+ arm = $armSpec.Name
+ workerIntegritySid = $workerSid
+ workerLaunchError = $launchError
+ marker = $armSpec.Marker
+ parentForegroundHandoff = $handoff
+ targetStateBeforeInjection = $before
+ targetStateAfterInjection = $after
+ targetStateChanged = ($before -ne $after)
+ markerPresentInTargetAfterwards = ($null -ne $after -and $after.Contains($armSpec.Marker))
+ observedBy = 'parent High-integrity WM_GETTEXT on the notepad Edit child, independent of SendInput return'
+ worker = $workerData
+ }
+ try { Stop-ScratchProcess -ProcessId $workerPid } catch { Write-ProbeLog -Message ('worker teardown: ' + $_.Exception.Message) -Level 'warn' }
+ }
+
+ $high = $arms | Where-Object { $_.arm -eq 'high' } | Select-Object -First 1
+ $medium = $arms | Where-Object { $_.arm -eq 'medium' } | Select-Object -First 1
+ $capture = [ordered]@{
+ question = 'does UIPI block SendInput and window messages from a Medium-integrity process into a High-integrity target, and are UIA reads still allowed'
+ invariant = 'Engineering Invariant #6 (UIPI)'
+ stack = 'managed (UIA reads) + raw Win32 SendInput/SendMessage/PostMessage'
+ scope = 'api-contract'
+ boundaryOrigin = 'manufactured by common.ps1 Start-MediumIntegrityProcess (DuplicateTokenEx + SetTokenInformation(TokenIntegrityLevel, S-1-16-8192) + CreateProcessAsUser), label asserted on read-back'
+ boundaryRuledOut = @(
+ 'Start-Process -Verb RunAs: AAM off on this box (FilterAdministratorToken unset, RID 500 full token) so it yields High-vs-High',
+ 'runas.exe /trustlevel:0x20000: restricts the token but leaves the mandatory label at S-1-16-12288'
+ )
+ uacPolicy = $uac
+ target = [ordered]@{
+ process = 'notepad.exe'
+ processId = $notepad.ProcessId
+ integritySid = $targetSid
+ windowHandle = $notepad.MainWindowHandle.ToInt64()
+ editChild = [ordered]@{ className = 'Edit'; windowHandle = $editHandle.ToInt64() }
+ launchedByThisProbe = $true
+ }
+ arms = $arms
+ findings = [ordered]@{
+ uiaReadFromMediumAgainstHigh = if ($medium -and $medium.worker) { $medium.worker.uiaRead.fromHandle } else { 'unavailable' }
+ sendInputFromHighLanded = if ($high) { $high.targetStateChanged } else { $null }
+ sendInputFromMediumLanded = if ($medium) { $medium.targetStateChanged } else { $null }
+ sendInputReturnIsNotEvidence = 'SendInput reports the event count it accepted in both arms; only the re-read of the target distinguishes them'
+ wmGetTextFromMediumBlocked = if ($medium -and $medium.worker) { ($null -eq $medium.worker.wmGetTextFromWorker.returned -or $medium.worker.wmGetTextFromWorker.returned -eq '') } else { $null }
+ }
+ }
+ Write-ProbeJson -Probe $Probe -Name 'uipi.json' -InputObject $capture | Out-Null
+
+ $summary['sessionIntegritySid'] = $uac['sessionIntegritySid']
+ $summary['targetIntegritySid'] = $targetSid
+ $summary['mediumArmIntegritySid'] = if ($medium) { $medium.workerIntegritySid } else { '' }
+ $summary['highArmInjectionLanded'] = $capture.findings.sendInputFromHighLanded
+ $summary['mediumArmInjectionLanded'] = $capture.findings.sendInputFromMediumLanded
+ $summary['mediumArmUiaRead'] = $capture.findings.uiaReadFromMediumAgainstHigh
+ $message = 'uipi measured across a manufactured Medium-vs-High boundary'
+} catch {
+ $status = 'fail'
+ $message = ($_.Exception.Message -replace '[\r\n]+', ' ')
+ Write-ProbeLog -Message ('probe failed: ' + $message) -Level 'error'
+} finally {
+ foreach ($id in @($script:Spawned)) {
+ try { Stop-ScratchProcess -ProcessId $id } catch { Write-ProbeLog -Message ('teardown: ' + $_.Exception.Message) -Level 'warn' }
+ }
+ foreach ($f in @(Get-ChildItem -LiteralPath (Get-CaptureDir -Probe $Probe) -File -ErrorAction SilentlyContinue)) {
+ if ($f.Name -like '*.normalized') { continue }
+ if (-not (Test-CaptureRedaction -Path $f.FullName)) { $status = 'fail'; $message = ('redaction residue in ' + $f.Name) }
+ }
+}
+
+Write-ProbeResult -Probe $Probe -Status $status -Message $message -Data $summary
+if ($status -eq 'fail') { exit 1 }
+exit 0
diff --git a/probes/windows/10-session-dpi.ps1 b/probes/windows/10-session-dpi.ps1
new file mode 100644
index 0000000..02063a5
--- /dev/null
+++ b/probes/windows/10-session-dpi.ps1
@@ -0,0 +1,393 @@
+<#
+.SYNOPSIS
+ Probe 10 (sub-phase 2.0, unit U8): session facts and the DPI-awareness bounds delta.
+
+.DESCRIPTION
+ Reads the same probe-launched element's BoundingRectangle from a DPI-aware child
+ process and a DPI-unaware child process, and reports the per-edge numeric delta.
+ The measurement is taken twice: at the display's recommended scale, and again after
+ the probe asks for the next scale step up (125%). The original scale is restored in
+ teardown and the restoration is proved by re-reading it back.
+
+ Delta fields are named deltaLeft/deltaTop/deltaWidth/deltaHeight on purpose. The
+ KTD9 normalizer buckets any key named exactly x/y/left/top/right/bottom/width/height
+ to an 8-pixel bucket to absorb layout jitter; a measured delta must not be bucketed
+ away, and "deltaLeft" has no word boundary before "Left", so it survives verbatim
+ while the raw rectangles it is derived from are still bucketed.
+
+ Multi-monitor and mixed-DPI behavior is a DEFERRED row: this VM has exactly one
+ display. It closes at sub-phase 2.4, which owns list_displays and per-monitor
+ scale_factor.
+#>
+[CmdletBinding()]
+param(
+ [switch]$Worker,
+ [string]$WorkerOut = '',
+ [string]$Mode = 'unaware',
+ [string]$TargetHwnd = '0',
+ [string]$EditHwnd = '0'
+)
+
+$ErrorActionPreference = 'Stop'
+. "$PSScriptRoot\common.ps1"
+
+$Probe = '10-session-dpi'
+$DpiSource = @'
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace AgentDesktopProbe {
+ [StructLayout(LayoutKind.Sequential)]
+ public struct DcLuid { public uint LowPart; public int HighPart; }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct DcHeader { public int type; public uint size; public DcLuid adapterId; public uint id; }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct DcScaleGet { public DcHeader header; public int minScaleRel; public int curScaleRel; public int maxScaleRel; }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct DcScaleSet { public DcHeader header; public int scaleRel; }
+
+ [StructLayout(LayoutKind.Sequential, Size = 72)]
+ public struct DcPathInfo { public DcLuid srcAdapterId; public uint srcId; }
+
+ [StructLayout(LayoutKind.Sequential, Size = 64)]
+ public struct DcModeInfo { public uint infoType; }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct Rect { public int Left; public int Top; public int Right; public int Bottom; }
+
+ public static class Dpi {
+ private delegate bool EnumProc(IntPtr h, IntPtr l);
+
+ [DllImport("user32.dll")] public static extern int GetDisplayConfigBufferSizes(uint flags, out uint numPath, out uint numMode);
+ [DllImport("user32.dll")] public static extern int QueryDisplayConfig(uint flags, ref uint numPath, [Out] DcPathInfo[] paths, ref uint numMode, [Out] DcModeInfo[] modes, IntPtr topology);
+ [DllImport("user32.dll")] public static extern int DisplayConfigGetDeviceInfo(ref DcScaleGet req);
+ [DllImport("user32.dll")] public static extern int DisplayConfigSetDeviceInfo(ref DcScaleSet req);
+ [DllImport("user32.dll", SetLastError = true)] public static extern IntPtr SetProcessDpiAwarenessContext(IntPtr context);
+ [DllImport("user32.dll")] public static extern int GetSystemMetrics(int index);
+ [DllImport("user32.dll")] public static extern bool GetWindowRect(IntPtr h, out Rect r);
+ [DllImport("user32.dll")] public static extern IntPtr MonitorFromPoint(long pt, uint flags);
+ [DllImport("user32.dll")] public static extern IntPtr GetDC(IntPtr h);
+ [DllImport("user32.dll")] public static extern int ReleaseDC(IntPtr h, IntPtr dc);
+ [DllImport("user32.dll")] private static extern bool EnumChildWindows(IntPtr parent, EnumProc cb, IntPtr l);
+ [DllImport("user32.dll", CharSet = CharSet.Unicode)] public static extern int GetClassNameW(IntPtr h, StringBuilder b, int max);
+ [DllImport("gdi32.dll")] public static extern int GetDeviceCaps(IntPtr dc, int index);
+ [DllImport("shcore.dll")] public static extern int GetProcessDpiAwareness(IntPtr proc, out int value);
+ [DllImport("shcore.dll")] public static extern int GetDpiForMonitor(IntPtr mon, int type, out uint dx, out uint dy);
+ [DllImport("kernel32.dll")] public static extern uint WTSGetActiveConsoleSessionId();
+ [DllImport("kernel32.dll")] public static extern uint GetCurrentProcessId();
+ [DllImport("kernel32.dll", SetLastError = true)] public static extern bool ProcessIdToSessionId(uint pid, out uint sid);
+
+ public static int LogPixels() {
+ IntPtr dc = GetDC(IntPtr.Zero);
+ int v = GetDeviceCaps(dc, 88);
+ ReleaseDC(IntPtr.Zero, dc);
+ return v;
+ }
+
+ public static IntPtr FindChildByClass(IntPtr parent, string wanted) {
+ IntPtr hit = IntPtr.Zero;
+ EnumChildWindows(parent, delegate(IntPtr h, IntPtr l) {
+ StringBuilder b = new StringBuilder(256);
+ GetClassNameW(h, b, 256);
+ if (String.Equals(b.ToString(), wanted, StringComparison.OrdinalIgnoreCase)) { hit = h; return false; }
+ return true;
+ }, IntPtr.Zero);
+ return hit;
+ }
+
+ public static bool GetSourceIds(out DcLuid adapterId, out uint sourceId) {
+ adapterId = new DcLuid();
+ sourceId = 0;
+ uint numPath = 0;
+ uint numMode = 0;
+ if (GetDisplayConfigBufferSizes(2, out numPath, out numMode) != 0) { return false; }
+ DcPathInfo[] paths = new DcPathInfo[numPath];
+ DcModeInfo[] modes = new DcModeInfo[numMode];
+ if (QueryDisplayConfig(2, ref numPath, paths, ref numMode, modes, IntPtr.Zero) != 0) { return false; }
+ if (numPath < 1) { return false; }
+ adapterId = paths[0].srcAdapterId;
+ sourceId = paths[0].srcId;
+ return true;
+ }
+
+ public static int[] GetScaleRange() {
+ DcLuid adapter;
+ uint source;
+ if (!GetSourceIds(out adapter, out source)) { return new int[] { -999, -999, -999, -999 }; }
+ DcScaleGet g = new DcScaleGet();
+ g.header.type = -3;
+ g.header.size = (uint)Marshal.SizeOf(typeof(DcScaleGet));
+ g.header.adapterId = adapter;
+ g.header.id = source;
+ int rc = DisplayConfigGetDeviceInfo(ref g);
+ return new int[] { rc, g.minScaleRel, g.curScaleRel, g.maxScaleRel };
+ }
+
+ public static int SetScaleRelative(int rel) {
+ DcLuid adapter;
+ uint source;
+ if (!GetSourceIds(out adapter, out source)) { return -999; }
+ DcScaleSet s = new DcScaleSet();
+ s.header.type = -4;
+ s.header.size = (uint)Marshal.SizeOf(typeof(DcScaleSet));
+ s.header.adapterId = adapter;
+ s.header.id = source;
+ s.scaleRel = rel;
+ return DisplayConfigSetDeviceInfo(ref s);
+ }
+ }
+}
+'@
+
+function Get-AwarenessName {
+ param([int]$Value)
+ switch ($Value) {
+ 0 { return 'PROCESS_DPI_UNAWARE' }
+ 1 { return 'PROCESS_SYSTEM_DPI_AWARE' }
+ 2 { return 'PROCESS_PER_MONITOR_DPI_AWARE' }
+ default { return ('unknown(' + $Value + ')') }
+ }
+}
+
+function Invoke-DpiWorker {
+ Add-Type -TypeDefinition $DpiSource -Language CSharp
+ $row = [ordered]@{ mode = $Mode; processId = [int][AgentDesktopProbe.Dpi]::GetCurrentProcessId() }
+ $a = 0
+ [void][AgentDesktopProbe.Dpi]::GetProcessDpiAwareness([IntPtr]::Zero, [ref]$a)
+ $row['awarenessAtStartup'] = Get-AwarenessName -Value $a
+ if ($Mode -eq 'aware') {
+ $rc = [AgentDesktopProbe.Dpi]::SetProcessDpiAwarenessContext([IntPtr](-4))
+ $row['setProcessDpiAwarenessContextPerMonitorV2'] = if ($rc -ne [IntPtr]::Zero) { 'succeeded' } else { 'failed, error ' + [Runtime.InteropServices.Marshal]::GetLastWin32Error() }
+ } else {
+ $row['setProcessDpiAwarenessContextPerMonitorV2'] = 'not attempted (unaware arm, forced with __COMPAT_LAYER=DPIUNAWARE)'
+ }
+ [void][AgentDesktopProbe.Dpi]::GetProcessDpiAwareness([IntPtr]::Zero, [ref]$a)
+ $row['awarenessEffective'] = Get-AwarenessName -Value $a
+ $dx = 0
+ $dy = 0
+ $mon = [AgentDesktopProbe.Dpi]::MonitorFromPoint(0, 2)
+ [void][AgentDesktopProbe.Dpi]::GetDpiForMonitor($mon, 0, [ref]$dx, [ref]$dy)
+ $row['monitorEffectiveDpiX'] = [int]$dx
+ $row['monitorEffectiveDpiY'] = [int]$dy
+ $row['logPixels'] = [AgentDesktopProbe.Dpi]::LogPixels()
+ $row['screenMetricsCx'] = [AgentDesktopProbe.Dpi]::GetSystemMetrics(0)
+ $row['screenMetricsCy'] = [AgentDesktopProbe.Dpi]::GetSystemMetrics(1)
+ $target = [IntPtr][int64]$TargetHwnd
+ $edit = [IntPtr][int64]$EditHwnd
+ $r = New-Object AgentDesktopProbe.Rect
+ [void][AgentDesktopProbe.Dpi]::GetWindowRect($target, [ref]$r)
+ $row['getWindowRect'] = [ordered]@{ Left = $r.Left; Top = $r.Top; Right = $r.Right; Bottom = $r.Bottom }
+ Add-Type -AssemblyName UIAutomationClient
+ Add-Type -AssemblyName UIAutomationTypes
+ foreach ($pair in @(@('window', $target), @('editChild', $edit))) {
+ $cell = [ordered]@{}
+ try {
+ $el = [System.Windows.Automation.AutomationElement]::FromHandle([IntPtr]$pair[1])
+ $b = $el.Current.BoundingRectangle
+ $cell['className'] = $el.Current.ClassName
+ $cell['Left'] = [int]$b.Left
+ $cell['Top'] = [int]$b.Top
+ $cell['Width'] = [int]$b.Width
+ $cell['Height'] = [int]$b.Height
+ } catch { $cell['error'] = $_.Exception.Message }
+ $row[('uiaBounds_' + $pair[0])] = $cell
+ }
+ [IO.File]::WriteAllText($WorkerOut, (ConvertTo-Json -InputObject $row -Depth 10), (New-Object System.Text.UTF8Encoding $false))
+}
+
+if ($Worker) {
+ try { Invoke-DpiWorker } catch {
+ [IO.File]::WriteAllText($WorkerOut, (ConvertTo-Json -InputObject ([ordered]@{ mode = $Mode; fatal = $_.Exception.Message }) -Depth 6), (New-Object System.Text.UTF8Encoding $false))
+ }
+ exit 0
+}
+
+$status = 'ok'
+$message = ''
+$summary = [ordered]@{}
+$script:Spawned = New-Object System.Collections.ArrayList
+$script:OriginalScaleRel = $null
+$script:WorkDir = Join-Path $env:TEMP 'agent-desktop-u8'
+
+function Invoke-DpiArm {
+ param([string]$ArmMode, [string]$Tag, [IntPtr]$Target, [IntPtr]$Edit)
+ $out = Join-Path $script:WorkDir ('dpi-' + $Tag + '-' + $ArmMode + '.json')
+ if (Test-Path -LiteralPath $out) { Remove-Item -LiteralPath $out -Force }
+ $argv = @('-NoProfile', '-NonInteractive', '-WindowStyle', 'Hidden', '-ExecutionPolicy', 'Bypass',
+ '-File', $PSCommandPath, '-Worker', '-WorkerOut', $out, '-Mode', $ArmMode,
+ '-TargetHwnd', ([string]$Target.ToInt64()), '-EditHwnd', ([string]$Edit.ToInt64()))
+ if ($ArmMode -eq 'unaware') { $env:__COMPAT_LAYER = 'DPIUNAWARE' } else { $env:__COMPAT_LAYER = $null }
+ $proc = Start-Process -FilePath (Join-Path $env:WINDIR 'System32\WindowsPowerShell\v1.0\powershell.exe') -ArgumentList $argv -WindowStyle Hidden -PassThru
+ Register-ScratchProcessId -ProcessId $proc.Id
+ [void]$script:Spawned.Add($proc.Id)
+ $env:__COMPAT_LAYER = $null
+ $deadline = (Get-Date).AddSeconds(120)
+ while ((Get-Date) -lt $deadline -and -not (Test-Path -LiteralPath $out)) { Start-Sleep -Milliseconds 400 }
+ try { Stop-ScratchProcess -ProcessId $proc.Id } catch { }
+ if (-not (Test-Path -LiteralPath $out)) { return [ordered]@{ mode = $ArmMode; fatal = 'worker produced no output' } }
+ return (Get-Content -LiteralPath $out -Raw | ConvertFrom-Json)
+}
+
+function Get-BoundsDelta {
+ param($Aware, $Unaware, [string]$Field)
+ $a = $Aware.$Field
+ $u = $Unaware.$Field
+ if ($null -eq $a -or $null -eq $u -or $a.error -or $u.error) { return [ordered]@{ measurable = $false } }
+ return [ordered]@{
+ measurable = $true
+ deltaLeft = ([int]$a.Left - [int]$u.Left)
+ deltaTop = ([int]$a.Top - [int]$u.Top)
+ deltaWidth = ([int]$a.Width - [int]$u.Width)
+ deltaHeight = ([int]$a.Height - [int]$u.Height)
+ }
+}
+
+try {
+ Initialize-ProbeNative
+ Add-Type -TypeDefinition $DpiSource -Language CSharp
+ if (-not (Test-Path -LiteralPath $script:WorkDir)) { New-Item -ItemType Directory -Path $script:WorkDir -Force | Out-Null }
+
+ $sid = 0
+ [void][AgentDesktopProbe.Dpi]::ProcessIdToSessionId([AgentDesktopProbe.Dpi]::GetCurrentProcessId(), [ref]$sid)
+ $session = [ordered]@{
+ sessionId = [int]$sid
+ activeConsoleSessionId = [int][AgentDesktopProbe.Dpi]::WTSGetActiveConsoleSessionId()
+ isRemoteSession = ([AgentDesktopProbe.Dpi]::GetSystemMetrics(0x1000) -ne 0)
+ windowStation = ([System.Environment]::GetEnvironmentVariable('SESSIONNAME'))
+ note = 'RDP session-transition behavior is not measurable here (physical console session); it closes at sub-phase 2.1 runner registration'
+ }
+
+ Add-Type -AssemblyName System.Windows.Forms
+ $displays = @()
+ foreach ($s in [System.Windows.Forms.Screen]::AllScreens) {
+ $displays += [ordered]@{
+ primary = $s.Primary
+ Left = $s.Bounds.Left
+ Top = $s.Bounds.Top
+ Width = $s.Bounds.Width
+ Height = $s.Bounds.Height
+ }
+ }
+ $monitorKeys = @(Get-ChildItem -LiteralPath 'HKCU:\Control Panel\Desktop\PerMonitorSettings' -ErrorAction SilentlyContinue | ForEach-Object { $_.PSChildName })
+ $range = [AgentDesktopProbe.Dpi]::GetScaleRange()
+ $script:OriginalScaleRel = $range[2]
+ $scale = [ordered]@{
+ displayConfigGetResult = $range[0]
+ scaleRelMin = $range[1]
+ scaleRelCurrent = $range[2]
+ scaleRelMax = $range[3]
+ monitorRegistryKeys = $monitorKeys
+ stepsAvailableAboveRecommended = ($range[3] - $range[1])
+ }
+
+ $notepad = Start-ScratchProcess -FilePath (Join-Path $env:WINDIR 'System32\notepad.exe') -TimeoutSec 20
+ [void]$script:Spawned.Add($notepad.ProcessId)
+ if ($notepad.MainWindowHandle -eq [IntPtr]::Zero) { throw 'dpi target notepad window never appeared' }
+ $editHandle = [AgentDesktopProbe.Dpi]::FindChildByClass($notepad.MainWindowHandle, 'Edit')
+ Start-Sleep -Seconds 1
+
+ $passes = @()
+ foreach ($step in @(
+ [pscustomobject]@{ Tag = 'recommended'; Rel = $script:OriginalScaleRel; Wanted = 'display recommended scale (100%)' },
+ [pscustomobject]@{ Tag = 'plus-one-step'; Rel = ($script:OriginalScaleRel + 1); Wanted = 'one scale step above recommended (125%)' })) {
+ $setResult = $null
+ if ($step.Rel -ne $script:OriginalScaleRel) {
+ $setResult = [AgentDesktopProbe.Dpi]::SetScaleRelative($step.Rel)
+ Start-Sleep -Milliseconds 1500
+ }
+ $after = [AgentDesktopProbe.Dpi]::GetScaleRange()
+ $regValue = $null
+ if ($monitorKeys.Count -gt 0) {
+ $regValue = (Get-ItemProperty -Path ('HKCU:\Control Panel\Desktop\PerMonitorSettings\' + $monitorKeys[0]) -Name 'DpiValue' -ErrorAction SilentlyContinue).DpiValue
+ }
+ $aware = Invoke-DpiArm -ArmMode 'aware' -Tag $step.Tag -Target $notepad.MainWindowHandle -Edit $editHandle
+ $unaware = Invoke-DpiArm -ArmMode 'unaware' -Tag $step.Tag -Target $notepad.MainWindowHandle -Edit $editHandle
+ $passes += [ordered]@{
+ pass = $step.Tag
+ requested = $step.Wanted
+ requestedScaleRel = $step.Rel
+ displayConfigSetResult = $setResult
+ scaleRelReportedAfterwards = $after[2]
+ registryDpiValueAfterwards = $regValue
+ effectiveDpiSeenByAwareArm = $aware.monitorEffectiveDpiX
+ scaleActuallyApplied = ($aware.monitorEffectiveDpiX -ne 96)
+ awareArm = $aware
+ unawareArm = $unaware
+ windowBoundsDelta = (Get-BoundsDelta -Aware $aware -Unaware $unaware -Field 'uiaBounds_window')
+ editChildBoundsDelta = (Get-BoundsDelta -Aware $aware -Unaware $unaware -Field 'uiaBounds_editChild')
+ }
+ }
+
+ $restoreResult = [AgentDesktopProbe.Dpi]::SetScaleRelative($script:OriginalScaleRel)
+ Start-Sleep -Milliseconds 1500
+ $restored = [AgentDesktopProbe.Dpi]::GetScaleRange()
+ $restoredReg = $null
+ if ($monitorKeys.Count -gt 0) {
+ $restoredReg = (Get-ItemProperty -Path ('HKCU:\Control Panel\Desktop\PerMonitorSettings\' + $monitorKeys[0]) -Name 'DpiValue' -ErrorAction SilentlyContinue).DpiValue
+ }
+ $teardown = [ordered]@{
+ originalScaleRel = $script:OriginalScaleRel
+ displayConfigSetResult = $restoreResult
+ scaleRelReadBackAfterwards = $restored[2]
+ registryDpiValueAfterwards = $restoredReg
+ restored = ($restored[2] -eq $script:OriginalScaleRel)
+ }
+
+ $capture = [ordered]@{
+ question = 'what bounds delta does DPI awareness produce between two processes reading the same element, and what scale can this environment actually apply'
+ stack = 'managed UIA for BoundingRectangle; raw Win32 for awareness, DisplayConfig and metrics'
+ scope = 'api-contract for the awareness APIs; app/provider and environment-specific for the achievable scale'
+ session = $session
+ displays = $displays
+ displayScaleCapability = $scale
+ method = 'two sibling powershell children read the same probe-launched notepad window and its Edit child; the unaware arm is forced with __COMPAT_LAYER=DPIUNAWARE, the aware arm calls SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)'
+ normalizationNote = 'raw rectangles are bucketed to 8px by the KTD9 normalizer; delta fields are named deltaLeft/deltaTop/deltaWidth/deltaHeight, which the bucketing regex does not match, so the measured delta survives the normalized twin verbatim'
+ passes = $passes
+ teardown = $teardown
+ deferred = [ordered]@{
+ row = 'multi-monitor and mixed-DPI bounds behavior, and any non-zero aware-vs-unaware delta'
+ reason = 'single display, and that display reports no EDID so Windows offers exactly one scale step (min=cur=max); the 125% request is accepted by DisplayConfigSetDeviceInfo and persisted to the registry but never takes effect'
+ closesAt = 'sub-phase 2.4 (owns list_displays and per-monitor scale_factor) on a runner with a scalable display'
+ neverLeaves = 'Phase 2'
+ }
+ }
+ Write-ProbeJson -Probe $Probe -Name 'session-dpi.json' -InputObject $capture | Out-Null
+
+ $summary['sessionId'] = $session.sessionId
+ $summary['isRemoteSession'] = $session.isRemoteSession
+ $summary['displayCount'] = $displays.Count
+ $summary['scaleRelMinCurMax'] = ('' + $scale.scaleRelMin + '/' + $scale.scaleRelCurrent + '/' + $scale.scaleRelMax)
+ $summary['awarenessPerMonitorV2'] = $passes[0].awareArm.setProcessDpiAwarenessContextPerMonitorV2
+ $summary['unawareArmAwareness'] = $passes[0].unawareArm.awarenessEffective
+ $summary['awareArmAwareness'] = $passes[0].awareArm.awarenessEffective
+ $summary['windowDeltaAtRecommended'] = ('' + $passes[0].windowBoundsDelta.deltaLeft + ',' + $passes[0].windowBoundsDelta.deltaTop + ',' + $passes[0].windowBoundsDelta.deltaWidth + ',' + $passes[0].windowBoundsDelta.deltaHeight)
+ $summary['windowDeltaAt125Request'] = ('' + $passes[1].windowBoundsDelta.deltaLeft + ',' + $passes[1].windowBoundsDelta.deltaTop + ',' + $passes[1].windowBoundsDelta.deltaWidth + ',' + $passes[1].windowBoundsDelta.deltaHeight)
+ $summary['scaleActuallyAppliedAt125Request'] = $passes[1].scaleActuallyApplied
+ $summary['scaleRestored'] = $teardown.restored
+ $message = 'session and dpi measured; scale request and restoration both verified by read-back'
+} catch {
+ $status = 'fail'
+ $message = ($_.Exception.Message -replace '[\r\n]+', ' ')
+ Write-ProbeLog -Message ('probe failed: ' + $message) -Level 'error'
+} finally {
+ if ($null -ne $script:OriginalScaleRel) {
+ try { [void][AgentDesktopProbe.Dpi]::SetScaleRelative($script:OriginalScaleRel) } catch { }
+ }
+ $env:__COMPAT_LAYER = $null
+ foreach ($id in @($script:Spawned)) {
+ try { Stop-ScratchProcess -ProcessId $id } catch { Write-ProbeLog -Message ('teardown: ' + $_.Exception.Message) -Level 'warn' }
+ }
+ foreach ($f in @(Get-ChildItem -LiteralPath (Get-CaptureDir -Probe $Probe) -File -ErrorAction SilentlyContinue)) {
+ if ($f.Name -like '*.normalized') { continue }
+ if (-not (Test-CaptureRedaction -Path $f.FullName)) { $status = 'fail'; $message = ('redaction residue in ' + $f.Name) }
+ }
+}
+
+Write-ProbeResult -Probe $Probe -Status $status -Message $message -Data $summary
+if ($status -eq 'fail') { exit 1 }
+exit 0
diff --git a/probes/windows/11-electron-activation.ps1 b/probes/windows/11-electron-activation.ps1
new file mode 100644
index 0000000..1c185e6
--- /dev/null
+++ b/probes/windows/11-electron-activation.ps1
@@ -0,0 +1,337 @@
+<#
+.SYNOPSIS
+ Probe 11 (sub-phase 2.0, unit U8): Chromium/Electron accessibility activation.
+
+.DESCRIPTION
+ Grades the committed phases.md claim that Chromium 138+ "exposes a UIA tree to any
+ UIA client with no flag" against the installed Obsidian build, on both client stacks
+ (KTD1): managed System.Windows.Automation and the U7 UIA3 COM shim.
+
+ Four Obsidian instances are launched, one per (stack x flag) cell, because the first
+ UIA client to touch a renderer activates accessibility for that process - reusing one
+ instance would make the second stack's "first contact" a settled reading.
+
+ The bundled Chromium and Electron versions are read off THIS installation by scanning
+ Obsidian.exe for the embedded user-agent string. ELECTRON_RUN_AS_NODE=1 with
+ -p process.versions produces no output on this VM and the framework DLLs carry
+ component versions, so the UA scan is the determination of record. The activation
+ verdict is only graded once that version is established.
+
+ Two hazards carried from earlier units, both recorded as probe-placement hazards and
+ explicitly NOT product claims:
+ - U3: leaving other windows on top of Obsidian held it at its first-contact node
+ count through a full settle. Mechanism not isolated. This probe places Obsidian
+ itself and records the foreground owner at read time.
+ - U4: matching a bare Chrome_WidgetWin_1 window found an unrelated window and
+ recorded 9 nodes / 0% coverage as if it were the Electron answer. This probe
+ matches only windows whose owning pid is one of the launched Obsidian pids.
+#>
+[CmdletBinding()]
+param()
+
+$ErrorActionPreference = 'Stop'
+. "$PSScriptRoot\common.ps1"
+
+$Probe = '11-electron-activation'
+$script:Spawned = New-Object System.Collections.ArrayList
+
+function Initialize-WindowSupport {
+ if ('AgentDesktopProbe.Win11' -as [type]) { return }
+ Add-Type -Language CSharp -TypeDefinition @'
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace AgentDesktopProbe {
+ public static class Win11 {
+ private delegate bool EnumProc(IntPtr h, IntPtr l);
+ [DllImport("user32.dll")] private static extern bool EnumWindows(EnumProc cb, IntPtr l);
+ [DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern int GetClassNameW(IntPtr h, StringBuilder b, int max);
+ [DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern int GetWindowTextW(IntPtr h, StringBuilder b, int max);
+ [DllImport("user32.dll")] private static extern bool IsWindowVisible(IntPtr h);
+ [DllImport("user32.dll")] private static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid);
+
+ public static string ClassOf(IntPtr h) { StringBuilder b = new StringBuilder(256); GetClassNameW(h, b, 256); return b.ToString(); }
+ public static string TitleOf(IntPtr h) { StringBuilder b = new StringBuilder(512); GetWindowTextW(h, b, 512); return b.ToString(); }
+ public static int PidOf(IntPtr h) { uint p; GetWindowThreadProcessId(h, out p); return (int)p; }
+
+ public static IntPtr[] VisibleTopLevel() {
+ List found = new List();
+ EnumWindows(delegate(IntPtr h, IntPtr l) { if (IsWindowVisible(h)) { found.Add(h); } return true; }, IntPtr.Zero);
+ return found.ToArray();
+ }
+ }
+}
+'@ | Out-Null
+}
+
+function Get-BundledVersions {
+ param([string]$ExePath)
+ $rx = New-Object Text.RegularExpressions.Regex 'Chrome/(\d+\.\d+\.\d+\.\d+) Electron/(\d+\.\d+\.\d+)'
+ $fs = [IO.File]::OpenRead($ExePath)
+ $buf = New-Object byte[] (8MB)
+ $prev = ''
+ $chrome = ''
+ $electron = ''
+ try {
+ while (($n = $fs.Read($buf, 0, 8MB)) -gt 0) {
+ $text = $prev + [Text.Encoding]::GetEncoding(28591).GetString($buf, 0, $n)
+ $m = $rx.Match($text)
+ if ($m.Success) { $chrome = $m.Groups[1].Value; $electron = $m.Groups[2].Value; break }
+ if ($text.Length -ge 256) { $prev = $text.Substring($text.Length - 256) } else { $prev = $text }
+ }
+ } finally { $fs.Close() }
+ return [ordered]@{
+ method = 'user-agent string scanned out of the shipped Obsidian.exe on this installation'
+ obsidianFileVersion = (Get-Item -LiteralPath $ExePath).VersionInfo.ProductVersion
+ chromiumVersion = $chrome
+ electronVersion = $electron
+ established = ($chrome -ne '' -and $electron -ne '')
+ rejectedMethods = @(
+ 'ELECTRON_RUN_AS_NODE=1 Obsidian.exe -p process.versions produced no output on this VM',
+ 'framework DLL file versions carry component versions, not the Chromium version'
+ )
+ }
+}
+
+function Start-ObsidianInstance {
+ param([string]$ExePath, [string[]]$ExtraArgs = @())
+ Initialize-WindowSupport
+ if ($ExtraArgs.Count -gt 0) { Start-Process -FilePath $ExePath -ArgumentList $ExtraArgs | Out-Null }
+ else { Start-Process -FilePath $ExePath | Out-Null }
+ $deadline = (Get-Date).AddSeconds(75)
+ while ((Get-Date) -lt $deadline) {
+ $pids = @(Get-Process -Name 'Obsidian' -ErrorAction SilentlyContinue | ForEach-Object { $_.Id })
+ foreach ($id in $pids) {
+ if (-not $script:Spawned.Contains($id)) {
+ Register-ScratchProcessId -ProcessId $id
+ [void]$script:Spawned.Add($id)
+ }
+ }
+ foreach ($h in [AgentDesktopProbe.Win11]::VisibleTopLevel()) {
+ if ([AgentDesktopProbe.Win11]::ClassOf($h) -ne 'Chrome_WidgetWin_1') { continue }
+ $owner = [AgentDesktopProbe.Win11]::PidOf($h)
+ if ($pids -notcontains $owner) { continue }
+ if (-not [AgentDesktopProbe.Win11]::TitleOf($h)) { continue }
+ Show-WindowNoActivate -WindowHandle $h -X 0 -Y 0 -Width 1280 -Height 720
+ return [pscustomobject]@{ WindowHandle = $h; ProcessId = $owner; AllPids = $pids }
+ }
+ Start-Sleep -Milliseconds 700
+ }
+ return $null
+}
+
+function Stop-AllObsidian {
+ foreach ($p in @(Get-Process -Name 'Obsidian' -ErrorAction SilentlyContinue)) {
+ try { Stop-Process -Id $p.Id -Force -ErrorAction Stop } catch { }
+ }
+ $deadline = (Get-Date).AddSeconds(20)
+ while ((Get-Date) -lt $deadline) {
+ if (-not (Get-Process -Name 'Obsidian' -ErrorAction SilentlyContinue)) { return $true }
+ Start-Sleep -Milliseconds 400
+ }
+ return (-not (Get-Process -Name 'Obsidian' -ErrorAction SilentlyContinue))
+}
+
+function Measure-ManagedTree {
+ param([IntPtr]$WindowHandle, [string]$Prefix = '')
+ Add-Type -AssemblyName UIAutomationClient
+ Add-Type -AssemblyName UIAutomationTypes
+ $root = [System.Windows.Automation.AutomationElement]::FromHandle($WindowHandle)
+ $out = [ordered]@{}
+ foreach ($view in @('RawView', 'ControlView')) {
+ $walker = if ($view -eq 'RawView') { [System.Windows.Automation.TreeWalker]::RawViewWalker } else { [System.Windows.Automation.TreeWalker]::ControlViewWalker }
+ $stack = New-Object System.Collections.Stack
+ $stack.Push($root)
+ $count = 0
+ $refable = 0
+ while ($stack.Count -gt 0 -and $count -lt 8000) {
+ $node = $stack.Pop()
+ $count++
+ if (@($node.GetSupportedPatterns()).Count -gt 0) { $refable++ }
+ $child = $walker.GetFirstChild($node)
+ while ($null -ne $child) {
+ $stack.Push($child)
+ $child = $walker.GetNextSibling($child)
+ }
+ }
+ $out[($Prefix + $view + 'Nodes')] = $count
+ $out[($Prefix + $view + 'RefableNodes')] = $refable
+ }
+ return $out
+}
+
+function Invoke-ComShim {
+ param([string]$ExePath, [string[]]$ShimArgs, [int]$TimeoutSec = 300)
+ $psi = New-Object System.Diagnostics.ProcessStartInfo
+ $psi.FileName = $ExePath
+ $psi.Arguments = ($ShimArgs -join ' ')
+ $psi.UseShellExecute = $false
+ $psi.RedirectStandardOutput = $true
+ $psi.RedirectStandardError = $true
+ $psi.CreateNoWindow = $true
+ $proc = [System.Diagnostics.Process]::Start($psi)
+ Register-ScratchProcessId -ProcessId $proc.Id
+ [void]$script:Spawned.Add($proc.Id)
+ $stdout = $proc.StandardOutput.ReadToEnd()
+ $stderr = $proc.StandardError.ReadToEnd()
+ if (-not $proc.WaitForExit($TimeoutSec * 1000)) { try { $proc.Kill() } catch { }; throw ('shim ' + $ShimArgs[0] + ' timed out') }
+ if ($proc.ExitCode -ne 0) { throw ('shim ' + $ShimArgs[0] + ' exited ' + $proc.ExitCode + ': ' + $stderr) }
+ return ($stdout | ConvertFrom-Json)
+}
+
+$status = 'ok'
+$message = ''
+$summary = [ordered]@{}
+
+try {
+ Initialize-ProbeNative
+ Initialize-WindowSupport
+ $obsidianExe = Join-Path $env:LOCALAPPDATA 'Programs\Obsidian\Obsidian.exe'
+ if (-not (Test-Path -LiteralPath $obsidianExe)) { throw ('Obsidian not installed at ' + $obsidianExe) }
+ $preexisting = @(Get-Process -Name 'Obsidian' -ErrorAction SilentlyContinue).Count -gt 0
+ $versions = Get-BundledVersions -ExePath $obsidianExe
+ if (-not $versions.established) { throw 'bundled Chromium version could not be read off this installation' }
+ Write-ProbeLog -Message ('obsidian ' + $versions.obsidianFileVersion + ' bundles chromium ' + $versions.chromiumVersion + ' / electron ' + $versions.electronVersion)
+
+ $csc = Join-Path $env:WINDIR 'Microsoft.NET\Framework64\v4.0.30319\csc.exe'
+ $buildDir = Join-Path $env:TEMP 'agent-desktop-uia3'
+ if (-not (Test-Path -LiteralPath $buildDir)) { New-Item -ItemType Directory -Path $buildDir -Force | Out-Null }
+ $shim = Join-Path $buildDir '08-uia3-com.exe'
+ $shimOut = (& $csc @('/nologo', '/target:exe', '/langversion:5', '/platform:anycpu', ('/out:' + $shim),
+ '/reference:System.dll', '/reference:System.Core.dll', (Join-Path $PSScriptRoot '08-uia3-com.cs')) 2>&1 | Out-String).Trim()
+ if ($LASTEXITCODE -ne 0) { throw ('csc.exe failed building the U7 shim: ' + $shimOut) }
+
+ $cells = @()
+ foreach ($flagArm in @(
+ [pscustomobject]@{ Name = 'no-flag'; Args = @() },
+ [pscustomobject]@{ Name = 'force-renderer-accessibility'; Args = @('--force-renderer-accessibility') })) {
+ foreach ($stack in @('managed', 'uia3-com')) {
+ if (-not (Stop-AllObsidian)) { throw 'could not bring Obsidian to a clean state before an arm' }
+ $inst = Start-ObsidianInstance -ExePath $obsidianExe -ExtraArgs $flagArm.Args
+ if ($null -eq $inst) { throw ('Obsidian window never appeared for arm ' + $flagArm.Name + '/' + $stack) }
+ $cell = [ordered]@{
+ flagArm = $flagArm.Name
+ stack = $stack
+ authoritative = ($stack -eq 'uia3-com')
+ windowClassName = [AgentDesktopProbe.Win11]::ClassOf($inst.WindowHandle)
+ windowHandle = $inst.WindowHandle.ToInt64()
+ windowHost = [ordered]@{ processId = $inst.ProcessId }
+ latencyObsidianProcessCountAtWindowDiscovery = $inst.AllPids.Count
+ foregroundOwner = [ordered]@{ processId = [AgentDesktopProbe.Native]::GetForegroundProcessId() }
+ settleMs = 8000
+ }
+ if ($stack -eq 'managed') {
+ $cell['firstContact'] = Measure-ManagedTree -WindowHandle $inst.WindowHandle -Prefix 'latencyFirstContact'
+ Start-Sleep -Milliseconds 8000
+ $cell['afterSettle'] = Measure-ManagedTree -WindowHandle $inst.WindowHandle -Prefix 'settled'
+ } else {
+ $walker = Invoke-ComShim -ExePath $shim -ShimArgs @('walker', '--hwnd', ('0x' + $inst.WindowHandle.ToInt64().ToString('x')),
+ '--label', 'obsidian', '--max-nodes', '20000', '--max-depth', '60', '--settle-ms', '8000')
+ $fc = [ordered]@{}
+ foreach ($v in $walker.result.viewsFirstContact) { $fc[('latencyFirstContact' + $v.view + 'Nodes')] = $v.nodeCount }
+ $st = [ordered]@{}
+ foreach ($v in $walker.result.viewsAfterSettle) { $st[('settled' + $v.view + 'Nodes')] = $v.nodeCount }
+ $census = Invoke-ComShim -ExePath $shim -ShimArgs @('census', '--target', ('obsidian=0x' + $inst.WindowHandle.ToInt64().ToString('x')),
+ '--max-nodes', '20000', '--max-depth', '60')
+ $t = $census.result.targets[0]
+ $st['settledRefableNodesWithAnyPattern'] = $t.elementsWithAnyPattern
+ $st['settledRefableNodesExcludingLegacy'] = $t.elementsWithAnyPatternExcludingLegacy
+ $cell['firstContact'] = $fc
+ $cell['afterSettle'] = $st
+ $cell['censusNote'] = 'census ran in a second shim process against the already-activated instance, so its counts are settled counts; per-element rows are deliberately not captured (R11: Obsidian note titles are content)'
+ }
+ $rawFc = $cell.firstContact['latencyFirstContactRawViewNodes']
+ $rawSt = $cell.afterSettle['settledRawViewNodes']
+ $cell['latencyRawViewGrowthRatio'] = if ($rawFc -gt 0) { [math]::Round(($rawSt / [double]$rawFc), 2) } else { $null }
+ $cell['activatedWithoutFlag'] = ($flagArm.Name -eq 'no-flag' -and $rawSt -gt $rawFc)
+ $cells += $cell
+ Write-ProbeLog -Message ('arm ' + $flagArm.Name + '/' + $stack + ': raw ' + $rawFc + ' -> ' + $rawSt)
+ }
+ }
+ $allGone = Stop-AllObsidian
+
+ $noFlagCom = $cells | Where-Object { $_.flagArm -eq 'no-flag' -and $_.stack -eq 'uia3-com' } | Select-Object -First 1
+ $flagCom = $cells | Where-Object { $_.flagArm -ne 'no-flag' -and $_.stack -eq 'uia3-com' } | Select-Object -First 1
+ $noFlagManaged = $cells | Where-Object { $_.flagArm -eq 'no-flag' -and $_.stack -eq 'managed' } | Select-Object -First 1
+ $flagManaged = $cells | Where-Object { $_.flagArm -ne 'no-flag' -and $_.stack -eq 'managed' } | Select-Object -First 1
+ $bothActivate = ($noFlagCom.activatedWithoutFlag -and $noFlagManaged.activatedWithoutFlag)
+ $flagVerdict = 'ungraded'
+ if ($versions.established) {
+ $major = [int]($versions.chromiumVersion -split '\.')[0]
+ if ($major -lt 138) {
+ $flagVerdict = 'flag still required - bundled Chromium predates the 138 auto-UIA default'
+ } elseif ($bothActivate -and $noFlagCom.afterSettle['settledRawViewNodes'] -eq $flagCom.afterSettle['settledRawViewNodes']) {
+ $flagVerdict = 'partially useful - redundant for eventual tree exposure: on Chromium ' + $versions.chromiumVersion +
+ ' both client stacks reach the same settled RawView count with no flag, so the phases.md claim holds for exposure. It is not redundant for first-contact readiness: without the flag first contact is deterministically the small pre-activation shell on every run and both stacks, while with the flag first contact is a race against the async tree build and was observed anywhere from a fraction of the settled count to nearly all of it. Neither arm removes the need to settle before trusting a first snapshot.'
+ } elseif ($bothActivate) {
+ $flagVerdict = 'redundant for tree exposure - both client stacks get a renderer tree with no flag on Chromium ' + $versions.chromiumVersion
+ } else {
+ $flagVerdict = 'still required - a 138+ Chromium did not expose a renderer tree without the flag on at least one stack'
+ }
+ }
+
+ $capture = [ordered]@{
+ question = 'does Chromium 138+ expose a UIA tree to any UIA client with no flag, and does --force-renderer-accessibility still change anything'
+ gradedAgainst = 'the committed phases.md line that Chromium 138+ exposes a UIA tree to any UIA client with no flag (P2-O15 / 2.4)'
+ stack = 'both: managed System.Windows.Automation and the U7 UIA3 COM shim (KTD1 - the COM rows are the authoritative ones)'
+ scope = 'app/provider - specific to this Obsidian/Electron/Chromium build; the activation mechanism is Chromium behavior, not a Windows API contract'
+ versions = $versions
+ obsidianPreexistingAtProbeStart = $preexisting
+ method = 'one fresh Obsidian instance per (stack x flag) cell, because the first UIA client to touch a renderer activates accessibility for that process'
+ placementHazards = @(
+ 'U3: an earlier revision that left other windows on top of Obsidian held it at its first-contact node count through a full 8s settle and a 16s instrumented hold, three runs running. Minimizing the covering windows fixed it. The mechanism was NOT isolated (Chromium native-window occlusion tracking is the obvious candidate, unproven). Carried here as a probe-placement hazard, explicitly not a product claim.',
+ 'U4: matching a bare Chrome_WidgetWin_1 window found an unrelated window and recorded 9 nodes / 0% coverage as if it were the Electron answer. This probe only accepts a window whose owning pid is one of the launched Obsidian pids, and reads the hosting pid back off the window.'
+ )
+ countStabilityNote = 'settled node counts are content-dependent (U7 measured RawView 172 on a bare walker run, U3 measured 119 through its probe). The stable fact is the growth ratio across the settle, not the absolute count.'
+ cells = $cells
+ findings = [ordered]@{
+ managedActivatesWithoutFlag = $noFlagManaged.activatedWithoutFlag
+ comActivatesWithoutFlag = $noFlagCom.activatedWithoutFlag
+ clientStackDivergence = ($noFlagManaged.activatedWithoutFlag -ne $noFlagCom.activatedWithoutFlag)
+ latencyComRawViewNoFlagFirstContact = $noFlagCom.firstContact['latencyFirstContactRawViewNodes']
+ latencyComRawViewWithFlagFirstContact = $flagCom.firstContact['latencyFirstContactRawViewNodes']
+ latencyManagedRawViewNoFlagFirstContact = $noFlagManaged.firstContact['latencyFirstContactRawViewNodes']
+ latencyManagedRawViewWithFlagFirstContact = $flagManaged.firstContact['latencyFirstContactRawViewNodes']
+ settledComRawViewNoFlag = $noFlagCom.afterSettle['settledRawViewNodes']
+ settledComRawViewWithFlag = $flagCom.afterSettle['settledRawViewNodes']
+ settledManagedRawViewNoFlag = $noFlagManaged.afterSettle['settledRawViewNodes']
+ settledManagedRawViewWithFlag = $flagManaged.afterSettle['settledRawViewNodes']
+ settledCountsMatchAcrossFlagArms = ($noFlagCom.afterSettle['settledRawViewNodes'] -eq $flagCom.afterSettle['settledRawViewNodes'] -and $noFlagManaged.afterSettle['settledRawViewNodes'] -eq $flagManaged.afterSettle['settledRawViewNodes'])
+ firstContactCountsAreRunVarying = 'first-contact counts are a race against Chromium async tree construction and are named latency* so the KTD9 normalizer canonicalizes them; the raw samples stay in this capture, the settled counts are the stable evidence'
+ flagVerdict = $flagVerdict
+ supersededPreFinding = 'the plan pre-finding (8 managed descendants, stable at 2/5/10s, candidate CONTRADICTS) is superseded: it was measured through an occluded window with a managed client, and neither the occlusion nor the settle window was controlled'
+ }
+ teardown = [ordered]@{ allObsidianProcessesTerminated = $allGone }
+ }
+ Write-ProbeJson -Probe $Probe -Name 'electron-activation.json' -InputObject $capture | Out-Null
+
+ $summary['chromiumVersion'] = $versions.chromiumVersion
+ $summary['electronVersion'] = $versions.electronVersion
+ $summary['obsidianVersion'] = $versions.obsidianFileVersion
+ $summary['comRawViewNoFlag'] = ('' + $capture.findings.latencyComRawViewNoFlagFirstContact + ' -> ' + $capture.findings.settledComRawViewNoFlag)
+ $summary['comRawViewWithFlag'] = ('' + $capture.findings.latencyComRawViewWithFlagFirstContact + ' -> ' + $capture.findings.settledComRawViewWithFlag)
+ $summary['managedRawViewNoFlag'] = ('' + $capture.findings.latencyManagedRawViewNoFlagFirstContact + ' -> ' + $capture.findings.settledManagedRawViewNoFlag)
+ $summary['managedRawViewWithFlag'] = ('' + $capture.findings.latencyManagedRawViewWithFlagFirstContact + ' -> ' + $capture.findings.settledManagedRawViewWithFlag)
+ $summary['clientStackDivergence'] = $capture.findings.clientStackDivergence
+ $summary['flagVerdict'] = $flagVerdict
+ $message = 'electron activation measured on both stacks with and without the flag'
+} catch {
+ $status = 'fail'
+ $message = ($_.Exception.Message -replace '[\r\n]+', ' ')
+ Write-ProbeLog -Message ('probe failed: ' + $message) -Level 'error'
+} finally {
+ [void](Stop-AllObsidian)
+ foreach ($id in @($script:Spawned)) {
+ try { Stop-ScratchProcess -ProcessId $id } catch { Write-ProbeLog -Message ('teardown: ' + $_.Exception.Message) -Level 'warn' }
+ }
+ foreach ($f in @(Get-ChildItem -LiteralPath (Get-CaptureDir -Probe $Probe) -File -ErrorAction SilentlyContinue)) {
+ if ($f.Name -like '*.normalized') { continue }
+ if (-not (Test-CaptureRedaction -Path $f.FullName)) { $status = 'fail'; $message = ('redaction residue in ' + $f.Name) }
+ }
+}
+
+Write-ProbeResult -Probe $Probe -Status $status -Message $message -Data $summary
+if ($status -eq 'fail') { exit 1 }
+exit 0
diff --git a/probes/windows/captures/09-elevation-uipi/uipi.json b/probes/windows/captures/09-elevation-uipi/uipi.json
new file mode 100644
index 0000000..b3fd9b2
--- /dev/null
+++ b/probes/windows/captures/09-elevation-uipi/uipi.json
@@ -0,0 +1,194 @@
+{
+ "question": "does UIPI block SendInput and window messages from a Medium-integrity process into a High-integrity target, and are UIA reads still allowed",
+ "invariant": "Engineering Invariant #6 (UIPI)",
+ "stack": "managed (UIA reads) + raw Win32 SendInput/SendMessage/PostMessage",
+ "scope": "api-contract",
+ "boundaryOrigin": "manufactured by common.ps1 Start-MediumIntegrityProcess (DuplicateTokenEx + SetTokenInformation(TokenIntegrityLevel, S-1-16-8192) + CreateProcessAsUser), label asserted on read-back",
+ "boundaryRuledOut": [
+ "Start-Process -Verb RunAs: AAM off on this box (FilterAdministratorToken unset, RID 500 full token) so it yields High-vs-High",
+ "runas.exe /trustlevel:0x20000: restricts the token but leaves the mandatory label at S-1-16-12288"
+ ],
+ "uacPolicy": {
+ "EnableLUA": 1,
+ "FilterAdministratorToken": "\u003cvalue not present\u003e",
+ "ConsentPromptBehaviorAdmin": 5,
+ "EnableInstallerDetection": 1,
+ "sessionIntegritySid": "S-1-16-12288",
+ "adminApprovalModeActive": false
+ },
+ "target": {
+ "process": "notepad.exe",
+ "processId": 7156,
+ "integritySid": "S-1-16-12288",
+ "windowHandle": 6948082,
+ "editChild": {
+ "className": "Edit",
+ "windowHandle": 5243138
+ },
+ "launchedByThisProbe": true
+ },
+ "arms": [
+ {
+ "arm": "high",
+ "workerIntegritySid": "S-1-16-12288",
+ "workerLaunchError": "",
+ "marker": "HHH",
+ "parentForegroundHandoff": "target foreground restored by parent ShowWindow(SW_MINIMIZE-\u003eSW_RESTORE) on the probe-launched window; SetForegroundWindow never called",
+ "targetStateBeforeInjection": "U8-BASE",
+ "targetStateAfterInjection": "HHHPU8-BASE",
+ "targetStateChanged": true,
+ "markerPresentInTargetAfterwards": true,
+ "observedBy": "parent High-integrity WM_GETTEXT on the notepad Edit child, independent of SendInput return",
+ "worker": {
+ "arm": "high",
+ "processId": 7916,
+ "addTypeCompilationAvailable": true,
+ "selfIntegritySid": "S-1-16-12288",
+ "targetIntegritySid": "S-1-16-12288",
+ "uiaRead": {
+ "fromHandle": "ok",
+ "name": "Untitled - Notepad",
+ "className": "Notepad",
+ "controlType": "Window",
+ "ownerProcessId": 7156,
+ "boundingRectangle": {
+ "Left": 476,
+ "Top": 100,
+ "Width": 768,
+ "Height": 525
+ },
+ "rawViewNodes": 3,
+ "editPatterns": [
+
+ ],
+ "editValueSeenByThisProcess": "\u003cno value/text pattern: Exception calling \"GetCurrentPattern\" with \"1\" argument(s): \"Unsupported Pattern.\"\u003e"
+ },
+ "wmGetTextFromWorker": {
+ "returned": "U8-BASE",
+ "sendMessageResult": 7,
+ "lastError": 0
+ },
+ "foregroundOwnerAtAssert": {
+ "processId": 7156
+ },
+ "foregroundAssertPre": "ok",
+ "sendInputEventsAccepted": 6,
+ "sendInputLastError": 0,
+ "sendInputStructSize": 40,
+ "foregroundAssertPost": "ok",
+ "postMessageWmChar": {
+ "returned": true,
+ "lastError": 203
+ },
+ "wmGetTextAfterFromWorker": {
+ "returned": "HHHPU8-BASE",
+ "sendMessageResult": 11,
+ "lastError": 0
+ },
+ "uiaReadAfter": {
+ "fromHandle": "ok",
+ "name": "Untitled - Notepad",
+ "className": "Notepad",
+ "controlType": "Window",
+ "ownerProcessId": 7156,
+ "boundingRectangle": {
+ "Left": 476,
+ "Top": 100,
+ "Width": 768,
+ "Height": 525
+ },
+ "rawViewNodes": 3,
+ "editPatterns": [
+
+ ],
+ "editValueSeenByThisProcess": "\u003cno value/text pattern: Exception calling \"GetCurrentPattern\" with \"1\" argument(s): \"Unsupported Pattern.\"\u003e"
+ }
+ }
+ },
+ {
+ "arm": "medium",
+ "workerIntegritySid": "S-1-16-8192",
+ "workerLaunchError": "",
+ "marker": "MMM",
+ "parentForegroundHandoff": "target foreground restored by parent ShowWindow(SW_MINIMIZE-\u003eSW_RESTORE) on the probe-launched window; SetForegroundWindow never called",
+ "targetStateBeforeInjection": "U8-BASE",
+ "targetStateAfterInjection": "U8-BASE",
+ "targetStateChanged": false,
+ "markerPresentInTargetAfterwards": false,
+ "observedBy": "parent High-integrity WM_GETTEXT on the notepad Edit child, independent of SendInput return",
+ "worker": {
+ "arm": "medium",
+ "processId": 7508,
+ "addTypeCompilationAvailable": false,
+ "addTypeCompilationError": "A required privilege is not held by the client",
+ "selfIntegritySid": "S-1-16-8192",
+ "targetIntegritySid": "S-1-16-12288",
+ "uiaRead": {
+ "fromHandle": "ok",
+ "name": "Untitled - Notepad",
+ "className": "Notepad",
+ "controlType": "Window",
+ "ownerProcessId": 7156,
+ "boundingRectangle": {
+ "Left": 476,
+ "Top": 100,
+ "Width": 768,
+ "Height": 525
+ },
+ "rawViewNodes": 3,
+ "editPatterns": [
+
+ ],
+ "editValueSeenByThisProcess": "\u003cno value/text pattern: Exception calling \"GetCurrentPattern\" with \"1\" argument(s): \"Unsupported Pattern.\"\u003e"
+ },
+ "wmGetTextFromWorker": {
+ "returned": "U8-BASE",
+ "sendMessageResult": 7,
+ "lastError": 0
+ },
+ "foregroundOwnerAtAssert": {
+ "processId": 7156
+ },
+ "foregroundAssertPre": "ok",
+ "sendInputEventsAccepted": 6,
+ "sendInputLastError": 0,
+ "sendInputStructSize": 40,
+ "foregroundAssertPost": "ok",
+ "postMessageWmChar": {
+ "returned": false,
+ "lastError": 5
+ },
+ "wmGetTextAfterFromWorker": {
+ "returned": "U8-BASE",
+ "sendMessageResult": 7,
+ "lastError": 0
+ },
+ "uiaReadAfter": {
+ "fromHandle": "ok",
+ "name": "Untitled - Notepad",
+ "className": "Notepad",
+ "controlType": "Window",
+ "ownerProcessId": 7156,
+ "boundingRectangle": {
+ "Left": 476,
+ "Top": 100,
+ "Width": 768,
+ "Height": 525
+ },
+ "rawViewNodes": 3,
+ "editPatterns": [
+
+ ],
+ "editValueSeenByThisProcess": "\u003cno value/text pattern: Exception calling \"GetCurrentPattern\" with \"1\" argument(s): \"Unsupported Pattern.\"\u003e"
+ }
+ }
+ }
+ ],
+ "findings": {
+ "uiaReadFromMediumAgainstHigh": "ok",
+ "sendInputFromHighLanded": true,
+ "sendInputFromMediumLanded": false,
+ "sendInputReturnIsNotEvidence": "SendInput reports the event count it accepted in both arms; only the re-read of the target distinguishes them",
+ "wmGetTextFromMediumBlocked": false
+ }
+}
\ No newline at end of file
diff --git a/probes/windows/captures/09-elevation-uipi/uipi.json.normalized b/probes/windows/captures/09-elevation-uipi/uipi.json.normalized
new file mode 100644
index 0000000..b9aca38
--- /dev/null
+++ b/probes/windows/captures/09-elevation-uipi/uipi.json.normalized
@@ -0,0 +1,194 @@
+{
+ "question": "does UIPI block SendInput and window messages from a Medium-integrity process into a High-integrity target, and are UIA reads still allowed",
+ "invariant": "Engineering Invariant #6 (UIPI)",
+ "stack": "managed (UIA reads) + raw Win32 SendInput/SendMessage/PostMessage",
+ "scope": "api-contract",
+ "boundaryOrigin": "manufactured by common.ps1 Start-MediumIntegrityProcess (DuplicateTokenEx + SetTokenInformation(TokenIntegrityLevel, S-1-16-8192) + CreateProcessAsUser), label asserted on read-back",
+ "boundaryRuledOut": [
+ "Start-Process -Verb RunAs: AAM off on this box (FilterAdministratorToken unset, RID 500 full token) so it yields High-vs-High",
+ "runas.exe /trustlevel:0x20000: restricts the token but leaves the mandatory label at S-1-16-12288"
+ ],
+ "uacPolicy": {
+ "EnableLUA": 1,
+ "FilterAdministratorToken": "\u003cvalue not present\u003e",
+ "ConsentPromptBehaviorAdmin": 5,
+ "EnableInstallerDetection": 1,
+ "sessionIntegritySid": "S-1-16-12288",
+ "adminApprovalModeActive": false
+ },
+ "target": {
+ "process": "notepad.exe",
+ "processId": ,
+ "integritySid": "S-1-16-12288",
+ "windowHandle": ,
+ "editChild": {
+ "className": "Edit",
+ "windowHandle":
+ },
+ "launchedByThisProbe": true
+ },
+ "arms": [
+ {
+ "arm": "high",
+ "workerIntegritySid": "S-1-16-12288",
+ "workerLaunchError": "",
+ "marker": "HHH",
+ "parentForegroundHandoff": "target foreground restored by parent ShowWindow(SW_MINIMIZE-\u003eSW_RESTORE) on the probe-launched window; SetForegroundWindow never called",
+ "targetStateBeforeInjection": "U8-BASE",
+ "targetStateAfterInjection": "HHHPU8-BASE",
+ "targetStateChanged": true,
+ "markerPresentInTargetAfterwards": true,
+ "observedBy": "parent High-integrity WM_GETTEXT on the notepad Edit child, independent of SendInput return",
+ "worker": {
+ "arm": "high",
+ "processId": ,
+ "addTypeCompilationAvailable": true,
+ "selfIntegritySid": "S-1-16-12288",
+ "targetIntegritySid": "S-1-16-12288",
+ "uiaRead": {
+ "fromHandle": "ok",
+ "name": "Untitled - Notepad",
+ "className": "Notepad",
+ "controlType": "Window",
+ "ownerProcessId": ,
+ "boundingRectangle": {
+ "Left": 480,
+ "Top": 96,
+ "Width": 768,
+ "Height": 528
+ },
+ "rawViewNodes": 3,
+ "editPatterns": [
+
+ ],
+ "editValueSeenByThisProcess": "\u003cno value/text pattern: Exception calling \"GetCurrentPattern\" with \"1\" argument(s): \"Unsupported Pattern.\"\u003e"
+ },
+ "wmGetTextFromWorker": {
+ "returned": "U8-BASE",
+ "sendMessageResult": 7,
+ "lastError": 0
+ },
+ "foregroundOwnerAtAssert": {
+ "processId":
+ },
+ "foregroundAssertPre": "ok",
+ "sendInputEventsAccepted": 6,
+ "sendInputLastError": 0,
+ "sendInputStructSize": 40,
+ "foregroundAssertPost": "ok",
+ "postMessageWmChar": {
+ "returned": true,
+ "lastError": 203
+ },
+ "wmGetTextAfterFromWorker": {
+ "returned": "HHHPU8-BASE",
+ "sendMessageResult": 11,
+ "lastError": 0
+ },
+ "uiaReadAfter": {
+ "fromHandle": "ok",
+ "name": "Untitled - Notepad",
+ "className": "Notepad",
+ "controlType": "Window",
+ "ownerProcessId": ,
+ "boundingRectangle": {
+ "Left": 480,
+ "Top": 96,
+ "Width": 768,
+ "Height": 528
+ },
+ "rawViewNodes": 3,
+ "editPatterns": [
+
+ ],
+ "editValueSeenByThisProcess": "\u003cno value/text pattern: Exception calling \"GetCurrentPattern\" with \"1\" argument(s): \"Unsupported Pattern.\"\u003e"
+ }
+ }
+ },
+ {
+ "arm": "medium",
+ "workerIntegritySid": "S-1-16-8192",
+ "workerLaunchError": "",
+ "marker": "MMM",
+ "parentForegroundHandoff": "target foreground restored by parent ShowWindow(SW_MINIMIZE-\u003eSW_RESTORE) on the probe-launched window; SetForegroundWindow never called",
+ "targetStateBeforeInjection": "U8-BASE",
+ "targetStateAfterInjection": "U8-BASE",
+ "targetStateChanged": false,
+ "markerPresentInTargetAfterwards": false,
+ "observedBy": "parent High-integrity WM_GETTEXT on the notepad Edit child, independent of SendInput return",
+ "worker": {
+ "arm": "medium",
+ "processId": ,
+ "addTypeCompilationAvailable": false,
+ "addTypeCompilationError": "A required privilege is not held by the client",
+ "selfIntegritySid": "S-1-16-8192",
+ "targetIntegritySid": "S-1-16-12288",
+ "uiaRead": {
+ "fromHandle": "ok",
+ "name": "Untitled - Notepad",
+ "className": "Notepad",
+ "controlType": "Window",
+ "ownerProcessId": ,
+ "boundingRectangle": {
+ "Left": 480,
+ "Top": 96,
+ "Width": 768,
+ "Height": 528
+ },
+ "rawViewNodes": 3,
+ "editPatterns": [
+
+ ],
+ "editValueSeenByThisProcess": "\u003cno value/text pattern: Exception calling \"GetCurrentPattern\" with \"1\" argument(s): \"Unsupported Pattern.\"\u003e"
+ },
+ "wmGetTextFromWorker": {
+ "returned": "U8-BASE",
+ "sendMessageResult": 7,
+ "lastError": 0
+ },
+ "foregroundOwnerAtAssert": {
+ "processId":
+ },
+ "foregroundAssertPre": "ok",
+ "sendInputEventsAccepted": 6,
+ "sendInputLastError": 0,
+ "sendInputStructSize": 40,
+ "foregroundAssertPost": "ok",
+ "postMessageWmChar": {
+ "returned": false,
+ "lastError": 5
+ },
+ "wmGetTextAfterFromWorker": {
+ "returned": "U8-BASE",
+ "sendMessageResult": 7,
+ "lastError": 0
+ },
+ "uiaReadAfter": {
+ "fromHandle": "ok",
+ "name": "Untitled - Notepad",
+ "className": "Notepad",
+ "controlType": "Window",
+ "ownerProcessId": ,
+ "boundingRectangle": {
+ "Left": 480,
+ "Top": 96,
+ "Width": 768,
+ "Height": 528
+ },
+ "rawViewNodes": 3,
+ "editPatterns": [
+
+ ],
+ "editValueSeenByThisProcess": "\u003cno value/text pattern: Exception calling \"GetCurrentPattern\" with \"1\" argument(s): \"Unsupported Pattern.\"\u003e"
+ }
+ }
+ }
+ ],
+ "findings": {
+ "uiaReadFromMediumAgainstHigh": "ok",
+ "sendInputFromHighLanded": true,
+ "sendInputFromMediumLanded": false,
+ "sendInputReturnIsNotEvidence": "SendInput reports the event count it accepted in both arms; only the re-read of the target distinguishes them",
+ "wmGetTextFromMediumBlocked": false
+ }
+}
\ No newline at end of file
diff --git a/probes/windows/captures/10-session-dpi/session-dpi.json b/probes/windows/captures/10-session-dpi/session-dpi.json
new file mode 100644
index 0000000..e822901
--- /dev/null
+++ b/probes/windows/captures/10-session-dpi/session-dpi.json
@@ -0,0 +1,224 @@
+{
+ "question": "what bounds delta does DPI awareness produce between two processes reading the same element, and what scale can this environment actually apply",
+ "stack": "managed UIA for BoundingRectangle; raw Win32 for awareness, DisplayConfig and metrics",
+ "scope": "api-contract for the awareness APIs; app/provider and environment-specific for the achievable scale",
+ "session": {
+ "sessionId": 1,
+ "activeConsoleSessionId": 1,
+ "isRemoteSession": false,
+ "windowStation": "Console",
+ "note": "RDP session-transition behavior is not measurable here (physical console session); it closes at sub-phase 2.1 runner registration"
+ },
+ "displays": [
+ {
+ "primary": true,
+ "Left": 0,
+ "Top": 0,
+ "Width": 1639,
+ "Height": 732
+ }
+ ],
+ "displayScaleCapability": {
+ "displayConfigGetResult": 0,
+ "scaleRelMin": 0,
+ "scaleRelCurrent": 0,
+ "scaleRelMax": 0,
+ "monitorRegistryKeys": [
+ "NOEDID_15AD_0405_00000000_000F0000_0^20ED182961F2CFDB3A2D28C95A99744F"
+ ],
+ "stepsAvailableAboveRecommended": 0
+ },
+ "method": "two sibling powershell children read the same probe-launched notepad window and its Edit child; the unaware arm is forced with __COMPAT_LAYER=DPIUNAWARE, the aware arm calls SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)",
+ "normalizationNote": "raw rectangles are bucketed to 8px by the KTD9 normalizer; delta fields are named deltaLeft/deltaTop/deltaWidth/deltaHeight, which the bucketing regex does not match, so the measured delta survives the normalized twin verbatim",
+ "passes": [
+ {
+ "pass": "recommended",
+ "requested": "display recommended scale (100%)",
+ "requestedScaleRel": 0,
+ "displayConfigSetResult": null,
+ "scaleRelReportedAfterwards": 0,
+ "registryDpiValueAfterwards": 0,
+ "effectiveDpiSeenByAwareArm": 96,
+ "scaleActuallyApplied": false,
+ "awareArm": {
+ "mode": "aware",
+ "processId": 8764,
+ "awarenessAtStartup": "PROCESS_DPI_UNAWARE",
+ "setProcessDpiAwarenessContextPerMonitorV2": "succeeded",
+ "awarenessEffective": "PROCESS_PER_MONITOR_DPI_AWARE",
+ "monitorEffectiveDpiX": 96,
+ "monitorEffectiveDpiY": 96,
+ "logPixels": 96,
+ "screenMetricsCx": 1639,
+ "screenMetricsCy": 732,
+ "getWindowRect": {
+ "Left": 476,
+ "Top": 100,
+ "Right": 1244,
+ "Bottom": 625
+ },
+ "uiaBounds_window": {
+ "className": "Notepad",
+ "Left": 476,
+ "Top": 100,
+ "Width": 768,
+ "Height": 525
+ },
+ "uiaBounds_editChild": {
+ "className": "Edit",
+ "Left": 484,
+ "Top": 151,
+ "Width": 752,
+ "Height": 443
+ }
+ },
+ "unawareArm": {
+ "mode": "unaware",
+ "processId": 304,
+ "awarenessAtStartup": "PROCESS_DPI_UNAWARE",
+ "setProcessDpiAwarenessContextPerMonitorV2": "not attempted (unaware arm, forced with __COMPAT_LAYER=DPIUNAWARE)",
+ "awarenessEffective": "PROCESS_DPI_UNAWARE",
+ "monitorEffectiveDpiX": 96,
+ "monitorEffectiveDpiY": 96,
+ "logPixels": 96,
+ "screenMetricsCx": 1639,
+ "screenMetricsCy": 732,
+ "getWindowRect": {
+ "Left": 476,
+ "Top": 100,
+ "Right": 1244,
+ "Bottom": 625
+ },
+ "uiaBounds_window": {
+ "className": "Notepad",
+ "Left": 476,
+ "Top": 100,
+ "Width": 768,
+ "Height": 525
+ },
+ "uiaBounds_editChild": {
+ "className": "Edit",
+ "Left": 484,
+ "Top": 151,
+ "Width": 752,
+ "Height": 443
+ }
+ },
+ "windowBoundsDelta": {
+ "measurable": true,
+ "deltaLeft": 0,
+ "deltaTop": 0,
+ "deltaWidth": 0,
+ "deltaHeight": 0
+ },
+ "editChildBoundsDelta": {
+ "measurable": true,
+ "deltaLeft": 0,
+ "deltaTop": 0,
+ "deltaWidth": 0,
+ "deltaHeight": 0
+ }
+ },
+ {
+ "pass": "plus-one-step",
+ "requested": "one scale step above recommended (125%)",
+ "requestedScaleRel": 1,
+ "displayConfigSetResult": 0,
+ "scaleRelReportedAfterwards": 1,
+ "registryDpiValueAfterwards": 1,
+ "effectiveDpiSeenByAwareArm": 96,
+ "scaleActuallyApplied": false,
+ "awareArm": {
+ "mode": "aware",
+ "processId": 8412,
+ "awarenessAtStartup": "PROCESS_DPI_UNAWARE",
+ "setProcessDpiAwarenessContextPerMonitorV2": "succeeded",
+ "awarenessEffective": "PROCESS_PER_MONITOR_DPI_AWARE",
+ "monitorEffectiveDpiX": 96,
+ "monitorEffectiveDpiY": 96,
+ "logPixels": 96,
+ "screenMetricsCx": 1639,
+ "screenMetricsCy": 732,
+ "getWindowRect": {
+ "Left": 476,
+ "Top": 100,
+ "Right": 1244,
+ "Bottom": 625
+ },
+ "uiaBounds_window": {
+ "className": "Notepad",
+ "Left": 476,
+ "Top": 100,
+ "Width": 768,
+ "Height": 525
+ },
+ "uiaBounds_editChild": {
+ "className": "Edit",
+ "Left": 484,
+ "Top": 151,
+ "Width": 752,
+ "Height": 443
+ }
+ },
+ "unawareArm": {
+ "mode": "unaware",
+ "processId": 5060,
+ "awarenessAtStartup": "PROCESS_DPI_UNAWARE",
+ "setProcessDpiAwarenessContextPerMonitorV2": "not attempted (unaware arm, forced with __COMPAT_LAYER=DPIUNAWARE)",
+ "awarenessEffective": "PROCESS_DPI_UNAWARE",
+ "monitorEffectiveDpiX": 96,
+ "monitorEffectiveDpiY": 96,
+ "logPixels": 96,
+ "screenMetricsCx": 1639,
+ "screenMetricsCy": 732,
+ "getWindowRect": {
+ "Left": 476,
+ "Top": 100,
+ "Right": 1244,
+ "Bottom": 625
+ },
+ "uiaBounds_window": {
+ "className": "Notepad",
+ "Left": 476,
+ "Top": 100,
+ "Width": 768,
+ "Height": 525
+ },
+ "uiaBounds_editChild": {
+ "className": "Edit",
+ "Left": 484,
+ "Top": 151,
+ "Width": 752,
+ "Height": 443
+ }
+ },
+ "windowBoundsDelta": {
+ "measurable": true,
+ "deltaLeft": 0,
+ "deltaTop": 0,
+ "deltaWidth": 0,
+ "deltaHeight": 0
+ },
+ "editChildBoundsDelta": {
+ "measurable": true,
+ "deltaLeft": 0,
+ "deltaTop": 0,
+ "deltaWidth": 0,
+ "deltaHeight": 0
+ }
+ }
+ ],
+ "teardown": {
+ "originalScaleRel": 0,
+ "displayConfigSetResult": 0,
+ "scaleRelReadBackAfterwards": 0,
+ "registryDpiValueAfterwards": 0,
+ "restored": true
+ },
+ "deferred": {
+ "row": "multi-monitor and mixed-DPI bounds behavior, and any non-zero aware-vs-unaware delta",
+ "reason": "single display, and that display reports no EDID so Windows offers exactly one scale step (min=cur=max); the 125% request is accepted by DisplayConfigSetDeviceInfo and persisted to the registry but never takes effect",
+ "closesAt": "sub-phase 2.4 (owns list_displays and per-monitor scale_factor) on a runner with a scalable display",
+ "neverLeaves": "Phase 2"
+ }
+}
\ No newline at end of file
diff --git a/probes/windows/captures/10-session-dpi/session-dpi.json.normalized b/probes/windows/captures/10-session-dpi/session-dpi.json.normalized
new file mode 100644
index 0000000..ac02188
--- /dev/null
+++ b/probes/windows/captures/10-session-dpi/session-dpi.json.normalized
@@ -0,0 +1,224 @@
+{
+ "question": "what bounds delta does DPI awareness produce between two processes reading the same element, and what scale can this environment actually apply",
+ "stack": "managed UIA for BoundingRectangle; raw Win32 for awareness, DisplayConfig and metrics",
+ "scope": "api-contract for the awareness APIs; app/provider and environment-specific for the achievable scale",
+ "session": {
+ "sessionId": 1,
+ "activeConsoleSessionId": 1,
+ "isRemoteSession": false,
+ "windowStation": "Console",
+ "note": "RDP session-transition behavior is not measurable here (physical console session); it closes at sub-phase 2.1 runner registration"
+ },
+ "displays": [
+ {
+ "primary": true,
+ "Left": 0,
+ "Top": 0,
+ "Width": 1640,
+ "Height": 736
+ }
+ ],
+ "displayScaleCapability": {
+ "displayConfigGetResult": 0,
+ "scaleRelMin": 0,
+ "scaleRelCurrent": 0,
+ "scaleRelMax": 0,
+ "monitorRegistryKeys": [
+ "NOEDID_15AD_0405_00000000_000F0000_0^20ED182961F2CFDB3A2D28C95A99744F"
+ ],
+ "stepsAvailableAboveRecommended": 0
+ },
+ "method": "two sibling powershell children read the same probe-launched notepad window and its Edit child; the unaware arm is forced with __COMPAT_LAYER=DPIUNAWARE, the aware arm calls SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)",
+ "normalizationNote": "raw rectangles are bucketed to 8px by the KTD9 normalizer; delta fields are named deltaLeft/deltaTop/deltaWidth/deltaHeight, which the bucketing regex does not match, so the measured delta survives the normalized twin verbatim",
+ "passes": [
+ {
+ "pass": "recommended",
+ "requested": "display recommended scale (100%)",
+ "requestedScaleRel": 0,
+ "displayConfigSetResult": null,
+ "scaleRelReportedAfterwards": 0,
+ "registryDpiValueAfterwards": 0,
+ "effectiveDpiSeenByAwareArm": 96,
+ "scaleActuallyApplied": false,
+ "awareArm": {
+ "mode": "aware",
+ "processId": ,
+ "awarenessAtStartup": "PROCESS_DPI_UNAWARE",
+ "setProcessDpiAwarenessContextPerMonitorV2": "succeeded",
+ "awarenessEffective": "PROCESS_PER_MONITOR_DPI_AWARE",
+ "monitorEffectiveDpiX": 96,
+ "monitorEffectiveDpiY": 96,
+ "logPixels": 96,
+ "screenMetricsCx": 1639,
+ "screenMetricsCy": 732,
+ "getWindowRect": {
+ "Left": 480,
+ "Top": 96,
+ "Right": 1248,
+ "Bottom": 624
+ },
+ "uiaBounds_window": {
+ "className": "Notepad",
+ "Left": 480,
+ "Top": 96,
+ "Width": 768,
+ "Height": 528
+ },
+ "uiaBounds_editChild": {
+ "className": "Edit",
+ "Left": 480,
+ "Top": 152,
+ "Width": 752,
+ "Height": 440
+ }
+ },
+ "unawareArm": {
+ "mode": "unaware",
+ "processId": ,
+ "awarenessAtStartup": "PROCESS_DPI_UNAWARE",
+ "setProcessDpiAwarenessContextPerMonitorV2": "not attempted (unaware arm, forced with __COMPAT_LAYER=DPIUNAWARE)",
+ "awarenessEffective": "PROCESS_DPI_UNAWARE",
+ "monitorEffectiveDpiX": 96,
+ "monitorEffectiveDpiY": 96,
+ "logPixels": 96,
+ "screenMetricsCx": 1639,
+ "screenMetricsCy": 732,
+ "getWindowRect": {
+ "Left": 480,
+ "Top": 96,
+ "Right": 1248,
+ "Bottom": 624
+ },
+ "uiaBounds_window": {
+ "className": "Notepad",
+ "Left": 480,
+ "Top": 96,
+ "Width": 768,
+ "Height": 528
+ },
+ "uiaBounds_editChild": {
+ "className": "Edit",
+ "Left": 480,
+ "Top": 152,
+ "Width": 752,
+ "Height": 440
+ }
+ },
+ "windowBoundsDelta": {
+ "measurable": true,
+ "deltaLeft": 0,
+ "deltaTop": 0,
+ "deltaWidth": 0,
+ "deltaHeight": 0
+ },
+ "editChildBoundsDelta": {
+ "measurable": true,
+ "deltaLeft": 0,
+ "deltaTop": 0,
+ "deltaWidth": 0,
+ "deltaHeight": 0
+ }
+ },
+ {
+ "pass": "plus-one-step",
+ "requested": "one scale step above recommended (125%)",
+ "requestedScaleRel": 1,
+ "displayConfigSetResult": 0,
+ "scaleRelReportedAfterwards": 1,
+ "registryDpiValueAfterwards": 1,
+ "effectiveDpiSeenByAwareArm": 96,
+ "scaleActuallyApplied": false,
+ "awareArm": {
+ "mode": "aware",
+ "processId": ,
+ "awarenessAtStartup": "PROCESS_DPI_UNAWARE",
+ "setProcessDpiAwarenessContextPerMonitorV2": "succeeded",
+ "awarenessEffective": "PROCESS_PER_MONITOR_DPI_AWARE",
+ "monitorEffectiveDpiX": 96,
+ "monitorEffectiveDpiY": 96,
+ "logPixels": 96,
+ "screenMetricsCx": 1639,
+ "screenMetricsCy": 732,
+ "getWindowRect": {
+ "Left": 480,
+ "Top": 96,
+ "Right": 1248,
+ "Bottom": 624
+ },
+ "uiaBounds_window": {
+ "className": "Notepad",
+ "Left": 480,
+ "Top": 96,
+ "Width": 768,
+ "Height": 528
+ },
+ "uiaBounds_editChild": {
+ "className": "Edit",
+ "Left": 480,
+ "Top": 152,
+ "Width": 752,
+ "Height": 440
+ }
+ },
+ "unawareArm": {
+ "mode": "unaware",
+ "processId": ,
+ "awarenessAtStartup": "PROCESS_DPI_UNAWARE",
+ "setProcessDpiAwarenessContextPerMonitorV2": "not attempted (unaware arm, forced with __COMPAT_LAYER=DPIUNAWARE)",
+ "awarenessEffective": "PROCESS_DPI_UNAWARE",
+ "monitorEffectiveDpiX": 96,
+ "monitorEffectiveDpiY": 96,
+ "logPixels": 96,
+ "screenMetricsCx": 1639,
+ "screenMetricsCy": 732,
+ "getWindowRect": {
+ "Left": 480,
+ "Top": 96,
+ "Right": 1248,
+ "Bottom": 624
+ },
+ "uiaBounds_window": {
+ "className": "Notepad",
+ "Left": 480,
+ "Top": 96,
+ "Width": 768,
+ "Height": 528
+ },
+ "uiaBounds_editChild": {
+ "className": "Edit",
+ "Left": 480,
+ "Top": 152,
+ "Width": 752,
+ "Height": 440
+ }
+ },
+ "windowBoundsDelta": {
+ "measurable": true,
+ "deltaLeft": 0,
+ "deltaTop": 0,
+ "deltaWidth": 0,
+ "deltaHeight": 0
+ },
+ "editChildBoundsDelta": {
+ "measurable": true,
+ "deltaLeft": 0,
+ "deltaTop": 0,
+ "deltaWidth": 0,
+ "deltaHeight": 0
+ }
+ }
+ ],
+ "teardown": {
+ "originalScaleRel": 0,
+ "displayConfigSetResult": 0,
+ "scaleRelReadBackAfterwards": 0,
+ "registryDpiValueAfterwards": 0,
+ "restored": true
+ },
+ "deferred": {
+ "row": "multi-monitor and mixed-DPI bounds behavior, and any non-zero aware-vs-unaware delta",
+ "reason": "single display, and that display reports no EDID so Windows offers exactly one scale step (min=cur=max); the 125% request is accepted by DisplayConfigSetDeviceInfo and persisted to the registry but never takes effect",
+ "closesAt": "sub-phase 2.4 (owns list_displays and per-monitor scale_factor) on a runner with a scalable display",
+ "neverLeaves": "Phase 2"
+ }
+}
\ No newline at end of file
diff --git a/probes/windows/captures/11-electron-activation/electron-activation.json b/probes/windows/captures/11-electron-activation/electron-activation.json
new file mode 100644
index 0000000..bd70644
--- /dev/null
+++ b/probes/windows/captures/11-electron-activation/electron-activation.json
@@ -0,0 +1,164 @@
+{
+ "question": "does Chromium 138+ expose a UIA tree to any UIA client with no flag, and does --force-renderer-accessibility still change anything",
+ "gradedAgainst": "the committed phases.md line that Chromium 138+ exposes a UIA tree to any UIA client with no flag (P2-O15 / 2.4)",
+ "stack": "both: managed System.Windows.Automation and the U7 UIA3 COM shim (KTD1 - the COM rows are the authoritative ones)",
+ "scope": "app/provider - specific to this Obsidian/Electron/Chromium build; the activation mechanism is Chromium behavior, not a Windows API contract",
+ "versions": {
+ "method": "user-agent string scanned out of the shipped Obsidian.exe on this installation",
+ "obsidianFileVersion": "1.12.7.0",
+ "chromiumVersion": "142.0.7444.265",
+ "electronVersion": "39.8.3",
+ "established": true,
+ "rejectedMethods": [
+ "ELECTRON_RUN_AS_NODE=1 Obsidian.exe -p process.versions produced no output on this VM",
+ "framework DLL file versions carry component versions, not the Chromium version"
+ ]
+ },
+ "obsidianPreexistingAtProbeStart": false,
+ "method": "one fresh Obsidian instance per (stack x flag) cell, because the first UIA client to touch a renderer activates accessibility for that process",
+ "placementHazards": [
+ "U3: an earlier revision that left other windows on top of Obsidian held it at its first-contact node count through a full 8s settle and a 16s instrumented hold, three runs running. Minimizing the covering windows fixed it. The mechanism was NOT isolated (Chromium native-window occlusion tracking is the obvious candidate, unproven). Carried here as a probe-placement hazard, explicitly not a product claim.",
+ "U4: matching a bare Chrome_WidgetWin_1 window found an unrelated window and recorded 9 nodes / 0% coverage as if it were the Electron answer. This probe only accepts a window whose owning pid is one of the launched Obsidian pids, and reads the hosting pid back off the window."
+ ],
+ "countStabilityNote": "settled node counts are content-dependent (U7 measured RawView 172 on a bare walker run, U3 measured 119 through its probe). The stable fact is the growth ratio across the settle, not the absolute count.",
+ "cells": [
+ {
+ "flagArm": "no-flag",
+ "stack": "managed",
+ "authoritative": false,
+ "windowClassName": "Chrome_WidgetWin_1",
+ "windowHandle": 5309714,
+ "windowHost": {
+ "processId": 9204
+ },
+ "latencyObsidianProcessCountAtWindowDiscovery": 4,
+ "foregroundOwner": {
+ "processId": 980
+ },
+ "settleMs": 8000,
+ "firstContact": {
+ "latencyFirstContactRawViewNodes": 13,
+ "latencyFirstContactRawViewRefableNodes": 13,
+ "latencyFirstContactControlViewNodes": 130,
+ "latencyFirstContactControlViewRefableNodes": 130
+ },
+ "afterSettle": {
+ "settledRawViewNodes": 172,
+ "settledRawViewRefableNodes": 172,
+ "settledControlViewNodes": 133,
+ "settledControlViewRefableNodes": 133
+ },
+ "latencyRawViewGrowthRatio": 13.23,
+ "activatedWithoutFlag": true
+ },
+ {
+ "flagArm": "no-flag",
+ "stack": "uia3-com",
+ "authoritative": true,
+ "windowClassName": "Chrome_WidgetWin_1",
+ "windowHandle": 6422788,
+ "windowHost": {
+ "processId": 9000
+ },
+ "latencyObsidianProcessCountAtWindowDiscovery": 4,
+ "foregroundOwner": {
+ "processId": 980
+ },
+ "settleMs": 8000,
+ "firstContact": {
+ "latencyFirstContactRawViewNodes": 13,
+ "latencyFirstContactControlViewNodes": 9,
+ "latencyFirstContactContentViewNodes": 9
+ },
+ "afterSettle": {
+ "settledRawViewNodes": 172,
+ "settledControlViewNodes": 133,
+ "settledContentViewNodes": 133,
+ "settledRefableNodesWithAnyPattern": 171,
+ "settledRefableNodesExcludingLegacy": 171
+ },
+ "censusNote": "census ran in a second shim process against the already-activated instance, so its counts are settled counts; per-element rows are deliberately not captured (R11: Obsidian note titles are content)",
+ "latencyRawViewGrowthRatio": 13.23,
+ "activatedWithoutFlag": true
+ },
+ {
+ "flagArm": "force-renderer-accessibility",
+ "stack": "managed",
+ "authoritative": false,
+ "windowClassName": "Chrome_WidgetWin_1",
+ "windowHandle": 11993994,
+ "windowHost": {
+ "processId": 7840
+ },
+ "latencyObsidianProcessCountAtWindowDiscovery": 4,
+ "foregroundOwner": {
+ "processId": 980
+ },
+ "settleMs": 8000,
+ "firstContact": {
+ "latencyFirstContactRawViewNodes": 164,
+ "latencyFirstContactRawViewRefableNodes": 163,
+ "latencyFirstContactControlViewNodes": 133,
+ "latencyFirstContactControlViewRefableNodes": 133
+ },
+ "afterSettle": {
+ "settledRawViewNodes": 172,
+ "settledRawViewRefableNodes": 172,
+ "settledControlViewNodes": 133,
+ "settledControlViewRefableNodes": 133
+ },
+ "latencyRawViewGrowthRatio": 1.05,
+ "activatedWithoutFlag": false
+ },
+ {
+ "flagArm": "force-renderer-accessibility",
+ "stack": "uia3-com",
+ "authoritative": true,
+ "windowClassName": "Chrome_WidgetWin_1",
+ "windowHandle": 5570818,
+ "windowHost": {
+ "processId": 6108
+ },
+ "latencyObsidianProcessCountAtWindowDiscovery": 4,
+ "foregroundOwner": {
+ "processId": 980
+ },
+ "settleMs": 8000,
+ "firstContact": {
+ "latencyFirstContactRawViewNodes": 141,
+ "latencyFirstContactControlViewNodes": 127,
+ "latencyFirstContactContentViewNodes": 131
+ },
+ "afterSettle": {
+ "settledRawViewNodes": 172,
+ "settledControlViewNodes": 133,
+ "settledContentViewNodes": 133,
+ "settledRefableNodesWithAnyPattern": 172,
+ "settledRefableNodesExcludingLegacy": 172
+ },
+ "censusNote": "census ran in a second shim process against the already-activated instance, so its counts are settled counts; per-element rows are deliberately not captured (R11: Obsidian note titles are content)",
+ "latencyRawViewGrowthRatio": 1.22,
+ "activatedWithoutFlag": false
+ }
+ ],
+ "findings": {
+ "managedActivatesWithoutFlag": true,
+ "comActivatesWithoutFlag": true,
+ "clientStackDivergence": false,
+ "latencyComRawViewNoFlagFirstContact": 13,
+ "latencyComRawViewWithFlagFirstContact": 141,
+ "latencyManagedRawViewNoFlagFirstContact": 13,
+ "latencyManagedRawViewWithFlagFirstContact": 164,
+ "settledComRawViewNoFlag": 172,
+ "settledComRawViewWithFlag": 172,
+ "settledManagedRawViewNoFlag": 172,
+ "settledManagedRawViewWithFlag": 172,
+ "settledCountsMatchAcrossFlagArms": true,
+ "firstContactCountsAreRunVarying": "first-contact counts are a race against Chromium async tree construction and are named latency* so the KTD9 normalizer canonicalizes them; the raw samples stay in this capture, the settled counts are the stable evidence",
+ "flagVerdict": "partially useful - redundant for eventual tree exposure: on Chromium 142.0.7444.265 both client stacks reach the same settled RawView count with no flag, so the phases.md claim holds for exposure. It is not redundant for first-contact readiness: without the flag first contact is deterministically the small pre-activation shell on every run and both stacks, while with the flag first contact is a race against the async tree build and was observed anywhere from a fraction of the settled count to nearly all of it. Neither arm removes the need to settle before trusting a first snapshot.",
+ "supersededPreFinding": "the plan pre-finding (8 managed descendants, stable at 2/5/10s, candidate CONTRADICTS) is superseded: it was measured through an occluded window with a managed client, and neither the occlusion nor the settle window was controlled"
+ },
+ "teardown": {
+ "allObsidianProcessesTerminated": true
+ }
+}
\ No newline at end of file
diff --git a/probes/windows/captures/11-electron-activation/electron-activation.json.normalized b/probes/windows/captures/11-electron-activation/electron-activation.json.normalized
new file mode 100644
index 0000000..a9235db
--- /dev/null
+++ b/probes/windows/captures/11-electron-activation/electron-activation.json.normalized
@@ -0,0 +1,164 @@
+{
+ "question": "does Chromium 138+ expose a UIA tree to any UIA client with no flag, and does --force-renderer-accessibility still change anything",
+ "gradedAgainst": "the committed phases.md line that Chromium 138+ exposes a UIA tree to any UIA client with no flag (P2-O15 / 2.4)",
+ "stack": "both: managed System.Windows.Automation and the U7 UIA3 COM shim (KTD1 - the COM rows are the authoritative ones)",
+ "scope": "app/provider - specific to this Obsidian/Electron/Chromium build; the activation mechanism is Chromium behavior, not a Windows API contract",
+ "versions": {
+ "method": "user-agent string scanned out of the shipped Obsidian.exe on this installation",
+ "obsidianFileVersion": "1.12.7.0",
+ "chromiumVersion": "142.0.7444.265",
+ "electronVersion": "39.8.3",
+ "established": true,
+ "rejectedMethods": [
+ "ELECTRON_RUN_AS_NODE=1 Obsidian.exe -p process.versions produced no output on this VM",
+ "framework DLL file versions carry component versions, not the Chromium version"
+ ]
+ },
+ "obsidianPreexistingAtProbeStart": false,
+ "method": "one fresh Obsidian instance per (stack x flag) cell, because the first UIA client to touch a renderer activates accessibility for that process",
+ "placementHazards": [
+ "U3: an earlier revision that left other windows on top of Obsidian held it at its first-contact node count through a full 8s settle and a 16s instrumented hold, three runs running. Minimizing the covering windows fixed it. The mechanism was NOT isolated (Chromium native-window occlusion tracking is the obvious candidate, unproven). Carried here as a probe-placement hazard, explicitly not a product claim.",
+ "U4: matching a bare Chrome_WidgetWin_1 window found an unrelated window and recorded 9 nodes / 0% coverage as if it were the Electron answer. This probe only accepts a window whose owning pid is one of the launched Obsidian pids, and reads the hosting pid back off the window."
+ ],
+ "countStabilityNote": "settled node counts are content-dependent (U7 measured RawView 172 on a bare walker run, U3 measured 119 through its probe). The stable fact is the growth ratio across the settle, not the absolute count.",
+ "cells": [
+ {
+ "flagArm": "no-flag",
+ "stack": "managed",
+ "authoritative": false,
+ "windowClassName": "Chrome_WidgetWin_1",
+ "windowHandle": ,
+ "windowHost": {
+ "processId":
+ },
+ "latencyObsidianProcessCountAtWindowDiscovery": ,
+ "foregroundOwner": {
+ "processId":
+ },
+ "settleMs": 8000,
+ "firstContact": {
+ "latencyFirstContactRawViewNodes": ,
+ "latencyFirstContactRawViewRefableNodes": ,
+ "latencyFirstContactControlViewNodes": ,
+ "latencyFirstContactControlViewRefableNodes":
+ },
+ "afterSettle": {
+ "settledRawViewNodes": 172,
+ "settledRawViewRefableNodes": 172,
+ "settledControlViewNodes": 133,
+ "settledControlViewRefableNodes": 133
+ },
+ "latencyRawViewGrowthRatio": ,
+ "activatedWithoutFlag": true
+ },
+ {
+ "flagArm": "no-flag",
+ "stack": "uia3-com",
+ "authoritative": true,
+ "windowClassName": "Chrome_WidgetWin_1",
+ "windowHandle": ,
+ "windowHost": {
+ "processId":
+ },
+ "latencyObsidianProcessCountAtWindowDiscovery": ,
+ "foregroundOwner": {
+ "processId":
+ },
+ "settleMs": 8000,
+ "firstContact": {
+ "latencyFirstContactRawViewNodes": ,
+ "latencyFirstContactControlViewNodes": ,
+ "latencyFirstContactContentViewNodes":
+ },
+ "afterSettle": {
+ "settledRawViewNodes": 172,
+ "settledControlViewNodes": 133,
+ "settledContentViewNodes": 133,
+ "settledRefableNodesWithAnyPattern": 171,
+ "settledRefableNodesExcludingLegacy": 171
+ },
+ "censusNote": "census ran in a second shim process against the already-activated instance, so its counts are settled counts; per-element rows are deliberately not captured (R11: Obsidian note titles are content)",
+ "latencyRawViewGrowthRatio": ,
+ "activatedWithoutFlag": true
+ },
+ {
+ "flagArm": "force-renderer-accessibility",
+ "stack": "managed",
+ "authoritative": false,
+ "windowClassName": "Chrome_WidgetWin_1",
+ "windowHandle": ,
+ "windowHost": {
+ "processId":
+ },
+ "latencyObsidianProcessCountAtWindowDiscovery": ,
+ "foregroundOwner": {
+ "processId":
+ },
+ "settleMs": 8000,
+ "firstContact": {
+ "latencyFirstContactRawViewNodes": ,
+ "latencyFirstContactRawViewRefableNodes": ,
+ "latencyFirstContactControlViewNodes": ,
+ "latencyFirstContactControlViewRefableNodes":
+ },
+ "afterSettle": {
+ "settledRawViewNodes": 172,
+ "settledRawViewRefableNodes": 172,
+ "settledControlViewNodes": 133,
+ "settledControlViewRefableNodes": 133
+ },
+ "latencyRawViewGrowthRatio": ,
+ "activatedWithoutFlag": false
+ },
+ {
+ "flagArm": "force-renderer-accessibility",
+ "stack": "uia3-com",
+ "authoritative": true,
+ "windowClassName": "Chrome_WidgetWin_1",
+ "windowHandle": ,
+ "windowHost": {
+ "processId":
+ },
+ "latencyObsidianProcessCountAtWindowDiscovery": ,
+ "foregroundOwner": {
+ "processId":
+ },
+ "settleMs": 8000,
+ "firstContact": {
+ "latencyFirstContactRawViewNodes": ,
+ "latencyFirstContactControlViewNodes": ,
+ "latencyFirstContactContentViewNodes":
+ },
+ "afterSettle": {
+ "settledRawViewNodes": 172,
+ "settledControlViewNodes": 133,
+ "settledContentViewNodes": 133,
+ "settledRefableNodesWithAnyPattern": 172,
+ "settledRefableNodesExcludingLegacy": 172
+ },
+ "censusNote": "census ran in a second shim process against the already-activated instance, so its counts are settled counts; per-element rows are deliberately not captured (R11: Obsidian note titles are content)",
+ "latencyRawViewGrowthRatio": ,
+ "activatedWithoutFlag": false
+ }
+ ],
+ "findings": {
+ "managedActivatesWithoutFlag": true,
+ "comActivatesWithoutFlag": true,
+ "clientStackDivergence": false,
+ "latencyComRawViewNoFlagFirstContact": ,
+ "latencyComRawViewWithFlagFirstContact": ,
+ "latencyManagedRawViewNoFlagFirstContact": ,
+ "latencyManagedRawViewWithFlagFirstContact": ,
+ "settledComRawViewNoFlag": 172,
+ "settledComRawViewWithFlag": 172,
+ "settledManagedRawViewNoFlag": 172,
+ "settledManagedRawViewWithFlag": 172,
+ "settledCountsMatchAcrossFlagArms": true,
+ "firstContactCountsAreRunVarying": "first-contact counts are a race against Chromium async tree construction and are named latency* so the KTD9 normalizer canonicalizes them; the raw samples stay in this capture, the settled counts are the stable evidence",
+ "flagVerdict": "partially useful - redundant for eventual tree exposure: on Chromium 142.0.7444.265 both client stacks reach the same settled RawView count with no flag, so the phases.md claim holds for exposure. It is not redundant for first-contact readiness: without the flag first contact is deterministically the small pre-activation shell on every run and both stacks, while with the flag first contact is a race against the async tree build and was observed anywhere from a fraction of the settled count to nearly all of it. Neither arm removes the need to settle before trusting a first snapshot.",
+ "supersededPreFinding": "the plan pre-finding (8 managed descendants, stable at 2/5/10s, candidate CONTRADICTS) is superseded: it was measured through an occluded window with a managed client, and neither the occlusion nor the settle window was controlled"
+ },
+ "teardown": {
+ "allObsidianProcessesTerminated": true
+ }
+}
\ No newline at end of file