diff --git a/src/windows/NativeWindowsUiProvider.test.ts b/src/windows/NativeWindowsUiProvider.test.ts index 70f95552a2..a5a718a994 100644 --- a/src/windows/NativeWindowsUiProvider.test.ts +++ b/src/windows/NativeWindowsUiProvider.test.ts @@ -9,6 +9,7 @@ import type { BinaryTarget } from "../domain/binaryTargetTypes.js"; import { MAX_WINDOWS_WINDOW_PNG_BYTES } from "../domain/native/windowsWindowScreenshot.js"; import { OwnedFileHandle } from "../filesystem/OwnedFileHandle.js"; import { RegularFileAdmissionFailure } from "../filesystem/RegularFile.js"; +import * as execution from "./WindowsWindowCaptureExecution.js"; import { captureWindowsWindowScreenshot, hashWindowsCaptureExecutable, @@ -56,6 +57,33 @@ const host = ( ...overrides, }); +it("attributes a helper-only close failure to selected-window resources", async () => { + const observation = { + reason: "Owned capture Job cleanup remains pending", + resources: ["capture-run:42"], + }; + const cleanup = vi + .spyOn(execution, "retryWindowsCaptureCleanup") + .mockResolvedValue(observation); + try { + const client = new NativeWindowsUiProvider( + {}, + host(), + "linux", + ).createClient(TARGET); + expect(await client.close()).toMatchObject({ + ok: false, + error: { + _tag: "AnalysisOutputError", + reason: "Selected-window capture resource cleanup remains incomplete.", + cleanup: observation, + }, + }); + } finally { + cleanup.mockRestore(); + } +}); + it("captures only an HWND whose live executable identity matches the active PE target", async () => { const captured = await captureWindowsWindowScreenshot( TARGET, diff --git a/src/windows/NativeWindowsUiProvider.ts b/src/windows/NativeWindowsUiProvider.ts index 630bd69ba0..1f00116156 100644 --- a/src/windows/NativeWindowsUiProvider.ts +++ b/src/windows/NativeWindowsUiProvider.ts @@ -238,10 +238,20 @@ export class NativeWindowsUiProvider implements AnalysisProvider { return incomplete.length === 0 ? ok(null) : err( - incompleteExecutableIdentityCleanupError({ - reason: incomplete.map((cleanup) => cleanup.reason).join("; "), - resources: incomplete.flatMap((cleanup) => cleanup.resources), - }), + new AnalysisOutputError( + OPERATION, + "Selected-window capture resource cleanup remains incomplete.", + { + cleanup: { + reason: incomplete + .map((cleanup) => cleanup.reason) + .join("; "), + resources: incomplete.flatMap( + (cleanup) => cleanup.resources, + ), + }, + }, + ), ); }, }; diff --git a/src/windows/NativeWindowsUiProviderMetadata.test.ts b/src/windows/NativeWindowsUiProviderMetadata.test.ts index ecd2b83747..c04518e855 100644 --- a/src/windows/NativeWindowsUiProviderMetadata.test.ts +++ b/src/windows/NativeWindowsUiProviderMetadata.test.ts @@ -24,3 +24,21 @@ it("requires affirmative inspected capture support on the shipped Windows archit }))[0], ).toMatchObject({ available: false, reason: "artifact unavailable" }); }); + +it("keeps retryable admission unavailable until a fresh successful snapshot", () => { + let readiness: WindowsWindowCaptureLoadResult = { + available: false, + reason: "Private runtime admission is still unresolved", + retryable: true, + }; + const first = nativeWindowsUiCapabilities("win32", "x64", () => readiness); + expect(first[0]).toMatchObject({ + available: false, + reason: readiness.reason, + }); + readiness = loaded(); + expect( + nativeWindowsUiCapabilities("win32", "x64", () => readiness)[0], + ).toMatchObject({ available: true }); + expect(first[0]).toMatchObject({ available: false }); +}); diff --git a/src/windows/NativeWindowsUiProviderMetadata.ts b/src/windows/NativeWindowsUiProviderMetadata.ts index b6de55e016..ca6258fe42 100644 --- a/src/windows/NativeWindowsUiProviderMetadata.ts +++ b/src/windows/NativeWindowsUiProviderMetadata.ts @@ -23,9 +23,7 @@ export const nativeWindowsUiCapabilities = ( ): readonly CapabilityDescriptor[] => { const loaded = platform === "win32" && architecture === "x64" ? load() : undefined; - const captureReady = - loaded?.available === true || - (loaded?.available === false && loaded.retryable === true); + const captureReady = loaded?.available === true; const availability = platform === "win32" && architecture === "x64" ? captureReady