refactor: prune platform split residue (#2123)

This commit is contained in:
Michał Pierzchała
2026-08-29 13:10:47 +02:00
committed by GitHub
parent ab20d5c2af
commit a6232e51cf
23 changed files with 44 additions and 111 deletions
-5
View File
@@ -140,11 +140,6 @@
"runXcrun"
]
},
{
"comment": "The interactions package facade is consumed through workspace package exports. Fallow does not follow that package boundary, so keep the synchronous helper explicitly accounted for.",
"file": "packages/platform-apple/src/interactions-facade.ts",
"exports": ["resolveAppleBackRunnerCommand"]
},
{
"comment": "The runner-operations package facade is consumed through workspace package exports. Fallow does not follow that package boundary, so keep the two operations without visible production edges here explicitly accounted for.",
"file": "packages/platform-apple/src/runner-operations-facade.ts",
-4
View File
@@ -28,10 +28,6 @@ on:
- 'src/**/__tests__/**/*.ts'
- 'packages/*/src/**/*.test.ts'
- 'packages/*/src/**/__tests__/**/*.ts'
- 'src/platforms/harmonyos/**'
- 'src/platforms/linux/**'
- 'src/platforms/vega/**'
- 'src/platforms/web/**'
- 'packages/platform-android/src/**'
- 'packages/platform-harmonyos/src/**'
- 'packages/platform-linux/src/**'
@@ -183,15 +183,15 @@ selection, R11/R13 package enumeration, and the composite typecheck project list
The Apple XCUITest runner client is a durable platform-owned implementation facet colocated
inside `packages/platform-apple` as the `src/runner/` subtree (#2040) — Apple mechanics belong to
the Apple package. R13 models the package by enumeration rather than by exception sprawl: the family
exports its root façade plus fourteen named domain/mechanics facades — `./app-lifecycle`,
`./app-resolution`, `./debug-symbols`, `./doctor`, `./interactions`, `./install-artifact`, `./macos`,
exports its root façade plus thirteen named domain/mechanics facades — `./app-lifecycle`,
`./app-resolution`, `./debug-symbols`, `./doctor`, `./install-artifact`, `./macos`,
`./perf`, `./physical-device`, `./runner-owner`, `./runner/operations`, `./simctl`, `./simulator`, and
`./tool-provider` — as well as exactly the `./runner`, `./runner/client`, and `./runner/test-host`
`./tool-provider` — as well as exactly the `./runner` and `./runner/test-host`
subpaths. The named facades replace root-only access for synchronous domain consumers without a
broad compatibility barrel; R13 pins the exact export set and allowed consumer seams. The
`./runner` façade subpath is the seam through which daemon and root consumers reach runner mechanics
directly today; the host-bound `./runner/client` factory has one composition root and
`./runner/test-host` one vitest installer; the facet owns its cache files and usbmux sockets (the
directly today; the host-bound runner client stays package-internal and `./runner/test-host` has one
vitest installer; the facet owns its cache files and usbmux sockets (the
ambient-host rule exempts exactly that subtree), while raw process primitives stay banned — host
authority still enters through one focused injected port (`AppleRunnerHost`: process execution,
diagnostics, retry, probes, locks, foreground Apple tooling, physical-device control) constructed by
+2 -2
View File
@@ -28,8 +28,8 @@ Acceptance criteria:
iOS simulator validation:
- Unit: `pnpm exec vitest run src/platforms/ios/__tests__/runner-command-retry.test.ts`.
- Unit bundle: `pnpm exec vitest run src/platforms/ios/__tests__/runner-client.test.ts src/platforms/ios/__tests__/runner-session.test.ts src/platforms/ios/__tests__/runner-command-retry.test.ts src/platforms/ios/__tests__/runner-provider.test.ts`.
- Unit: `pnpm exec vitest run packages/platform-apple/src/runner/__tests__/runner-command-retry.test.ts`.
- Unit bundle: `pnpm exec vitest run packages/platform-apple/src/runner/__tests__/runner-client.test.ts packages/platform-apple/src/runner/__tests__/runner-session.test.ts packages/platform-apple/src/runner/__tests__/runner-command-retry.test.ts packages/platform-apple/src/runner/__tests__/runner-provider.test.ts`.
- Build: `pnpm build:xcuitest`.
- Manual sim smoke after build:
- `pnpm build`
+2 -2
View File
@@ -387,7 +387,7 @@
"count": 1
}
},
"src/platforms/boot-diagnostics.ts": {
"packages/provision-kit/src/boot-diagnostics.ts": {
"complexity_critical": {
"count": 1
},
@@ -565,7 +565,7 @@
"src/daemon/handlers/session.ts:complexity",
"src/replay/script-utils.ts:high impact",
"packages/selectors/src/internal/predicates.ts:high impact",
"src/platforms/boot-diagnostics.ts:complexity",
"packages/provision-kit/src/boot-diagnostics.ts:complexity",
"src/compat/maestro/support.ts:high impact",
"src/daemon/session-routing.ts:high impact",
"src/daemon/handlers/session-state.ts:complexity",
@@ -3,8 +3,8 @@ import { beforeEach, test, vi } from 'vitest';
import { ANDROID_EMULATOR } from './test-utils/device-fixtures.ts';
import './test-utils/android-host-test-setup.ts';
import { withAndroidAdbProvider, type AndroidAdbExecutor } from '../adb-executor.ts';
import { activateAndroidTestIme } from '../ime-activation.ts';
import {
activateAndroidTestIme,
isAndroidTestImeActive,
resetAndroidTestImeActivationCacheForTests,
restoreOrphanedAndroidTestImeOnDaemonStartup,
@@ -38,9 +38,9 @@ vi.mock('../ime-helper.ts', async (importOriginal) => {
import { ANDROID_EMULATOR } from './test-utils/device-fixtures.ts';
import { withAndroidAdbProvider, type AndroidAdbExecutor } from '../adb-executor.ts';
import { activateAndroidTestIme } from '../ime-activation.ts';
import { resetAndroidImeHelperInstallCache } from '../ime-helper.ts';
import {
activateAndroidTestIme,
isAndroidTestImeActive,
restoreAndroidTestIme,
restoreOrphanedAndroidTestImeOnDaemonStartup,
@@ -1,6 +1,5 @@
import { requireAndroidAdbHost, runAndroidHostAdb } from './adb-host.ts';
export { activateAndroidTestIme } from './ime-activation.ts';
export {
restoreAndroidTestIme,
restoreOrphanedAndroidTestImeOnDaemonStartup,
-8
View File
@@ -33,10 +33,6 @@
"types": "./src/doctor-facade.ts",
"default": "./src/doctor-facade.ts"
},
"./interactions": {
"types": "./src/interactions-facade.ts",
"default": "./src/interactions-facade.ts"
},
"./install-artifact": {
"types": "./src/install-artifact-facade.ts",
"default": "./src/install-artifact-facade.ts"
@@ -61,10 +57,6 @@
"types": "./src/runner-operations-facade.ts",
"default": "./src/runner-operations-facade.ts"
},
"./runner/client": {
"types": "./src/runner/client.ts",
"default": "./src/runner/client.ts"
},
"./runner/test-host": {
"types": "./src/runner/test-host.ts",
"default": "./src/runner/test-host.ts"
@@ -2,7 +2,11 @@ import { beforeEach, test, vi } from 'vitest';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
import { iosRunnerOverrides, performGestureApple } from '../../interactions.ts';
import {
iosRunnerOverrides,
performGestureApple,
resolveAppleBackRunnerCommand,
} from '../../interactions.ts';
import { runAppleRunnerCommand } from '../runner-client.ts';
import { AppError } from '@agent-device/kernel/errors';
import {
@@ -34,6 +38,12 @@ beforeEach(() => {
mockRunAppleRunnerCommand.mockImplementation(runnerActual.runAppleRunnerCommand);
});
test('resolveAppleBackRunnerCommand maps the default and explicit back modes', () => {
assert.equal(resolveAppleBackRunnerCommand(), 'backInApp');
assert.equal(resolveAppleBackRunnerCommand('in-app'), 'backInApp');
assert.equal(resolveAppleBackRunnerCommand('system'), 'backSystem');
});
function twoFingerPanPlan(): Extract<GesturePlan, { topology: 'two' }> {
return {
topology: 'two',
@@ -1,7 +1,6 @@
import assert from 'node:assert/strict';
import { test } from 'vitest';
import { buildAppNotInstalledError } from '@agent-device/platform-apple/app-resolution';
import { resolveAppleBackRunnerCommand } from '@agent-device/platform-apple/interactions';
import { createLocalAppleToolProvider } from './index.ts';
import { buildSimctlArgs } from '@agent-device/platform-apple/simctl';
@@ -21,7 +20,6 @@ test('Apple domain facades preserve synchronous helper contracts', () => {
const appError = buildAppNotInstalledError('Shoply');
assert.equal(appError.code, 'APP_NOT_INSTALLED');
assert.equal(resolveAppleBackRunnerCommand(), 'backInApp');
assert.deepEqual(buildSimctlArgs(['list']), ['simctl', 'list']);
assert.equal(appError instanceof Promise, false);
});
@@ -1 +0,0 @@
export { resolveAppleBackRunnerCommand } from './interactions.ts';
+1 -1
View File
@@ -211,7 +211,7 @@ export type AppleRunnerHost = {
emitRequestProgress(event: RequestProgressEvent): void;
getRequestSignal(requestId: string | undefined): AbortSignal | undefined;
isRequestCanceled(requestId: string | undefined): boolean;
// Boot-failure classification (src/platforms/boot-diagnostics.ts)
// Boot-failure classification (@agent-device/provision-kit/boot-diagnostics)
classifyBootFailure(input: {
error?: unknown;
message?: string;
@@ -1,10 +1,7 @@
import { test } from 'vitest';
import assert from 'node:assert/strict';
import {
bootFailureHint,
classifyBootFailure,
isInfrastructureBootFailureReason,
} from './boot-diagnostics.ts';
import { bootFailureHint, classifyBootFailure } from './boot-diagnostics.ts';
import { isInfrastructureBootFailureReason } from '@agent-device/contracts/boot-failure';
import { AppError } from '@agent-device/kernel/errors';
test('classifyBootFailure maps timeout errors', () => {
@@ -1,6 +1,5 @@
import { asAppError } from '@agent-device/kernel/errors';
import type { InfrastructureBootFailureReason } from '@agent-device/contracts/boot-failure';
export { isInfrastructureBootFailureReason } from '@agent-device/contracts/boot-failure';
export type { InfrastructureBootFailureReason } from '@agent-device/contracts/boot-failure';
export type BootFailureReason =
+7
View File
@@ -34,6 +34,13 @@ test('the live ios.yml paths-ignore agrees with the selector over every tracked
assert.deepEqual(messages(base), []);
});
test('the live iOS route carries no filters for the retired src/platforms tree', () => {
assert.deepEqual(
iosLane.pathsIgnore.filter((pattern) => pattern.startsWith('src/platforms/')),
[],
);
});
test('the routed lane derives its needs from its declared gate plus the sampled checks', () => {
assert.ok(iosLane.gates.includes('swift-runner-ios'), 'ios.yml still declares the runner build');
assert.deepEqual([...IOS.sampled], ['replay-ios', 'replay-ios-device']);
+2 -2
View File
@@ -97,9 +97,9 @@ test('logical modules reject forbidden imports', () => {
new Map([
[
'packages/replay-test/src/internal/scheduler.ts',
"import type { Device } from '../../../../src/platforms/device.ts';",
"import type { Device } from '../../../../src/providers/device.ts';",
],
['src/platforms/device.ts', 'export type Device = { id: string };'],
['src/providers/device.ts', 'export type Device = { id: string };'],
]),
);
+2 -14
View File
@@ -44,23 +44,12 @@ export const LOGICAL_MODULE_POLICIES: readonly LogicalModulePolicy[] = [
{
name: 'ad-replay',
roots: ['packages/ad-replay/src/'],
forbiddenTargetRoots: [
'src/daemon/',
'src/platforms/',
'src/providers/',
'src/compat/',
'packages/maestro/',
],
forbiddenTargetRoots: ['src/daemon/', 'src/providers/', 'src/compat/', 'packages/maestro/'],
},
{
name: 'maestro',
roots: ['packages/maestro/src/'],
forbiddenTargetRoots: [
'src/daemon/',
'src/platforms/',
'src/providers/',
'packages/ad-replay/',
],
forbiddenTargetRoots: ['src/daemon/', 'src/providers/', 'packages/ad-replay/'],
},
{
// Replay-test schedules and reports; it must stay format-neutral. `src/request/` is
@@ -71,7 +60,6 @@ export const LOGICAL_MODULE_POLICIES: readonly LogicalModulePolicy[] = [
roots: ['packages/replay-test/src/'],
forbiddenTargetRoots: [
'src/daemon/',
'src/platforms/',
'src/providers/',
'src/request/',
'src/replay/',
@@ -533,13 +533,11 @@ test('the real tree parses, declares, and passes R11', () => {
'@agent-device/platform-apple/debug-symbols',
'@agent-device/platform-apple/doctor',
'@agent-device/platform-apple/install-artifact',
'@agent-device/platform-apple/interactions',
'@agent-device/platform-apple/macos',
'@agent-device/platform-apple/perf',
'@agent-device/platform-apple/physical-device',
'@agent-device/platform-apple/runner',
'@agent-device/platform-apple/runner-owner',
'@agent-device/platform-apple/runner/client',
'@agent-device/platform-apple/runner/operations',
'@agent-device/platform-apple/runner/test-host',
'@agent-device/platform-apple/simctl',
@@ -31,13 +31,11 @@ function declarations(): PlatformPackageDeclaration[] {
'@agent-device/platform-apple/app-resolution',
'@agent-device/platform-apple/debug-symbols',
'@agent-device/platform-apple/doctor',
'@agent-device/platform-apple/interactions',
'@agent-device/platform-apple/install-artifact',
'@agent-device/platform-apple/macos',
'@agent-device/platform-apple/perf',
'@agent-device/platform-apple/physical-device',
'@agent-device/platform-apple/runner',
'@agent-device/platform-apple/runner/client',
'@agent-device/platform-apple/runner/test-host',
'@agent-device/platform-apple/runner/operations',
'@agent-device/platform-apple/runner-owner',
@@ -279,22 +277,6 @@ test('the apple runner mechanics facet subpaths are the enumerated exception', (
);
assert.deepEqual(checkPlatformPackagePolicy(sources, declarations()), []);
// The host-bound client factory has exactly one composition root.
const clientSources = validSources();
clientSources.set(
'packages/platform-apple/src/core/runner-client.ts',
"import { createAppleRunnerClient } from '@agent-device/platform-apple/runner/client';",
);
assert.deepEqual(checkPlatformPackagePolicy(clientSources, declarations()), []);
clientSources.set(
'src/daemon/handlers/session.ts',
"import { createAppleRunnerClient } from '@agent-device/platform-apple/runner/client';",
);
assert.match(
messages(clientSources).join('\n'),
/only the composition root packages\/platform-apple\/src\/core\/runner-client\.ts may construct/,
);
// The test-host installer is a single vitest setup file, dynamic imports included.
const testHostSources = validSources();
testHostSources.set(
@@ -328,7 +310,6 @@ test('the Apple domain facades preserve synchronous helpers without widening the
const sources = validSources();
for (const specifier of [
'@agent-device/platform-apple/app-resolution',
'@agent-device/platform-apple/interactions',
'@agent-device/platform-apple/install-artifact',
'@agent-device/platform-apple/perf',
'@agent-device/platform-apple/physical-device',
+1 -13
View File
@@ -60,9 +60,7 @@ export function checkRetiredPlatformsZone(files: readonly string[]): LayeringVio
}));
}
const APPLE_RUNNER_FACADE = '@agent-device/platform-apple/runner';
const APPLE_RUNNER_CLIENT = '@agent-device/platform-apple/runner/client';
const APPLE_RUNNER_TEST_HOST = '@agent-device/platform-apple/runner/test-host';
const APPLE_RUNNER_CLIENT_COMPOSITION = 'packages/platform-apple/src/core/runner-client.ts';
const APPLE_RUNNER_TEST_HOST_INSTALLER = 'scripts/vitest-apple-runner-host-setup.ts';
const ANDROID_MECHANICS_FACADE = '@agent-device/platform-android/mechanics';
const ANDROID_HOST_FACET = '@agent-device/platform-android/adb-host';
@@ -70,13 +68,11 @@ const ANDROID_HOST_BINDING = 'src/platform-runtime-android-adb-host.ts';
const MECHANICS_FACET_SUBPATHS: Readonly<Partial<Record<PlatformFamily, readonly string[]>>> = {
apple: [
APPLE_RUNNER_FACADE,
APPLE_RUNNER_CLIENT,
APPLE_RUNNER_TEST_HOST,
'@agent-device/platform-apple/app-lifecycle',
'@agent-device/platform-apple/app-resolution',
'@agent-device/platform-apple/debug-symbols',
'@agent-device/platform-apple/doctor',
'@agent-device/platform-apple/interactions',
'@agent-device/platform-apple/install-artifact',
'@agent-device/platform-apple/macos',
'@agent-device/platform-apple/perf',
@@ -258,15 +254,7 @@ function checkSource(file: string, source: string): LayeringViolation[] {
),
);
}
if (site.spec === APPLE_RUNNER_CLIENT && file !== APPLE_RUNNER_CLIENT_COMPOSITION) {
violations.push(
violation(
file,
site.line,
`'${APPLE_RUNNER_CLIENT}' is the host-bound runner client factory — only the composition root ${APPLE_RUNNER_CLIENT_COMPOSITION} may construct the client`,
),
);
} else if (site.spec === APPLE_RUNNER_TEST_HOST && file !== APPLE_RUNNER_TEST_HOST_INSTALLER) {
if (site.spec === APPLE_RUNNER_TEST_HOST && file !== APPLE_RUNNER_TEST_HOST_INSTALLER) {
violations.push(
violation(
file,
+7 -11
View File
@@ -113,10 +113,8 @@ export const FACADE_BUDGETS: Readonly<Record<string, number>> = Object.freeze({
// --- @agent-device/platform-apple/runner ---
// #2040 extraction: the façade stays types/pure-helpers/bundle-ids; the whole
// client implementation loads only through the './client' subpath, which the
// root composition module reaches behind its consumers' dynamic imports.
// client implementation is package-internal and loads only behind consumers' dynamic imports.
'packages/platform-apple/src/runner/index.ts': 13,
'packages/platform-apple/src/runner/client.ts': 46,
'packages/platform-apple/src/runner/test-host.ts': 2,
// --- @agent-device/capture-kit ---
@@ -148,7 +146,7 @@ export const FACADE_BUDGETS: Readonly<Record<string, number>> = Object.freeze({
// --- @agent-device/provision-kit ---
'packages/provision-kit/src/app-resolution-cache.ts': 1,
'packages/provision-kit/src/boot-diagnostics.ts': 4,
'packages/provision-kit/src/boot-diagnostics.ts': 3,
'packages/provision-kit/src/install-artifact-archive-context.ts': 10,
'packages/provision-kit/src/install-source.ts': 25,
'packages/provision-kit/src/install-source-network.ts': 3,
@@ -294,23 +292,22 @@ export const FACADE_BUDGETS: Readonly<Record<string, number>> = Object.freeze({
'packages/platform-android/src/adb-host.ts': 1,
// The named mechanics facet is intentionally implementation-eager once selected. Its exact
// closure is pinned so a future facade expansion is visible in review.
'packages/platform-android/src/mechanics.ts': 177,
'packages/platform-android/src/mechanics.ts': 176,
// --- @agent-device/platform-apple ---
'packages/platform-apple/src/index.ts': 1,
'packages/platform-apple/src/app-lifecycle-facade.ts': 120,
'packages/platform-apple/src/app-lifecycle-facade.ts': 119,
'packages/platform-apple/src/app-resolution-facade.ts': 61,
'packages/platform-apple/src/debug-symbols-facade.ts': 24,
'packages/platform-apple/src/doctor-facade.ts': 101,
'packages/platform-apple/src/interactions-facade.ts': 117,
'packages/platform-apple/src/doctor-facade.ts': 100,
'packages/platform-apple/src/install-artifact-facade.ts': 41,
'packages/platform-apple/src/macos-facade.ts': 25,
'packages/platform-apple/src/perf-facade.ts': 60,
'packages/platform-apple/src/physical-device-facade.ts': 47,
'packages/platform-apple/src/runner-operations-facade.ts': 100,
'packages/platform-apple/src/runner-operations-facade.ts': 99,
'packages/platform-apple/src/runner-owner-facade.ts': 2,
'packages/platform-apple/src/simctl-facade.ts': 18,
'packages/platform-apple/src/simulator-facade.ts': 26,
'packages/platform-apple/src/simulator-facade.ts': 25,
'packages/platform-apple/src/tool-provider-facade.ts': 14,
// --- @agent-device/platform-harmonyos ---
@@ -419,7 +416,6 @@ const APPLE_DOMAIN_MECHANICS_ENTRY_FILES: ReadonlySet<string> = new Set([
'packages/platform-apple/src/app-resolution-facade.ts',
'packages/platform-apple/src/debug-symbols-facade.ts',
'packages/platform-apple/src/doctor-facade.ts',
'packages/platform-apple/src/interactions-facade.ts',
'packages/platform-apple/src/install-artifact-facade.ts',
'packages/platform-apple/src/macos-facade.ts',
'packages/platform-apple/src/perf-facade.ts',
-10
View File
@@ -8,7 +8,6 @@ import { setActiveProviderDeviceRuntimes } from '../../provider-device-runtime.t
import type { ProviderDeviceRuntime } from '@agent-device/contracts/device';
import { getInteractor, getLocalInteractor } from '../../core/interactors.ts';
import { resolveAppleBackRunnerCommand } from '@agent-device/platform-apple/interactions';
const iosSimulator: DeviceInfo = {
platform: 'apple',
@@ -51,15 +50,6 @@ afterEach(() => {
setActiveProviderDeviceRuntimes([]);
});
test('resolveAppleBackRunnerCommand defaults plain back to in-app navigation', () => {
assert.equal(resolveAppleBackRunnerCommand(), 'backInApp');
});
test('resolveAppleBackRunnerCommand maps explicit back modes to runner commands', () => {
assert.equal(resolveAppleBackRunnerCommand('in-app'), 'backInApp');
assert.equal(resolveAppleBackRunnerCommand('system'), 'backSystem');
});
test('provider device interactor receives runner context from core resolution', async () => {
const device: DeviceInfo = {
platform: 'apple',