Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ Known open defects:
- Graphics future path: Vulkan is a Graphics Lab future path, excluded from General Preview and v1; D3D11 compatibility is a Graphics Lab future path, excluded from General Preview and v1.
- Guest platform: QEMU virt-compatible guest contract with documented deviations.

State reviewed 2026-09-25 at commit `02052c31dbe2c0cdefe7e93affd31300a2e43d76`. This block is generated from [`capabilities/windows-hvf.json`](capabilities/windows-hvf.json) by `scripts/render-capability-status.py`.
State reviewed 2026-09-25 at commit `e9a2a68e116c14d394ad50c39b25c2975a8ce0cf`. This block is generated from [`capabilities/windows-hvf.json`](capabilities/windows-hvf.json) by `scripts/render-capability-status.py`.
<!-- END GENERATED: capability-summary -->

See the [current status](STATUS.md) and
Expand Down
2 changes: 1 addition & 1 deletion STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Known open defects:
- Graphics future path: Vulkan is a Graphics Lab future path, excluded from General Preview and v1; D3D11 compatibility is a Graphics Lab future path, excluded from General Preview and v1.
- Guest platform: QEMU virt-compatible guest contract with documented deviations.

State reviewed 2026-09-25 at commit `02052c31dbe2c0cdefe7e93affd31300a2e43d76`. This block is generated from [`capabilities/windows-hvf.json`](capabilities/windows-hvf.json) by `scripts/render-capability-status.py`.
State reviewed 2026-09-25 at commit `e9a2a68e116c14d394ad50c39b25c2975a8ce0cf`. This block is generated from [`capabilities/windows-hvf.json`](capabilities/windows-hvf.json) by `scripts/render-capability-status.py`.
<!-- END GENERATED: capability-summary -->

## How to read the generated status
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ final class T17Accessibility: T17UIControlling {
RunLoop.current.run(until: Date().addingTimeInterval(0.1))
} while Date() < deadline
let application = AXUIElementCreateApplication(pid)
throw T17Blocker(code: "ui-element-missing", detail: "required accessibility identifier was not found: \(identifier); windows=\((attribute(application, kAXWindowsAttribute as CFString) as? [AXUIElement]).map { String($0.count) } ?? "unanswered") timeout_s=\(timeout)")
throw T17MissingIdentifierDiagnostic.capture(application: application, pid: pid, identifier: identifier, timeout: timeout)
}

