diff --git a/packages/device-selection/package.json b/packages/device-selection/package.json index 57e6e8e52e..4c7c335d56 100644 --- a/packages/device-selection/package.json +++ b/packages/device-selection/package.json @@ -11,6 +11,10 @@ "@agent-device/kernel": "workspace:*" }, "exports": { + "./composed-inventory-gateways": { + "types": "./src/composed-inventory-gateways.ts", + "default": "./src/composed-inventory-gateways.ts" + }, "./dispatch-resolve": { "types": "./src/dispatch-resolve.ts", "default": "./src/dispatch-resolve.ts" @@ -26,6 +30,10 @@ "./open-target": { "types": "./src/open-target.ts", "default": "./src/open-target.ts" + }, + "./inventory-gateways-fixtures": { + "types": "./src/inventory-gateways.fixtures.ts", + "default": "./src/inventory-gateways.fixtures.ts" } } } diff --git a/packages/device-selection/src/__tests__/composed-inventory-gateways.test.ts b/packages/device-selection/src/__tests__/composed-inventory-gateways.test.ts new file mode 100644 index 0000000000..8a1868c395 --- /dev/null +++ b/packages/device-selection/src/__tests__/composed-inventory-gateways.test.ts @@ -0,0 +1,332 @@ +import type { + DeviceInventoryRequest, + ProviderDeviceInventorySource, +} from '@agent-device/contracts/device'; +import { + type DeviceInventorySource, + type InventoryPlatformModule, + createPlatformModuleRegistry, +} from '@agent-device/contracts/platform-module'; +import type { + DeviceInventoryHost, + PlatformRequestScope, +} from '@agent-device/contracts/platform-runtime-host'; +import { resolveTargetDeviceSelection } from '../dispatch-resolve.ts'; +import { withDeviceInventoryContext } from '../device-inventory-context.ts'; +import { PLATFORMS, type DeviceInfo, type Platform } from '@agent-device/kernel/device'; +import { describe, expect, test, vi } from 'vitest'; +import { createComposedDeviceInventoryGateways } from '../composed-inventory-gateways.ts'; + +const scope: PlatformRequestScope = Object.freeze({ + signal: new AbortController().signal, + diagnostics: Object.freeze({ emit: () => {} }), + progress: Object.freeze({ report: () => {} }), +}); + +const host: DeviceInventoryHost = Object.freeze({ + commands: Object.freeze({ + which: async () => undefined, + run: async () => ({ stdout: '', stderr: '', exitCode: 0 }), + }), + appleTools: Object.freeze({ + isXcrunAvailable: async () => false, + run: async () => ({ stdout: '', stderr: '', exitCode: 0 }), + }), + toolchains: Object.freeze({ prepare: async () => {} }), + files: Object.freeze({ + isExecutable: async () => false, + createTemporaryTextFile: async () => { + throw new Error('unused'); + }, + }), + hostOs: 'darwin', + hostName: 'test-host', + homeDirectory: '/tmp/test-home', + observations: Object.freeze({ deviceBooted: async () => {} }), +}); + +describe('composed device inventory gateway', () => { + test('an authoritative provider inventory, including empty, loads no local host or source', async () => { + for (const devices of [[], [device('android', 'remote')]]) { + const local = inventoryWorld(); + const provider: ProviderDeviceInventorySource = { + discover: async () => ({ kind: 'inventory', devices }), + }; + const gateways = createComposedDeviceInventoryGateways({ + registry: local.registry, + loadHost: local.loadHost, + provider, + }); + + expect(await gateways.providerFirst.discover({ platform: 'android' }, scope)).toEqual( + devices, + ); + expect(local.hostLoads()).toBe(0); + expect(local.sourceLoads()).toEqual([]); + } + }); + + test('declined providers fall through, while malformed outcomes fail closed', async () => { + const local = inventoryWorld(); + const declined = createComposedDeviceInventoryGateways({ + registry: local.registry, + loadHost: local.loadHost, + provider: { discover: async () => ({ kind: 'declined' }) }, + }); + expect(await declined.providerFirst.discover({ platform: 'android' }, scope)).toEqual([ + device('android'), + ]); + + const malformed = createComposedDeviceInventoryGateways({ + registry: local.registry, + loadHost: local.loadHost, + provider: { discover: async () => undefined } as unknown as ProviderDeviceInventorySource, + }); + await expect( + malformed.providerFirst.discover({ platform: 'android' }, scope), + ).rejects.toMatchObject({ details: { reason: 'provider_inventory_contract_violation' } }); + }); + + test('forwards the request signal into an in-flight provider source', async () => { + const controller = new AbortController(); + const requestScope = { ...scope, signal: controller.signal }; + const observed = vi.fn(); + const provider: ProviderDeviceInventorySource = { + discover: async (_request, signal) => { + observed(signal); + return await new Promise((_resolve, reject) => { + signal.addEventListener('abort', () => reject(signal.reason), { once: true }); + }); + }, + }; + const local = inventoryWorld(); + const gateways = createComposedDeviceInventoryGateways({ + registry: local.registry, + loadHost: local.loadHost, + provider, + }); + const pending = gateways.providerFirst.discover({ platform: 'android' }, requestScope); + controller.abort(new Error('cancelled')); + + await expect(pending).rejects.toThrow('cancelled'); + expect(observed).toHaveBeenCalledWith(controller.signal); + expect(local.hostLoads()).toBe(0); + }); + + test('keeps local projection policy out of the provider request', async () => { + const received: unknown[] = []; + const provider: ProviderDeviceInventorySource = { + discover: async (request) => { + received.push(request); + return { kind: 'inventory', devices: [] }; + }, + }; + const local = inventoryWorld(); + const gateways = createComposedDeviceInventoryGateways({ + registry: local.registry, + loadHost: local.loadHost, + provider, + }); + + await gateways.providerFirst.discover( + { + platform: 'ios', + kind: 'simulator', + booted: true, + iosSimulatorSetPath: '/tmp/isolated-simulators', + androidSerialAllowlist: ['emulator-5554'], + }, + scope, + ); + + expect(received).toEqual([ + { + platform: 'ios', + iosSimulatorSetPath: '/tmp/isolated-simulators', + androidSerialAllowlist: ['emulator-5554'], + }, + ]); + expect(local.hostLoads()).toBe(0); + }); + + test('applies public Apple aliases and all internal projection filters centrally', async () => { + const devices = [ + { ...device('apple', 'ios-default'), appleOs: 'ios' as const, target: undefined }, + { ...device('apple', 'mac'), appleOs: 'macos' as const, target: 'desktop' as const }, + { ...device('apple', 'shutdown'), appleOs: 'ios' as const, booted: false }, + device('android', 'other-family'), + ]; + const provider: ProviderDeviceInventorySource = { + discover: async () => ({ kind: 'inventory', devices }), + }; + const local = inventoryWorld(); + const gateways = createComposedDeviceInventoryGateways({ + registry: local.registry, + loadHost: local.loadHost, + provider, + }); + + const projected = await gateways.providerFirst.discover( + { platform: 'ios', target: 'mobile', kind: 'device', booted: true }, + scope, + ); + expect(projected.map(({ id }) => id)).toEqual(['ios-default']); + }); + + test('loads only the selected family and retries failed host and source loads', async () => { + let hostAttempts = 0; + let androidAttempts = 0; + const local = inventoryWorld({ + android: async () => { + androidAttempts += 1; + if (androidAttempts === 1) throw new Error('source load failed'); + return source([device('android')]); + }, + }); + const gateways = createComposedDeviceInventoryGateways({ + registry: local.registry, + loadHost: async () => { + hostAttempts += 1; + if (hostAttempts === 1) throw new Error('host load failed'); + return host; + }, + }); + + await expect(gateways.localOnly.discover({ platform: 'android' }, scope)).rejects.toThrow( + 'host load failed', + ); + await expect(gateways.localOnly.discover({ platform: 'android' }, scope)).rejects.toThrow( + 'source load failed', + ); + expect(await gateways.localOnly.discover({ platform: 'android' }, scope)).toEqual([ + device('android'), + ]); + expect(hostAttempts).toBe(2); + expect(androidAttempts).toBe(2); + expect(local.sourceLoads()).toEqual(['android', 'android']); + }); + + test('unfiltered local discovery is fail-soft and preserves selector order', async () => { + const local = inventoryWorld({ + harmonyos: async () => source([], new Error('hdc unavailable')), + }); + const gateways = createComposedDeviceInventoryGateways({ + registry: local.registry, + loadHost: local.loadHost, + }); + + const devices = await gateways.localOnly.discover({}, scope); + expect(devices.map(({ platform }) => platform)).toEqual(['android', 'apple', 'vega', 'linux']); + expect(local.sourceLoads()).toEqual(['android', 'harmonyos', 'apple', 'vega', 'linux']); + }); + + test('explicit local failures propagate and family sources cannot return another family', async () => { + const failing = inventoryWorld({ android: async () => source([], new Error('adb failed')) }); + await expect( + createComposedDeviceInventoryGateways({ + registry: failing.registry, + loadHost: failing.loadHost, + }).localOnly.discover({ platform: 'android' }, scope), + ).rejects.toThrow('adb failed'); + + const lying = inventoryWorld({ + android: async () => source([device('linux', 'wrong-family')]), + }); + await expect( + createComposedDeviceInventoryGateways({ + registry: lying.registry, + loadHost: lying.loadHost, + }).localOnly.discover({ platform: 'android' }, scope), + ).rejects.toMatchObject({ + details: { expectedFamily: 'android', actualFamily: 'linux' }, + }); + }); + + test('narrows app-based simulator selection through the injected probe', async () => { + // The composer builds on the caller-supplied probe; production wiring attaches the real + // Apple one and src/platform-runtime-device-inventory.test.ts pins that composition. + const installedApps: Record = { 'sim-b': 'com.example.demo' }; + const calls: string[] = []; + const local = inventoryWorld({ + apple: async () => source([simulator('sim-a'), simulator('sim-b')]), + }); + const gateways = createComposedDeviceInventoryGateways({ + registry: local.registry, + loadHost: local.loadHost, + findInstalledApp: async (device) => { + calls.push(device.id); + return installedApps[device.id]; + }, + }); + + const selection = await withDeviceInventoryContext({ ...gateways, requestScope: scope }, () => + resolveTargetDeviceSelection( + { platform: 'ios' }, + { + appleSimulatorAppTarget: 'com.example.demo', + }, + ), + ); + + expect(selection.device.id).toBe('sim-b'); + expect(selection.reason).toBe('single-app-installed-local'); + expect(selection.source).toBe('local'); + expect(selection.candidateCount).toBe(1); + expect([...calls].sort()).toEqual(['sim-a', 'sim-b']); + }); +}); + +function inventoryWorld( + overrides: Partial Promise>> = {}, +) { + const loaded: Platform[] = []; + let hosts = 0; + const modules: InventoryPlatformModule[] = PLATFORMS.map((family) => ({ + family, + loadInventory: async () => { + loaded.push(family); + return await (overrides[family]?.() ?? Promise.resolve(source([device(family)]))); + }, + })); + return { + registry: createPlatformModuleRegistry(modules), + loadHost: async () => { + hosts += 1; + return host; + }, + hostLoads: () => hosts, + sourceLoads: () => [...loaded], + }; +} + +function source(devices: readonly DeviceInfo[], failure?: Error): DeviceInventorySource { + return { + discover: async (_request: Readonly) => { + if (failure) throw failure; + return devices; + }, + }; +} + +function device(platform: Platform, id = `${platform}-device`): DeviceInfo { + return { + platform, + id, + name: id, + kind: 'device', + target: platform === 'vega' ? 'tv' : 'mobile', + booted: true, + }; +} + +function simulator(id: string): DeviceInfo { + return { + platform: 'apple', + id, + name: id, + kind: 'simulator', + appleOs: 'ios', + target: 'mobile', + booted: true, + }; +} diff --git a/packages/device-selection/src/__tests__/device-selection-resolver.test.ts b/packages/device-selection/src/__tests__/device-selection-resolver.test.ts index 2d1840a0ea..4a8a338785 100644 --- a/packages/device-selection/src/__tests__/device-selection-resolver.test.ts +++ b/packages/device-selection/src/__tests__/device-selection-resolver.test.ts @@ -7,7 +7,7 @@ import { resolveExistingSessionDeviceSelection, resolveInventoryDeviceSelection, } from '../device-selection-resolver.ts'; -import { withTestDeviceInventory } from './test-utils/device-inventory-gateways.ts'; +import { withTestDeviceInventory } from '../inventory-gateways.fixtures.ts'; import { ANDROID_EMULATOR, IOS_SIMULATOR, diff --git a/packages/device-selection/src/__tests__/dispatch-resolve.test.ts b/packages/device-selection/src/__tests__/dispatch-resolve.test.ts index f6821dd8fa..f983d4e5fe 100644 --- a/packages/device-selection/src/__tests__/dispatch-resolve.test.ts +++ b/packages/device-selection/src/__tests__/dispatch-resolve.test.ts @@ -11,7 +11,7 @@ import { import { withTestDeviceInventory, withTestDeviceInventoryProvider as withDeviceInventoryProvider, -} from './test-utils/device-inventory-gateways.ts'; +} from '../inventory-gateways.fixtures.ts'; import type { DeviceInfo } from '@agent-device/kernel/device'; import type { DeviceInventoryRequest } from '@agent-device/contracts/device'; import { AppError } from '@agent-device/kernel/errors'; diff --git a/packages/device-selection/src/__tests__/inventory-gateways.fixtures.test.ts b/packages/device-selection/src/__tests__/inventory-gateways.fixtures.test.ts new file mode 100644 index 0000000000..94f4052cae --- /dev/null +++ b/packages/device-selection/src/__tests__/inventory-gateways.fixtures.test.ts @@ -0,0 +1,35 @@ +import { describe, expect, test } from 'vitest'; +import type { ProviderDeviceInventorySource } from '@agent-device/contracts/device'; +import { createTestDeviceInventoryGateways } from '../inventory-gateways.fixtures.ts'; + +const scope = { + signal: new AbortController().signal, + diagnostics: { emit: () => {} }, + progress: { report: () => {} }, +} as const; + +describe('test device inventory gateways', () => { + test('a malformed provider outcome fails closed through the shared composer assertion', async () => { + const gateways = createTestDeviceInventoryGateways({ + provider: { discover: async () => undefined } as unknown as ProviderDeviceInventorySource, + }); + await expect( + gateways.providerFirst.discover({ platform: 'android' }, scope), + ).rejects.toMatchObject({ details: { reason: 'provider_inventory_contract_violation' } }); + }); + + test('a declined provider falls through to the local inventory', async () => { + const device = { + platform: 'android' as const, + id: 'emulator-5554', + name: 'Pixel', + kind: 'emulator' as const, + booted: true, + }; + const gateways = createTestDeviceInventoryGateways({ + provider: { discover: async () => ({ kind: 'declined' }) }, + local: async () => [device], + }); + expect(await gateways.providerFirst.discover({ platform: 'android' }, scope)).toEqual([device]); + }); +}); diff --git a/packages/device-selection/src/__tests__/test-utils/device-inventory-gateways.ts b/packages/device-selection/src/__tests__/test-utils/device-inventory-gateways.ts deleted file mode 100644 index f67f628b9d..0000000000 --- a/packages/device-selection/src/__tests__/test-utils/device-inventory-gateways.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { - filterDeviceInventoryProjection, - LOCAL_DEVICE_INVENTORY_PLATFORM_SELECTORS, - projectProviderDeviceInventoryRequest, - type DeviceInventoryProvider, - type DeviceInventoryRequest, - type ProviderDeviceInventorySource, -} from '@agent-device/contracts/device'; -import type { - ComposedDeviceInventoryGateways, - DeviceInventoryGateway, - InstalledAppProbe, - ProviderAwareDeviceInventoryGateway, -} from '@agent-device/contracts/platform-module'; -import type { PlatformRequestScope } from '@agent-device/contracts/platform-runtime-host'; -import { - isApplePlatform, - type DeviceInfo, - type Platform, - type PlatformSelector, -} from '@agent-device/kernel/device'; -import { withDeviceInventoryContext } from '../../device-inventory-context.ts'; - -export type TestDeviceInventoryOptions = Readonly<{ - provider?: ProviderDeviceInventorySource; - local?: (request: Readonly) => Promise; - findInstalledApp?: InstalledAppProbe; -}>; - -const testRequestScope: PlatformRequestScope = Object.freeze({ - signal: new AbortController().signal, - diagnostics: Object.freeze({ emit: () => {} }), - progress: Object.freeze({ report: () => {} }), -}); - -type LocalDiscover = (request: Readonly) => Promise; - -async function discoverLocalFamily( - localDiscover: LocalDiscover, - platform: PlatformSelector, - request: Readonly, - scope: PlatformRequestScope, -): Promise { - scope.signal.throwIfAborted(); - const family: Platform = isApplePlatform(platform) ? 'apple' : platform; - const devices = (await localDiscover(request)).filter((device) => device.platform === family); - return filterDeviceInventoryProjection(devices, request); -} - -async function discoverLocal( - localDiscover: LocalDiscover, - request: Readonly, - scope: PlatformRequestScope, -): Promise { - scope.signal.throwIfAborted(); - if (request.platform) { - return await discoverLocalFamily(localDiscover, request.platform, request, scope); - } - const perFamily = await Promise.all( - LOCAL_DEVICE_INVENTORY_PLATFORM_SELECTORS.map(async (selector) => { - try { - return await discoverLocalFamily( - localDiscover, - selector, - { ...request, platform: selector }, - scope, - ); - } catch { - return []; - } - }), - ); - scope.signal.throwIfAborted(); - return perFamily.flat(); -} - -function createTestDeviceInventoryGateways( - options: TestDeviceInventoryOptions = {}, -): ComposedDeviceInventoryGateways { - const localDiscover = options.local ?? (async () => [] as readonly DeviceInfo[]); - const localOnly: DeviceInventoryGateway = Object.freeze({ - discover: (request, scope) => discoverLocal(localDiscover, request, scope), - }); - const provider = options.provider; - const discoverWithSource: ProviderAwareDeviceInventoryGateway['discoverWithSource'] = async ( - request, - scope, - ) => { - if (provider) { - scope.signal.throwIfAborted(); - const result = await provider.discover( - projectProviderDeviceInventoryRequest(request), - scope.signal, - ); - scope.signal.throwIfAborted(); - if (result.kind === 'inventory') { - return { - devices: filterDeviceInventoryProjection( - result.devices.map((device) => ({ ...device })), - request, - ), - source: 'provider', - }; - } - } - return { devices: await localOnly.discover(request, scope), source: 'local' }; - }; - const providerFirst: ProviderAwareDeviceInventoryGateway = Object.freeze({ - discover: async (request, scope) => (await discoverWithSource(request, scope)).devices, - discoverWithSource, - }); - return Object.freeze({ - providerFirst, - localOnly, - findInstalledApp: options.findInstalledApp, - }); -} - -export async function withTestDeviceInventory( - options: TestDeviceInventoryOptions, - task: () => Promise, -): Promise { - return await withDeviceInventoryContext( - { ...createTestDeviceInventoryGateways(options), requestScope: testRequestScope }, - task, - ); -} - -/** Test-only bridge for fixtures that still implement the public nullable provider port. */ -export async function withTestDeviceInventoryProvider( - provider: DeviceInventoryProvider, - task: () => Promise, -): Promise { - return await withTestDeviceInventory( - { - provider: { - discover: async (request, signal) => { - signal.throwIfAborted(); - const devices = await provider(request, signal); - return devices === null || devices === undefined - ? { kind: 'declined' } - : { kind: 'inventory', devices }; - }, - }, - }, - task, - ); -} diff --git a/packages/device-selection/src/composed-inventory-gateways.ts b/packages/device-selection/src/composed-inventory-gateways.ts new file mode 100644 index 0000000000..96b4d2237b --- /dev/null +++ b/packages/device-selection/src/composed-inventory-gateways.ts @@ -0,0 +1,238 @@ +import { + filterDeviceInventoryProjection, + LOCAL_DEVICE_INVENTORY_PLATFORM_SELECTORS, + projectProviderDeviceInventoryRequest, + type ProviderDeviceInventorySource, +} from '@agent-device/contracts/device'; +import type { + ComposedDeviceInventoryGateways, + DeviceInventoryGateway, + DeviceInventorySource, + InstalledAppProbe, + InventoryPlatformModule, + PlatformModuleRegistry, + ProviderAwareDeviceInventoryGateway, +} from '@agent-device/contracts/platform-module'; +import type { + DeviceInventoryHost, + DeviceInventoryHostFor, + PlatformRequestScope, +} from '@agent-device/contracts/platform-runtime-host'; +import { + isApplePlatform, + type DeviceInfo, + type Platform, + type PlatformSelector, +} from '@agent-device/kernel/device'; +import { AppError } from '@agent-device/kernel/errors'; + +export function createComposedDeviceInventoryGateways( + options: Readonly<{ + registry: PlatformModuleRegistry; + loadHost: () => Promise; + provider?: ProviderDeviceInventorySource; + /** + * Installed-app probe device selection uses to narrow several booted simulators before + * committing to one. The host composes one in (production passes the Apple simulator + * probe); tests inject or omit one. Absent, selection falls back to the inventory rules. + */ + findInstalledApp?: InstalledAppProbe; + }>, +): ComposedDeviceInventoryGateways { + const localOnly = createLocalDeviceInventoryGateway(options.registry, options.loadHost); + const provider = options.provider; + const discoverWithSource: ProviderAwareDeviceInventoryGateway['discoverWithSource'] = provider + ? async (request, scope) => { + scope.signal.throwIfAborted(); + const provided = await provider.discover( + projectProviderDeviceInventoryRequest(request), + scope.signal, + ); + scope.signal.throwIfAborted(); + assertProviderOutcome(provided); + if (provided.kind === 'inventory') { + return { + devices: filterDeviceInventoryProjection( + provided.devices.map((device) => ({ ...device })), + request, + ), + source: 'provider', + }; + } + return { devices: await localOnly.discover(request, scope), source: 'local' }; + } + : async (request, scope) => ({ + devices: await localOnly.discover(request, scope), + source: 'local', + }); + const providerFirst: ProviderAwareDeviceInventoryGateway = Object.freeze({ + discover: async (request, scope) => (await discoverWithSource(request, scope)).devices, + discoverWithSource, + }); + return Object.freeze({ providerFirst, localOnly, findInstalledApp: options.findInstalledApp }); +} + +function createLocalDeviceInventoryGateway( + registry: PlatformModuleRegistry, + loadHost: () => Promise, +): DeviceInventoryGateway { + let hostLoad: Promise | undefined; + const requiredHost = async (): Promise => { + hostLoad ??= loadHost(); + try { + return await hostLoad; + } catch (error) { + hostLoad = undefined; + throw error; + } + }; + const sourceLoads = new Map>(); + const loadSource = async (family: Platform): Promise => { + const existing = sourceLoads.get(family); + if (existing) return await existing; + const pending = loadFamilyInventory(registry.get(family), requiredHost); + sourceLoads.set(family, pending); + try { + return await pending; + } catch (error) { + if (sourceLoads.get(family) === pending) sourceLoads.delete(family); + throw error; + } + }; + + return Object.freeze({ + discover: async (request, scope) => { + scope.signal.throwIfAborted(); + if (request.platform) { + return await discoverFamily( + loadSource, + familyForSelector(request.platform), + request, + scope, + ); + } + + const perFamily = await Promise.all( + LOCAL_DEVICE_INVENTORY_PLATFORM_SELECTORS.map(async (selector) => { + try { + const devices = await discoverFamily( + loadSource, + familyForSelector(selector), + { ...request, platform: selector }, + scope, + ); + return Array.isArray(devices) ? devices : []; + } catch { + return []; + } + }), + ); + scope.signal.throwIfAborted(); + return perFamily.flat(); + }, + }); +} + +async function loadFamilyInventory( + module: InventoryPlatformModule, + requiredHost: () => Promise, +): Promise { + switch (module.family) { + case 'apple': + return await module.loadInventory(appleInventoryHost(await requiredHost())); + case 'android': + return await module.loadInventory(androidInventoryHost(await requiredHost())); + case 'harmonyos': + return await module.loadInventory(harmonyInventoryHost(await requiredHost())); + case 'vega': + return await module.loadInventory(vegaInventoryHost(await requiredHost())); + case 'linux': + return await module.loadInventory(linuxInventoryHost(await requiredHost())); + case 'web': + return await module.loadInventory(); + } +} + +function appleInventoryHost(host: DeviceInventoryHost): DeviceInventoryHostFor<'apple'> { + return Object.freeze({ + appleTools: host.appleTools, + files: host.files, + hostName: host.hostName, + hostOs: host.hostOs, + observations: host.observations, + }); +} + +function androidInventoryHost(host: DeviceInventoryHost): DeviceInventoryHostFor<'android'> { + return Object.freeze({ + commands: host.commands, + files: host.files, + homeDirectory: host.homeDirectory, + toolchains: host.toolchains, + }); +} + +function harmonyInventoryHost(host: DeviceInventoryHost): DeviceInventoryHostFor<'harmonyos'> { + return Object.freeze({ + commands: host.commands, + files: host.files, + toolchains: host.toolchains, + }); +} + +function vegaInventoryHost(host: DeviceInventoryHost): DeviceInventoryHostFor<'vega'> { + return Object.freeze({ + commands: host.commands, + files: host.files, + homeDirectory: host.homeDirectory, + }); +} + +function linuxInventoryHost(host: DeviceInventoryHost): DeviceInventoryHostFor<'linux'> { + return Object.freeze({ hostName: host.hostName, hostOs: host.hostOs }); +} + +async function discoverFamily( + loadSource: (family: Platform) => Promise, + family: Platform, + request: Parameters[0], + scope: PlatformRequestScope, +): Promise { + const source = await loadSource(family); + scope.signal.throwIfAborted(); + const devices = await source.discover(request, scope); + scope.signal.throwIfAborted(); + assertSourceFamily(family, devices); + return filterDeviceInventoryProjection(devices, request); +} + +// The composition-level contract every device-inventory source must honor: a family's +// source only ever reports devices of its own family. +function assertSourceFamily(family: Platform, devices: readonly DeviceInfo[]): void { + const outside = devices.find((device) => device.platform !== family); + if (!outside) return; + throw new AppError( + 'COMMAND_FAILED', + `Platform inventory source ${family} returned a ${outside.platform} device.`, + { expectedFamily: family, actualFamily: outside.platform, deviceId: outside.id }, + ); +} + +// A malformed provider outcome (not a declined/inventory tagged object) is a provider +// contract violation, not a local-inventory fallback. +function assertProviderOutcome( + value: unknown, +): asserts value is Awaited> { + if (value !== null && typeof value === 'object') { + const outcome = value as { kind?: unknown; devices?: unknown }; + if (outcome.kind === 'declined' && !('devices' in outcome)) return; + if (outcome.kind === 'inventory' && Array.isArray(outcome.devices)) return; + } + throw new AppError('COMMAND_FAILED', 'Provider device inventory returned an invalid outcome.', { + reason: 'provider_inventory_contract_violation', + }); +} + +function familyForSelector(selector: PlatformSelector): Platform { + return isApplePlatform(selector) ? 'apple' : selector; +} diff --git a/src/__tests__/test-utils/device-inventory-gateways.ts b/packages/device-selection/src/inventory-gateways.fixtures.ts similarity index 86% rename from src/__tests__/test-utils/device-inventory-gateways.ts rename to packages/device-selection/src/inventory-gateways.fixtures.ts index e8f49766b1..0d9dc963e7 100644 --- a/src/__tests__/test-utils/device-inventory-gateways.ts +++ b/packages/device-selection/src/inventory-gateways.fixtures.ts @@ -6,6 +6,7 @@ import { import { type ComposedDeviceInventoryGateways, type DeviceInventorySource, + type InstalledAppProbe, type InventoryPlatformModule, createPlatformModuleRegistry, } from '@agent-device/contracts/platform-module'; @@ -14,12 +15,13 @@ import type { PlatformRequestScope, } from '@agent-device/contracts/platform-runtime-host'; import type { DeviceInfo, Platform } from '@agent-device/kernel/device'; -import { withDeviceInventoryContext } from '@agent-device/device-selection/device-inventory-context'; -import { createComposedDeviceInventoryGateways } from '../../platform-runtime-device-inventory.ts'; +import { createComposedDeviceInventoryGateways } from './composed-inventory-gateways.ts'; +import { withDeviceInventoryContext } from './device-inventory-context.ts'; -type TestDeviceInventoryOptions = Readonly<{ +export type TestDeviceInventoryOptions = Readonly<{ provider?: ProviderDeviceInventorySource; local?: (request: Readonly) => Promise; + findInstalledApp?: InstalledAppProbe; }>; const testRequestScope: PlatformRequestScope = Object.freeze({ @@ -28,17 +30,6 @@ const testRequestScope: PlatformRequestScope = Object.freeze({ progress: Object.freeze({ report: () => {} }), }); -export function createTestDeviceInventoryGateways( - options: TestDeviceInventoryOptions = {}, -): ComposedDeviceInventoryGateways { - const localDiscover = options.local ?? (async () => []); - return createComposedDeviceInventoryGateways({ - registry: createPlatformModuleRegistry(testInventoryModules(localDiscover)), - loadHost: async () => testInventoryHost, - provider: options.provider, - }); -} - const testInventoryHost: DeviceInventoryHost = Object.freeze({ commands: Object.freeze({ which: async () => undefined, @@ -78,6 +69,19 @@ function testInventoryModules( ]; } +export function createTestDeviceInventoryGateways( + options: TestDeviceInventoryOptions = {}, +): ComposedDeviceInventoryGateways { + return createComposedDeviceInventoryGateways({ + registry: createPlatformModuleRegistry( + testInventoryModules(options.local ?? (async () => [] as readonly DeviceInfo[])), + ), + loadHost: async () => testInventoryHost, + provider: options.provider, + findInstalledApp: options.findInstalledApp, + }); +} + /** Test-only bridge for fixtures that still implement the public nullable provider port. */ export function createTestDeviceInventoryGatewaysFromProvider( provider: DeviceInventoryProvider, @@ -110,10 +114,7 @@ export async function withTestDeviceInventoryProvider( task: () => Promise, ): Promise { return await withDeviceInventoryContext( - { - ...createTestDeviceInventoryGatewaysFromProvider(provider), - requestScope: testRequestScope, - }, + { ...createTestDeviceInventoryGatewaysFromProvider(provider), requestScope: testRequestScope }, task, ); } diff --git a/packages/platform-android/package.json b/packages/platform-android/package.json index 336b10b9b3..408fef48cc 100644 --- a/packages/platform-android/package.json +++ b/packages/platform-android/package.json @@ -31,6 +31,14 @@ "./mechanics": { "types": "./src/mechanics.ts", "default": "./src/mechanics.ts" + }, + "./snapshot-capture-fixtures": { + "types": "./src/snapshot-capture.fixtures.ts", + "default": "./src/snapshot-capture.fixtures.ts" + }, + "./ui-hierarchy-fixtures": { + "types": "./src/ui-hierarchy.fixtures.ts", + "default": "./src/ui-hierarchy.fixtures.ts" } } } diff --git a/src/__tests__/test-utils/android-ime-capture.raw.json b/packages/platform-android/src/fixtures/android-ime-capture.raw.json similarity index 100% rename from src/__tests__/test-utils/android-ime-capture.raw.json rename to packages/platform-android/src/fixtures/android-ime-capture.raw.json diff --git a/src/__tests__/test-utils/android-qs-shade-capture.raw.json b/packages/platform-android/src/fixtures/android-qs-shade-capture.raw.json similarity index 100% rename from src/__tests__/test-utils/android-qs-shade-capture.raw.json rename to packages/platform-android/src/fixtures/android-qs-shade-capture.raw.json diff --git a/src/__tests__/test-utils/android-snapshot-capture.ts b/packages/platform-android/src/snapshot-capture.fixtures.ts similarity index 88% rename from src/__tests__/test-utils/android-snapshot-capture.ts rename to packages/platform-android/src/snapshot-capture.fixtures.ts index fc2e91c795..288d437d36 100644 --- a/src/__tests__/test-utils/android-snapshot-capture.ts +++ b/packages/platform-android/src/snapshot-capture.fixtures.ts @@ -3,10 +3,7 @@ import type { SnapshotOcclusionContextEvidence, } from '@agent-device/contracts/capture'; import type { RawSnapshotNode } from '@agent-device/kernel/snapshot'; -import { - createAndroidSnapshotCapture, - type AndroidSnapshotCapture, -} from '@agent-device/platform-android/mechanics'; +import { createAndroidSnapshotCapture, type AndroidSnapshotCapture } from './mechanics.ts'; export function makeAndroidSnapshotCapture( nodes: RawSnapshotNode[], diff --git a/src/__tests__/test-utils/android-ui-hierarchy-fixtures.ts b/packages/platform-android/src/ui-hierarchy.fixtures.ts similarity index 97% rename from src/__tests__/test-utils/android-ui-hierarchy-fixtures.ts rename to packages/platform-android/src/ui-hierarchy.fixtures.ts index 1ddb747243..f483518d74 100644 --- a/src/__tests__/test-utils/android-ui-hierarchy-fixtures.ts +++ b/packages/platform-android/src/ui-hierarchy.fixtures.ts @@ -1,8 +1,8 @@ // Real archived device trees, regenerated from a device and never hand-edited (a // hand-written id is how a fixture stops proving anything). Static import, not an // `fs` read: it typechecks each tree against `RawSnapshotNode[]`. -import imeCapture from './android-ime-capture.raw.json' with { type: 'json' }; -import qsShadeCapture from './android-qs-shade-capture.raw.json' with { type: 'json' }; +import imeCapture from './fixtures/android-ime-capture.raw.json' with { type: 'json' }; +import qsShadeCapture from './fixtures/android-qs-shade-capture.raw.json' with { type: 'json' }; import { buildUiHierarchySnapshot, parseUiHierarchyTree, @@ -10,7 +10,7 @@ import { buildAndroidSnapshotClickabilityEvidence, type AndroidUiHierarchy, type AndroidUiHierarchySnapshotOptions, -} from '@agent-device/platform-android/mechanics'; +} from './mechanics.ts'; import type { RawSnapshotNode } from '@agent-device/kernel/snapshot'; /** diff --git a/packages/replay-port/package.json b/packages/replay-port/package.json index 56951e62f9..88eb85c27f 100644 --- a/packages/replay-port/package.json +++ b/packages/replay-port/package.json @@ -52,6 +52,10 @@ "types": "./src/daemon-port/script-source.ts", "default": "./src/daemon-port/script-source.ts" }, + "./script-source-bundle": { + "types": "./src/script-source-bundle.ts", + "default": "./src/script-source-bundle.ts" + }, "./session-replay-action-runtime": { "types": "./src/daemon-port/session-replay-action-runtime.ts", "default": "./src/daemon-port/session-replay-action-runtime.ts" @@ -151,6 +155,14 @@ "./test-command": { "types": "./src/daemon-port/test-command.ts", "default": "./src/daemon-port/test-command.ts" + }, + "./source-discovery": { + "types": "./src/source-discovery.ts", + "default": "./src/source-discovery.ts" + }, + "./replay-script-source-fixtures": { + "types": "./src/replay-script-source.fixtures.ts", + "default": "./src/replay-script-source.fixtures.ts" } } } diff --git a/src/commands/replay/script-source-bundle.test.ts b/packages/replay-port/src/__tests__/script-source-bundle.test.ts similarity index 96% rename from src/commands/replay/script-source-bundle.test.ts rename to packages/replay-port/src/__tests__/script-source-bundle.test.ts index 0a73c558a7..0c5592373a 100644 --- a/src/commands/replay/script-source-bundle.test.ts +++ b/packages/replay-port/src/__tests__/script-source-bundle.test.ts @@ -4,8 +4,8 @@ import { expect, test } from 'vitest'; import { loadReplayScriptSourceBundle, REPLAY_SCRIPT_SOURCE_BUNDLE_MAX_BYTES, -} from './script-source-bundle.ts'; -import { mkdtempForTestSync } from '@agent-device/test-kit/tmp-dir'; +} from '@agent-device/replay-port/script-source-bundle'; +import { mkdtempForTestSync } from '../tmp-dir.fixtures.ts'; // #1802: the caller reads every script file a replay run needs and sends the text. These pin the // collection itself; that the daemon consumes only the result is pinned beside the replay handler. diff --git a/src/commands/replay/source-discovery.test.ts b/packages/replay-port/src/__tests__/source-discovery.test.ts similarity index 98% rename from src/commands/replay/source-discovery.test.ts rename to packages/replay-port/src/__tests__/source-discovery.test.ts index b1ad3687b3..2e8ead6c14 100644 --- a/src/commands/replay/source-discovery.test.ts +++ b/packages/replay-port/src/__tests__/source-discovery.test.ts @@ -5,8 +5,8 @@ import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import { AppError } from '@agent-device/kernel/errors'; -import { discoverReplaySourcePaths } from './source-discovery.ts'; -import { mkdtempForTestSync } from '@agent-device/test-kit/tmp-dir'; +import { discoverReplaySourcePaths } from '@agent-device/replay-port/source-discovery'; +import { mkdtempForTestSync } from '../tmp-dir.fixtures.ts'; // #1802: path expansion, traversal ordering and file-type routing are CALLER work — `test` // inputs name files on the machine that typed the command. These pin the traversal itself; diff --git a/src/__tests__/test-utils/replay-script-source.ts b/packages/replay-port/src/replay-script-source.fixtures.ts similarity index 84% rename from src/__tests__/test-utils/replay-script-source.ts rename to packages/replay-port/src/replay-script-source.fixtures.ts index f043da01c4..d10529d1c2 100644 --- a/src/__tests__/test-utils/replay-script-source.ts +++ b/packages/replay-port/src/replay-script-source.fixtures.ts @@ -1,10 +1,11 @@ +// The single owner of the client-collected replay source bundles test requests carry (#1802). import fs from 'node:fs'; import type { ReplayScriptSourceBundle } from '@agent-device/contracts/replay'; +import type { DaemonWireRequest } from '@agent-device/contracts/command'; import type { MaestroSourceReader } from '@agent-device/maestro'; import { resolveUserPath } from '@agent-device/host-kit/file'; -import type { DaemonRequest } from '../../daemon/daemon-request.ts'; -import { loadReplayScriptSourceBundle } from '../../commands/replay/script-source-bundle.ts'; -import { discoverReplaySourcePaths } from '../../commands/replay/source-discovery.ts'; +import { loadReplayScriptSourceBundle } from './script-source-bundle.ts'; +import { discoverReplaySourcePaths } from './source-discovery.ts'; /** A native `.ad` script's one-entry source bundle for test requests. */ export function replayScriptSourceBundleFor(filePath: string): ReplayScriptSourceBundle { @@ -44,7 +45,9 @@ export const noMaestroIncludeSources: MaestroSourceReader = (resolvedPath) => { * the CLI boundary (pinned in `cli-client-commands.test.ts`), and a daemon-level harness must * still deliver the request so the daemon's own response is what the case observes. */ -export async function withClientReplayScriptSources(req: DaemonRequest): Promise { +export async function withClientReplayScriptSources( + req: DaemonWireRequest, +): Promise { if (req.command !== 'replay' && req.command !== 'test') return req; const inputs = req.positionals ?? []; const entryPath = inputs[0]; @@ -76,6 +79,9 @@ export async function withClientReplayScriptSources(req: DaemonRequest): Promise } } -function withFlags(req: DaemonRequest, flags: Partial): DaemonRequest { +function withFlags( + req: DaemonWireRequest, + flags: Partial>, +): DaemonWireRequest { return { ...req, flags: { ...(req.flags ?? {}), ...flags } }; } diff --git a/src/commands/replay/script-source-bundle.ts b/packages/replay-port/src/script-source-bundle.ts similarity index 100% rename from src/commands/replay/script-source-bundle.ts rename to packages/replay-port/src/script-source-bundle.ts diff --git a/src/commands/replay/source-discovery.ts b/packages/replay-port/src/source-discovery.ts similarity index 100% rename from src/commands/replay/source-discovery.ts rename to packages/replay-port/src/source-discovery.ts diff --git a/packages/test-kit/package.json b/packages/test-kit/package.json index b508b8c19a..ba9fc020f5 100644 --- a/packages/test-kit/package.json +++ b/packages/test-kit/package.json @@ -57,6 +57,10 @@ "types": "./src/loopback.ts", "default": "./src/loopback.ts" }, + "./plugin.fixtures": { + "types": "./src/plugin.fixtures.ts", + "default": "./src/plugin.fixtures.ts" + }, "./property-arbitraries": { "types": "./src/property-arbitraries.ts", "default": "./src/property-arbitraries.ts" diff --git a/src/plugins/plugin.fixtures.ts b/packages/test-kit/src/plugin.fixtures.ts similarity index 98% rename from src/plugins/plugin.fixtures.ts rename to packages/test-kit/src/plugin.fixtures.ts index 88d1082f10..bce8780eac 100644 --- a/src/plugins/plugin.fixtures.ts +++ b/packages/test-kit/src/plugin.fixtures.ts @@ -2,7 +2,7 @@ import crypto from 'node:crypto'; import fs from 'node:fs'; import path from 'node:path'; import type { ProviderProfileField } from '@agent-device/contracts/provider-profile-fields'; -import { mkdtempForTestSync } from '@agent-device/test-kit/tmp-dir'; +import { mkdtempForTestSync } from './tmp-dir.ts'; export function pluginHome() { const home = mkdtempForTestSync('provider-plugins-'); diff --git a/scripts/__tests__/eager-closure-budgets.ts b/scripts/__tests__/eager-closure-budgets.ts index 351a00821d..e17b81e142 100644 --- a/scripts/__tests__/eager-closure-budgets.ts +++ b/scripts/__tests__/eager-closure-budgets.ts @@ -132,7 +132,39 @@ export const NEW_ENTRY_CEILINGS: Readonly> = Objec */ export const APPROVED_OVER_CEILING: Readonly< Record -> = Object.freeze({}); +> = Object.freeze({ + // The single owner of the client-collected replay source bundles tests carry (#1802). Its + // weight is the real collector it exists to share, not fixture code. + 'packages/replay-port/src/replay-script-source.fixtures.ts': { + issue: '#3517', + reason: + 'Test-only request fixture whose whole contract is running the real client collector ' + + '(script-source-bundle + source-discovery) so the root and daemon test trees cannot ' + + 'drift from it; the weight is the ad-script format parser behind resolveReplayFormat. ' + + 'Making that lazy would fork a second collector, which is the duplication #3517 removes. ' + + 'No platform implementation; never bundled.', + owner: 'replay-port', + }, + // The two Android fixture subpaths are by design the REAL walker on archived device trees: + // their whole contract is running the shipped projection over captures no hand-written node + // could stand in for, so their closure IS the mechanics the subpath exposes. + 'packages/platform-android/src/snapshot-capture.fixtures.ts': { + issue: '#3517', + reason: + 'Test-only Android capture builders over the package mechanics facade, shared by the ' + + 'daemon and root test trees; a fixture that simulated the walk would prove nothing about ' + + 'production inclusion decisions. Test-only subpath; never bundled.', + owner: 'platform-android', + }, + 'packages/platform-android/src/ui-hierarchy.fixtures.ts': { + issue: '#3517', + reason: + 'Test-only archived device trees (IME, QS shade) plus the real non-raw/interactive walks ' + + 'over them, shared by the daemon and root test trees; the closure is the mechanics facade ' + + 'the fixtures must run. Test-only subpath; never bundled.', + owner: 'platform-android', + }, +}); /** The category is a function of the path, never a hand-written column. */ export function entryCategoryOf(entryFile: string): EntryCategory { @@ -151,6 +183,10 @@ export function entryCategoryOf(entryFile: string): EntryCategory { const PLATFORM_MECHANICS_ENTRY_PREFIXES = [ 'packages/platform-apple/src/runner/', 'packages/platform-android/src/mechanics.ts', + // The shared Android test fixtures ARE the mechanics exercised on archived device trees + // (#3517); like the mechanics facet, their closure is the implementation they export. + 'packages/platform-android/src/snapshot-capture.fixtures.ts', + 'packages/platform-android/src/ui-hierarchy.fixtures.ts', ] as const; const APPLE_DOMAIN_MECHANICS_ENTRY_FILES: ReadonlySet = new Set([ diff --git a/scripts/check-affected/device-lanes.test.ts b/scripts/check-affected/device-lanes.test.ts index b4552b6e9e..b3f3eb388d 100644 --- a/scripts/check-affected/device-lanes.test.ts +++ b/scripts/check-affected/device-lanes.test.ts @@ -137,7 +137,7 @@ test('unit tests under src/ and packages/*/src own no lane', () => { assert.deepEqual(lanes(file), [], file); } // Non-TypeScript under __tests__ is a fixture the selector cannot place, not a unit test. - assert.ok(!isUnitTest('src/__tests__/test-utils/android-ime-capture.raw.json')); + assert.ok(!isUnitTest('packages/platform-android/src/fixtures/android-ime-capture.raw.json')); // Package tests outside src/ have no owner and keep failing open. assert.ok(!isUnitTest('packages/maestro/test/conformance/verify.test.ts')); assert.equal( diff --git a/scripts/layering/package-boundaries.test.ts b/scripts/layering/package-boundaries.test.ts index b732afa650..87ff27dfe7 100644 --- a/scripts/layering/package-boundaries.test.ts +++ b/scripts/layering/package-boundaries.test.ts @@ -617,12 +617,6 @@ test('the real tree parses, declares, and passes R11', () => { (pkg) => pkg.name === '@agent-device/platform-android', ); assert.ok(platformAndroidPackage, 'platform-android package must exist'); - assert.deepEqual([...platformAndroidPackage.exportTargets.keys()].sort(), [ - '@agent-device/platform-android', - '@agent-device/platform-android/adb-host', - '@agent-device/platform-android/device-boot', - '@agent-device/platform-android/mechanics', - ]); assert.deepEqual([...platformAndroidPackage.workspaceDependencies].sort(), [ '@agent-device/capture-kit', '@agent-device/contracts', diff --git a/scripts/layering/package-boundaries.ts b/scripts/layering/package-boundaries.ts index 6a5721f9f1..2ee38883b3 100644 --- a/scripts/layering/package-boundaries.ts +++ b/scripts/layering/package-boundaries.ts @@ -8,7 +8,7 @@ // Evidence: 76453add71 (#1494, #1490 W0) established the workspace split this rule protects; // 83322a3f2f (#1574) pinned exact facade symbols for every workspace package; #3279 moved the // type-check onto `tsc -b` project references and retired the checks it now enforces. -// Cost: 447 LOC (rule) + 978 LOC (test). +// Cost: 510 LOC (rule) + 998 LOC (test). // Kill criterion: retire a remaining branch only when something owns its mechanism, not merely // its symptom. An undeclared `workspace:*` sibling is type-clean and runtime-green INSIDE // this repo because pnpm links every workspace package under the root `node_modules` — the @@ -25,6 +25,8 @@ // and via its package specifier instantiates twice in one process — duplicate AppError, broken // instanceof), and, for `scripts/` files which sit outside the type graph by design, the // root-side unknown-package and exports-map checks that compiled files get from TS2307. +// On top, `TEST_ONLY_FIXTURE_SUBPATHS` mirrors R13's Android fixture gate for the non-platform +// packages: a production file importing one of those fixture subpaths is an R11 violation. import fs from 'node:fs'; import path from 'node:path'; @@ -169,6 +171,26 @@ export function insideCompiledSources(file: string): boolean { return /^packages\/[^/]+\/src\//.test(file) || /^src\//.test(file) || /^test\//.test(file); } +/** + * Workspace fixture subpaths only non-production files may import — the same claim R13's + * `ANDROID_TEST_ONLY_SUBPATHS` makes for the platform packages, which R13 cannot express for + * non-platform packages. A production file (root or package) importing one is a violation. + */ +export const TEST_ONLY_FIXTURE_SUBPATHS: ReadonlySet = new Set([ + '@agent-device/device-selection/inventory-gateways-fixtures', + '@agent-device/replay-port/replay-script-source-fixtures', +]); + +/** Consumer side of `TEST_ONLY_FIXTURE_SUBPATHS`, mirroring R13's non-production predicate. */ +function importsTestOnlyFixtureFromProduction(file: string, specifier: string): boolean { + if (!TEST_ONLY_FIXTURE_SUBPATHS.has(specifier)) return false; + // Root `test/`, package-local `packages/*/test/`, *.test.ts, and __tests__ trees are all + // test consumers; anything else is production. + const isTestConsumer = + /(?:^|\/)test\//.test(file) || file.endsWith('.test.ts') || file.includes('/__tests__/'); + return !isTestConsumer; +} + /** * Rules for files INSIDE a package. An undeclared sibling import is R11's alone: pnpm links * every workspace package under the root `node_modules`, so the import resolves, compiles, and @@ -217,6 +239,16 @@ export function checkPackageInternalSites( } const name = specifierPackageName(site.specifier); if (!name || !name.startsWith('@agent-device/')) continue; + if (importsTestOnlyFixtureFromProduction(site.file, site.specifier)) { + violations.push({ + rule: 'R11 package-boundaries', + file: site.file, + line: site.line, + message: + `'${site.specifier}' is a test-only fixture subpath — production code may not ` + + `import it; use the package's production subpath instead.`, + }); + } const target = packageByName(allPackages, name); if (!target) { if (!compiled) { @@ -288,6 +320,16 @@ export function checkRootSites( } const name = specifierPackageName(site.specifier); if (!name || !name.startsWith('@agent-device/')) continue; + if (importsTestOnlyFixtureFromProduction(site.file, site.specifier)) { + violations.push({ + rule: 'R11 package-boundaries', + file: site.file, + line: site.line, + message: + `'${site.specifier}' is a test-only fixture subpath — production code may not ` + + `import it; use the package's production subpath instead.`, + }); + } const target = packageByName(packages, name); if (!target) { if (!compiled) { diff --git a/scripts/layering/platform-package-policy.test.ts b/scripts/layering/platform-package-policy.test.ts index 79a1436771..fb4c1270ee 100644 --- a/scripts/layering/platform-package-policy.test.ts +++ b/scripts/layering/platform-package-policy.test.ts @@ -54,6 +54,8 @@ function declarations(): PlatformPackageDeclaration[] { '@agent-device/platform-android/adb-host', '@agent-device/platform-android/device-boot', '@agent-device/platform-android/mechanics', + '@agent-device/platform-android/snapshot-capture-fixtures', + '@agent-device/platform-android/ui-hierarchy-fixtures', ] : [`@agent-device/platform-${family}`], })); @@ -428,6 +430,31 @@ test('the Android mechanics facet is the named consumer seam and adb host wiring ); }); +test('the Android test-only fixture subpaths accept test consumers and deny production ones', () => { + const fixtureImport = + "import { createAndroidSnapshotCapture } from '@agent-device/platform-android/snapshot-capture-fixtures';"; + const allowed = validSources(); + allowed.set('src/__tests__/android-ui-hierarchy.test.ts', fixtureImport); + allowed.set('src/daemon/__tests__/android-system-dialog-occlusion.test.ts', fixtureImport); + allowed.set('test/integration/provider-scenarios/android.test.ts', fixtureImport); + assert.deepEqual( + messages(allowed).filter((message) => message.includes('may import')), + [], + ); + + // Production consumers ride no facet seam: importing a fixture is an R13 violation, even + // from a file the mechanics facet would otherwise accept. + for (const file of ['src/commands/replay/index.ts', 'src/core/interactors/android.ts']) { + const denied = validSources(); + denied.set(file, fixtureImport); + assert.match( + messages(denied).join('\n'), + /may import '@agent-device\/platform-android\/snapshot-capture-fixtures'/, + file, + ); + } +}); + test('contracts never depend on a concrete platform package in production or tests', () => { for (const statement of [ "import { applePlatformMetadata } from '@agent-device/platform-apple';", @@ -564,12 +591,12 @@ test('composition imports are category-based for future contracts and host adapt "import { createAndroidObservationHost } from './platform-runtime-android-observation-host.ts';", "import { createPlatformRuntimeHost } from './platform-runtime-operation-host.ts';", "import { hostCommandRunner } from './platform-runtime-host/command.ts';", - "import { createComposedDeviceInventoryGateways } from './platform-runtime-device-inventory.ts';", + "import { attachAppleInstalledAppProbe } from './platform-runtime-device-inventory.ts';", 'void hostCommandRunner;', 'void createComposedPlatformRuntimeGateway;', 'void createAndroidObservationHost;', 'void createPlatformRuntimeHost;', - 'void createComposedDeviceInventoryGateways;', + 'void attachAppleInstalledAppProbe;', 'export type Scope = PlatformRequestScope;', ].join('\n'), ); diff --git a/scripts/layering/platform-package-policy.ts b/scripts/layering/platform-package-policy.ts index 4f276650b1..ed5bbb27ca 100644 --- a/scripts/layering/platform-package-policy.ts +++ b/scripts/layering/platform-package-policy.ts @@ -98,8 +98,17 @@ const MECHANICS_FACET_SUBPATHS: Readonly !declaration.exportedSubpaths.includes(subpath)) diff --git a/scripts/layering/retired-paths-policy.test.ts b/scripts/layering/retired-paths-policy.test.ts index 675459bc69..67a1be18ea 100644 --- a/scripts/layering/retired-paths-policy.test.ts +++ b/scripts/layering/retired-paths-policy.test.ts @@ -80,7 +80,7 @@ test('R71 rejects a planted production file under retired src/replay by name', ( file: 'src/replay/planted-production-file.ts', line: 1, message: - 'src/replay/ is retired; caller source acquisition belongs under src/commands/replay/ ' + + 'src/replay/ is retired; caller source acquisition belongs under packages/replay-port/src/ ' + 'and replay-test presentation belongs under src/cli/replay-test/.', }, ]); @@ -95,7 +95,7 @@ test('R71 rejects a planted production file under retired src/replay by name', ( test('R71 accepts the current replay owners and ignores the src/utils zone', () => { assert.deepEqual( retiredPathRuleViolations('R71', [ - 'src/commands/replay/script-source-bundle.ts', + 'packages/replay-port/src/script-source-bundle.ts', 'src/cli/replay-test/reporting.ts', 'src/daemon/replay-script-source.ts', 'src/replay-next.ts', diff --git a/scripts/layering/retired-paths-policy.ts b/scripts/layering/retired-paths-policy.ts index 7bf0a43392..c4bb48bcb1 100644 --- a/scripts/layering/retired-paths-policy.ts +++ b/scripts/layering/retired-paths-policy.ts @@ -30,7 +30,7 @@ export const RETIRED_PATH_RULES: Readonly { + // A consumer package declaring both fixture owners, so only the fixture predicate can fire. + const consumer: WorkspacePackage = { + dir: 'packages/daemon-side', + name: '@agent-device/daemon-side', + exportTargets: new Map(), + workspaceDependencies: new Set(['@agent-device/device-selection', '@agent-device/replay-port']), + workspaceDependencyField: 'dependencies', + externalDependencies: new Map(), + }; + const rootDeps = new Set(consumer.workspaceDependencies); + // Pin the set independently: iterating the checker's own set would let deleting an entry + // silently delete its accept/deny coverage with it. + assert.deepEqual([...TEST_ONLY_FIXTURE_SUBPATHS].sort(), [ + '@agent-device/device-selection/inventory-gateways-fixtures', + '@agent-device/replay-port/replay-script-source-fixtures', + ]); + for (const specifier of TEST_ONLY_FIXTURE_SUBPATHS) { + const sites = (file: string) => specifierSites(file, `import { f } from '${specifier}';`); + for (const file of [ + 'src/daemon/__tests__/consumer.test.ts', + 'test/integration/consumer.ts', + 'packages/daemon-side/src/__tests__/consumer.test.ts', + 'packages/daemon-side/test/harness.ts', + ]) { + assert.deepEqual(checkRootSites(sites(file), ALL, rootDeps, true), [], file); + assert.deepEqual(checkPackageInternalSites(consumer, sites(file), ALL, true), [], file); + } + // Production consumers ride no seam: the same import from a root or package + // production file is an R11 violation in both scans. + for (const file of ['src/commands/inventory.ts', 'packages/daemon-side/src/inventory.ts']) { + const violations = file.startsWith('src/') + ? checkRootSites(sites(file), ALL, rootDeps, true) + : checkPackageInternalSites(consumer, sites(file), ALL, true); + assert.equal(violations.length, 1, file); + assert.match(violations[0]!.message, /test-only fixture subpath/); + } + } +}); + +test('the platform-android manifest names its test-only fixture subpaths', () => { + // The shared Android capture/walk fixtures (#3517). R13 denies production consumers + // (platform-package-policy.test.ts); their place in the manifest's export list is the + // R11-side half of the same inventory, pinned here with the full key list rather than + // growing the R11 real-tree test past the size tripwire. + const platformAndroidPackage = readWorkspacePackages(repoRoot).find( + (pkg) => pkg.name === '@agent-device/platform-android', + ); + assert.ok(platformAndroidPackage, 'platform-android package must exist'); + assert.deepEqual([...platformAndroidPackage.exportTargets.keys()].sort(), [ + '@agent-device/platform-android', + '@agent-device/platform-android/adb-host', + '@agent-device/platform-android/device-boot', + '@agent-device/platform-android/mechanics', + // Test-only entries: the shared Android capture/walk fixtures (#3517). + '@agent-device/platform-android/snapshot-capture-fixtures', + '@agent-device/platform-android/ui-hierarchy-fixtures', + ]); +}); diff --git a/src/__tests__/android-ui-hierarchy-scope.test.ts b/src/__tests__/android-ui-hierarchy-scope.test.ts index a74f7e0adf..a9f2047ac7 100644 --- a/src/__tests__/android-ui-hierarchy-scope.test.ts +++ b/src/__tests__/android-ui-hierarchy-scope.test.ts @@ -3,7 +3,7 @@ import assert from 'node:assert/strict'; import fs from 'node:fs'; import path from 'node:path'; import { buildSnapshotState } from '@agent-device/capture-kit/snapshot-state'; -import { parseUiHierarchy } from './test-utils/android-ui-hierarchy-fixtures.ts'; +import { parseUiHierarchy } from '@agent-device/platform-android/ui-hierarchy-fixtures'; import { AndroidSnapshotPresentationFailure, isAndroidSnapshotPresentationFailure, diff --git a/src/__tests__/android-ui-hierarchy.test.ts b/src/__tests__/android-ui-hierarchy.test.ts index 0fc7ef29ed..64cfdb7a89 100644 --- a/src/__tests__/android-ui-hierarchy.test.ts +++ b/src/__tests__/android-ui-hierarchy.test.ts @@ -9,7 +9,7 @@ import { androidUiNodes, parseUiHierarchyTree, } from '@agent-device/platform-android/mechanics'; -import { parseUiHierarchy } from './test-utils/android-ui-hierarchy-fixtures.ts'; +import { parseUiHierarchy } from '@agent-device/platform-android/ui-hierarchy-fixtures'; function publishUiHierarchy(xml: string) { const captured = parseUiHierarchy(xml, 800, {}); diff --git a/src/__tests__/cli-startup-import-closure.test.ts b/src/__tests__/cli-startup-import-closure.test.ts index 416d482fd2..598c5263b5 100644 --- a/src/__tests__/cli-startup-import-closure.test.ts +++ b/src/__tests__/cli-startup-import-closure.test.ts @@ -121,7 +121,7 @@ test('the CLI startup import closure never evaluates the Maestro engine', () => test('the replay command source closure keeps Maestro behind flow selection', () => { const replayEntry = path.join(srcRoot, 'commands/replay/index.ts'); const replayClosure = eagerClosureOf(replayEntry); - const sourceBundle = path.join(srcRoot, 'commands/replay/script-source-bundle.ts'); + const sourceBundle = path.join(srcRoot, '../packages/replay-port/src/script-source-bundle.ts'); const offenders: string[] = []; expect(replayClosure).toContain(sourceBundle); diff --git a/src/__tests__/cloud-connect-testmu.test.ts b/src/__tests__/cloud-connect-testmu.test.ts index d72aafac68..068ad8ae4a 100644 --- a/src/__tests__/cloud-connect-testmu.test.ts +++ b/src/__tests__/cloud-connect-testmu.test.ts @@ -12,7 +12,7 @@ import { verifyTestMuConnection } from '@agent-device/testmu/connection-verifica import testMuPlugin from '@agent-device/testmu'; import { createPluginHost } from '../plugins/host.ts'; import { resolveConnectProviderProfile } from '../cli/connection/connect-provider-adapters.ts'; -import { selectPlugin, pluginHome } from '../plugins/plugin.fixtures.ts'; +import { selectPlugin, pluginHome } from '@agent-device/test-kit/plugin.fixtures'; import { installedPlugins } from '../plugins/store.ts'; import manifest from '@agent-device/testmu/package.json' with { type: 'json' }; import type { PluginConnection } from '../plugins/connection.ts'; diff --git a/src/__tests__/daemon-composition/handlers/lease-provider-credentials.test.ts b/src/__tests__/daemon-composition/handlers/lease-provider-credentials.test.ts index 9c7a8e2b3d..c7ef858510 100644 --- a/src/__tests__/daemon-composition/handlers/lease-provider-credentials.test.ts +++ b/src/__tests__/daemon-composition/handlers/lease-provider-credentials.test.ts @@ -11,7 +11,7 @@ import { providerCredentialFingerprint, readDaemonProviderCredentials, } from '../../../provider-credential-fingerprint.ts'; -import { pluginHome, selectPluginManifest } from '../../../plugins/plugin.fixtures.ts'; +import { pluginHome, selectPluginManifest } from '@agent-device/test-kit/plugin.fixtures'; /** * Lease refusal compares the client's credential fingerprint against the digest the daemon diff --git a/src/__tests__/daemon-composition/handlers/snapshot-alert-android-occlusion.test.ts b/src/__tests__/daemon-composition/handlers/snapshot-alert-android-occlusion.test.ts index e9464c2087..84c8e6e23e 100644 --- a/src/__tests__/daemon-composition/handlers/snapshot-alert-android-occlusion.test.ts +++ b/src/__tests__/daemon-composition/handlers/snapshot-alert-android-occlusion.test.ts @@ -13,7 +13,7 @@ vi.mock('@agent-device/platform-android/mechanics', async (importOriginal) => { import { pressAndroid, snapshotAndroid } from '@agent-device/platform-android/mechanics'; import { ANDROID_EMULATOR } from '@agent-device/test-kit/device-fixtures'; import { createAndroidInteractor } from '../../../core/interactors/android.ts'; -import { makeAndroidSnapshotCapture } from '../../test-utils/android-snapshot-capture.ts'; +import { makeAndroidSnapshotCapture } from '@agent-device/platform-android/snapshot-capture-fixtures'; // R59 moved the alert legs onto the Android interactor, which is where the occlusion reading // they depend on lives: the legs capture through the same presentation pass `snapshot` publishes, diff --git a/src/__tests__/daemon-composition/http-server-limrun-uploaded-apps.test.ts b/src/__tests__/daemon-composition/http-server-limrun-uploaded-apps.test.ts index eec1b4e051..742de15b8a 100644 --- a/src/__tests__/daemon-composition/http-server-limrun-uploaded-apps.test.ts +++ b/src/__tests__/daemon-composition/http-server-limrun-uploaded-apps.test.ts @@ -4,7 +4,7 @@ import path from 'node:path'; import { test, vi } from 'vitest'; import { createLimrunRuntime } from '@agent-device/provider-limrun'; import { createProviderDeviceRuntimeRequestProviders } from '../../provider-device-runtime.ts'; -import { createTestDeviceInventoryGateways } from '../test-utils/device-inventory-gateways.ts'; +import { createTestDeviceInventoryGateways } from '@agent-device/device-selection/inventory-gateways-fixtures'; import { closeLoopbackServer, listenOnLoopback, diff --git a/src/__tests__/daemon-composition/interaction/interaction-ios-tap-outcome.test.ts b/src/__tests__/daemon-composition/interaction/interaction-ios-tap-outcome.test.ts index 8cc4d79428..2695a56e01 100644 --- a/src/__tests__/daemon-composition/interaction/interaction-ios-tap-outcome.test.ts +++ b/src/__tests__/daemon-composition/interaction/interaction-ios-tap-outcome.test.ts @@ -15,7 +15,7 @@ import { import { makeSessionStore } from '../../../daemon/__tests__/support/store-factory.ts'; import { SessionScriptWriter } from '../../../daemon/session-script-writer.ts'; import { runReplayForTest } from '../../../daemon/__tests__/replay-runtime/replay-command-fixture.ts'; -import { replayScriptSourceBundleFor } from '../../test-utils/replay-script-source.ts'; +import { replayScriptSourceBundleFor } from '@agent-device/replay-port/replay-script-source-fixtures'; import { imageViewerNodes, profileNodes, diff --git a/src/__tests__/daemon-composition/is-runtime.test.ts b/src/__tests__/daemon-composition/is-runtime.test.ts index c413e78ce9..a319bb5bc6 100644 --- a/src/__tests__/daemon-composition/is-runtime.test.ts +++ b/src/__tests__/daemon-composition/is-runtime.test.ts @@ -9,7 +9,7 @@ import { makeIosSession, } from '../../daemon/__tests__/support/session-factories.ts'; import { makeSessionStore } from '../../daemon/__tests__/support/store-factory.ts'; -import { withTestDeviceInventory } from '../test-utils/device-inventory-gateways.ts'; +import { withTestDeviceInventory } from '@agent-device/device-selection/inventory-gateways-fixtures'; import { makeSnapshotState } from '@agent-device/selectors/snapshot-geometry-fixtures'; import type { PostGestureOutcome } from '@agent-device/kernel/snapshot'; import type { DaemonRequest } from '../../daemon/daemon-request.ts'; diff --git a/src/__tests__/daemon-composition/request-dispatch-disclosure.test.ts b/src/__tests__/daemon-composition/request-dispatch-disclosure.test.ts index 0099365042..47e143a0a9 100644 --- a/src/__tests__/daemon-composition/request-dispatch-disclosure.test.ts +++ b/src/__tests__/daemon-composition/request-dispatch-disclosure.test.ts @@ -24,7 +24,7 @@ import { DISPATCH_DISCLOSURE_TABLE_PATH, dispatchDisclosureRowsOwnedBy, } from '@agent-device/contracts/dispatch-disclosure-fixtures'; -import { createTestDeviceInventoryGateways } from '../test-utils/device-inventory-gateways.ts'; +import { createTestDeviceInventoryGateways } from '@agent-device/device-selection/inventory-gateways-fixtures'; import { makeAndroidSession, makeIosAppSession, diff --git a/src/__tests__/daemon-composition/request-platform-providers.test.ts b/src/__tests__/daemon-composition/request-platform-providers.test.ts index 7e1e44888c..72043c1ce2 100644 --- a/src/__tests__/daemon-composition/request-platform-providers.test.ts +++ b/src/__tests__/daemon-composition/request-platform-providers.test.ts @@ -12,7 +12,7 @@ import { makeMacOsSession, makeSession, } from '../../daemon/__tests__/support/session-factories.ts'; -import { withTestDeviceInventoryProvider as withTargetDeviceResolutionScope } from '../test-utils/device-inventory-gateways.ts'; +import { withTestDeviceInventoryProvider as withTargetDeviceResolutionScope } from '@agent-device/device-selection/inventory-gateways-fixtures'; import { createLocalAppleToolProvider, runXcrun } from '@agent-device/platform-apple/tool-provider'; import { buildSimctlArgsForDevice } from '@agent-device/platform-apple/simctl'; import type { AndroidAdbExecutor } from '@agent-device/platform-android/mechanics'; diff --git a/src/__tests__/daemon-composition/request-router-android-modal.test.ts b/src/__tests__/daemon-composition/request-router-android-modal.test.ts index 6c9b67a872..3acd1fa2b6 100644 --- a/src/__tests__/daemon-composition/request-router-android-modal.test.ts +++ b/src/__tests__/daemon-composition/request-router-android-modal.test.ts @@ -1,4 +1,4 @@ -import { createTestDeviceInventoryGateways } from '../test-utils/device-inventory-gateways.ts'; +import { createTestDeviceInventoryGateways } from '@agent-device/device-selection/inventory-gateways-fixtures'; import { test, expect, vi } from 'vitest'; import path from 'node:path'; diff --git a/src/__tests__/daemon-composition/request-router-android-perf.test.ts b/src/__tests__/daemon-composition/request-router-android-perf.test.ts index c9a159791e..30a52c1dc1 100644 --- a/src/__tests__/daemon-composition/request-router-android-perf.test.ts +++ b/src/__tests__/daemon-composition/request-router-android-perf.test.ts @@ -1,4 +1,4 @@ -import { createTestDeviceInventoryGateways } from '../test-utils/device-inventory-gateways.ts'; +import { createTestDeviceInventoryGateways } from '@agent-device/device-selection/inventory-gateways-fixtures'; import { expect, test } from 'vitest'; import { createRequestHandler } from '../../daemon/__tests__/test-device-runtime-gateway.ts'; import { LeaseRegistry } from '../../daemon/lease-registry.ts'; diff --git a/src/__tests__/daemon-composition/request-router-android-snapshot-helper.test.ts b/src/__tests__/daemon-composition/request-router-android-snapshot-helper.test.ts index 9aa08ef42d..7c6cc38fb4 100644 --- a/src/__tests__/daemon-composition/request-router-android-snapshot-helper.test.ts +++ b/src/__tests__/daemon-composition/request-router-android-snapshot-helper.test.ts @@ -1,4 +1,4 @@ -import { createTestDeviceInventoryGateways } from '../test-utils/device-inventory-gateways.ts'; +import { createTestDeviceInventoryGateways } from '@agent-device/device-selection/inventory-gateways-fixtures'; import { beforeEach, expect, test } from 'vitest'; import { createRequestHandler } from '../../daemon/__tests__/test-device-runtime-gateway.ts'; import { LeaseRegistry } from '../../daemon/lease-registry.ts'; diff --git a/src/__tests__/daemon-composition/request-router-fill-text-stdin.test.ts b/src/__tests__/daemon-composition/request-router-fill-text-stdin.test.ts index 297075a27f..629b4e880f 100644 --- a/src/__tests__/daemon-composition/request-router-fill-text-stdin.test.ts +++ b/src/__tests__/daemon-composition/request-router-fill-text-stdin.test.ts @@ -7,7 +7,7 @@ import { expect, test } from 'vitest'; import fs from 'node:fs'; import path from 'node:path'; -import { createTestDeviceInventoryGateways } from '../test-utils/device-inventory-gateways.ts'; +import { createTestDeviceInventoryGateways } from '@agent-device/device-selection/inventory-gateways-fixtures'; import type { WebProvider } from '@agent-device/platform-web'; import type { DaemonRequest } from '../../daemon/daemon-request.ts'; import type { SessionState } from '../../daemon/session-state.ts'; @@ -21,7 +21,7 @@ import { import { WEB_DESKTOP_DEVICE } from '@agent-device/test-kit/device-fixtures'; import { mkdtempForTestSync } from '@agent-device/test-kit/tmp-dir'; import { testCreateCommandSurface } from './command-surface-fixture.ts'; -import { replayScriptSourceBundleFor } from '../test-utils/replay-script-source.ts'; +import { replayScriptSourceBundleFor } from '@agent-device/replay-port/replay-script-source-fixtures'; import { createPlatformRuntimeGateway, createRequestPlatformProviders, diff --git a/src/__tests__/daemon-composition/request-router-lock-policy.test.ts b/src/__tests__/daemon-composition/request-router-lock-policy.test.ts index 9c658461ce..1c67eb48fb 100644 --- a/src/__tests__/daemon-composition/request-router-lock-policy.test.ts +++ b/src/__tests__/daemon-composition/request-router-lock-policy.test.ts @@ -1,7 +1,7 @@ import { createTestDeviceInventoryGateways, createTestDeviceInventoryGatewaysFromProvider, -} from '../test-utils/device-inventory-gateways.ts'; +} from '@agent-device/device-selection/inventory-gateways-fixtures'; import { test, expect, vi, beforeEach } from 'vitest'; import { legacyDispatchCapture } from '../../daemon/__tests__/legacy-snapshot-capture-fixture.ts'; import path from 'node:path'; diff --git a/src/__tests__/daemon-composition/request-router-macos-app-lease.test.ts b/src/__tests__/daemon-composition/request-router-macos-app-lease.test.ts index 4875c8f609..02604ce4ff 100644 --- a/src/__tests__/daemon-composition/request-router-macos-app-lease.test.ts +++ b/src/__tests__/daemon-composition/request-router-macos-app-lease.test.ts @@ -1,6 +1,6 @@ import { afterEach, beforeEach, expect, test, vi } from 'vitest'; import path from 'node:path'; -import { createTestDeviceInventoryGateways } from '../test-utils/device-inventory-gateways.ts'; +import { createTestDeviceInventoryGateways } from '@agent-device/device-selection/inventory-gateways-fixtures'; import { MACOS_DEVICE } from '@agent-device/test-kit/device-fixtures'; import { makeSessionStore } from '../../daemon/__tests__/support/store-factory.ts'; import { makeMacOsSession } from '../../daemon/__tests__/support/session-factories.ts'; diff --git a/src/__tests__/daemon-composition/request-router-provider-credentials.test.ts b/src/__tests__/daemon-composition/request-router-provider-credentials.test.ts index 64df4cf00a..fe5f0d00af 100644 --- a/src/__tests__/daemon-composition/request-router-provider-credentials.test.ts +++ b/src/__tests__/daemon-composition/request-router-provider-credentials.test.ts @@ -1,6 +1,6 @@ import path from 'node:path'; import { expect, test } from 'vitest'; -import { createTestDeviceInventoryGateways } from '../test-utils/device-inventory-gateways.ts'; +import { createTestDeviceInventoryGateways } from '@agent-device/device-selection/inventory-gateways-fixtures'; import { makeSessionStore } from '../../daemon/__tests__/support/store-factory.ts'; import { mkdtempForTestSync } from '@agent-device/test-kit/tmp-dir'; import { diff --git a/src/__tests__/daemon-composition/request-router-record-runtime-lock.test.ts b/src/__tests__/daemon-composition/request-router-record-runtime-lock.test.ts index c9bd99c4d7..96433041cd 100644 --- a/src/__tests__/daemon-composition/request-router-record-runtime-lock.test.ts +++ b/src/__tests__/daemon-composition/request-router-record-runtime-lock.test.ts @@ -10,7 +10,7 @@ import type { PlatformRuntimeOperations } from '@agent-device/contracts/platform import type { ScreenRecordingLiveHandle } from '@agent-device/contracts/screen-recording-runtime'; import { createDurableResourceEnvelope } from '@agent-device/capture-kit'; import type { DeviceInfo } from '@agent-device/kernel/device'; -import { createTestDeviceInventoryGateways } from '../test-utils/device-inventory-gateways.ts'; +import { createTestDeviceInventoryGateways } from '@agent-device/device-selection/inventory-gateways-fixtures'; import { unavailableDeploymentSnapshotAndShutdownOperationFacts } from '@agent-device/test-kit/runtime-operation-facts'; import { makeSessionStore } from '../../daemon/__tests__/support/store-factory.ts'; import { mkdtempForTestSync } from '@agent-device/test-kit/tmp-dir'; diff --git a/src/__tests__/daemon-composition/request-router-recording-health.test.ts b/src/__tests__/daemon-composition/request-router-recording-health.test.ts index b14d4c3070..e3109cb15e 100644 --- a/src/__tests__/daemon-composition/request-router-recording-health.test.ts +++ b/src/__tests__/daemon-composition/request-router-recording-health.test.ts @@ -1,4 +1,4 @@ -import { createTestDeviceInventoryGateways } from '../test-utils/device-inventory-gateways.ts'; +import { createTestDeviceInventoryGateways } from '@agent-device/device-selection/inventory-gateways-fixtures'; import { legacyDispatchCapture } from '../../daemon/__tests__/legacy-snapshot-capture-fixture.ts'; import { test, expect, vi, beforeEach } from 'vitest'; diff --git a/src/__tests__/daemon-composition/request-router-replay-scope.test.ts b/src/__tests__/daemon-composition/request-router-replay-scope.test.ts index f41c2b8e6c..20ed0e0ee3 100644 --- a/src/__tests__/daemon-composition/request-router-replay-scope.test.ts +++ b/src/__tests__/daemon-composition/request-router-replay-scope.test.ts @@ -1,4 +1,4 @@ -import { createTestDeviceInventoryGateways } from '../test-utils/device-inventory-gateways.ts'; +import { createTestDeviceInventoryGateways } from '@agent-device/device-selection/inventory-gateways-fixtures'; import { legacyDispatchCapture } from '../../daemon/__tests__/legacy-snapshot-capture-fixture.ts'; import { beforeEach, expect, test, vi } from 'vitest'; import fs from 'node:fs'; diff --git a/src/__tests__/daemon-composition/request-router-replay-test-ime.test.ts b/src/__tests__/daemon-composition/request-router-replay-test-ime.test.ts index 9761d3e8e0..8f02895b41 100644 --- a/src/__tests__/daemon-composition/request-router-replay-test-ime.test.ts +++ b/src/__tests__/daemon-composition/request-router-replay-test-ime.test.ts @@ -5,7 +5,7 @@ * lifecycle binding, where the platform host performs the activation. Physical-device * defaults stay off: the activation seam fires only for an explicit opt-in. */ -import { createTestDeviceInventoryGateways } from '../test-utils/device-inventory-gateways.ts'; +import { createTestDeviceInventoryGateways } from '@agent-device/device-selection/inventory-gateways-fixtures'; import { beforeEach, expect, test, vi } from 'vitest'; import fs from 'node:fs'; @@ -38,7 +38,7 @@ vi.mock('@agent-device/platform-android/mechanics', async (importOriginal) => { import { ANDROID_DEVICE } from '@agent-device/test-kit/device-fixtures'; import { makeSessionStore } from '../../daemon/__tests__/support/store-factory.ts'; -import { replayScriptSourceBundleFor } from '../test-utils/replay-script-source.ts'; +import { replayScriptSourceBundleFor } from '@agent-device/replay-port/replay-script-source-fixtures'; import { LeaseRegistry } from '../../daemon/lease-registry.ts'; import { createRequestHandler, diff --git a/src/__tests__/daemon-composition/request-router-screenshot.test.ts b/src/__tests__/daemon-composition/request-router-screenshot.test.ts index aea0c395d2..636187e1ca 100644 --- a/src/__tests__/daemon-composition/request-router-screenshot.test.ts +++ b/src/__tests__/daemon-composition/request-router-screenshot.test.ts @@ -1,4 +1,4 @@ -import { createTestDeviceInventoryGateways } from '../test-utils/device-inventory-gateways.ts'; +import { createTestDeviceInventoryGateways } from '@agent-device/device-selection/inventory-gateways-fixtures'; import { legacyDispatchCapture } from '../../daemon/__tests__/legacy-snapshot-capture-fixture.ts'; import { test, expect, vi, beforeEach } from 'vitest'; import fs from 'node:fs'; diff --git a/src/__tests__/platform-runtime-apple-resources.test.ts b/src/__tests__/platform-runtime-apple-resources.test.ts index 9bce56fd11..94dbeae82e 100644 --- a/src/__tests__/platform-runtime-apple-resources.test.ts +++ b/src/__tests__/platform-runtime-apple-resources.test.ts @@ -1,7 +1,7 @@ import { expect, test, vi } from 'vitest'; import { AppError } from '@agent-device/kernel/errors'; import { appleSessionObservation } from '../platform-runtime-apple-resources.ts'; -import { withTestDeviceInventory } from './test-utils/device-inventory-gateways.ts'; +import { withTestDeviceInventory } from '@agent-device/device-selection/inventory-gateways-fixtures'; test('foreground observation uses request-local inventory without consulting a provider', async () => { const local = vi.fn(async () => []); diff --git a/src/__tests__/provider-device-runtimes.test.ts b/src/__tests__/provider-device-runtimes.test.ts index 9971c34546..75a87e39cf 100644 --- a/src/__tests__/provider-device-runtimes.test.ts +++ b/src/__tests__/provider-device-runtimes.test.ts @@ -4,7 +4,11 @@ import { createDefaultProviderRuntimeComposition, createDaemonProviderRuntimeComposition, } from '../provider-device-runtimes.ts'; -import { pluginHome, selectPlugin, registrationSource } from '../plugins/plugin.fixtures.ts'; +import { + pluginHome, + selectPlugin, + registrationSource, +} from '@agent-device/test-kit/plugin.fixtures'; test('daemon composition registers installed provider runtimes and their lazy platform modules', async () => { const { home, env } = pluginHome(); diff --git a/src/__tests__/test-utils/device-inventory-gateways.test.ts b/src/__tests__/test-utils/device-inventory-gateways.test.ts deleted file mode 100644 index e4cc480ba5..0000000000 --- a/src/__tests__/test-utils/device-inventory-gateways.test.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { ProviderDeviceInventorySource } from '@agent-device/contracts/device'; -import type { PlatformRequestScope } from '@agent-device/contracts/platform-runtime-host'; -import { expect, test } from 'vitest'; -import { createTestDeviceInventoryGateways } from './device-inventory-gateways.ts'; - -const scope: PlatformRequestScope = Object.freeze({ - signal: new AbortController().signal, - diagnostics: Object.freeze({ emit: () => {} }), - progress: Object.freeze({ report: () => {} }), -}); - -test('test inventory gateways preserve the production fail-closed provider contract', async () => { - const gateways = createTestDeviceInventoryGateways({ - provider: { discover: async () => undefined } as unknown as ProviderDeviceInventorySource, - }); - - await expect(gateways.providerFirst.discover({}, scope)).rejects.toMatchObject({ - details: { reason: 'provider_inventory_contract_violation' }, - }); -}); diff --git a/src/cli/commands/__tests__/plugins.test.ts b/src/cli/commands/__tests__/plugins.test.ts index 897f063c04..fa9fba17da 100644 --- a/src/cli/commands/__tests__/plugins.test.ts +++ b/src/cli/commands/__tests__/plugins.test.ts @@ -3,7 +3,7 @@ import fs from 'node:fs'; import path from 'node:path'; import { afterEach, test, vi } from 'vitest'; import { runCli } from '../../../cli.ts'; -import { pluginHome, selectPlugin } from '../../../plugins/plugin.fixtures.ts'; +import { pluginHome, selectPlugin } from '@agent-device/test-kit/plugin.fixtures'; import { parseRawArgs, usageForCommand } from '../../parser/args.ts'; import { beginStdoutCapture } from '../../../__tests__/test-utils/stream-capture.ts'; diff --git a/src/cli/connection/provider-policy.test.ts b/src/cli/connection/provider-policy.test.ts index b6ced9d546..42b1c5b3ca 100644 --- a/src/cli/connection/provider-policy.test.ts +++ b/src/cli/connection/provider-policy.test.ts @@ -3,7 +3,7 @@ import fs from 'node:fs'; import path from 'node:path'; import { test } from 'vitest'; import { connectionProviderCapabilities, isConnectProviderName } from './provider-policy.ts'; -import { pluginHome, selectPlugin } from '../../plugins/plugin.fixtures.ts'; +import { pluginHome, selectPlugin } from '@agent-device/test-kit/plugin.fixtures'; import { installedPlugins } from '../../plugins/store.ts'; test('provider policy projects provider identity into semantic capabilities', () => { diff --git a/src/cli/replay-test/__tests__/session-test-reporter-values.test.ts b/src/cli/replay-test/__tests__/session-test-reporter-values.test.ts index 3e6f4153c4..02b2d2ca17 100644 --- a/src/cli/replay-test/__tests__/session-test-reporter-values.test.ts +++ b/src/cli/replay-test/__tests__/session-test-reporter-values.test.ts @@ -28,7 +28,7 @@ import { handleSessionCommands } from '../../../daemon/handlers/__tests__/sessio import { SessionStore } from '../../../daemon/session-store.ts'; import type { DaemonRequest, DaemonResponse } from '../../../daemon/daemon-request.ts'; import { withRequestProgressSink } from '@agent-device/host-kit/request'; -import { withTestDeviceInventoryProvider as withDeviceInventoryProvider } from '../../../__tests__/test-utils/device-inventory-gateways.ts'; +import { withTestDeviceInventoryProvider as withDeviceInventoryProvider } from '@agent-device/device-selection/inventory-gateways-fixtures'; import type { DeviceInfo } from '@agent-device/kernel/device'; import { getReplayTestReporterExitCode, diff --git a/src/commands/replay/index.ts b/src/commands/replay/index.ts index 9afae42b3d..28e13047f8 100644 --- a/src/commands/replay/index.ts +++ b/src/commands/replay/index.ts @@ -32,8 +32,8 @@ import { parseReplayCliEnvEntries, readReplayCliEnvEntries, } from '@agent-device/ad-script'; -import { loadReplayScriptSourceBundle } from './script-source-bundle.ts'; -import { discoverReplaySourcePaths } from './source-discovery.ts'; +import { loadReplayScriptSourceBundle } from '@agent-device/replay-port/script-source-bundle'; +import { discoverReplaySourcePaths } from '@agent-device/replay-port/source-discovery'; const REPLAY_COMMAND_NAME = 'replay'; const TEST_COMMAND_NAME = 'test'; diff --git a/src/commands/schema/cli-config.test.ts b/src/commands/schema/cli-config.test.ts index c5b0e245f7..06f7cd0492 100644 --- a/src/commands/schema/cli-config.test.ts +++ b/src/commands/schema/cli-config.test.ts @@ -2,7 +2,7 @@ import assert from 'node:assert/strict'; import fs from 'node:fs'; import path from 'node:path'; import { test } from 'vitest'; -import { pluginHome } from '../../plugins/plugin.fixtures.ts'; +import { pluginHome } from '@agent-device/test-kit/plugin.fixtures'; import { resolveConfigBackedFlagDefaults } from './cli-config.ts'; test('AGENT_DEVICE_HOME relocates user defaults; project and explicit config cannot select plugins', () => { diff --git a/src/core/__tests__/snapshot-chrome-android-statusbar.test.ts b/src/core/__tests__/snapshot-chrome-android-statusbar.test.ts index 594a50c060..3339bfb367 100644 --- a/src/core/__tests__/snapshot-chrome-android-statusbar.test.ts +++ b/src/core/__tests__/snapshot-chrome-android-statusbar.test.ts @@ -15,7 +15,7 @@ import { ANDROID_QS_SHADE_CAPTURE_RAW_NODES, walkInteractiveOnlyAndroidFixture, walkNonRawAndroidFixture, -} from '../../__tests__/test-utils/android-ui-hierarchy-fixtures.ts'; +} from '@agent-device/platform-android/ui-hierarchy-fixtures'; import { isAndroidSystemChromeWindowResourceId } from '@agent-device/contracts/android-system-chrome'; /** diff --git a/src/core/interactors/android.test.ts b/src/core/interactors/android.test.ts index fa58f526a8..79a6a0a4d8 100644 --- a/src/core/interactors/android.test.ts +++ b/src/core/interactors/android.test.ts @@ -10,7 +10,7 @@ import { scrollAndroid, snapshotAndroid, } from '@agent-device/platform-android/mechanics'; -import { makeAndroidSnapshotCapture } from '../../__tests__/test-utils/android-snapshot-capture.ts'; +import { makeAndroidSnapshotCapture } from '@agent-device/platform-android/snapshot-capture-fixtures'; vi.mock('@agent-device/platform-android/mechanics', async (importOriginal) => { const actual = await importOriginal(); diff --git a/src/daemon-composition.ts b/src/daemon-composition.ts index 8ce83f5649..41f0c1a13a 100644 --- a/src/daemon-composition.ts +++ b/src/daemon-composition.ts @@ -118,7 +118,7 @@ export const createDaemonRuntimeDeps: ComposeDaemonRuntimeDeps = async ( ownedProcessRecords, }, }); - const deviceInventoryGateways = createPlatformDeviceInventoryGateways( + const deviceInventoryGateways = await createPlatformDeviceInventoryGateways( providerRuntimeProviders.deviceInventorySource, ); // Root composition of the platform-services port and the owner-scoped claim-recovery gateway. diff --git a/src/daemon/__tests__/android-system-dialog-occlusion.test.ts b/src/daemon/__tests__/android-system-dialog-occlusion.test.ts index 35970cbd9e..58f2005c0b 100644 --- a/src/daemon/__tests__/android-system-dialog-occlusion.test.ts +++ b/src/daemon/__tests__/android-system-dialog-occlusion.test.ts @@ -14,7 +14,7 @@ import { recoverAndroidBlockingSystemDialog as recoverOwnedAndroidBlockingSystem import { androidObservation } from '../../platform-runtime.ts'; import { makeAndroidSession } from './support/session-factories.ts'; import { makeTestScreenRecordingResource } from './support/screen-recording-live-handle.ts'; -import { makeAndroidSnapshotCapture } from '../../__tests__/test-utils/android-snapshot-capture.ts'; +import { makeAndroidSnapshotCapture } from '@agent-device/platform-android/snapshot-capture-fixtures'; const recoverAndroidBlockingSystemDialog = ( params: Omit[0], 'observation'>, diff --git a/src/daemon/__tests__/android-system-dialog-ref-frame.test.ts b/src/daemon/__tests__/android-system-dialog-ref-frame.test.ts index e87668bee2..6e787f6b7b 100644 --- a/src/daemon/__tests__/android-system-dialog-ref-frame.test.ts +++ b/src/daemon/__tests__/android-system-dialog-ref-frame.test.ts @@ -12,7 +12,7 @@ import { recoverAndroidBlockingSystemDialog as recoverOwnedAndroidBlockingSystem import { androidObservation } from '../../platform-runtime.ts'; import { makeAndroidSession } from './support/session-factories.ts'; import { makeTestScreenRecordingResource } from './support/screen-recording-live-handle.ts'; -import { makeAndroidSnapshotCapture } from '../../__tests__/test-utils/android-snapshot-capture.ts'; +import { makeAndroidSnapshotCapture } from '@agent-device/platform-android/snapshot-capture-fixtures'; import { refFrameState } from '../ref-frame.ts'; const recoverAndroidBlockingSystemDialog = ( diff --git a/src/daemon/__tests__/back-runtime.test.ts b/src/daemon/__tests__/back-runtime.test.ts index 63697502b9..5dc2420152 100644 --- a/src/daemon/__tests__/back-runtime.test.ts +++ b/src/daemon/__tests__/back-runtime.test.ts @@ -16,7 +16,7 @@ import { import { deviceShape } from '@agent-device/kernel/device'; import { makeSession } from './support/session-factories.ts'; import { makeSessionStore } from './support/store-factory.ts'; -import { createTestDeviceInventoryGateways } from '../../__tests__/test-utils/device-inventory-gateways.ts'; +import { createTestDeviceInventoryGateways } from '@agent-device/device-selection/inventory-gateways-fixtures'; import { LeaseRegistry } from '../lease-registry.ts'; import { activateCompleteRefFrame, refFrameState } from '../ref-frame.ts'; import type { BindDeviceRuntime, InspectDeviceRuntimeFacts } from '../request-runtime-binding.ts'; diff --git a/src/daemon/__tests__/daemon-policy.test.ts b/src/daemon/__tests__/daemon-policy.test.ts index 63b8ea9262..a3f85fb172 100644 --- a/src/daemon/__tests__/daemon-policy.test.ts +++ b/src/daemon/__tests__/daemon-policy.test.ts @@ -8,7 +8,7 @@ vi.mock('../device/device-ready.ts', () => ({ ensureDeviceReady: vi.fn(async () import type { DeviceInfo } from '@agent-device/kernel/device'; import { ANDROID_EMULATOR, IOS_SIMULATOR } from '@agent-device/test-kit/device-fixtures'; -import { createTestDeviceInventoryGateways } from '../../__tests__/test-utils/device-inventory-gateways.ts'; +import { createTestDeviceInventoryGateways } from '@agent-device/device-selection/inventory-gateways-fixtures'; import { makeIosSession } from './support/session-factories.ts'; import { makeSessionStore } from './support/store-factory.ts'; import { mkdtempForTestSync } from '@agent-device/test-kit/tmp-dir'; diff --git a/src/daemon/__tests__/focus-runtime.test.ts b/src/daemon/__tests__/focus-runtime.test.ts index e6e93b94e9..8a97b29908 100644 --- a/src/daemon/__tests__/focus-runtime.test.ts +++ b/src/daemon/__tests__/focus-runtime.test.ts @@ -16,7 +16,7 @@ import { import { deviceShape } from '@agent-device/kernel/device'; import { makeSession } from './support/session-factories.ts'; import { makeSessionStore } from './support/store-factory.ts'; -import { createTestDeviceInventoryGateways } from '../../__tests__/test-utils/device-inventory-gateways.ts'; +import { createTestDeviceInventoryGateways } from '@agent-device/device-selection/inventory-gateways-fixtures'; import { LeaseRegistry } from '../lease-registry.ts'; import { activateCompleteRefFrame, refFrameState } from '../ref-frame.ts'; import type { BindDeviceRuntime, InspectDeviceRuntimeFacts } from '../request-runtime-binding.ts'; diff --git a/src/daemon/__tests__/human-control-router-fixture.ts b/src/daemon/__tests__/human-control-router-fixture.ts index 6d986b41e4..095fa4d8fb 100644 --- a/src/daemon/__tests__/human-control-router-fixture.ts +++ b/src/daemon/__tests__/human-control-router-fixture.ts @@ -1,4 +1,4 @@ -import { createTestDeviceInventoryGateways } from '../../__tests__/test-utils/device-inventory-gateways.ts'; +import { createTestDeviceInventoryGateways } from '@agent-device/device-selection/inventory-gateways-fixtures'; import { daemonPlatformServicesFixture, daemonClaimRecoveryGatewayFixture, diff --git a/src/daemon/__tests__/orientation-runtime.test.ts b/src/daemon/__tests__/orientation-runtime.test.ts index bc97a0abcd..d0a4032a73 100644 --- a/src/daemon/__tests__/orientation-runtime.test.ts +++ b/src/daemon/__tests__/orientation-runtime.test.ts @@ -39,7 +39,7 @@ import { import { deviceShape } from '@agent-device/kernel/device'; import { makeSession } from './support/session-factories.ts'; import { makeSessionStore } from './support/store-factory.ts'; -import { createTestDeviceInventoryGateways } from '../../__tests__/test-utils/device-inventory-gateways.ts'; +import { createTestDeviceInventoryGateways } from '@agent-device/device-selection/inventory-gateways-fixtures'; import { LeaseRegistry } from '../lease-registry.ts'; import { activateCompleteRefFrame, refFrameState } from '../ref-frame.ts'; import type { BindDeviceRuntime, InspectDeviceRuntimeFacts } from '../request-runtime-binding.ts'; diff --git a/src/daemon/__tests__/replay-device-selection.test.ts b/src/daemon/__tests__/replay-device-selection.test.ts index 4811e3995d..6cace56ba2 100644 --- a/src/daemon/__tests__/replay-device-selection.test.ts +++ b/src/daemon/__tests__/replay-device-selection.test.ts @@ -12,7 +12,7 @@ import { mkdtempForTestSync } from '@agent-device/test-kit/tmp-dir'; import { maestroScriptSourceBundleFor, replayScriptSourceBundleFor, -} from '../../__tests__/test-utils/replay-script-source.ts'; +} from '@agent-device/replay-port/replay-script-source-fixtures'; test('replay leaves deep-link opens to normal device resolution', async () => { const root = mkdtempForTestSync('agent-device-replay-device-selection-'); diff --git a/src/daemon/__tests__/replay-divergence/session-replay-divergence-android-occlusion.test.ts b/src/daemon/__tests__/replay-divergence/session-replay-divergence-android-occlusion.test.ts index 3696abd78e..3a68f14d51 100644 --- a/src/daemon/__tests__/replay-divergence/session-replay-divergence-android-occlusion.test.ts +++ b/src/daemon/__tests__/replay-divergence/session-replay-divergence-android-occlusion.test.ts @@ -5,7 +5,7 @@ import { makeAndroidSession } from '../support/session-factories.ts'; import { ANDROID_QS_SHADE_CAPTURE_RAW_NODES, walkNonRawAndroidFixture, -} from '../../../__tests__/test-utils/android-ui-hierarchy-fixtures.ts'; +} from '@agent-device/platform-android/ui-hierarchy-fixtures'; import { mkdtempForTestSync } from '@agent-device/test-kit/tmp-dir'; import { SessionStore } from '../../session-store.ts'; import { replayCoordinatorForTest, replaySessionForTest } from './replay-session-fixture.ts'; diff --git a/src/daemon/__tests__/replay-divergence/session-replay-divergence-chrome-filter.test.ts b/src/daemon/__tests__/replay-divergence/session-replay-divergence-chrome-filter.test.ts index 86e3c076f3..9182a31436 100644 --- a/src/daemon/__tests__/replay-divergence/session-replay-divergence-chrome-filter.test.ts +++ b/src/daemon/__tests__/replay-divergence/session-replay-divergence-chrome-filter.test.ts @@ -5,7 +5,7 @@ import { makeAndroidSession, makeIosSession } from '../support/session-factories import { ANDROID_IME_CAPTURE_RAW_NODES, walkNonRawAndroidFixture, -} from '../../../__tests__/test-utils/android-ui-hierarchy-fixtures.ts'; +} from '@agent-device/platform-android/ui-hierarchy-fixtures'; import { SessionStore } from '../../session-store.ts'; import { buildReplayFailureDivergence } from '@agent-device/replay-port/session-replay-divergence'; import { replayDivergenceForTest } from './replay-session-fixture.ts'; diff --git a/src/daemon/__tests__/replay-divergence/session-replay-divergence-overlay.test.ts b/src/daemon/__tests__/replay-divergence/session-replay-divergence-overlay.test.ts index 46bc5d297d..f4d6cc3876 100644 --- a/src/daemon/__tests__/replay-divergence/session-replay-divergence-overlay.test.ts +++ b/src/daemon/__tests__/replay-divergence/session-replay-divergence-overlay.test.ts @@ -5,7 +5,7 @@ import { makeAndroidSession } from '../support/session-factories.ts'; import { ANDROID_IME_CAPTURE_RAW_NODES, walkNonRawAndroidFixture, -} from '../../../__tests__/test-utils/android-ui-hierarchy-fixtures.ts'; +} from '@agent-device/platform-android/ui-hierarchy-fixtures'; import { SessionStore } from '../../session-store.ts'; import { buildReplayFailureDivergence } from '@agent-device/replay-port/session-replay-divergence'; import { replayDivergenceForTest } from './replay-session-fixture.ts'; diff --git a/src/daemon/__tests__/replay-maestro/session-replay-maestro-error-projection.test.ts b/src/daemon/__tests__/replay-maestro/session-replay-maestro-error-projection.test.ts index d8cd6f3708..96e659ae46 100644 --- a/src/daemon/__tests__/replay-maestro/session-replay-maestro-error-projection.test.ts +++ b/src/daemon/__tests__/replay-maestro/session-replay-maestro-error-projection.test.ts @@ -6,7 +6,7 @@ import { runTypedMaestroReplay } from '@agent-device/replay-port/session-replay- import { SessionStore } from '../../session-store.ts'; import { replayCommandForTest } from '../replay-runtime/replay-command-fixture.ts'; import { makeIosSession } from '../support/session-factories.ts'; -import { maestroScriptSourceBundleFor } from '../../../__tests__/test-utils/replay-script-source.ts'; +import { maestroScriptSourceBundleFor } from '@agent-device/replay-port/replay-script-source-fixtures'; import { mkdtempForTestSync } from '@agent-device/test-kit/tmp-dir'; import type { DaemonRequest } from '../../daemon-request.ts'; diff --git a/src/daemon/__tests__/replay-maestro/session-replay-maestro-failure.test.ts b/src/daemon/__tests__/replay-maestro/session-replay-maestro-failure.test.ts index 7d067f2836..a35138f136 100644 --- a/src/daemon/__tests__/replay-maestro/session-replay-maestro-failure.test.ts +++ b/src/daemon/__tests__/replay-maestro/session-replay-maestro-failure.test.ts @@ -1,4 +1,4 @@ -import { noMaestroIncludeSources } from '../../../__tests__/test-utils/replay-script-source.ts'; +import { noMaestroIncludeSources } from '@agent-device/replay-port/replay-script-source-fixtures'; import { beforeEach, expect, test, vi } from 'vitest'; import { mkdtempForTestSync } from '@agent-device/test-kit/tmp-dir'; diff --git a/src/daemon/__tests__/replay-maestro/session-replay-maestro-remote-evalscript.test.ts b/src/daemon/__tests__/replay-maestro/session-replay-maestro-remote-evalscript.test.ts index aeaeab97af..cb6c0a952a 100644 --- a/src/daemon/__tests__/replay-maestro/session-replay-maestro-remote-evalscript.test.ts +++ b/src/daemon/__tests__/replay-maestro/session-replay-maestro-remote-evalscript.test.ts @@ -7,7 +7,7 @@ import { LeaseRegistry } from '../../lease-registry.ts'; import { platformResourceCleanupFixture } from '../support/platform-resource-cleanup-fixture.ts'; import { SessionStore } from '../../session-store.ts'; import { makeIosSession } from '../support/session-factories.ts'; -import { maestroScriptSourceBundleFor } from '../../../__tests__/test-utils/replay-script-source.ts'; +import { maestroScriptSourceBundleFor } from '@agent-device/replay-port/replay-script-source-fixtures'; import { mkdtempForTestSync } from '@agent-device/test-kit/tmp-dir'; import type { DaemonRequest } from '../../daemon-request.ts'; import { diff --git a/src/daemon/__tests__/replay-maestro/session-replay-maestro-session-address.test.ts b/src/daemon/__tests__/replay-maestro/session-replay-maestro-session-address.test.ts index 0f7e309025..8880859e54 100644 --- a/src/daemon/__tests__/replay-maestro/session-replay-maestro-session-address.test.ts +++ b/src/daemon/__tests__/replay-maestro/session-replay-maestro-session-address.test.ts @@ -6,7 +6,7 @@ import { runTypedMaestroReplay } from '@agent-device/replay-port/session-replay- import { SessionStore } from '../../session-store.ts'; import { replayCommandForTest } from '../replay-runtime/replay-command-fixture.ts'; import { makeIosSession } from '../support/session-factories.ts'; -import { maestroScriptSourceBundleFor } from '../../../__tests__/test-utils/replay-script-source.ts'; +import { maestroScriptSourceBundleFor } from '@agent-device/replay-port/replay-script-source-fixtures'; import { mkdtempForTestSync } from '@agent-device/test-kit/tmp-dir'; import type { DaemonRequest } from '../../daemon-request.ts'; diff --git a/src/daemon/__tests__/replay-maestro/session-replay-runtime-maestro-dispatch.test.ts b/src/daemon/__tests__/replay-maestro/session-replay-runtime-maestro-dispatch.test.ts index c5022b6de6..3cc75220c7 100644 --- a/src/daemon/__tests__/replay-maestro/session-replay-runtime-maestro-dispatch.test.ts +++ b/src/daemon/__tests__/replay-maestro/session-replay-runtime-maestro-dispatch.test.ts @@ -6,7 +6,7 @@ import { test } from 'vitest'; import { mkdtempForTestSync } from '@agent-device/test-kit/tmp-dir'; import type { DaemonRequest, DaemonResponse } from '../../daemon-request.ts'; import { SessionStore } from '../../session-store.ts'; -import { replayScriptSourceBundleFor } from '../../../__tests__/test-utils/replay-script-source.ts'; +import { replayScriptSourceBundleFor } from '@agent-device/replay-port/replay-script-source-fixtures'; import { makeIosSession } from '../support/session-factories.ts'; const { runReplayFixture, runReplayForTest, assertNoUnresolvedInterpolation } = diff --git a/src/daemon/__tests__/replay-maestro/session-replay-runtime-maestro-shell-artifacts.test.ts b/src/daemon/__tests__/replay-maestro/session-replay-runtime-maestro-shell-artifacts.test.ts index 0f189ffbb4..35f904e81a 100644 --- a/src/daemon/__tests__/replay-maestro/session-replay-runtime-maestro-shell-artifacts.test.ts +++ b/src/daemon/__tests__/replay-maestro/session-replay-runtime-maestro-shell-artifacts.test.ts @@ -5,7 +5,7 @@ import path from 'node:path'; import { test } from 'vitest'; import { mkdtempForTestSync } from '@agent-device/test-kit/tmp-dir'; import { SessionStore } from '../../session-store.ts'; -import { replayScriptSourceBundleFor } from '../../../__tests__/test-utils/replay-script-source.ts'; +import { replayScriptSourceBundleFor } from '@agent-device/replay-port/replay-script-source-fixtures'; const { runReplayFixture, runReplayForTest } = maestroReplayFixture; diff --git a/src/daemon/__tests__/replay-maestro/session-replay-runtime-maestro.fixtures.ts b/src/daemon/__tests__/replay-maestro/session-replay-runtime-maestro.fixtures.ts index 4fc367d40a..c99e275462 100644 --- a/src/daemon/__tests__/replay-maestro/session-replay-runtime-maestro.fixtures.ts +++ b/src/daemon/__tests__/replay-maestro/session-replay-runtime-maestro.fixtures.ts @@ -51,7 +51,7 @@ import type { ReplayScriptSourceBundle } from '@agent-device/contracts/replay'; import { maestroScriptSourceBundleFor, replayScriptSourceBundleFor, -} from '../../../__tests__/test-utils/replay-script-source.ts'; +} from '@agent-device/replay-port/replay-script-source-fixtures'; import { captureSnapshotThroughLegacyDispatchFixture } from '../legacy-snapshot-capture-fixture.ts'; import { captureSnapshotWithInteractor } from '../../snapshot-interactor-capture.ts'; import { seedReplayFixtureSession } from '../replay-runtime/session-replay-runtime.fixtures.ts'; diff --git a/src/daemon/__tests__/replay-runtime/application.test.ts b/src/daemon/__tests__/replay-runtime/application.test.ts index 0f3c95cafb..33bd6be350 100644 --- a/src/daemon/__tests__/replay-runtime/application.test.ts +++ b/src/daemon/__tests__/replay-runtime/application.test.ts @@ -10,7 +10,7 @@ import { } from '@agent-device/host-kit/request'; import { mkdtempForTestSync } from '@agent-device/test-kit/tmp-dir'; import { makeIosSession } from '../support/session-factories.ts'; -import { withClientReplayScriptSources } from '../../../__tests__/test-utils/replay-script-source.ts'; +import { withClientReplayScriptSources } from '@agent-device/replay-port/replay-script-source-fixtures'; import type { DaemonRequest, DaemonResponse } from '../../daemon-request.ts'; import { SessionStore } from '../../session-store.ts'; import { diff --git a/src/daemon/__tests__/replay-runtime/session-replay-runtime-failure.test.ts b/src/daemon/__tests__/replay-runtime/session-replay-runtime-failure.test.ts index 2f3e154f81..08aa5b5960 100644 --- a/src/daemon/__tests__/replay-runtime/session-replay-runtime-failure.test.ts +++ b/src/daemon/__tests__/replay-runtime/session-replay-runtime-failure.test.ts @@ -16,7 +16,7 @@ import { SessionStore } from '../../session-store.ts'; import type { DaemonResponse } from '../../daemon-request.ts'; import { makeIosSession } from '../support/session-factories.ts'; import { formatReplayDivergenceReport } from '@agent-device/ad-replay/divergence'; -import { maestroScriptSourceBundleFor } from '../../../__tests__/test-utils/replay-script-source.ts'; +import { maestroScriptSourceBundleFor } from '@agent-device/replay-port/replay-script-source-fixtures'; import { captureSnapshotThroughLegacyDispatchFixture, legacyDispatchCapture, diff --git a/src/daemon/__tests__/replay-runtime/session-replay-runtime.fixtures.ts b/src/daemon/__tests__/replay-runtime/session-replay-runtime.fixtures.ts index 65ef66b68b..016cb7a884 100644 --- a/src/daemon/__tests__/replay-runtime/session-replay-runtime.fixtures.ts +++ b/src/daemon/__tests__/replay-runtime/session-replay-runtime.fixtures.ts @@ -1,7 +1,7 @@ import fs from 'node:fs'; import path from 'node:path'; import { makeAndroidSession, makeIosSession } from '../support/session-factories.ts'; -import { replayScriptSourceBundleFor } from '../../../__tests__/test-utils/replay-script-source.ts'; +import { replayScriptSourceBundleFor } from '@agent-device/replay-port/replay-script-source-fixtures'; import type { SessionStore } from '../../session-store.ts'; import type { DaemonRequest } from '../../daemon-request.ts'; diff --git a/src/daemon/__tests__/replay-runtime/session-replay-script-source.test.ts b/src/daemon/__tests__/replay-runtime/session-replay-script-source.test.ts index 186f7d6661..0521fb3dea 100644 --- a/src/daemon/__tests__/replay-runtime/session-replay-script-source.test.ts +++ b/src/daemon/__tests__/replay-runtime/session-replay-script-source.test.ts @@ -25,7 +25,7 @@ import { makeIosSession } from '../support/session-factories.ts'; import { maestroScriptSourceBundleFor, replayScriptSourceBundleFor, -} from '../../../__tests__/test-utils/replay-script-source.ts'; +} from '@agent-device/replay-port/replay-script-source-fixtures'; import { REPLAY_SCRIPT_SOURCE_REQUIRED_MESSAGE } from '@agent-device/replay-port/script-source'; import { captureSnapshotThroughLegacyDispatchFixture, diff --git a/src/daemon/__tests__/replay-suite/session-test-command-trust.test.ts b/src/daemon/__tests__/replay-suite/session-test-command-trust.test.ts index 88f31ab89c..54432d01cc 100644 --- a/src/daemon/__tests__/replay-suite/session-test-command-trust.test.ts +++ b/src/daemon/__tests__/replay-suite/session-test-command-trust.test.ts @@ -12,7 +12,7 @@ import { } from '../../handlers/session-replay-command.ts'; import { runReplayTestCommand } from '@agent-device/replay-port/test-command'; import type { ReplayCommand, ReplayTestCommand } from '@agent-device/replay-port/command-types'; -import { replayScriptSourceBundleFor } from '../../../__tests__/test-utils/replay-script-source.ts'; +import { replayScriptSourceBundleFor } from '@agent-device/replay-port/replay-script-source-fixtures'; import { mkdtempForTestSync } from '@agent-device/test-kit/tmp-dir'; import type { DaemonInvokeFn, DaemonRequest } from '../../daemon-request.ts'; diff --git a/src/daemon/__tests__/replay-suite/session-test-source-discovery.test.ts b/src/daemon/__tests__/replay-suite/session-test-source-discovery.test.ts index 7d847dec4b..8f95c8d4ee 100644 --- a/src/daemon/__tests__/replay-suite/session-test-source-discovery.test.ts +++ b/src/daemon/__tests__/replay-suite/session-test-source-discovery.test.ts @@ -7,7 +7,7 @@ import { mkdtempForTestSync } from '@agent-device/test-kit/tmp-dir'; import { maestroScriptSourceBundleFor, replayScriptSourceBundleFor, -} from '../../../__tests__/test-utils/replay-script-source.ts'; +} from '@agent-device/replay-port/replay-script-source-fixtures'; // #1478 P3b: the daemon adapter's inspection capability. #1802 moved expansion and file reading // to the caller, so what is left here is exactly format routing plus per-engine manifest diff --git a/src/daemon/__tests__/replay-suite/session-test-suite-command-video.test.ts b/src/daemon/__tests__/replay-suite/session-test-suite-command-video.test.ts index abe292545b..7b202e88c4 100644 --- a/src/daemon/__tests__/replay-suite/session-test-suite-command-video.test.ts +++ b/src/daemon/__tests__/replay-suite/session-test-suite-command-video.test.ts @@ -13,7 +13,7 @@ import { splitReplayCommandRequest, } from '@agent-device/replay-port/replay-dispatch-envelope'; import { mkdtempForTestSync } from '@agent-device/test-kit/tmp-dir'; -import { replayScriptSourceBundleFor } from '../../../__tests__/test-utils/replay-script-source.ts'; +import { replayScriptSourceBundleFor } from '@agent-device/replay-port/replay-script-source-fixtures'; import { unavailableBindDevice, unavailableBindExactDevice, diff --git a/src/daemon/__tests__/replay-suite/session-test-suite.test.ts b/src/daemon/__tests__/replay-suite/session-test-suite.test.ts index 05518921b3..763620a45f 100644 --- a/src/daemon/__tests__/replay-suite/session-test-suite.test.ts +++ b/src/daemon/__tests__/replay-suite/session-test-suite.test.ts @@ -32,7 +32,7 @@ import { registerRequestAbort, } from '@agent-device/host-kit/request'; -import { withTestDeviceInventoryProvider as withDeviceInventoryProvider } from '../../../__tests__/test-utils/device-inventory-gateways.ts'; +import { withTestDeviceInventoryProvider as withDeviceInventoryProvider } from '@agent-device/device-selection/inventory-gateways-fixtures'; import type { DeviceInfo } from '@agent-device/kernel/device'; import { makeAndroidSession, makeMacOsSession } from '../support/session-factories.ts'; import { mkdtempForTestSync } from '@agent-device/test-kit/tmp-dir'; diff --git a/src/daemon/__tests__/request-handler-catalog.test.ts b/src/daemon/__tests__/request-handler-catalog.test.ts index 9fce2657c6..e38d8a1c99 100644 --- a/src/daemon/__tests__/request-handler-catalog.test.ts +++ b/src/daemon/__tests__/request-handler-catalog.test.ts @@ -3,7 +3,7 @@ import assert from 'node:assert/strict'; import fs from 'node:fs'; import path from 'node:path'; import { test } from 'vitest'; -import { withTestDeviceInventoryProvider as withTargetDeviceResolutionScope } from '../../__tests__/test-utils/device-inventory-gateways.ts'; +import { withTestDeviceInventoryProvider as withTargetDeviceResolutionScope } from '@agent-device/device-selection/inventory-gateways-fixtures'; import { INTERNAL_COMMANDS, PUBLIC_COMMANDS } from '@agent-device/command-registry/catalog'; import { commandDescriptors } from '@agent-device/command-registry/registry'; import { isRequestCanceledError, type AppError } from '@agent-device/kernel/errors'; diff --git a/src/daemon/__tests__/request-router-apps-admission.test.ts b/src/daemon/__tests__/request-router-apps-admission.test.ts index 0b87c7c99b..f80c094e72 100644 --- a/src/daemon/__tests__/request-router-apps-admission.test.ts +++ b/src/daemon/__tests__/request-router-apps-admission.test.ts @@ -3,7 +3,7 @@ import { expect, test, vi } from 'vitest'; import path from 'node:path'; import type { DeviceRuntimeGateway } from '@agent-device/contracts/platform-runtime'; import type { PlatformRuntimeOperations } from '@agent-device/contracts/platform-runtime-operations'; -import { createTestDeviceInventoryGateways } from '../../__tests__/test-utils/device-inventory-gateways.ts'; +import { createTestDeviceInventoryGateways } from '@agent-device/device-selection/inventory-gateways-fixtures'; import { makeSessionStore } from './support/store-factory.ts'; import { LeaseRegistry } from '../lease-registry.ts'; import type { DaemonRequest } from '../daemon-request.ts'; diff --git a/src/daemon/__tests__/request-router-artifacts-web.test.ts b/src/daemon/__tests__/request-router-artifacts-web.test.ts index 4132fd662a..332d4e24e9 100644 --- a/src/daemon/__tests__/request-router-artifacts-web.test.ts +++ b/src/daemon/__tests__/request-router-artifacts-web.test.ts @@ -1,4 +1,4 @@ -import { createTestDeviceInventoryGateways } from '../../__tests__/test-utils/device-inventory-gateways.ts'; +import { createTestDeviceInventoryGateways } from '@agent-device/device-selection/inventory-gateways-fixtures'; import { test, expect } from 'vitest'; import fs from 'node:fs'; diff --git a/src/daemon/__tests__/request-router-cost.test.ts b/src/daemon/__tests__/request-router-cost.test.ts index 0037f5c7d1..e6acc3a174 100644 --- a/src/daemon/__tests__/request-router-cost.test.ts +++ b/src/daemon/__tests__/request-router-cost.test.ts @@ -1,4 +1,4 @@ -import { createTestDeviceInventoryGateways } from '../../__tests__/test-utils/device-inventory-gateways.ts'; +import { createTestDeviceInventoryGateways } from '@agent-device/device-selection/inventory-gateways-fixtures'; import { test, expect, vi, beforeEach } from 'vitest'; import path from 'node:path'; diff --git a/src/daemon/__tests__/request-router-custom-action-flags.test.ts b/src/daemon/__tests__/request-router-custom-action-flags.test.ts index 14771d8dd1..ecf17314ac 100644 --- a/src/daemon/__tests__/request-router-custom-action-flags.test.ts +++ b/src/daemon/__tests__/request-router-custom-action-flags.test.ts @@ -1,4 +1,4 @@ -import { createTestDeviceInventoryGateways } from '../../__tests__/test-utils/device-inventory-gateways.ts'; +import { createTestDeviceInventoryGateways } from '@agent-device/device-selection/inventory-gateways-fixtures'; /** * `--actions` reads custom actions through the private-AX snapshot path, which * a raw capture does not use. Answering the pair with a capture that diff --git a/src/daemon/__tests__/request-router-developer-dir.test.ts b/src/daemon/__tests__/request-router-developer-dir.test.ts index 73513b5f36..4fd02d8867 100644 --- a/src/daemon/__tests__/request-router-developer-dir.test.ts +++ b/src/daemon/__tests__/request-router-developer-dir.test.ts @@ -1,4 +1,4 @@ -import { createTestDeviceInventoryGateways } from '../../__tests__/test-utils/device-inventory-gateways.ts'; +import { createTestDeviceInventoryGateways } from '@agent-device/device-selection/inventory-gateways-fixtures'; import path from 'node:path'; import { expect, test } from 'vitest'; import { runCmd } from '@agent-device/host-kit/command'; diff --git a/src/daemon/__tests__/request-router-error-redaction.test.ts b/src/daemon/__tests__/request-router-error-redaction.test.ts index db1e92b841..e75cdcd043 100644 --- a/src/daemon/__tests__/request-router-error-redaction.test.ts +++ b/src/daemon/__tests__/request-router-error-redaction.test.ts @@ -18,7 +18,7 @@ vi.mock('../device/device-ready.ts', () => ({ ensureDeviceReady: vi.fn(async () import { AppError } from '@agent-device/kernel/errors'; import type { DeviceRuntimeGateway, RuntimeFacts } from '@agent-device/contracts/platform-runtime'; import type { PlatformRuntimeOperations } from '@agent-device/contracts/platform-runtime-operations'; -import { createTestDeviceInventoryGateways } from '../../__tests__/test-utils/device-inventory-gateways.ts'; +import { createTestDeviceInventoryGateways } from '@agent-device/device-selection/inventory-gateways-fixtures'; import { makeIosAppSession } from './support/session-factories.ts'; import { makeSessionStore } from './support/store-factory.ts'; import { mkdtempForTestSync } from '@agent-device/test-kit/tmp-dir'; diff --git a/src/daemon/__tests__/request-router-events.test.ts b/src/daemon/__tests__/request-router-events.test.ts index 2faa781ff0..82af3eab62 100644 --- a/src/daemon/__tests__/request-router-events.test.ts +++ b/src/daemon/__tests__/request-router-events.test.ts @@ -1,4 +1,4 @@ -import { createTestDeviceInventoryGateways } from '../../__tests__/test-utils/device-inventory-gateways.ts'; +import { createTestDeviceInventoryGateways } from '@agent-device/device-selection/inventory-gateways-fixtures'; import { test, expect } from 'vitest'; import fs from 'node:fs'; diff --git a/src/daemon/__tests__/request-router-idle-expired.test.ts b/src/daemon/__tests__/request-router-idle-expired.test.ts index 373c97e566..394336d6af 100644 --- a/src/daemon/__tests__/request-router-idle-expired.test.ts +++ b/src/daemon/__tests__/request-router-idle-expired.test.ts @@ -1,4 +1,4 @@ -import { createTestDeviceInventoryGateways } from '../../__tests__/test-utils/device-inventory-gateways.ts'; +import { createTestDeviceInventoryGateways } from '@agent-device/device-selection/inventory-gateways-fixtures'; /** * #2833: when a command finds no session because this daemon already expired it for idleness, the * answer stays `SESSION_NOT_FOUND` but carries the typed reason, the window it missed, and the device diff --git a/src/daemon/__tests__/request-router-open-claim.test.ts b/src/daemon/__tests__/request-router-open-claim.test.ts index 84147da293..65ddd3e71a 100644 --- a/src/daemon/__tests__/request-router-open-claim.test.ts +++ b/src/daemon/__tests__/request-router-open-claim.test.ts @@ -1,4 +1,4 @@ -import { createTestDeviceInventoryGateways } from '../../__tests__/test-utils/device-inventory-gateways.ts'; +import { createTestDeviceInventoryGateways } from '@agent-device/device-selection/inventory-gateways-fixtures'; import { test, expect, vi, beforeEach } from 'vitest'; import fs from 'node:fs'; diff --git a/src/daemon/__tests__/request-router-open.test.ts b/src/daemon/__tests__/request-router-open.test.ts index 79b0430fa6..d8f52d2688 100644 --- a/src/daemon/__tests__/request-router-open.test.ts +++ b/src/daemon/__tests__/request-router-open.test.ts @@ -1,4 +1,4 @@ -import { createTestDeviceInventoryGateways } from '../../__tests__/test-utils/device-inventory-gateways.ts'; +import { createTestDeviceInventoryGateways } from '@agent-device/device-selection/inventory-gateways-fixtures'; import { daemonPlatformServicesFixture, daemonClaimRecoveryGatewayFixture, @@ -10,7 +10,7 @@ import fs from 'node:fs'; import path from 'node:path'; import { getResolveTargetDeviceMock } from './request-router-dispatch-mocks.ts'; -import { replayScriptSourceBundleFor } from '../../__tests__/test-utils/replay-script-source.ts'; +import { replayScriptSourceBundleFor } from '@agent-device/replay-port/replay-script-source-fixtures'; import { fakeCreateCommandSurface } from './support/command-surface-fake.ts'; vi.mock('../device/device-ready.ts', () => ({ ensureDeviceReady: vi.fn(async () => {}) })); diff --git a/src/daemon/__tests__/request-router-record-flags.test.ts b/src/daemon/__tests__/request-router-record-flags.test.ts index c4b2ccea8c..b7502bae68 100644 --- a/src/daemon/__tests__/request-router-record-flags.test.ts +++ b/src/daemon/__tests__/request-router-record-flags.test.ts @@ -1,4 +1,4 @@ -import { createTestDeviceInventoryGateways } from '../../__tests__/test-utils/device-inventory-gateways.ts'; +import { createTestDeviceInventoryGateways } from '@agent-device/device-selection/inventory-gateways-fixtures'; /** * #1271 stage 2 (ADR 0012 amendment): `--record` and `--no-record` express * opposite recording intents for the same action, so both together is diff --git a/src/daemon/__tests__/request-router-repair-expired.test.ts b/src/daemon/__tests__/request-router-repair-expired.test.ts index f83d373fb8..98b6d7f943 100644 --- a/src/daemon/__tests__/request-router-repair-expired.test.ts +++ b/src/daemon/__tests__/request-router-repair-expired.test.ts @@ -1,4 +1,4 @@ -import { createTestDeviceInventoryGateways } from '../../__tests__/test-utils/device-inventory-gateways.ts'; +import { createTestDeviceInventoryGateways } from '@agent-device/device-selection/inventory-gateways-fixtures'; /** * ADR 0012 decision 6, R7 (C5a): when a repair session was reaped before it was * finalized, the request router rewrites the resulting `SESSION_NOT_FOUND` into diff --git a/src/daemon/__tests__/request-router-replay-env.test.ts b/src/daemon/__tests__/request-router-replay-env.test.ts index c658c386fb..7d9e59e65c 100644 --- a/src/daemon/__tests__/request-router-replay-env.test.ts +++ b/src/daemon/__tests__/request-router-replay-env.test.ts @@ -1,4 +1,4 @@ -import { createTestDeviceInventoryGateways } from '../../__tests__/test-utils/device-inventory-gateways.ts'; +import { createTestDeviceInventoryGateways } from '@agent-device/device-selection/inventory-gateways-fixtures'; import fs from 'node:fs'; import path from 'node:path'; import { expect, test } from 'vitest'; @@ -6,7 +6,7 @@ import { makeSessionStore } from './support/store-factory.ts'; import { LeaseRegistry } from '../lease-registry.ts'; import { createRequestHandler } from './test-device-runtime-gateway.ts'; import { mkdtempForTestSync } from '@agent-device/test-kit/tmp-dir'; -import { replayScriptSourceBundleFor } from '../../__tests__/test-utils/replay-script-source.ts'; +import { replayScriptSourceBundleFor } from '@agent-device/replay-port/replay-script-source-fixtures'; import { fakeCreateCommandSurface } from './support/command-surface-fake.ts'; function createHarness() { diff --git a/src/daemon/__tests__/request-router-response-level.test.ts b/src/daemon/__tests__/request-router-response-level.test.ts index 40ca7dc34b..344bad3d7b 100644 --- a/src/daemon/__tests__/request-router-response-level.test.ts +++ b/src/daemon/__tests__/request-router-response-level.test.ts @@ -1,4 +1,4 @@ -import { createTestDeviceInventoryGateways } from '../../__tests__/test-utils/device-inventory-gateways.ts'; +import { createTestDeviceInventoryGateways } from '@agent-device/device-selection/inventory-gateways-fixtures'; import { test, expect, vi, beforeEach } from 'vitest'; import path from 'node:path'; diff --git a/src/daemon/__tests__/request-router-session-address.test.ts b/src/daemon/__tests__/request-router-session-address.test.ts index f2b35b5a92..3e0dd2d3d7 100644 --- a/src/daemon/__tests__/request-router-session-address.test.ts +++ b/src/daemon/__tests__/request-router-session-address.test.ts @@ -1,4 +1,4 @@ -import { createTestDeviceInventoryGateways } from '../../__tests__/test-utils/device-inventory-gateways.ts'; +import { createTestDeviceInventoryGateways } from '@agent-device/device-selection/inventory-gateways-fixtures'; import { legacyDispatchCapture } from './legacy-snapshot-capture-fixture.ts'; import { test, expect, vi, beforeEach } from 'vitest'; diff --git a/src/daemon/__tests__/request-router-typed-error.test.ts b/src/daemon/__tests__/request-router-typed-error.test.ts index 774d8862ea..3f364c27a0 100644 --- a/src/daemon/__tests__/request-router-typed-error.test.ts +++ b/src/daemon/__tests__/request-router-typed-error.test.ts @@ -1,4 +1,4 @@ -import { createTestDeviceInventoryGateways } from '../../__tests__/test-utils/device-inventory-gateways.ts'; +import { createTestDeviceInventoryGateways } from '@agent-device/device-selection/inventory-gateways-fixtures'; import { legacyDispatchCapture } from './legacy-snapshot-capture-fixture.ts'; import { test, expect, vi, beforeEach } from 'vitest'; import fs from 'node:fs'; diff --git a/src/daemon/__tests__/request-runtime-binding-router.test.ts b/src/daemon/__tests__/request-runtime-binding-router.test.ts index 342009c6b3..25d1be7565 100644 --- a/src/daemon/__tests__/request-runtime-binding-router.test.ts +++ b/src/daemon/__tests__/request-runtime-binding-router.test.ts @@ -9,7 +9,7 @@ import { import type { PlatformRuntimeOperations } from '@agent-device/contracts/platform-runtime-operations'; import { createAppLogStartResult, createDurableResourceEnvelope } from '@agent-device/capture-kit'; import { createTestAppLogLiveHandle } from '@agent-device/test-kit/app-log-live-handle'; -import { createTestDeviceInventoryGateways } from '../../__tests__/test-utils/device-inventory-gateways.ts'; +import { createTestDeviceInventoryGateways } from '@agent-device/device-selection/inventory-gateways-fixtures'; import { unavailableDeploymentSnapshotAndShutdownOperationFacts } from '@agent-device/test-kit/runtime-operation-facts'; import { makeSessionStore } from './support/store-factory.ts'; import { LeaseRegistry } from '../lease-registry.ts'; diff --git a/src/daemon/__tests__/request-save-script-transports.test.ts b/src/daemon/__tests__/request-save-script-transports.test.ts index 7e4ae3b329..c157f0c050 100644 --- a/src/daemon/__tests__/request-save-script-transports.test.ts +++ b/src/daemon/__tests__/request-save-script-transports.test.ts @@ -1,6 +1,6 @@ import { storeSessionForTest } from './support/store-factory.ts'; import { isSessionRecording } from '../session-script-publication-capability.ts'; -import { createTestDeviceInventoryGateways } from '../../__tests__/test-utils/device-inventory-gateways.ts'; +import { createTestDeviceInventoryGateways } from '@agent-device/device-selection/inventory-gateways-fixtures'; /** * #1478 (P4-pre): raw-wire counterfactuals for the `flags.saveScript` seam. * diff --git a/src/daemon/__tests__/selector-capture-binding.test.ts b/src/daemon/__tests__/selector-capture-binding.test.ts index eea4a2fca3..f38b1a7101 100644 --- a/src/daemon/__tests__/selector-capture-binding.test.ts +++ b/src/daemon/__tests__/selector-capture-binding.test.ts @@ -3,7 +3,7 @@ import { expect, test } from 'vitest'; import { ANDROID_EMULATOR, IOS_SIMULATOR } from '@agent-device/test-kit/device-fixtures'; import { makeAndroidSession, makeIosSession } from './support/session-factories.ts'; import { makeSessionStore } from './support/store-factory.ts'; -import { withTestDeviceInventory } from '../../__tests__/test-utils/device-inventory-gateways.ts'; +import { withTestDeviceInventory } from '@agent-device/device-selection/inventory-gateways-fixtures'; import { resolveBoundSelectorCapture } from '../selector-capture-binding.ts'; import { createBoundSelectorRuntime } from '../selector-runtime-backend.ts'; import { selectorCaptureFixture } from './selector-capture-fixture.ts'; diff --git a/src/daemon/__tests__/snapshot-custom-actions-platform-guard.test.ts b/src/daemon/__tests__/snapshot-custom-actions-platform-guard.test.ts index ebf2fec7ff..addc1883ac 100644 --- a/src/daemon/__tests__/snapshot-custom-actions-platform-guard.test.ts +++ b/src/daemon/__tests__/snapshot-custom-actions-platform-guard.test.ts @@ -1,4 +1,4 @@ -import { createTestDeviceInventoryGateways } from '../../__tests__/test-utils/device-inventory-gateways.ts'; +import { createTestDeviceInventoryGateways } from '@agent-device/device-selection/inventory-gateways-fixtures'; /** * Custom actions are read through the private-AX snapshot backend, which only * the iOS simulator has. Every other target would accept `--actions`, take an diff --git a/src/daemon/__tests__/system-button-runtime.test.ts b/src/daemon/__tests__/system-button-runtime.test.ts index e5b138c32e..7ea81ecb8d 100644 --- a/src/daemon/__tests__/system-button-runtime.test.ts +++ b/src/daemon/__tests__/system-button-runtime.test.ts @@ -19,7 +19,7 @@ import type { SystemButton } from '@agent-device/contracts/system-button-runtime import { deviceShape, type DeviceInfo } from '@agent-device/kernel/device'; import { makeSession } from './support/session-factories.ts'; import { makeSessionStore } from './support/store-factory.ts'; -import { createTestDeviceInventoryGateways } from '../../__tests__/test-utils/device-inventory-gateways.ts'; +import { createTestDeviceInventoryGateways } from '@agent-device/device-selection/inventory-gateways-fixtures'; import { LeaseRegistry } from '../lease-registry.ts'; import { activateCompleteRefFrame, refFrameState } from '../ref-frame.ts'; import type { BindDeviceRuntime, InspectDeviceRuntimeFacts } from '../request-runtime-binding.ts'; diff --git a/src/daemon/__tests__/test-device-runtime-gateway.ts b/src/daemon/__tests__/test-device-runtime-gateway.ts index 900f64e8da..a51d2fec58 100644 --- a/src/daemon/__tests__/test-device-runtime-gateway.ts +++ b/src/daemon/__tests__/test-device-runtime-gateway.ts @@ -22,7 +22,7 @@ import type { BindDeviceRuntime, BindExactDeviceRuntime } from '../request-runti import type { DeviceInfo } from '@agent-device/kernel/device'; import { applicationLifecycleRuntimeFixture } from './application-lifecycle-runtime-fixture.ts'; import { createUnavailableRuntimeFactsForTest } from '@agent-device/test-kit/runtime-operation-facts'; -import { withClientReplayScriptSources } from '../../__tests__/test-utils/replay-script-source.ts'; +import { withClientReplayScriptSources } from '@agent-device/replay-port/replay-script-source-fixtures'; import type { DaemonInvokeFn } from '../daemon-request.ts'; import { clearAndroidObservationFixture } from './android-observation-fixture.ts'; import { daemonPlatformServicesFixture } from './platform-services-fixture.ts'; diff --git a/src/daemon/__tests__/tv-remote-runtime.test.ts b/src/daemon/__tests__/tv-remote-runtime.test.ts index 1289fcb584..2fd82435cc 100644 --- a/src/daemon/__tests__/tv-remote-runtime.test.ts +++ b/src/daemon/__tests__/tv-remote-runtime.test.ts @@ -16,7 +16,7 @@ import { tvRemoteRuntimeOperationFacts } from '@agent-device/contracts/tv-remote import { deviceShape } from '@agent-device/kernel/device'; import { makeSession } from './support/session-factories.ts'; import { makeSessionStore } from './support/store-factory.ts'; -import { createTestDeviceInventoryGateways } from '../../__tests__/test-utils/device-inventory-gateways.ts'; +import { createTestDeviceInventoryGateways } from '@agent-device/device-selection/inventory-gateways-fixtures'; import { LeaseRegistry } from '../lease-registry.ts'; import { activateCompleteRefFrame, refFrameState } from '../ref-frame.ts'; import type { BindDeviceRuntime, InspectDeviceRuntimeFacts } from '../request-runtime-binding.ts'; diff --git a/src/daemon/__tests__/viewport-runtime.test.ts b/src/daemon/__tests__/viewport-runtime.test.ts index ce0b68cd0a..4e74cd6a2f 100644 --- a/src/daemon/__tests__/viewport-runtime.test.ts +++ b/src/daemon/__tests__/viewport-runtime.test.ts @@ -16,7 +16,7 @@ import { viewportRuntimeOperationFacts } from '@agent-device/contracts/viewport- import { deviceShape } from '@agent-device/kernel/device'; import { makeSession } from './support/session-factories.ts'; import { makeSessionStore } from './support/store-factory.ts'; -import { createTestDeviceInventoryGateways } from '../../__tests__/test-utils/device-inventory-gateways.ts'; +import { createTestDeviceInventoryGateways } from '@agent-device/device-selection/inventory-gateways-fixtures'; import { LeaseRegistry } from '../lease-registry.ts'; import { activateCompleteRefFrame, refFrameState } from '../ref-frame.ts'; import type { BindDeviceRuntime, InspectDeviceRuntimeFacts } from '../request-runtime-binding.ts'; diff --git a/src/daemon/handlers/__tests__/session-appstate-device-runtime.test.ts b/src/daemon/handlers/__tests__/session-appstate-device-runtime.test.ts index d968bc0c2f..e5413faee7 100644 --- a/src/daemon/handlers/__tests__/session-appstate-device-runtime.test.ts +++ b/src/daemon/handlers/__tests__/session-appstate-device-runtime.test.ts @@ -3,7 +3,7 @@ import { test, expect, vi } from 'vitest'; import { handleAppStateCommand } from '../session-appstate.ts'; import { makeSessionStore } from '../../__tests__/support/store-factory.ts'; -import { withTestDeviceInventory } from '../../../__tests__/test-utils/device-inventory-gateways.ts'; +import { withTestDeviceInventory } from '@agent-device/device-selection/inventory-gateways-fixtures'; import { unavailableDeploymentSnapshotAndShutdownOperationFacts } from '@agent-device/test-kit/runtime-operation-facts'; import { applicationLifecycleOperationFacts } from '@agent-device/contracts/application-lifecycle-runtime'; import { diff --git a/src/daemon/handlers/__tests__/session-command-harness.ts b/src/daemon/handlers/__tests__/session-command-harness.ts index 9600a43f45..112d3f7119 100644 --- a/src/daemon/handlers/__tests__/session-command-harness.ts +++ b/src/daemon/handlers/__tests__/session-command-harness.ts @@ -23,7 +23,7 @@ import type { TargetShutdownResult } from '@agent-device/contracts/device'; import { isIosFamily, type DeviceInfo } from '@agent-device/kernel/device'; import { beforeEach, vi } from 'vitest'; import { applicationLifecycleRuntimeFixture } from '../../__tests__/application-lifecycle-runtime-fixture.ts'; -import { withClientReplayScriptSources } from '../../../__tests__/test-utils/replay-script-source.ts'; +import { withClientReplayScriptSources } from '@agent-device/replay-port/replay-script-source-fixtures'; import { createUnavailableRuntimeFactsForTest } from '@agent-device/test-kit/runtime-operation-facts'; import { platformResourceCleanupFixture } from '../../__tests__/support/platform-resource-cleanup-fixture.ts'; diff --git a/src/daemon/handlers/__tests__/session-command-replay.test.ts b/src/daemon/handlers/__tests__/session-command-replay.test.ts index 099b8272c9..00a783d3a9 100644 --- a/src/daemon/handlers/__tests__/session-command-replay.test.ts +++ b/src/daemon/handlers/__tests__/session-command-replay.test.ts @@ -6,7 +6,7 @@ import { makeSessionStore } from './session-test-harness.ts'; import { handleSessionCommands } from './session-command-harness.ts'; import type { DaemonRequest } from '../../daemon-request.ts'; -import { replayScriptSourceBundleFor } from '../../../__tests__/test-utils/replay-script-source.ts'; +import { replayScriptSourceBundleFor } from '@agent-device/replay-port/replay-script-source-fixtures'; import { mkdtempForTestSync } from '@agent-device/test-kit/tmp-dir'; import { fakeCreateCommandSurface } from '../../__tests__/support/command-surface-fake.ts'; diff --git a/src/daemon/handlers/__tests__/session-doctor-device.test.ts b/src/daemon/handlers/__tests__/session-doctor-device.test.ts index 86860aacba..e113a25f84 100644 --- a/src/daemon/handlers/__tests__/session-doctor-device.test.ts +++ b/src/daemon/handlers/__tests__/session-doctor-device.test.ts @@ -3,7 +3,7 @@ import { test } from 'vitest'; import { withTestDeviceInventory, withTestDeviceInventoryProvider as withDeviceInventoryProvider, -} from '../../../__tests__/test-utils/device-inventory-gateways.ts'; +} from '@agent-device/device-selection/inventory-gateways-fixtures'; import { AppError } from '@agent-device/kernel/errors'; import type { DeviceInfo } from '@agent-device/kernel/device'; import { attachAdbFailureHint } from '@agent-device/platform-android/mechanics'; diff --git a/src/daemon/handlers/__tests__/session-doctor-warmup.test.ts b/src/daemon/handlers/__tests__/session-doctor-warmup.test.ts index 34bc9833ee..c584f3b333 100644 --- a/src/daemon/handlers/__tests__/session-doctor-warmup.test.ts +++ b/src/daemon/handlers/__tests__/session-doctor-warmup.test.ts @@ -4,7 +4,7 @@ import { isActiveProviderDevice } from '../../provider-device-admission.ts'; import { handleDoctorCommand } from '../session-doctor.ts'; import { createHostDiagnostics } from '../../../platform-runtime-host-diagnostics.ts'; import { makeSessionStore } from '../../__tests__/support/store-factory.ts'; -import { withTestDeviceInventory } from '../../../__tests__/test-utils/device-inventory-gateways.ts'; +import { withTestDeviceInventory } from '@agent-device/device-selection/inventory-gateways-fixtures'; import type { DaemonResponse } from '../../daemon-request.ts'; const { mockAppleRunnerWarmupCheck } = vi.hoisted(() => ({ diff --git a/src/daemon/handlers/__tests__/session-relaunch-close.test.ts b/src/daemon/handlers/__tests__/session-relaunch-close.test.ts index cf4dacf64b..9c624929f1 100644 --- a/src/daemon/handlers/__tests__/session-relaunch-close.test.ts +++ b/src/daemon/handlers/__tests__/session-relaunch-close.test.ts @@ -7,7 +7,7 @@ import { fakeCreateCommandSurface } from '../../__tests__/support/command-surfac // The daemon reads provider ownership through its own typed admission seam; these suites // install the seam over runtime fakes, the way root composition installs the real one. const providerAdmission = providerDeviceAdmissionFixture(); -import { createTestDeviceInventoryGateways } from '../../../__tests__/test-utils/device-inventory-gateways.ts'; +import { createTestDeviceInventoryGateways } from '@agent-device/device-selection/inventory-gateways-fixtures'; import { makeSessionStore } from '../../__tests__/support/store-factory.ts'; import { makeSession } from '../../__tests__/support/session-factories.ts'; import { getResolveTargetDeviceMock } from '../../__tests__/request-router-dispatch-mocks.ts'; diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-capabilities.test.ts b/src/daemon/session-lifecycle/internal/__tests__/session-capabilities.test.ts index 1a923ae3dd..509a78a4ed 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-capabilities.test.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-capabilities.test.ts @@ -6,7 +6,7 @@ import { PUBLIC_COMMANDS } from '@agent-device/command-registry/catalog'; import { LINUX_DEVICE, WEB_DESKTOP_DEVICE } from '@agent-device/test-kit/device-fixtures'; import { makeAndroidSession, makeSession } from '../../../__tests__/support/session-factories.ts'; import { makeSessionStore } from '../../../__tests__/support/store-factory.ts'; -import { withTestDeviceInventoryProvider as withTargetDeviceResolutionScope } from '../../../../__tests__/test-utils/device-inventory-gateways.ts'; +import { withTestDeviceInventoryProvider as withTargetDeviceResolutionScope } from '@agent-device/device-selection/inventory-gateways-fixtures'; import { unavailableDeploymentSnapshotAndShutdownOperationFacts } from '@agent-device/test-kit/runtime-operation-facts'; import type { DeviceInfo } from '@agent-device/kernel/device'; import { applicationLifecycleOperationFacts } from '@agent-device/contracts/application-lifecycle-runtime'; diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-devices-batch-runtime.test.ts b/src/daemon/session-lifecycle/internal/__tests__/session-devices-batch-runtime.test.ts index 56592b2925..1766eefa55 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-devices-batch-runtime.test.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-devices-batch-runtime.test.ts @@ -21,7 +21,7 @@ import { makeSessionStore } from '../../../__tests__/support/store-factory.ts'; import { makeSession } from '../../../__tests__/support/session-factories.ts'; import type { DaemonRequest, DaemonResponse } from '../../../daemon-request.ts'; -import { withTestDeviceInventory } from '../../../../__tests__/test-utils/device-inventory-gateways.ts'; +import { withTestDeviceInventory } from '@agent-device/device-selection/inventory-gateways-fixtures'; import type { DeviceInfo } from '@agent-device/kernel/device'; import { readCurrentOwnerIdentity } from '@agent-device/host-kit/process'; import { mkdtempForTestSync } from '@agent-device/test-kit/tmp-dir'; diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-open-url-prewarm.test.ts b/src/daemon/session-lifecycle/internal/__tests__/session-open-url-prewarm.test.ts index 893efe1b5a..ecde5dd772 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-open-url-prewarm.test.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-open-url-prewarm.test.ts @@ -49,7 +49,7 @@ import { } from '../../../__tests__/test-device-runtime-gateway.ts'; import { dispatchApplicationLifecycleEffect } from '../../../__tests__/application-lifecycle-runtime-fixture.ts'; import { getResolveTargetDeviceMock } from '../../../__tests__/request-router-dispatch-mocks.ts'; -import { createTestDeviceInventoryGateways } from '../../../../__tests__/test-utils/device-inventory-gateways.ts'; +import { createTestDeviceInventoryGateways } from '@agent-device/device-selection/inventory-gateways-fixtures'; import { makeSessionStore } from '../../../__tests__/support/store-factory.ts'; import { makeSession } from '../../../__tests__/support/session-factories.ts'; import { LeaseRegistry } from '../../../lease-registry.ts'; diff --git a/src/platform-runtime-device-inventory.test.ts b/src/platform-runtime-device-inventory.test.ts index f4e0378688..41119b45bd 100644 --- a/src/platform-runtime-device-inventory.test.ts +++ b/src/platform-runtime-device-inventory.test.ts @@ -1,7 +1,3 @@ -import type { - DeviceInventoryRequest, - ProviderDeviceInventorySource, -} from '@agent-device/contracts/device'; import { type DeviceInventorySource, type InventoryPlatformModule, @@ -13,9 +9,9 @@ import type { } from '@agent-device/contracts/platform-runtime-host'; import { resolveTargetDeviceSelection } from '@agent-device/device-selection/dispatch-resolve'; import { withDeviceInventoryContext } from '@agent-device/device-selection/device-inventory-context'; -import { PLATFORMS, type DeviceInfo, type Platform } from '@agent-device/kernel/device'; +import { PLATFORMS, type DeviceInfo } from '@agent-device/kernel/device'; import { describe, expect, test, vi } from 'vitest'; -import { createComposedDeviceInventoryGateways } from './platform-runtime-device-inventory.ts'; +import { attachAppleInstalledAppProbe } from './platform-runtime-device-inventory.ts'; const simctl = vi.hoisted(() => ({ listapps: [] as Array<{ deviceId: string; apps: Record }>, @@ -53,227 +49,50 @@ const host: DeviceInventoryHost = Object.freeze({ files: Object.freeze({ isExecutable: async () => false, createTemporaryTextFile: async () => { - throw new Error('unused'); + throw new Error('unused test inventory temporary file'); }, }), - hostOs: 'darwin', + hostOs: 'other', hostName: 'test-host', homeDirectory: '/tmp/test-home', observations: Object.freeze({ deviceBooted: async () => {} }), }); -describe('composed device inventory gateway', () => { - test('an authoritative provider inventory, including empty, loads no local host or source', async () => { - for (const devices of [[], [device('android', 'remote')]]) { - const local = inventoryWorld(); - const provider: ProviderDeviceInventorySource = { - discover: async () => ({ kind: 'inventory', devices }), - }; - const gateways = createComposedDeviceInventoryGateways({ - registry: local.registry, - loadHost: local.loadHost, - provider, - }); - - expect(await gateways.providerFirst.discover({ platform: 'android' }, scope)).toEqual( - devices, - ); - expect(local.hostLoads()).toBe(0); - expect(local.sourceLoads()).toEqual([]); - } - }); - - test('declined providers fall through, while malformed outcomes fail closed', async () => { - const local = inventoryWorld(); - const declined = createComposedDeviceInventoryGateways({ - registry: local.registry, - loadHost: local.loadHost, - provider: { discover: async () => ({ kind: 'declined' }) }, - }); - expect(await declined.providerFirst.discover({ platform: 'android' }, scope)).toEqual([ - device('android'), - ]); - - const malformed = createComposedDeviceInventoryGateways({ - registry: local.registry, - loadHost: local.loadHost, - provider: { discover: async () => undefined } as unknown as ProviderDeviceInventorySource, - }); - await expect( - malformed.providerFirst.discover({ platform: 'android' }, scope), - ).rejects.toMatchObject({ details: { reason: 'provider_inventory_contract_violation' } }); - }); - - test('forwards the request signal into an in-flight provider source', async () => { - const controller = new AbortController(); - const requestScope = { ...scope, signal: controller.signal }; - const observed = vi.fn(); - const provider: ProviderDeviceInventorySource = { - discover: async (_request, signal) => { - observed(signal); - return await new Promise((_resolve, reject) => { - signal.addEventListener('abort', () => reject(signal.reason), { once: true }); - }); - }, - }; - const local = inventoryWorld(); - const gateways = createComposedDeviceInventoryGateways({ - registry: local.registry, - loadHost: local.loadHost, - provider, - }); - const pending = gateways.providerFirst.discover({ platform: 'android' }, requestScope); - controller.abort(new Error('cancelled')); - - await expect(pending).rejects.toThrow('cancelled'); - expect(observed).toHaveBeenCalledWith(controller.signal); - expect(local.hostLoads()).toBe(0); - }); - - test('keeps local projection policy out of the provider request', async () => { - const received: unknown[] = []; - const provider: ProviderDeviceInventorySource = { - discover: async (request) => { - received.push(request); - return { kind: 'inventory', devices: [] }; - }, - }; - const local = inventoryWorld(); - const gateways = createComposedDeviceInventoryGateways({ - registry: local.registry, - loadHost: local.loadHost, - provider, - }); - - await gateways.providerFirst.discover( - { - platform: 'ios', - kind: 'simulator', - booted: true, - iosSimulatorSetPath: '/tmp/isolated-simulators', - androidSerialAllowlist: ['emulator-5554'], - }, - scope, - ); - - expect(received).toEqual([ - { - platform: 'ios', - iosSimulatorSetPath: '/tmp/isolated-simulators', - androidSerialAllowlist: ['emulator-5554'], - }, - ]); - expect(local.hostLoads()).toBe(0); - }); - - test('applies public Apple aliases and all internal projection filters centrally', async () => { - const devices = [ - { ...device('apple', 'ios-default'), appleOs: 'ios' as const, target: undefined }, - { ...device('apple', 'mac'), appleOs: 'macos' as const, target: 'desktop' as const }, - { ...device('apple', 'shutdown'), appleOs: 'ios' as const, booted: false }, - device('android', 'other-family'), - ]; - const provider: ProviderDeviceInventorySource = { - discover: async () => ({ kind: 'inventory', devices }), - }; - const local = inventoryWorld(); - const gateways = createComposedDeviceInventoryGateways({ - registry: local.registry, - loadHost: local.loadHost, - provider, - }); - - const projected = await gateways.providerFirst.discover( - { platform: 'ios', target: 'mobile', kind: 'device', booted: true }, - scope, - ); - expect(projected.map(({ id }) => id)).toEqual(['ios-default']); - }); - - test('loads only the selected family and retries failed host and source loads', async () => { - let hostAttempts = 0; - let androidAttempts = 0; - const local = inventoryWorld({ - android: async () => { - androidAttempts += 1; - if (androidAttempts === 1) throw new Error('source load failed'); - return source([device('android')]); - }, - }); - const gateways = createComposedDeviceInventoryGateways({ - registry: local.registry, - loadHost: async () => { - hostAttempts += 1; - if (hostAttempts === 1) throw new Error('host load failed'); - return host; - }, - }); - - await expect(gateways.localOnly.discover({ platform: 'android' }, scope)).rejects.toThrow( - 'host load failed', - ); - await expect(gateways.localOnly.discover({ platform: 'android' }, scope)).rejects.toThrow( - 'source load failed', - ); - expect(await gateways.localOnly.discover({ platform: 'android' }, scope)).toEqual([ - device('android'), - ]); - expect(hostAttempts).toBe(2); - expect(androidAttempts).toBe(2); - expect(local.sourceLoads()).toEqual(['android', 'android']); - }); - - test('unfiltered local discovery is fail-soft and preserves selector order', async () => { - const local = inventoryWorld({ - harmonyos: async () => source([], new Error('hdc unavailable')), - }); - const gateways = createComposedDeviceInventoryGateways({ - registry: local.registry, - loadHost: local.loadHost, - }); - - const devices = await gateways.localOnly.discover({}, scope); - expect(devices.map(({ platform }) => platform)).toEqual(['android', 'apple', 'vega', 'linux']); - expect(local.sourceLoads()).toEqual(['android', 'harmonyos', 'apple', 'vega', 'linux']); - }); - - test('explicit local failures propagate and family sources cannot return another family', async () => { - const failing = inventoryWorld({ android: async () => source([], new Error('adb failed')) }); - await expect( - createComposedDeviceInventoryGateways({ - registry: failing.registry, - loadHost: failing.loadHost, - }).localOnly.discover({ platform: 'android' }, scope), - ).rejects.toThrow('adb failed'); +function simulator(id: string): DeviceInfo { + return { + platform: 'apple', + id, + name: id, + kind: 'simulator', + appleOs: 'ios', + target: 'mobile', + booted: true, + }; +} - const lying = inventoryWorld({ - android: async () => source([device('linux', 'wrong-family')]), - }); - await expect( - createComposedDeviceInventoryGateways({ - registry: lying.registry, - loadHost: lying.loadHost, - }).localOnly.discover({ platform: 'android' }, scope), - ).rejects.toMatchObject({ - details: { expectedFamily: 'android', actualFamily: 'linux' }, - }); - }); +function source(devices: readonly DeviceInfo[]): DeviceInventorySource { + return { discover: async () => devices }; +} +describe('production device inventory composition', () => { test('narrows app-based simulator selection through the factory-installed probe', async () => { - // Guards the production wiring: the package-level selection tests inject their own - // probe, so only this path proves the factory attaches one to the request context. + // Guards the production wiring: the package-level composer only knows the injected + // probe, so only this path proves the root factory attaches the Apple one to the + // request context. simctl.listapps = [ { deviceId: 'sim-b', apps: { 'com.example.demo': { Bundle: 'com.example.demo', CFBundleName: 'Demo' } }, }, ]; - const local = inventoryWorld({ - apple: async () => source([simulator('sim-a'), simulator('sim-b')]), - }); - const gateways = createComposedDeviceInventoryGateways({ - registry: local.registry, - loadHost: local.loadHost, + const modules: InventoryPlatformModule[] = PLATFORMS.map((family) => ({ + family, + loadInventory: async () => + source(family === 'apple' ? [simulator('sim-a'), simulator('sim-b')] : []), + })); + const gateways = await attachAppleInstalledAppProbe({ + registry: createPlatformModuleRegistry(modules), + loadHost: async () => host, }); const selection = await withDeviceInventoryContext({ ...gateways, requestScope: scope }, () => @@ -292,58 +111,3 @@ describe('composed device inventory gateway', () => { expect([...simctl.calls].sort()).toEqual(['sim-a', 'sim-b']); }); }); - -function inventoryWorld( - overrides: Partial Promise>> = {}, -) { - const loaded: Platform[] = []; - let hosts = 0; - const modules: InventoryPlatformModule[] = PLATFORMS.map((family) => ({ - family, - loadInventory: async () => { - loaded.push(family); - return await (overrides[family]?.() ?? Promise.resolve(source([device(family)]))); - }, - })); - return { - registry: createPlatformModuleRegistry(modules), - loadHost: async () => { - hosts += 1; - return host; - }, - hostLoads: () => hosts, - sourceLoads: () => [...loaded], - }; -} - -function source(devices: readonly DeviceInfo[], failure?: Error): DeviceInventorySource { - return { - discover: async (_request: Readonly) => { - if (failure) throw failure; - return devices; - }, - }; -} - -function device(platform: Platform, id = `${platform}-device`): DeviceInfo { - return { - platform, - id, - name: id, - kind: 'device', - target: platform === 'vega' ? 'tv' : 'mobile', - booted: true, - }; -} - -function simulator(id: string): DeviceInfo { - return { - platform: 'apple', - id, - name: id, - kind: 'simulator', - appleOs: 'ios', - target: 'mobile', - booted: true, - }; -} diff --git a/src/platform-runtime-device-inventory.ts b/src/platform-runtime-device-inventory.ts index f6f3c22792..5b67320975 100644 --- a/src/platform-runtime-device-inventory.ts +++ b/src/platform-runtime-device-inventory.ts @@ -1,235 +1,31 @@ -import { - filterDeviceInventoryProjection, - LOCAL_DEVICE_INVENTORY_PLATFORM_SELECTORS, - projectProviderDeviceInventoryRequest, - type ProviderDeviceInventorySource, -} from '@agent-device/contracts/device'; import type { ComposedDeviceInventoryGateways, - DeviceInventoryGateway, - DeviceInventorySource, InstalledAppProbe, InventoryPlatformModule, PlatformModuleRegistry, - ProviderAwareDeviceInventoryGateway, } from '@agent-device/contracts/platform-module'; -import type { - DeviceInventoryHost, - DeviceInventoryHostFor, - PlatformRequestScope, -} from '@agent-device/contracts/platform-runtime-host'; -import { - isApplePlatform, - type DeviceInfo, - type Platform, - type PlatformSelector, -} from '@agent-device/kernel/device'; -import { AppError } from '@agent-device/kernel/errors'; +import type { DeviceInventoryHost } from '@agent-device/contracts/platform-runtime-host'; +import type { ProviderDeviceInventorySource } from '@agent-device/contracts/device'; -export function createComposedDeviceInventoryGateways( +// Composes the package-level gateways and attaches the Apple installed-app probe this host owns. +export async function attachAppleInstalledAppProbe( options: Readonly<{ registry: PlatformModuleRegistry; loadHost: () => Promise; provider?: ProviderDeviceInventorySource; }>, -): ComposedDeviceInventoryGateways { - const localOnly = createLocalDeviceInventoryGateway(options.registry, options.loadHost); - const provider = options.provider; - const discoverWithSource: ProviderAwareDeviceInventoryGateway['discoverWithSource'] = provider - ? async (request, scope) => { - scope.signal.throwIfAborted(); - const provided = await provider.discover( - projectProviderDeviceInventoryRequest(request), - scope.signal, - ); - scope.signal.throwIfAborted(); - assertProviderOutcome(provided); - if (provided.kind === 'inventory') { - return { - devices: filterDeviceInventoryProjection( - provided.devices.map((device) => ({ ...device })), - request, - ), - source: 'provider', - }; - } - return { devices: await localOnly.discover(request, scope), source: 'local' }; - } - : async (request, scope) => ({ - devices: await localOnly.discover(request, scope), - source: 'local', - }); - const providerFirst: ProviderAwareDeviceInventoryGateway = Object.freeze({ - discover: async (request, scope) => (await discoverWithSource(request, scope)).devices, - discoverWithSource, - }); - // Function-scoped: device selection reads this probe from the request context, and loading it - // must not evaluate Apple app-resolution mechanics until a selection actually narrows. - const findInstalledApp: InstalledAppProbe = async (device, appTarget) => { - const { findIosSimulatorInstalledApp } = - await import('@agent-device/platform-apple/app-resolution'); - return await findIosSimulatorInstalledApp(device, appTarget); - }; - return Object.freeze({ providerFirst, localOnly, findInstalledApp }); -} - -function createLocalDeviceInventoryGateway( - registry: PlatformModuleRegistry, - loadHost: () => Promise, -): DeviceInventoryGateway { - let hostLoad: Promise | undefined; - const requiredHost = async (): Promise => { - hostLoad ??= loadHost(); - try { - return await hostLoad; - } catch (error) { - hostLoad = undefined; - throw error; - } - }; - const sourceLoads = new Map>(); - const loadSource = async (family: Platform): Promise => { - const existing = sourceLoads.get(family); - if (existing) return await existing; - const pending = loadFamilyInventory(registry.get(family), requiredHost); - sourceLoads.set(family, pending); - try { - return await pending; - } catch (error) { - if (sourceLoads.get(family) === pending) sourceLoads.delete(family); - throw error; - } - }; - - return Object.freeze({ - discover: async (request, scope) => { - scope.signal.throwIfAborted(); - if (request.platform) { - return await discoverFamily( - loadSource, - familyForSelector(request.platform), - request, - scope, - ); - } - - const perFamily = await Promise.all( - LOCAL_DEVICE_INVENTORY_PLATFORM_SELECTORS.map(async (selector) => { - try { - const devices = await discoverFamily( - loadSource, - familyForSelector(selector), - { ...request, platform: selector }, - scope, - ); - return Array.isArray(devices) ? devices : []; - } catch { - return []; - } - }), - ); - scope.signal.throwIfAborted(); - return perFamily.flat(); - }, - }); -} - -async function loadFamilyInventory( - module: InventoryPlatformModule, - requiredHost: () => Promise, -): Promise { - switch (module.family) { - case 'apple': - return await module.loadInventory(appleInventoryHost(await requiredHost())); - case 'android': - return await module.loadInventory(androidInventoryHost(await requiredHost())); - case 'harmonyos': - return await module.loadInventory(harmonyInventoryHost(await requiredHost())); - case 'vega': - return await module.loadInventory(vegaInventoryHost(await requiredHost())); - case 'linux': - return await module.loadInventory(linuxInventoryHost(await requiredHost())); - case 'web': - return await module.loadInventory(); - } -} - -function appleInventoryHost(host: DeviceInventoryHost): DeviceInventoryHostFor<'apple'> { - return Object.freeze({ - appleTools: host.appleTools, - files: host.files, - hostName: host.hostName, - hostOs: host.hostOs, - observations: host.observations, - }); -} - -function androidInventoryHost(host: DeviceInventoryHost): DeviceInventoryHostFor<'android'> { - return Object.freeze({ - commands: host.commands, - files: host.files, - homeDirectory: host.homeDirectory, - toolchains: host.toolchains, - }); -} - -function harmonyInventoryHost(host: DeviceInventoryHost): DeviceInventoryHostFor<'harmonyos'> { - return Object.freeze({ - commands: host.commands, - files: host.files, - toolchains: host.toolchains, - }); -} - -function vegaInventoryHost(host: DeviceInventoryHost): DeviceInventoryHostFor<'vega'> { - return Object.freeze({ - commands: host.commands, - files: host.files, - homeDirectory: host.homeDirectory, - }); -} - -function linuxInventoryHost(host: DeviceInventoryHost): DeviceInventoryHostFor<'linux'> { - return Object.freeze({ hostName: host.hostName, hostOs: host.hostOs }); -} - -async function discoverFamily( - loadSource: (family: Platform) => Promise, - family: Platform, - request: Parameters[0], - scope: PlatformRequestScope, -): Promise { - const source = await loadSource(family); - scope.signal.throwIfAborted(); - const devices = await source.discover(request, scope); - scope.signal.throwIfAborted(); - assertSourceFamily(family, devices); - return filterDeviceInventoryProjection(devices, request); -} - -function familyForSelector(selector: PlatformSelector): Platform { - return isApplePlatform(selector) ? 'apple' : selector; -} - -function assertSourceFamily(family: Platform, devices: readonly DeviceInfo[]): void { - const outside = devices.find((device) => device.platform !== family); - if (!outside) return; - throw new AppError( - 'COMMAND_FAILED', - `Platform inventory source ${family} returned a ${outside.platform} device.`, - { expectedFamily: family, actualFamily: outside.platform, deviceId: outside.id }, - ); -} - -function assertProviderOutcome( - value: unknown, -): asserts value is Awaited> { - if (value !== null && typeof value === 'object') { - const outcome = value as { kind?: unknown; devices?: unknown }; - if (outcome.kind === 'declined' && !('devices' in outcome)) return; - if (outcome.kind === 'inventory' && Array.isArray(outcome.devices)) return; - } - throw new AppError('COMMAND_FAILED', 'Provider device inventory returned an invalid outcome.', { - reason: 'provider_inventory_contract_violation', - }); -} +): Promise { + // Function-scoped: the composition root's eager closure is the hub every daemon start and CLI + // run pays for, so the package composer is evaluated only when an inventory composition runs. + const { createComposedDeviceInventoryGateways: compose } = + await import('@agent-device/device-selection/composed-inventory-gateways'); + return compose({ ...options, findInstalledApp: appleInstalledAppProbe }); +} + +// Function-scoped: device selection reads this probe from the request context, and loading it +// must not evaluate Apple app-resolution mechanics until a selection actually narrows. +const appleInstalledAppProbe: InstalledAppProbe = async (device, appTarget) => { + const { findIosSimulatorInstalledApp } = + await import('@agent-device/platform-apple/app-resolution'); + return await findIosSimulatorInstalledApp(device, appTarget); +}; diff --git a/src/platform-runtime-gateway.test.ts b/src/platform-runtime-gateway.test.ts index e3d6d83a47..96b1739359 100644 --- a/src/platform-runtime-gateway.test.ts +++ b/src/platform-runtime-gateway.test.ts @@ -20,7 +20,7 @@ import { handleAppStateCommand } from './daemon/handlers/session-appstate.ts'; import { createRequestRuntimeBindings } from './daemon/request-runtime-binding.ts'; import { createRequestDispatchLedger } from './daemon/request-dispatch-ledger.ts'; import { makeSessionStore } from './daemon/__tests__/support/store-factory.ts'; -import { withTestDeviceInventory } from './__tests__/test-utils/device-inventory-gateways.ts'; +import { withTestDeviceInventory } from '@agent-device/device-selection/inventory-gateways-fixtures'; import { createComposedPlatformRuntimeGateway } from './platform-runtime-gateway.ts'; import { gatewayFixture as gateway, diff --git a/src/platform-runtime.test.ts b/src/platform-runtime.test.ts index 0401324ecd..373be8b3a8 100644 --- a/src/platform-runtime.test.ts +++ b/src/platform-runtime.test.ts @@ -61,7 +61,7 @@ describe('platform module metadata composition', () => { diagnostics: { emit: () => {} }, progress: { report: () => {} }, } as const; - const providerGateways = createPlatformDeviceInventoryGateways({ + const providerGateways = await createPlatformDeviceInventoryGateways({ discover: async () => ({ kind: 'inventory', devices: [ @@ -81,7 +81,7 @@ describe('platform module metadata composition', () => { ); expect(hostAdapter.evaluations).toBe(0); - const localGateways = createPlatformDeviceInventoryGateways(); + const localGateways = await createPlatformDeviceInventoryGateways(); expect(await localGateways.localOnly.discover({ platform: 'web' }, scope)).toHaveLength(1); expect(hostAdapter.evaluations).toBe(0); diff --git a/src/platform-runtime.ts b/src/platform-runtime.ts index 2cf772f07a..d58deb05a0 100644 --- a/src/platform-runtime.ts +++ b/src/platform-runtime.ts @@ -53,7 +53,7 @@ import { createComposedPlatformRuntimeGateway, type PlatformRuntimeProviderRegistration, } from './platform-runtime-gateway.ts'; -import { createComposedDeviceInventoryGateways } from './platform-runtime-device-inventory.ts'; +import { attachAppleInstalledAppProbe } from './platform-runtime-device-inventory.ts'; import { createAndroidObservationHost } from './platform-runtime-android-observation-host.ts'; import type { RequestPlatformProviderOptions } from './platform-runtime/request-providers.ts'; @@ -101,10 +101,10 @@ export const platformModuleRegistry = createPlatformModuleRegistry([ webInventoryModule, ]); -export function createPlatformDeviceInventoryGateways( +export async function createPlatformDeviceInventoryGateways( provider?: ProviderDeviceInventorySource, -): ComposedDeviceInventoryGateways { - return createComposedDeviceInventoryGateways({ +): Promise { + return await attachAppleInstalledAppProbe({ registry: platformModuleRegistry, loadHost: async () => { const { createDeviceInventoryHost } = await import('./platform-runtime-host.ts'); diff --git a/src/plugins/load.test.ts b/src/plugins/load.test.ts index 3bf512dee2..ae83608b3b 100644 --- a/src/plugins/load.test.ts +++ b/src/plugins/load.test.ts @@ -8,7 +8,7 @@ import { selectPlugin, registrationSource, webDriverPluginSource, -} from './plugin.fixtures.ts'; +} from '@agent-device/test-kit/plugin.fixtures'; import { AppError } from '@agent-device/kernel/errors'; import type { ProviderPluginHost } from '../sdk/plugins.ts'; import type { DeviceLease, ProviderDeviceRuntime } from '@agent-device/contracts/device'; diff --git a/src/plugins/manifest.test.ts b/src/plugins/manifest.test.ts index 58767eb244..dfbac3e776 100644 --- a/src/plugins/manifest.test.ts +++ b/src/plugins/manifest.test.ts @@ -4,7 +4,7 @@ import path from 'node:path'; import { test } from 'vitest'; import { readPluginManifest, RESERVED_PLUGIN_PROVIDERS } from './manifest.ts'; import { DEFAULT_PROVIDER_RUNTIME_REQUIRED_IDS } from '../provider-device-runtimes.ts'; -import { pluginHome, writePlugin } from './plugin.fixtures.ts'; +import { pluginHome, writePlugin } from '@agent-device/test-kit/plugin.fixtures'; test('manifest compatibility is checked without evaluating plugin code', () => { const { home } = pluginHome(); diff --git a/src/plugins/store.test.ts b/src/plugins/store.test.ts index d10648a187..f111359af3 100644 --- a/src/plugins/store.test.ts +++ b/src/plugins/store.test.ts @@ -4,7 +4,7 @@ import path from 'node:path'; import { afterEach, test, vi } from 'vitest'; import { runCmd } from '@agent-device/host-kit/command'; import { changePlugin, installedPlugins, listPlugins } from './store.ts'; -import { pluginHome, writePlugin, selectPlugin } from './plugin.fixtures.ts'; +import { pluginHome, writePlugin, selectPlugin } from '@agent-device/test-kit/plugin.fixtures'; vi.mock('@agent-device/host-kit/command', () => ({ runCmd: vi.fn() })); afterEach(() => vi.mocked(runCmd).mockReset()); diff --git a/src/provider-credential-fingerprint.test.ts b/src/provider-credential-fingerprint.test.ts index 6ce107c060..3b7415fcac 100644 --- a/src/provider-credential-fingerprint.test.ts +++ b/src/provider-credential-fingerprint.test.ts @@ -3,7 +3,7 @@ import { providerCredentialFingerprint, readDaemonProviderCredentials, } from './provider-credential-fingerprint.ts'; -import { pluginHome, selectPluginManifest } from './plugins/plugin.fixtures.ts'; +import { pluginHome, selectPluginManifest } from '@agent-device/test-kit/plugin.fixtures'; const BROWSERSTACK_ENV = { BROWSERSTACK_USERNAME: 'user', BROWSERSTACK_ACCESS_KEY: 'key-1' }; diff --git a/test/integration/nightly/concurrency-torture/bindings.ts b/test/integration/nightly/concurrency-torture/bindings.ts index 267aac83c9..319815c18a 100644 --- a/test/integration/nightly/concurrency-torture/bindings.ts +++ b/test/integration/nightly/concurrency-torture/bindings.ts @@ -26,7 +26,7 @@ import type { SessionStore } from '../../../../src/daemon/session-store.ts'; import { resolveRequestExecutionLockPlan } from '../../../../src/daemon/request-binding.ts'; import { shouldLockSessionExecution } from '../../../../src/daemon/daemon-command-registry.ts'; import { PUBLIC_COMMANDS } from '@agent-device/command-registry/catalog'; -import { withTestDeviceInventoryProvider as withDeviceInventoryProvider } from '../../../../src/__tests__/test-utils/device-inventory-gateways.ts'; +import { withTestDeviceInventoryProvider as withDeviceInventoryProvider } from '@agent-device/device-selection/inventory-gateways-fixtures'; import type { LockKey } from './deterministic-scheduler.ts'; diff --git a/test/integration/nightly/concurrency-torture/real-scope-serialization.ts b/test/integration/nightly/concurrency-torture/real-scope-serialization.ts index af33d3da68..567a50a7ce 100644 --- a/test/integration/nightly/concurrency-torture/real-scope-serialization.ts +++ b/test/integration/nightly/concurrency-torture/real-scope-serialization.ts @@ -22,7 +22,7 @@ import path from 'node:path'; import { createRequestExecutionScope } from '../../../../src/daemon/request-execution-scope.ts'; import { LeaseRegistry } from '../../../../src/daemon/lease-registry.ts'; import { SessionStore } from '../../../../src/daemon/session-store.ts'; -import { withTestDeviceInventoryProvider as withDeviceInventoryProvider } from '../../../../src/__tests__/test-utils/device-inventory-gateways.ts'; +import { withTestDeviceInventoryProvider as withDeviceInventoryProvider } from '@agent-device/device-selection/inventory-gateways-fixtures'; import type { CommandFlags } from '@agent-device/contracts/command'; import type { DaemonRequest } from '../../../../src/daemon/daemon-request.ts'; diff --git a/test/integration/provider-scenarios/harness.ts b/test/integration/provider-scenarios/harness.ts index c3a46626d1..f2803bf4a9 100644 --- a/test/integration/provider-scenarios/harness.ts +++ b/test/integration/provider-scenarios/harness.ts @@ -32,7 +32,7 @@ import type { import type { SessionState } from '../../../src/daemon/session-state.ts'; import { runCmdBackground } from '@agent-device/host-kit/command'; import { createOwnedProcessRecordStore } from '@agent-device/host-kit/process'; -import { withClientReplayScriptSources } from '../../../src/__tests__/test-utils/replay-script-source.ts'; +import { withClientReplayScriptSources } from '@agent-device/replay-port/replay-script-source-fixtures'; import type { DeviceInventoryProvider, ProviderDeviceRuntime, @@ -41,7 +41,7 @@ import type { import { createTestDeviceInventoryGateways, createTestDeviceInventoryGatewaysFromProvider, -} from '../../../src/__tests__/test-utils/device-inventory-gateways.ts'; +} from '@agent-device/device-selection/inventory-gateways-fixtures'; import { createHostDiagnostics } from '../../../src/platform-runtime-host-diagnostics.ts'; import type { PlatformRuntimeProviderRegistration } from '../../../src/platform-runtime-gateway.ts'; import { createProviderPlatformRuntimeRegistrations } from '../../../src/provider-device-runtimes.ts';