private func snapshotElement(_ identifier: String, role expectedRole: String?) throws -> AXUIElement? {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import AppKit
import ApplicationServices
import Darwin
import Foundation

/// Failure-only metadata. Never reads AX titles, values, descriptions or paths.
enum T17MissingIdentifierDiagnostic {
struct Label: Hashable {
let role: String
let identifier: String
}
struct Inventory {
var nodes = 0
var limited = false
var errors = 0
var labels: [Label] = []
}
struct Observation {
var pidProbe = "unknown"
var appPresent = false
var active: Bool?
var frontmost: Bool?
var windowsStatus = -1
var windowsCount: Int?
var focusStatus = -1
var focusPresent = false
var mainStatus = -1
var mainPresent = false
var inventory = Inventory()
}

private static let knownIDs: Set<String> = [
"bridgevm.dashboard.advanced", "bridgevm.windows.runtime.view",
"bridgevm.windows.install.view", "bridgevm.windows.runtime.start",
"bridgevm.windows.install.stage", "bridgevm.windows.install.failure",
]
private static let knownRoles: Set<String> = [
"AXApplication", "AXWindow", "AXButton", "AXGroup", "AXScrollArea",
"AXStaticText", "AXDialog", "AXSheet", "AXUnknown",
]

static func capture(application: AXUIElement, pid: pid_t, identifier: String,
timeout: TimeInterval) -> T17Blocker {
var sample = Observation()
let probe = Darwin.kill(pid, 0)
sample.pidProbe = probe == 0 ? "present" : errno == ESRCH ? "missing" : errno == EPERM ? "denied" : "unknown"
let running = NSRunningApplication(processIdentifier: pid)
sample.appPresent = running != nil
sample.active = running?.isActive
sample.frontmost = NSWorkspace.shared.frontmostApplication.map { $0.processIdentifier == pid }
let timeoutStatus = AXUIElementSetMessagingTimeout(application, 0.25)
if timeoutStatus != .success {
sample.windowsStatus = Int(timeoutStatus.rawValue)
sample.focusStatus = Int(timeoutStatus.rawValue)
sample.mainStatus = Int(timeoutStatus.rawValue)
sample.inventory.errors = 1
return failure(identifier: identifier, timeout: timeout, observation: sample)
}
let windows = query(application, kAXWindowsAttribute as CFString)
let windowNodes = windows.1 as? [AXUIElement]
sample.windowsStatus = Int(windows.0.rawValue)
sample.windowsCount = windowNodes?.count
let focus = query(application, kAXFocusedWindowAttribute as CFString)
sample.focusStatus = Int(focus.0.rawValue)
sample.focusPresent = focus.1.map { CFGetTypeID($0) == AXUIElementGetTypeID() } ?? false
let main = query(application, kAXMainWindowAttribute as CFString)
sample.mainStatus = Int(main.0.rawValue)
sample.mainPresent = main.1.map { CFGetTypeID($0) == AXUIElementGetTypeID() } ?? false
let roots = [application] + Array((windowNodes ?? []).prefix(8))
let deadline = Date().addingTimeInterval(3)
sample.inventory = inventory(roots: roots, budget: { Date() < deadline },
metadata: { node in
(try read(node, kAXRoleAttribute as CFString) as? String,
try read(node, kAXIdentifierAttribute as CFString) as? String)
}, related: { try read($0, kAXChildrenAttribute as CFString) as? [AXUIElement] ?? [] },
same: { CFEqual($0, $1) })
if (windowNodes?.count ?? 0) > 8 { sample.inventory.limited = true }
return failure(identifier: identifier, timeout: timeout, observation: sample)
}

static func failure(identifier: String, timeout: TimeInterval,
observation: Observation) -> T17Blocker {
T17Blocker(code: "ui-element-missing",
detail: format(identifier: identifier, timeout: timeout, observation: observation))
}

static func inventory<Node>(roots: [Node], budget: () -> Bool,
metadata: (Node) throws -> (String?, String?),
related: (Node) throws -> [Node],
same: (Node, Node) -> Bool) -> Inventory {
let cap = 128
var pending = Array(roots.prefix(cap)), seen: [Node] = []
var cursor = 0
var result = Inventory()
result.limited = roots.count > cap
var labels = Set<Label>()
while cursor < pending.count && budget() {
let node = pending[cursor]; cursor += 1
if seen.contains(where: { same($0, node) }) { continue }
seen.append(node); result.nodes += 1
do {
let (role, identifier) = try metadata(node)
if let identifier, knownIDs.contains(identifier) {
labels.insert(Label(role: knownRoles.contains(role ?? "") ? role! : "other",
identifier: identifier))
}
} catch { result.errors += 1 }
do {
for child in try related(node) {
if seen.contains(where: { same($0, child) }) || pending.contains(where: { same($0, child) }) { continue }
if pending.count == cap { result.limited = true; break }
pending.append(child)
}
} catch { result.errors += 1 }
}
if cursor < pending.count { result.limited = true }
result.labels = labels.sorted { $0.identifier == $1.identifier ? $0.role < $1.role : $0.identifier < $1.identifier }
return result
}

static func format(identifier: String, timeout: TimeInterval, observation: Observation) -> String {
let safeIdentifier = identifier.utf8.count <= 96 && identifier.hasPrefix("bridgevm.")
&& identifier.utf8.allSatisfy { (48...57).contains($0) || (65...90).contains($0)
|| (97...122).contains($0) || [45, 46, 95].contains($0) } ? identifier : "other"
let time = timeout.isFinite && (0...1800).contains(timeout) ? String(timeout) : "unknown"
let count = observation.windowsStatus == 0 ? observation.windowsCount.flatMap { $0 >= 0 ? String(min(9_999, $0)) : nil } : nil
let prefix = "required accessibility identifier was not found: \(safeIdentifier); windows=\(count ?? "unanswered") timeout_s=\(time)"
let safeProbe = ["present", "missing", "denied", "unknown"].contains(observation.pidProbe) ? observation.pidProbe : "unknown"
let labels = observation.inventory.labels.filter { knownIDs.contains($0.identifier) }.prefix(8)
.map { "\($0.identifier):\(knownRoles.contains($0.role) ? $0.role : "other")" }.joined(separator: ",")
let suffix = "; ax_diag=v1,pid_probe=\(safeProbe),ns_app=\(observation.appPresent),active=\(token(observation.active)),front=\(token(observation.frontmost)),"
+ "ax_windows=\(observation.windowsStatus)/\(count ?? "unknown"),ax_focus=\(observation.focusStatus)/\(observation.focusPresent),"
+ "ax_main=\(observation.mainStatus)/\(observation.mainPresent),nodes=\(min(128, max(0, observation.inventory.nodes))),"
+ "limited=\(observation.inventory.limited),errors=\(min(999, max(0, observation.inventory.errors))),known=[\(labels)]"
return String((prefix + suffix).prefix(512))
}

private static func token(_ value: Bool?) -> String { value.map(String.init) ?? "unknown" }
private static func query(_ node: AXUIElement, _ name: CFString) -> (AXError, CFTypeRef?) {
var value: CFTypeRef?
let status = AXUIElementCopyAttributeValue(node, name, &value)
return (status, value)
}
private static func read(_ node: AXUIElement, _ name: CFString) throws -> CFTypeRef? {
guard AXUIElementSetMessagingTimeout(node, 0.25) == .success else { throw AXReadFailure.failed }
let (status, value) = query(node, name)
guard status == .success || status == .noValue || status == .attributeUnsupported else { throw AXReadFailure.failed }
return value
}
private enum AXReadFailure: Error { case failed }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import XCTest
@testable import BridgeVMProductE2E

final class T17MissingIdentifierDiagnosticTests: XCTestCase {
private typealias Probe = T17MissingIdentifierDiagnostic
private enum ReadError: Error { case failed }

func testDashboardFailureKeepsCodePrefixTimeoutAndBoundedMetadata() {
var sample = Probe.Observation()
sample.pidProbe = "present"; sample.appPresent = true
sample.active = false; sample.frontmost = false
sample.windowsStatus = 0; sample.windowsCount = 0
sample.focusStatus = -25205; sample.mainStatus = -25205
sample.inventory = .init(nodes: 5, limited: false, errors: 1,
labels: [.init(role: "AXButton", identifier: "bridgevm.dashboard.advanced")])
let blocker = Probe.failure(identifier: "bridgevm.dashboard.advanced", timeout: 60, observation: sample)
XCTAssertEqual(blocker.code, "ui-element-missing")
XCTAssertTrue(blocker.detail.hasPrefix("required accessibility identifier was not found: bridgevm.dashboard.advanced; windows=0 timeout_s=60.0"))
XCTAssertTrue(blocker.detail.contains("pid_probe=present,ns_app=true,active=false,front=false"))
XCTAssertTrue(blocker.detail.contains("ax_windows=0/0"))
XCTAssertTrue(blocker.detail.contains("bridgevm.dashboard.advanced:AXButton"))
XCTAssertLessThanOrEqual(blocker.detail.utf8.count, 512)
}

func testAXWindowsErrorIsUnansweredRatherThanFalseZero() {
var sample = Probe.Observation()
sample.windowsStatus = -25204; sample.windowsCount = nil
sample.focusStatus = -25205; sample.mainStatus = -25205
let detail = Probe.failure(identifier: "bridgevm.dashboard.advanced", timeout: 60,
observation: sample).detail
XCTAssertTrue(detail.contains("windows=unanswered timeout_s=60.0"))
XCTAssertTrue(detail.contains("ax_windows=-25204/unknown"))
XCTAssertTrue(detail.contains("ax_focus=-25205/false"))
XCTAssertTrue(detail.contains("ax_main=-25205/false"))
sample.windowsStatus = 0; sample.windowsCount = -1
let malformed = Probe.failure(identifier: "bridgevm.dashboard.advanced", timeout: 60, observation: sample).detail
XCTAssertTrue(malformed.contains("windows=unanswered timeout_s=60.0"))
}

func testInventoryDeduplicatesCycleAndCapsLongTree() {
let cycle = Probe.inventory(roots: [0], budget: { true },
metadata: { _ in ("AXButton", "bridgevm.dashboard.advanced") },
related: { [$0 == 0 ? 1 : 0] }, same: ==)
XCTAssertEqual(cycle.nodes, 2)
XCTAssertFalse(cycle.limited)
XCTAssertEqual(cycle.labels.count, 1)
let long = Probe.inventory(roots: [0], budget: { true },
metadata: { _ in ("AXButton", "bridgevm.dashboard.advanced") },
related: { [$0 + 1] }, same: ==)
XCTAssertEqual(long.nodes, 128)
XCTAssertTrue(long.limited)
XCTAssertEqual(long.labels.count, 1)
}

func testDiagnosticReadFailureKeepsOriginalBlockerAndNoPrivateText() {
let failed = Probe.inventory(roots: [0], budget: { true },
metadata: { _ in throw ReadError.failed },
related: { _ in throw ReadError.failed }, same: ==)
XCTAssertEqual(failed.errors, 2)
var sample = Probe.Observation()
sample.pidProbe = "/private/media.iso"
sample.windowsStatus = -25204
sample.inventory = failed
sample.inventory.labels = [
.init(role: "/private/window-title", identifier: "bridgevm.dashboard.advanced"),
.init(role: "AXButton", identifier: "/private/guest.iso"),
]
let blocker = Probe.failure(identifier: "/private/request.iso", timeout: 60, observation: sample)
XCTAssertEqual(blocker.code, "ui-element-missing")
XCTAssertTrue(blocker.detail.hasPrefix("required accessibility identifier was not found: other; windows=unanswered timeout_s=60.0"))
XCTAssertTrue(blocker.detail.contains("pid_probe=unknown"))
XCTAssertTrue(blocker.detail.contains("errors=2"))
XCTAssertFalse(blocker.detail.contains("/private/"))
XCTAssertFalse(blocker.detail.contains("window-title"))
XCTAssertLessThanOrEqual(blocker.detail.utf8.count, 512)
XCTAssertTrue(blocker.detail.utf8.allSatisfy { $0 < 128 })
}

func testBudgetExpirationIsExplicitAndPreservesFailureCode() {
let stopped = Probe.inventory(roots: [0], budget: { false },
metadata: { _ in XCTFail("expired budget read metadata"); return (nil, nil) },
related: { _ in XCTFail("expired budget read children"); return [] }, same: ==)
XCTAssertEqual(stopped.nodes, 0)
XCTAssertTrue(stopped.limited)
var sample = Probe.Observation(); sample.inventory = stopped
let blocker = Probe.failure(identifier: "bridgevm.dashboard.advanced", timeout: 60, observation: sample)
XCTAssertEqual(blocker.code, "ui-element-missing")
XCTAssertTrue(blocker.detail.contains("limited=true"))
}
}
7 changes: 4 additions & 3 deletions capabilities/windows-hvf.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions docs/document-manifest.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -228,5 +228,6 @@ docs/history/windows-hvf/a9-runtime-share-press-recovery-20260921.md historical-
docs/history/windows-hvf/a9-modal-chooser-handoff-20260921.md historical-evidence windows-regression-history docs/windows-arm/capability-matrix.md
docs/history/windows-hvf/a9-role-first-chooser-identity-20260921.md historical-evidence windows-regression-history docs/windows-arm/capability-matrix.md
docs/history/windows-hvf/a9-selection-confirmation-read-20260921.md historical-evidence windows-regression-history docs/windows-arm/capability-matrix.md
docs/history/windows-hvf/a9-r34-dashboard-ax-timeout-20260925.md historical-evidence windows-regression-history docs/windows-arm/capability-matrix.md
docs/windows-arm/evidence/a19-t20-r6-20260925.md current windows-storage -
docs/windows-arm/evidence/a19-t21-r1-20260925.md historical-evidence windows-storage STATUS.md
60 changes: 60 additions & 0 deletions docs/history/windows-hvf/a9-r34-dashboard-ax-timeout-20260925.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# A9 r34 dashboard Accessibility timeout — failed physical diagnostic

Classification: one failed, nonpromoting physical diagnostic. The current A9
criterion, defect wording and product state come from
`capabilities/windows-hvf.json`; A9 remains OPEN and the product remains
ENGINEERING_PREVIEW. No clean-machine ISO or import journey is proven here.

## Sealed observation

The exact-main source was `bc5f11e53f704f156016ebf65d9fc150f56b3189`
(M1). The input manifest SHA-256 was
`09518a48f9a58b5028faa943e5b4e64740d1e0347ba6b1011252eae7dd4aed44`.
Physical job `t17-bc5f11e5-first-ready-diagnostic-r34` ended failed with exit
status 1. Its public and private strict T17 receipts were byte-identical at
SHA-256 `373d557f50d6b6a601218c71ecb7ef55a81938ace1fc26ec2f8f466a4d5f19dd`.
Both official receipt validators passed. This proves the receipts are valid;
their result is failure: one run, zero first READY passes,
`criterion_pass=false`, and `capability_promotion=false`. Worker cleanup was
verified.

The authenticated lane result SHA-256 was
`f12d72ca4741066753aa994c7bb1a8733ca1d898de65a9879feb0eb5a2750410`.
It recorded `failure_code=ui-element-missing` and
`required accessibility identifier was not found:
bridgevm.dashboard.advanced; windows=0 timeout_s=60.0`. The lane recorded VM
creation, Windows installation and Secure Boot provisioning as complete, then
failed while looking for `bridgevm.dashboard.advanced`, before pressing
`bridgevm.windows.runtime.start` and before first READY. `windows=0` is the
final AXWindows count, not proof that the app crashed. This differs from r33,
which reached a later first-boot boundary without READY/PONG and had an
incomplete host-stop record. Neither run proves an A9 journey.

The lane recorded final disk SHA-256
`e442a89c2e4cfbecca3206108a881c7e801ddd40d27e039a9302ecd073759ba0`
and final vars SHA-256
`50b92f5ca65fde2d6ea324941fd3a5640bc1785ef902ab6782b5de422a2d5785`.
These are lane-only recorded values: the public failed-run receipt reports
final hashes as `absent`, and the cleaned underlying files cannot be rehashed.
The lane's guest-evidence digest matches an unproven nonce sentinel, not a
guest READY or completed journey. No failure-time screenshot, framebuffer,
AX window/process sample, host-stop state, timer or GIC packet was retained.
The private helper log was empty and the scratch tree was removed. The
failure's cause is unknown.

## Deterministic follow-up and limit

Separately committed source `33c016f025a1fd454d2d0af3175f0dfeb2ad4c17`
(S, parent M2 `5290c561b8664518c8b34ff6a9149568e4afe247`) records bounded,
path-free AX role/identifier, raw AX error and process-liveness observations
when this identifier lookup times out. It preserves the 60-second timeout and
`ui-element-missing` failure. The first focused compile failed on a String to
CFString conversion; the failed log SHA-256 is
`07b8d7569028638af6d97b59442394f1bb8ba9b93f2dcf513607f673dada5e30`.
After correction, five focused Swift tests passed (log SHA-256
`9a3e9be7e14c202522dbe4e02ae633cf071deb53f4a8080599c84b27261fe7f8`),
and structural budgets passed (log SHA-256
`1c6f98de106b54f3478f7d776cf37c2a6b7b115eb9fff1982e67d2fbdb8e9d4a`).
These deterministic checks do not show that a later physical run will capture
the missing state or complete a guest journey. Exact integrated-head hosted
verification and a fresh sealed physical diagnostic remain separate gates.
Loading
Loading