From a362a3aa9f4f3e0c2fd902cdeffa5f28502ff340 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Wed, 7 Oct 2026 22:22:26 +0200 Subject: [PATCH 1/5] refactor: remove test-only seams, dead option fields, and duplicate tests - DaemonError is NormalizedError; one wire shape instead of two copies - host-kit: one override-path resolver, inline the canceled-command wrapper, and export the abort-aware sleep instead of a second module - provision-kit: drop the downloadTimeoutMs option no caller supplies - android observation reads blocking dialogs through app-parsers - delete shutdownSimulator, providerDeviceAdmission(), the unused CoordinateGesturePayload, getCloseShutdown, prefixItems, and selectMaestroSnapshotMatches, each reachable only from tests - import isLikelyStaleSnapshotDrop instead of a verbatim copy - drop five tests that restated a sibling or their own input, and wire the never-run integration-progress model test into check:affected:test Co-Authored-By: Claude Fable 5.1 --- package.json | 2 +- .../src/snapshot/snapshot-freshness/index.ts | 1 + packages/contracts/src/gesture-input.ts | 2 - packages/contracts/src/platform-runtime.ts | 4 - .../src/internal/exec-kill-settle.test.ts | 2 +- packages/host-kit/src/internal/exec.ts | 38 ++++------ .../host-kit/src/internal/host-process.ts | 2 +- .../host-kit/src/internal/process-lock.ts | 2 +- packages/host-kit/src/internal/retry.ts | 2 +- packages/host-kit/src/internal/timeouts.ts | 3 - packages/host-kit/src/retry.ts | 9 ++- packages/kernel/src/errors.ts | 22 +----- .../__tests__/runtime-target-ranking.test.ts | 22 +----- .../src/internal/runtime-target-ranking.ts | 9 --- packages/platform-android/src/observation.ts | 75 +++---------------- .../__tests__/simulator-booted-memo.test.ts | 22 ------ packages/platform-apple/src/core/simulator.ts | 24 ------ packages/provision-kit/src/install-source.ts | 20 ++--- .../lockfile-install-sync.test.ts | 26 ------- scripts/check-affected/model.test.ts | 17 ----- .../deep-button.test.ts | 11 --- scripts/mutation/config.test.ts | 1 - src/commands/capture/runtime/snapshot.ts | 6 +- src/commands/command-contract.ts | 1 - .../provider-device-admission.test.ts | 11 +-- src/daemon/provider-device-admission.ts | 4 - .../output-economy.baseline.test.ts | 12 --- 27 files changed, 55 insertions(+), 295 deletions(-) delete mode 100644 packages/host-kit/src/internal/timeouts.ts diff --git a/package.json b/package.json index 040fd12169..ac0d9f71ee 100644 --- a/package.json +++ b/package.json @@ -148,7 +148,7 @@ "fallow:baseline": "fallow dead-code --save-baseline fallow-baselines/dead-code.json --summary && fallow health --report-only --save-baseline fallow-baselines/health.json --summary", "check:fallow": "fallow audit", "check:affected": "node --experimental-strip-types scripts/check-affected/run.ts", - "check:affected:test": "node --experimental-strip-types scripts/node-test-tmpdir.ts --experimental-strip-types --test scripts/check-affected/model.test.ts scripts/check-affected/platform-packages.test.ts scripts/check-affected/device-lanes.test.ts scripts/check-affected/lockfile-install-sync.test.ts scripts/check-affected/run.test.ts", + "check:affected:test": "node --experimental-strip-types scripts/node-test-tmpdir.ts --experimental-strip-types --test scripts/check-affected/model.test.ts scripts/integration-progress-model.test.ts scripts/check-affected/platform-packages.test.ts scripts/check-affected/device-lanes.test.ts scripts/check-affected/lockfile-install-sync.test.ts scripts/check-affected/run.test.ts", "gate": "node --experimental-strip-types scripts/gate/run.ts", "check:gate-manifest": "node --experimental-strip-types scripts/gate/check.ts", "check:gate-manifest:test": "node --experimental-strip-types scripts/node-test-tmpdir.ts --experimental-strip-types --test scripts/gate/*.test.ts", diff --git a/packages/capture-kit/src/snapshot/snapshot-freshness/index.ts b/packages/capture-kit/src/snapshot/snapshot-freshness/index.ts index 0837ecb5d5..63a1e04915 100644 --- a/packages/capture-kit/src/snapshot/snapshot-freshness/index.ts +++ b/packages/capture-kit/src/snapshot/snapshot-freshness/index.ts @@ -5,6 +5,7 @@ export { ANDROID_FRESHNESS_WINDOW_MS, androidFreshnessReason, buildSnapshotSignatures, + isLikelyStaleSnapshotDrop, isNavigationSensitiveAction, } from './android.ts'; export { diff --git a/packages/contracts/src/gesture-input.ts b/packages/contracts/src/gesture-input.ts index d8e2ffde30..d2294fd554 100644 --- a/packages/contracts/src/gesture-input.ts +++ b/packages/contracts/src/gesture-input.ts @@ -85,8 +85,6 @@ export type GesturePayload = | TransformGesturePayload | DragGesturePayload; -export type CoordinateGesturePayload = Exclude; - export function readGesturePayload(input: unknown): GesturePayload { const record = readRecord(input); const kind = readEnum(record, 'kind', GESTURE_KINDS); diff --git a/packages/contracts/src/platform-runtime.ts b/packages/contracts/src/platform-runtime.ts index dff20619e5..8692c3c685 100644 --- a/packages/contracts/src/platform-runtime.ts +++ b/packages/contracts/src/platform-runtime.ts @@ -253,10 +253,6 @@ export type DeviceBindingRequest = Readonly<{ export type DeviceRuntimeGateway = Readonly<{ inspectFacts(device: DeviceInfo): Promise>; bind(request: DeviceBindingRequest): Promise>; - /** Complete close compatibility capability; unlike bind, this never admits a runtime facet. */ - getCloseShutdown?: () => Promise< - import('./device-shutdown-runtime.ts').DeviceShutdownCloseCapability - >; shutdown(): Promise; /** Package-coordinated durable lifecycle phases used for startup recovery and daemon shutdown. */ applicationLifecycle?: ApplicationLifecycleResourceLifecycle; diff --git a/packages/host-kit/src/internal/exec-kill-settle.test.ts b/packages/host-kit/src/internal/exec-kill-settle.test.ts index 98de96233d..f9701ad61e 100644 --- a/packages/host-kit/src/internal/exec-kill-settle.test.ts +++ b/packages/host-kit/src/internal/exec-kill-settle.test.ts @@ -11,7 +11,7 @@ import { type ExecBackgroundOptions, } from './exec.ts'; import { shellQuote } from '@agent-device/kernel/device-shell'; -import { sleep } from './timeouts.ts'; +import { sleep } from './retry.ts'; import { mkdtempForTestSync } from './tmp-dir.fixtures.ts'; // A direct child can hand our stdout/stderr pipes to a descendant, and `close` waits diff --git a/packages/host-kit/src/internal/exec.ts b/packages/host-kit/src/internal/exec.ts index 17ece9eb6a..d7adefd874 100644 --- a/packages/host-kit/src/internal/exec.ts +++ b/packages/host-kit/src/internal/exec.ts @@ -313,28 +313,28 @@ export async function resolveExecutableOverridePath( rawPath: string | undefined, envName: string, ): Promise { - const candidate = normalizeOverridePath(rawPath, envName, 'executable'); - if (!candidate) return undefined; - if (!(await isExecutablePath(candidate))) { - throw new AppError( - 'TOOL_MISSING', - `${envName} points to a missing or non-executable file: ${candidate}`, - { envName, path: candidate }, - ); - } - return candidate; + return await resolveOverridePath(rawPath, envName, 'executable', isExecutablePath); } export async function resolveFileOverridePath( rawPath: string | undefined, envName: string, ): Promise { - const candidate = normalizeOverridePath(rawPath, envName, 'file'); + return await resolveOverridePath(rawPath, envName, 'file', isFilePath); +} + +async function resolveOverridePath( + rawPath: string | undefined, + envName: string, + kind: 'executable' | 'file', + isUsable: (candidate: string) => Promise, +): Promise { + const candidate = normalizeOverridePath(rawPath, envName, kind); if (!candidate) return undefined; - if (!(await isFilePath(candidate))) { + if (!(await isUsable(candidate))) { throw new AppError( 'TOOL_MISSING', - `${envName} points to a missing or non-file path: ${candidate}`, + `${envName} points to a missing or non-${kind} ${kind === 'file' ? 'path' : 'file'}: ${candidate}`, { envName, path: candidate }, ); } @@ -647,14 +647,6 @@ function createStdinError( ); } -function createCommandCanceledError( - executable: string, - cmd: string, - args: readonly string[], -): AppError { - return createRequestCanceledError({ cmd, args, executable }); -} - function createTimeoutError( executable: string, cmd: string, @@ -768,7 +760,7 @@ function spawnRejectionError( err: Error, ): AppError { return abort.didAbort - ? createCommandCanceledError(executable, cmd, args) + ? createRequestCanceledError({ cmd, args, executable }) : createSpawnError(executable, cmd, args, err); } @@ -782,7 +774,7 @@ function commandCloseFailure( stdout: string, stderr: string, ): AppError | null { - if (abort.didAbort) return createCommandCanceledError(executable, cmd, args); + if (abort.didAbort) return createRequestCanceledError({ cmd, args, executable }); if (exitCode !== 0 && !allowFailure) { return createExitError(executable, cmd, args, exitCode, stdout, stderr); } diff --git a/packages/host-kit/src/internal/host-process.ts b/packages/host-kit/src/internal/host-process.ts index 316605cce9..f292b5065a 100644 --- a/packages/host-kit/src/internal/host-process.ts +++ b/packages/host-kit/src/internal/host-process.ts @@ -1,6 +1,6 @@ import type { ExecOptions, ExecResult } from './exec.ts'; import { runCmd, runCmdSync } from './exec.ts'; -import { sleep } from './timeouts.ts'; +import { sleep } from './retry.ts'; const PS_TIMEOUT_MS = 1_000; const HOST_PS_COMMAND = process.platform === 'win32' ? 'ps' : '/bin/ps'; diff --git a/packages/host-kit/src/internal/process-lock.ts b/packages/host-kit/src/internal/process-lock.ts index d5352530b4..7d0e3bc7ef 100644 --- a/packages/host-kit/src/internal/process-lock.ts +++ b/packages/host-kit/src/internal/process-lock.ts @@ -10,7 +10,7 @@ import { ownerIdentityMatches, type OwnerLiveness, } from './owner-identity.ts'; -import { sleep } from './timeouts.ts'; +import { sleep } from './retry.ts'; const OWNER_FILE_NAME = 'owner.json'; const DEFAULT_LOCK_TIMEOUT_MS = 30_000; diff --git a/packages/host-kit/src/internal/retry.ts b/packages/host-kit/src/internal/retry.ts index f6a529b401..8f143c5370 100644 --- a/packages/host-kit/src/internal/retry.ts +++ b/packages/host-kit/src/internal/retry.ts @@ -224,7 +224,7 @@ function computeDelay(base: number, max: number, jitter: number, attempt: number return Math.max(0, exp + (Math.random() * 2 - 1) * jitterAmount); } -function sleep(ms: number, signal?: AbortSignal): Promise { +export function sleep(ms: number, signal?: AbortSignal): Promise { return new Promise((resolve) => { if (signal?.aborted) { resolve(); diff --git a/packages/host-kit/src/internal/timeouts.ts b/packages/host-kit/src/internal/timeouts.ts deleted file mode 100644 index 421bda080a..0000000000 --- a/packages/host-kit/src/internal/timeouts.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function sleep(ms: number): Promise { - return new Promise((resolve) => setTimeout(resolve, ms)); -} diff --git a/packages/host-kit/src/retry.ts b/packages/host-kit/src/retry.ts index 7c112562c4..6f5496352a 100644 --- a/packages/host-kit/src/retry.ts +++ b/packages/host-kit/src/retry.ts @@ -1,2 +1,7 @@ -export { Deadline, type DeadlineClock, isEnvTruthy, retryWithPolicy } from './internal/retry.ts'; -export { sleep } from './internal/timeouts.ts'; +export { + Deadline, + type DeadlineClock, + isEnvTruthy, + retryWithPolicy, + sleep, +} from './internal/retry.ts'; diff --git a/packages/kernel/src/errors.ts b/packages/kernel/src/errors.ts index 56ed18b6bd..33d23c4572 100644 --- a/packages/kernel/src/errors.ts +++ b/packages/kernel/src/errors.ts @@ -222,27 +222,7 @@ function readDeviceList(value: unknown): Array<{ id: string; name: string }> { * error representation because clients immediately rehydrate this wire shape * into `AppError` before rendering or handling it. */ -export type DaemonError = { - code: string; - message: string; - cause?: ErrorCause; - hint?: string; - diagnosticId?: string; - /** Path on the DAEMON host. Meaningful to a local caller only (#1801). */ - logPath?: string; - /** Why no readable path is named; set by the client, never by the daemon. */ - logPathUnavailable?: string; - /** - * Additive locator (#1801) for the request diagnostics record `logPath` - * names, so a remote caller can fetch it over the daemon API instead of - * being handed a path on a filesystem it cannot read. - */ - diagnosticsRecord?: DiagnosticsRecordRef; - details?: ErrorWireDetails; - /** Additive retry and platform-support signals; absent when not derivable. */ - retriable?: boolean; - supportedOn?: string; -}; +export type DaemonError = NormalizedError; /** Rehydrate a daemon transport error into the error type used by local callers. */ export function throwDaemonError(error: DaemonError): never { diff --git a/packages/maestro/src/internal/__tests__/runtime-target-ranking.test.ts b/packages/maestro/src/internal/__tests__/runtime-target-ranking.test.ts index 3807a8b209..1afd6a852b 100644 --- a/packages/maestro/src/internal/__tests__/runtime-target-ranking.test.ts +++ b/packages/maestro/src/internal/__tests__/runtime-target-ranking.test.ts @@ -1,10 +1,6 @@ import { expect, test } from 'vitest'; import { attachSnapshotClickabilityEvidence } from '@agent-device/contracts/capture'; -import { - rankMaestroCandidates, - selectMaestroSnapshotMatch, - selectMaestroSnapshotMatches, -} from '../runtime-target-ranking.ts'; +import { rankMaestroCandidates, selectMaestroSnapshotMatch } from '../runtime-target-ranking.ts'; import { makeSnapshot } from './runtime-target-fixtures.ts'; test('typed target matching preserves snapshot read order', () => { @@ -226,22 +222,6 @@ test('authored index sorts by y then x with missing bounds last', () => { expect(selectMaestroSnapshotMatch(snapshot.nodes, 2)).toBeNull(); }); -test('nested childOf applies its own index before scoping the candidate', () => { - const snapshot = makeSnapshot([ - { index: 0, identifier: 'row', rect: { x: 0, y: 200, width: 200, height: 80 } }, - { index: 1, identifier: 'row', rect: { x: 0, y: 20, width: 200, height: 80 } }, - { index: 2, parentIndex: 0, label: 'Delete', rect: { x: 120, y: 220, width: 60, height: 40 } }, - { index: 3, parentIndex: 1, label: 'Delete', rect: { x: 120, y: 40, width: 60, height: 40 } }, - ]); - - expect( - selectMaestroSnapshotMatches(snapshot, { - text: 'Delete', - childOf: { id: 'row', index: 1 }, - }).map((node) => node.index), - ).toEqual([2]); -}); - test('target selection never fabricates a rectangle or promotes to an ancestor', () => { const snapshot = makeSnapshot([ { diff --git a/packages/maestro/src/internal/runtime-target-ranking.ts b/packages/maestro/src/internal/runtime-target-ranking.ts index a762fbe35a..291e3c8a50 100644 --- a/packages/maestro/src/internal/runtime-target-ranking.ts +++ b/packages/maestro/src/internal/runtime-target-ranking.ts @@ -66,15 +66,6 @@ export function matchMaestroCandidatesWithResolver( return { matches, parentMatched }; } -export function selectMaestroSnapshotMatches( - snapshot: SnapshotState, - selector: MaestroSelector, - platform?: MaestroPlatform, -): SnapshotNode[] { - const clickability = platform ? resolveMaestroClickability(snapshot, platform) : undefined; - return createMaestroResolver(snapshot, clickability).resolve(selector).indexed; -} - export function selectMaestroPositionMatches( snapshot: SnapshotState, relation: MaestroPositionRelation, diff --git a/packages/platform-android/src/observation.ts b/packages/platform-android/src/observation.ts index 68fbab4f5a..b93bb6bf17 100644 --- a/packages/platform-android/src/observation.ts +++ b/packages/platform-android/src/observation.ts @@ -1,5 +1,4 @@ import type { - AndroidBlockingDialogFocus, AndroidBlockingDialogObservation, AndroidObservationAdapter, AndroidObservationHost, @@ -8,14 +7,12 @@ import type { AppStateRuntimeResult } from '@agent-device/contracts/app-state-ru import type { DeviceInfo } from '@agent-device/kernel/device'; import { deviceShellArgv } from '@agent-device/kernel/device-shell'; import { AppError } from '@agent-device/kernel/errors'; +import { + ANDROID_FOCUS_MARKERS, + ANDROID_FOCUSED_WINDOW_MARKER, + readAndroidBlockingDialogFocus, +} from './app-parsers.ts'; -const FOCUSED_WINDOW_MARKER = 'mCurrentFocus=Window{'; -const FOCUS_MARKERS = [ - FOCUSED_WINDOW_MARKER, - 'mFocusedApp=AppWindowToken{', - 'mResumedActivity:', - 'ResumedActivity:', -] as const; const WINDOW_DUMPS: readonly (readonly string[])[] = [ ['dumpsys', 'window', 'windows'], ['dumpsys', 'window'], @@ -24,9 +21,10 @@ const ACTIVITY_DUMPS: readonly (readonly string[])[] = [ ['dumpsys', 'activity', 'activities'], ['dumpsys', 'activity'], ]; -const FOCUS_LINE = new RegExp(`(?:${FOCUS_MARKERS.map(escapeRegExp).join('|')})(.*)$`, 'gm'); -const ANR_TITLE = /\bApplication Not Responding:\s*([A-Za-z0-9_.]+)/i; -const PACKAGE_NAME = /\b([A-Za-z][A-Za-z0-9_]*(?:\.[A-Za-z0-9_]+)+)\b/; +const FOCUS_LINE = new RegExp( + `(?:${ANDROID_FOCUS_MARKERS.map(escapeRegExp).join('|')})(.*)$`, + 'gm', +); const PERMISSION_PACKAGES = new Set([ 'com.android.permissioncontroller', 'com.google.android.permissioncontroller', @@ -113,7 +111,7 @@ async function readBlockingDialog( read: DumpReader = createDumpReader(host, device), ): Promise { for (const args of orderedDumps(device, 'blockingDialog')) { - const parsed = parseBlockingDialog(await read(args)); + const parsed = readAndroidBlockingDialogFocus(await read(args)); if (parsed.focus) return { status: 'dialog', focus: parsed.focus }; if (parsed.focusObserved) return { status: 'clear' }; } @@ -137,11 +135,11 @@ function orderedDumps(device: DeviceInfo, question: Question): readonly (readonl function recordSections(device: DeviceInfo, key: string, text: string): void { if (!text) return; const memo = everAnswered.get(device.id) ?? new Map(); - preservePositiveMemo(memo, `foreground ${key}`, markerPattern(FOCUS_MARKERS).test(text)); + preservePositiveMemo(memo, `foreground ${key}`, markerPattern(ANDROID_FOCUS_MARKERS).test(text)); preservePositiveMemo( memo, `blockingDialog ${key}`, - markerPattern([FOCUSED_WINDOW_MARKER]).test(text), + markerPattern([ANDROID_FOCUSED_WINDOW_MARKER]).test(text), ); everAnswered.set(device.id, memo); } @@ -160,55 +158,6 @@ function parseForeground(text: string): AppStateRuntimeResult | null { return null; } -function parseBlockingDialog(text: string): { - focusObserved: boolean; - focus: AndroidBlockingDialogFocus | null; -} { - let focusObserved = false; - const lines = text.split('\n'); - for (const marker of FOCUS_MARKERS) { - for (const line of lines) { - const index = line.indexOf(marker); - if (index === -1) continue; - if (marker === FOCUSED_WINDOW_MARKER) focusObserved = true; - const raw = line.trim(); - const segment = - line - .slice(index + marker.length) - .split('}')[0] - ?.trim() ?? ''; - const focus = parseDialogFocus(segment, raw); - if (focus) return { focusObserved, focus }; - } - } - return { focusObserved, focus: null }; -} - -function parseDialogFocus(segment: string, raw: string): AndroidBlockingDialogFocus | null { - const anrPackage = ANR_TITLE.exec(segment)?.[1]; - if (anrPackage) { - return { - package: anrPackage, - focusedWindow: `Application Not Responding: ${anrPackage}`, - raw, - }; - } - const normalizedSegment = segment.toLowerCase(); - if ( - !normalizedSegment.includes("isn't responding") && - !normalizedSegment.includes('is not responding') - ) { - return null; - } - const responding = segment.trim().replaceAll(/\s+/g, ' '); - const packageName = PACKAGE_NAME.exec(responding)?.[1]; - return { - ...(packageName ? { package: packageName } : {}), - focusedWindow: responding, - raw, - }; -} - function markerPattern(markers: readonly string[]): RegExp { return new RegExp(markers.map(escapeRegExp).join('|')); } diff --git a/packages/platform-apple/src/core/__tests__/simulator-booted-memo.test.ts b/packages/platform-apple/src/core/__tests__/simulator-booted-memo.test.ts index 541f30b9eb..1991ff970a 100644 --- a/packages/platform-apple/src/core/__tests__/simulator-booted-memo.test.ts +++ b/packages/platform-apple/src/core/__tests__/simulator-booted-memo.test.ts @@ -3,7 +3,6 @@ import type { DeviceInfo } from '@agent-device/kernel/device'; import { ensureBootedSimulator, markSimulatorBooted, - shutdownSimulator, SIMULATOR_BOOTED_MEMO_TTL_MS, wasSimulatorRecentlyObservedBooted, } from '../simulator.ts'; @@ -62,27 +61,6 @@ test('ensureBootedSimulator skips the state listing within the booted memo TTL', expect(countSimctlListCalls()).toBe(2); }); -test('shutdownSimulator invalidates the booted memo', async () => { - await ensureBootedSimulator(simulator); - expect(countSimctlListCalls()).toBe(1); - - await shutdownSimulator(simulator); - - await ensureBootedSimulator(simulator); - expect(countSimctlListCalls()).toBe(2); -}); - -test('shutdownSimulator forwards cancellation to the native simctl command', async () => { - const controller = new AbortController(); - - await shutdownSimulator(simulator, controller.signal); - - expect(mockRunXcrun).toHaveBeenCalledWith( - ['simctl', 'shutdown', simulator.id], - expect.objectContaining({ signal: controller.signal }), - ); -}); - test('markSimulatorBooted seeds the memo so the first boot check skips the listing', async () => { markSimulatorBooted(simulator); diff --git a/packages/platform-apple/src/core/simulator.ts b/packages/platform-apple/src/core/simulator.ts index 7ffd1b56d1..7317ada69a 100644 --- a/packages/platform-apple/src/core/simulator.ts +++ b/packages/platform-apple/src/core/simulator.ts @@ -56,10 +56,6 @@ export function markSimulatorBooted(device: DeviceInfo): void { simulatorBootedMemo.set(simulatorBootedMemoKey(device), true); } -function clearSimulatorBootedMemo(device: DeviceInfo): void { - simulatorBootedMemo.delete(simulatorBootedMemoKey(device)); -} - export function requireSimulatorDevice(device: DeviceInfo, command: string): void { if (device.kind !== 'simulator') { throw new AppError('UNSUPPORTED_OPERATION', `${command} is only supported on iOS simulators`); @@ -209,26 +205,6 @@ export async function ensureBootedSimulator( await openIosSimulatorApp({ signal: options.signal }); } -export async function shutdownSimulator( - device: DeviceInfo, - signal?: AbortSignal, -): Promise<{ - success: boolean; - exitCode: number; - stdout: string; - stderr: string; -}> { - clearSimulatorBootedMemo(device); - const args = buildSimctlArgsForDevice(device, ['shutdown', device.id]); - const result = await runXcrun(args, { allowFailure: true, timeoutMs: 15_000, signal }); - return { - success: result.exitCode === 0, - exitCode: result.exitCode, - stdout: result.stdout, - stderr: result.stderr, - }; -} - async function getSimulatorState(device: DeviceInfo, signal?: AbortSignal): Promise { const simctlArgs = buildSimctlArgsForDevice(device, ['list', 'devices', '-j']); const result = await runXcrun(simctlArgs, { diff --git a/packages/provision-kit/src/install-source.ts b/packages/provision-kit/src/install-source.ts index 6db5b72c21..7435a1466f 100644 --- a/packages/provision-kit/src/install-source.ts +++ b/packages/provision-kit/src/install-source.ts @@ -27,7 +27,6 @@ export type MaterializeInstallableOptions = { ) => boolean; installableLabel: string; signal?: AbortSignal; - downloadTimeoutMs?: number; }; export type MaterializedInstallable = { @@ -51,10 +50,7 @@ export async function materializeInstallablePath( ): Promise { const cleanupTasks: Array<() => Promise> = []; try { - const localSource = await materializeLocalSource(options.source, { - signal: options.signal, - downloadTimeoutMs: options.downloadTimeoutMs, - }); + const localSource = await materializeLocalSource(options.source, { signal: options.signal }); cleanupTasks.push(localSource.cleanup); const resolved = await resolveInstallableCandidate(localSource.localPath, { archivePath: undefined, @@ -89,7 +85,7 @@ function expandSourcePath(inputPath: string): string { async function materializeLocalSource( source: LocalInstallSource, - options?: { signal?: AbortSignal; downloadTimeoutMs?: number }, + options?: { signal?: AbortSignal }, ): Promise { if (source.kind === 'path') { return { @@ -117,19 +113,23 @@ async function downloadToTempFile( tempDir: string, url: string, headers?: Record, - options?: { signal?: AbortSignal; downloadTimeoutMs?: number }, + options?: { signal?: AbortSignal }, ): Promise { const requestSignal = options?.signal; if (requestSignal?.aborted) { throw createRequestCanceledError(); } - const timeoutMs = options?.downloadTimeoutMs ?? DEFAULT_SOURCE_DOWNLOAD_TIMEOUT_MS; - const timeoutSignal = AbortSignal.timeout(timeoutMs); + const timeoutSignal = AbortSignal.timeout(DEFAULT_SOURCE_DOWNLOAD_TIMEOUT_MS); const signal = requestSignal ? AbortSignal.any([requestSignal, timeoutSignal]) : timeoutSignal; try { return await downloadInstallSource({ tempDir, url, headers, signal }); } catch (error) { - throw classifyDownloadError(error, requestSignal, timeoutSignal, timeoutMs); + throw classifyDownloadError( + error, + requestSignal, + timeoutSignal, + DEFAULT_SOURCE_DOWNLOAD_TIMEOUT_MS, + ); } } diff --git a/scripts/check-affected/lockfile-install-sync.test.ts b/scripts/check-affected/lockfile-install-sync.test.ts index da6076bf12..994bb50be1 100644 --- a/scripts/check-affected/lockfile-install-sync.test.ts +++ b/scripts/check-affected/lockfile-install-sync.test.ts @@ -44,17 +44,6 @@ test('reports install-missing when a source checkout has no installed snapshot', }); }); -test('reports install-missing for a fresh worktree that has no node_modules directory at all', () => { - const root = mkdtempForTestSync('agent-device-lockfile-sync-fresh-worktree-'); - writeLockfile(root, 'lockfileVersion: 9.0\n'); - assert.equal(fs.existsSync(path.join(root, 'node_modules')), false); - - assert.deepEqual(checkLockfileInstallSync(root), { - status: 'out-of-sync', - reason: 'install-missing', - }); -}); - test('reports no-source-checkout when there is no pnpm-lock.yaml', () => { const root = mkdtempForTestSync('agent-device-lockfile-sync-packaged-'); fs.writeFileSync(path.join(root, 'package.json'), '{"name":"agent-device"}\n'); @@ -68,18 +57,3 @@ test('reports no-source-checkout even when an installed snapshot exists without assert.deepEqual(checkLockfileInstallSync(root), { status: 'no-source-checkout' }); }); - -test('checks each worktree against its own lockfile state', () => { - const worktreeA = mkdtempForTestSync('agent-device-lockfile-sync-worktree-a-'); - const worktreeB = mkdtempForTestSync('agent-device-lockfile-sync-worktree-b-'); - writeLockfile(worktreeA, 'lockfileVersion: 9.0\nimporters:\n .: {}\n'); - writeInstalledSnapshot(worktreeA, 'lockfileVersion: 9.0\nimporters:\n .: {}\n'); - writeLockfile( - worktreeB, - 'lockfileVersion: 9.0\nimporters:\n .:\n dependencies:\n newDep: 1.0.0\n', - ); - writeInstalledSnapshot(worktreeB, 'lockfileVersion: 9.0\nimporters:\n .: {}\n'); - - assert.deepEqual(checkLockfileInstallSync(worktreeA), { status: 'in-sync' }); - assert.deepEqual(checkLockfileInstallSync(worktreeB), { status: 'out-of-sync', reason: 'stale' }); -}); diff --git a/scripts/check-affected/model.test.ts b/scripts/check-affected/model.test.ts index 3b335d66c1..ead87c347e 100644 --- a/scripts/check-affected/model.test.ts +++ b/scripts/check-affected/model.test.ts @@ -409,21 +409,4 @@ test('vitest-related delegates changed paths to Vitest instead of modeling proje ); }); -// Guards the catalog against reality, not fixtures: the self-test above uses a -// hand-built scripts map, so this resolves every catalog entry against the real -// package.json. A renamed/removed script fails here instead of -// leaving `pnpm check:affected` broken on the exact command the docs advertise. const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..'); - -test('catalog resolves against the real package.json', () => { - const pkg = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8')) as { - scripts?: Record; - }; - const scripts = pkg.scripts ?? {}; - for (const spec of CHECK_CATALOG) { - assert.doesNotThrow( - () => resolveCommand(spec, scripts, 'origin/main'), - `catalog entry "${spec.id}" must resolve against the real package.json`, - ); - } -}); diff --git a/scripts/ios-snapshot-benchmark/deep-button.test.ts b/scripts/ios-snapshot-benchmark/deep-button.test.ts index 1feb9b4b4d..b91619367f 100644 --- a/scripts/ios-snapshot-benchmark/deep-button.test.ts +++ b/scripts/ios-snapshot-benchmark/deep-button.test.ts @@ -3,7 +3,6 @@ import { test } from 'vitest'; import { assertInvalidShallowRuleFails, assertSafeFullRulePasses, - deepButtonFixtureEvidence, readDeepButtonFixtureArtifact, } from './deep-button.ts'; @@ -16,16 +15,6 @@ test('the checked-in fixture has a real 72-level ancestor chain', () => { assert.notDeepEqual(artifact.before.changedNode, artifact.after.changedNode); }); -test('the shallow control observes no change while the full tree changes', () => { - const evidence = deepButtonFixtureEvidence(); - assert.equal(evidence.artifact, 'deep-button-fixture.v1.json'); - assert.equal(evidence.depth, 72); - assert.equal(evidence.before.surfaceDigest, evidence.after.surfaceDigest); - assert.notEqual(evidence.before.fullDigest, evidence.after.fullDigest); - assert.ok(!evidence.after.surfaceNodeIds.includes(evidence.changedDescendant)); - assert.ok(evidence.after.fullNodeIds.includes(evidence.changedDescendant)); -}); - test('the planted invalid rule is red and the full rule is green', () => { assert.throws(assertInvalidShallowRuleFails, /changed descendant was omitted/); assert.doesNotThrow(assertSafeFullRulePasses); diff --git a/scripts/mutation/config.test.ts b/scripts/mutation/config.test.ts index c6896a5735..c61e60a9c9 100644 --- a/scripts/mutation/config.test.ts +++ b/scripts/mutation/config.test.ts @@ -35,7 +35,6 @@ test('stryker owns no pass/fail threshold — the lane never gates on a score', }); test('the config content hash is stable and content-addressed', () => { - assert.equal(configHash('a'), configHash('a')); assert.notEqual(configHash('a'), configHash('b')); assert.match(configHash('a'), /^sha256:[0-9a-f]{12}$/); }); diff --git a/src/commands/capture/runtime/snapshot.ts b/src/commands/capture/runtime/snapshot.ts index fd1f2becb2..de306a185e 100644 --- a/src/commands/capture/runtime/snapshot.ts +++ b/src/commands/capture/runtime/snapshot.ts @@ -27,6 +27,7 @@ import { renderSnapshotQualityWarnings, truncatedCaptureWarning, } from '@agent-device/capture-kit/quality-warnings'; +import { isLikelyStaleSnapshotDrop } from '@agent-device/capture-kit/snapshot-freshness'; import { formatPostGestureOutcomeWarning } from '@agent-device/capture-kit/post-gesture-stability'; import { buildSnapshotVisibility } from '@agent-device/capture-kit/snapshot-visibility'; import { ANDROID_SYSTEM_SURFACE_DISCLOSURE } from '@agent-device/contracts/android-system-surface-disclosure'; @@ -462,8 +463,3 @@ function formatFreshnessWarnings( } return freshness.reason === 'sharp-drop' ? [STALE_SNAPSHOT_DROP_WARNING] : []; } - -function isLikelyStaleSnapshotDrop(previousCount: number, currentCount: number): boolean { - if (previousCount < 12) return false; - return currentCount <= Math.floor(previousCount * 0.2); -} diff --git a/src/commands/command-contract.ts b/src/commands/command-contract.ts index f01c8f2612..8456ec0776 100644 --- a/src/commands/command-contract.ts +++ b/src/commands/command-contract.ts @@ -9,7 +9,6 @@ export type JsonSchema = { items?: JsonSchema; minItems?: number; maxItems?: number; - prefixItems?: readonly JsonSchema[]; oneOf?: readonly JsonSchema[]; not?: JsonSchema; enum?: readonly unknown[]; diff --git a/src/daemon/__tests__/provider-device-admission.test.ts b/src/daemon/__tests__/provider-device-admission.test.ts index c90ea1b65a..c70424cadb 100644 --- a/src/daemon/__tests__/provider-device-admission.test.ts +++ b/src/daemon/__tests__/provider-device-admission.test.ts @@ -1,22 +1,15 @@ -import { afterEach, beforeEach, expect, test } from 'vitest'; +import { afterEach, expect, test } from 'vitest'; import type { DeviceInfo } from '@agent-device/kernel/device'; import { installProviderDeviceAdmission, isActiveProviderDevice, - providerDeviceAdmission, } from '../provider-device-admission.ts'; const providerDevice = { id: 'provider-1', name: 'Cloud iPhone' } as unknown as DeviceInfo; const localDevice = { id: 'local-1', name: 'iPhone 16' } as unknown as DeviceInfo; -let previous = providerDeviceAdmission(); - -beforeEach(() => { - previous = providerDeviceAdmission(); -}); - afterEach(() => { - installProviderDeviceAdmission(previous); + installProviderDeviceAdmission({ isActive: () => false }); }); test('an un-composed process treats every device as local', () => { diff --git a/src/daemon/provider-device-admission.ts b/src/daemon/provider-device-admission.ts index 8d69a4850f..bae7032244 100644 --- a/src/daemon/provider-device-admission.ts +++ b/src/daemon/provider-device-admission.ts @@ -29,10 +29,6 @@ export function installProviderDeviceAdmission(admission: ProviderDeviceAdmissio installedProviderDeviceAdmission = admission; } -export function providerDeviceAdmission(): ProviderDeviceAdmission { - return installedProviderDeviceAdmission; -} - export function isActiveProviderDevice(device: DeviceInfo): boolean { return installedProviderDeviceAdmission.isActive(device); } diff --git a/test/output-economy/output-economy.baseline.test.ts b/test/output-economy/output-economy.baseline.test.ts index 53460c793f..37ee5ddafb 100644 --- a/test/output-economy/output-economy.baseline.test.ts +++ b/test/output-economy/output-economy.baseline.test.ts @@ -1,7 +1,6 @@ import fs from 'node:fs'; import path from 'node:path'; import { describe, expect, test } from 'vitest'; -import { defaultHintForCode, retriableForErrorCode } from '@agent-device/kernel/errors'; import { runCmdSync } from '@agent-device/host-kit/command'; import { findEconomyBudgetIncreases, @@ -181,17 +180,6 @@ describe('actionability and reliability floors', () => { }); expect(actual['not-settled.default.text']!.hints).toBe(1); }); - - test('policy-derived failures inherit hint and retry from production normalization', () => { - expect(rendered.errorPolicyNormalized).toMatchObject({ - code: 'DEVICE_IN_USE', - hint: defaultHintForCode('DEVICE_IN_USE'), - retriable: retriableForErrorCode('DEVICE_IN_USE'), - details: { reason: 'session-lock' }, - }); - expect(rendered.errorPolicyNormalized.retriable).toBe(true); - expect(rendered.errorPolicyNormalized.hint).toBeTruthy(); - }); }); describe('baseline-independent actionability floors', () => { From 4153a98077cf862dca6559100b6428951f4cd08f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Wed, 7 Oct 2026 22:31:48 +0200 Subject: [PATCH 2/5] test: drop tests that restate a sibling, and derive CheckId from ALL_CHECKS Each removed test had a stronger owner in the same or a neighbouring file: a byte-identical plan-validator case, idle-reap and surface-signature cases implied by a tolerance test, a perf positional case subsumed by the case-insensitive one, interactor-catalog subsets of the seven-operation test, hint regexes duplicated by exact pins, a stub-client clipboard case covered through the real CLI, and a zip fixture nothing imports. The check-id union and the ALL_CHECKS array listed the same 59 ids twice; the type is now derived from the array. Co-Authored-By: Claude Fable 5.1 --- .../src/interactor-operation-catalog.test.ts | 37 ---------- .../__tests__/install-artifact.fixtures.ts | 24 ------ .../__tests__/help-conformance-bench.test.ts | 8 -- scripts/check-affected/model.ts | 74 ++----------------- src/__tests__/cli-client-commands.test.ts | 25 ------- src/__tests__/cli-perf.test.ts | 18 ----- .../kernel/errors-code-policy.test.ts | 13 ---- src/__tests__/kernel/errors-metadata.test.ts | 20 ----- .../interaction-surface-signature.test.ts | 7 -- .../interaction-touch-targets.test.ts | 13 ---- src/daemon/server/daemon-idle-reap.test.ts | 6 -- 11 files changed, 6 insertions(+), 239 deletions(-) delete mode 100644 packages/platform-apple/src/core/__tests__/install-artifact.fixtures.ts diff --git a/packages/contracts/src/interactor-operation-catalog.test.ts b/packages/contracts/src/interactor-operation-catalog.test.ts index 5da951594d..f0f5e530b7 100644 --- a/packages/contracts/src/interactor-operation-catalog.test.ts +++ b/packages/contracts/src/interactor-operation-catalog.test.ts @@ -16,27 +16,6 @@ const device = { const available = { available: true } as const; const unavailable = { available: false, reason: 'unsupported-device-kind' } as const; -test('binds every operation the facts admitted, driving the resolved interactor', async () => { - const back = vi.fn(async () => undefined); - const home = vi.fn(async () => undefined); - const resolveInteractor = vi.fn(async () => ({ back, home }) as unknown as Interactor); - const signal = new AbortController().signal; - - const operations = bindAdmittedLocalInteractorOperations({ - device, - signal, - resolveInteractor, - facts: { back: available, home: available }, - }); - - expect(operations.back).toBeTypeOf('function'); - expect(operations.home).toBeTypeOf('function'); - await operations.back?.({}); - await operations.home?.({}); - expect(back).toHaveBeenCalledOnce(); - expect(home).toHaveBeenCalledOnce(); -}); - test('skips an operation the facts refused', () => { const operations = bindAdmittedLocalInteractorOperations({ device, @@ -51,22 +30,6 @@ test('skips an operation the facts refused', () => { // The facts a caller passes are the ONLY source of truth for what binds — there is no separate, // caller-maintained operation list that could drift from them. -test('binds every admitted operation the facts name, not just the ones a caller had in mind', () => { - const operations = bindAdmittedLocalInteractorOperations({ - device, - signal: new AbortController().signal, - resolveInteractor: vi.fn(), - facts: { back: available, home: available, tvRemote: available }, - }); - - expect(operations.back).toBeTypeOf('function'); - expect(operations.home).toBeTypeOf('function'); - expect(operations.tvRemote).toBeTypeOf('function'); -}); - -// The walk list and the per-operation binder records are derived from one canonical tuple, so -// they can't drift at the type level; this pins the runtime behavior — every one of the seven -// operations binds when its fact admits it, not just the two or three most tests exercise. test('binds every one of the seven navigation operations when every fact admits it', async () => { const back = vi.fn(async () => undefined); const home = vi.fn(async () => undefined); diff --git a/packages/platform-apple/src/core/__tests__/install-artifact.fixtures.ts b/packages/platform-apple/src/core/__tests__/install-artifact.fixtures.ts deleted file mode 100644 index 93577bd1a6..0000000000 --- a/packages/platform-apple/src/core/__tests__/install-artifact.fixtures.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { promises as fs } from 'node:fs'; -import path from 'node:path'; -import { runCmdSync } from '@agent-device/host-kit/command'; - -export async function createZipFixture( - archivePath: string, - directories: string[], - files: Array<{ source: string; target: string }> = [], -): Promise { - const staging = await fs.mkdtemp(path.join(path.dirname(archivePath), 'zip-fixture-')); - try { - for (const directory of directories) { - await fs.mkdir(path.join(staging, directory), { recursive: true }); - } - for (const file of files) { - const target = path.join(staging, file.target); - await fs.mkdir(path.dirname(target), { recursive: true }); - await fs.copyFile(file.source, target); - } - runCmdSync('zip', ['-qr', archivePath, '.'], { cwd: staging }); - } finally { - await fs.rm(staging, { recursive: true, force: true }); - } -} diff --git a/scripts/__tests__/help-conformance-bench.test.ts b/scripts/__tests__/help-conformance-bench.test.ts index dfd9dbfe98..12c0289fdf 100644 --- a/scripts/__tests__/help-conformance-bench.test.ts +++ b/scripts/__tests__/help-conformance-bench.test.ts @@ -470,14 +470,6 @@ test('plan validator rejects a doubled && as an empty chain operand', async () = assert.equal(closeSegment.issues.length, 0); }); -test('plan validator still allows a quoted && to pass through a single segment unsplit', async () => { - const [single] = await validatePlanCommands([ - 'agent-device fill \'label="A && B"\' "value" --settle', - ]); - assert.equal(single.issues.length, 0); - assert.deepEqual(single.tokens, ['agent-device', 'fill', 'label="A && B"', 'value', '--settle']); -}); - test('case matchers score parsed tokens so shell quoting does not change results', async () => { const commands = [ 'agent-device open "com.example.shop"', diff --git a/scripts/check-affected/model.ts b/scripts/check-affected/model.ts index b98605822b..f1ed6994e2 100644 --- a/scripts/check-affected/model.ts +++ b/scripts/check-affected/model.ts @@ -27,73 +27,9 @@ import { deviceLaneOwnership } from './device-lanes.ts'; // the command-reference docs gate, which used to be reachable only as a workflow // job nothing in this repo could name. The run-gate action is the only way a CI lane // declares ownership, so `check:gate-manifest` fails when a registered check has no lane. -// Raw shell earns no ownership credit. -export type CheckId = - | 'format' - | 'lint' - | 'typecheck' - | 'test-app-typecheck' - | 'test-app-security' - | 'layering' - | 'di-seams' - | 'fallow' - | 'mcp-metadata' - | 'build' - | 'package' - | 'vitest-related' - | 'unit' - | 'unit-ci' - | 'coverage' - | 'provider-integration' - | 'integration-node' - | 'macos-coverage' - | 'ios-snapshot-differential' - | 'integration-progress' - | 'swift-runner-ios' - | 'swift-runner-macos' - | 'android-helpers' - | 'macos-helper' - | 'web-smoke' - | 'replay-compat' - | 'daemon-wire-compat' - // Tooling gates: each proves one of the checkers above still behaves. - | 'affected-selector' - | 'gate-manifest' - | 'gate-manifest-model' - | 'depgraph' - | 'tmpdir-leaks' - | 'tmpdir-leaks-model' - | 'coverage-model' - | 'wire-compat-model' - | 'production-exports' - | 'bundle-owner-files' - | 'freerange' - | 'fixture-cache' - | 'fixture-fallback' - | 'command-docs' - | 'agent-guidance' - | 'xctest-selection' - | 'packaged-runner-swift' - // Gates that drive their own runner — declared nowhere, registered here. - | 'maestro-conformance' - | 'maestro-differential' - | 'maestro-regenerate' - | 'fuzz-parsers' - | 'mutation' - | 'mutation-affected' - | 'mutation-check' - | 'mutation-model' - | 'concurrency-torture' - | 'replay-ios' - | 'replay-ios-device' - | 'replay-macos' - | 'replay-linux' - | 'linux-command-evidence' - | 'replay-android'; - -// The complete local check universe. A fail-open plan selects all of these; -// keep it in sync with the catalog in checks.ts (asserted by the self-test). -export const ALL_CHECKS: readonly CheckId[] = [ +// Raw shell earns no ownership credit. A fail-open plan selects all of these; keep it in sync +// with the catalog in checks.ts (asserted by the self-test). +export const ALL_CHECKS = [ 'format', 'lint', 'typecheck', @@ -155,7 +91,9 @@ export const ALL_CHECKS: readonly CheckId[] = [ 'replay-linux', 'linux-command-evidence', 'replay-android', -]; +] as const; + +export type CheckId = (typeof ALL_CHECKS)[number]; export type SelectionReason = { check: CheckId; diff --git a/src/__tests__/cli-client-commands.test.ts b/src/__tests__/cli-client-commands.test.ts index b3382c6ba9..7c8e509807 100644 --- a/src/__tests__/cli-client-commands.test.ts +++ b/src/__tests__/cli-client-commands.test.ts @@ -828,31 +828,6 @@ test('wait keeps CLI bare text behavior through the typed client command API', a assert.equal(observed?.timeoutMs, 1500); }); -test('clipboard read keeps human text output through the typed client command API', async () => { - const client = createStubClient({ - installFromSource: async () => { - throw new Error('unexpected install call'); - }, - }); - client.command.clipboard = async () => ({ action: 'read', text: 'hello' }); - - const stdout = await captureStdout(async () => { - const handled = await tryRunClientBackedCommand({ - command: 'clipboard', - positionals: ['read'], - flags: { - json: false, - help: false, - version: false, - }, - client, - }); - assert.equal(handled, true); - }); - - assert.equal(stdout, 'hello\n'); -}); - test('keyboard status prints Android input ownership in human output', async () => { const client = createStubClient({ installFromSource: async () => { diff --git a/src/__tests__/cli-perf.test.ts b/src/__tests__/cli-perf.test.ts index a993f96504..0f4d5144f3 100644 --- a/src/__tests__/cli-perf.test.ts +++ b/src/__tests__/cli-perf.test.ts @@ -97,24 +97,6 @@ test('perf frames forwards frames area and prints focused frame summary', async assert.equal(result.stdout, 'Frame health: dropped 3.1% (12/390 frames) window 12s\n'); }); -test('perf frames sample forwards explicit sample action to daemon', async () => { - const result = await runCliCapture(['perf', 'frames', 'sample', '--json'], async () => ({ - ok: true, - data: { - metrics: { - fps: { - available: false, - reason: 'No frame data.', - }, - }, - }, - })); - - assert.equal(result.code, null); - assert.equal(result.calls[0]?.command, 'perf'); - assert.deepEqual(result.calls[0]?.positionals, ['frames', 'sample']); -}); - test('perf memory sample forwards memory area and prints compact memory summary', async () => { const result = await runCliCapture(['perf', 'memory', 'sample'], async () => ({ ok: true, diff --git a/src/__tests__/kernel/errors-code-policy.test.ts b/src/__tests__/kernel/errors-code-policy.test.ts index e04f2688d7..4845dd0295 100644 --- a/src/__tests__/kernel/errors-code-policy.test.ts +++ b/src/__tests__/kernel/errors-code-policy.test.ts @@ -200,19 +200,6 @@ test('defaultHintForCode falls back to the generic retry hint for a daemon/runne ); }); -test('defaultHintForCode never returns a falsy hint for any known code — printHumanError silently drops a falsy hint', () => { - // src/commands/output/error.ts's printHumanError only prints "Hint: ..." when - // `normalized.hint` is truthy, so an empty-string hint would silently vanish - // from CLI output rather than fail loudly. - for (const code of KNOWN_APP_ERROR_CODES) { - const hint = defaultHintForCode(code); - assert.ok(hint, `expected a non-empty hint for ${code}`); - } -}); - -// --- normalizeAgentDeviceError: the SDK-facing name for normalizeError --- -// (src/sdk/index.ts re-exports it under this name for public consumers.) - test('normalizeAgentDeviceError delegates to normalizeError, including the context argument', () => { const err = new AppError('SESSION_NOT_FOUND', 'gone'); const normalized = normalizeAgentDeviceError(err, { diff --git a/src/__tests__/kernel/errors-metadata.test.ts b/src/__tests__/kernel/errors-metadata.test.ts index 27f622d6b1..c6702e483b 100644 --- a/src/__tests__/kernel/errors-metadata.test.ts +++ b/src/__tests__/kernel/errors-metadata.test.ts @@ -48,16 +48,6 @@ test('normalizeError redacts and bounds a structured cause before putting it on assert.ok((normalized.cause?.message.length ?? 0) < cause.message.length); }); -test('normalizeError provides app discovery guidance for app-not-installed errors', () => { - const normalized = normalizeError( - new AppError('APP_NOT_INSTALLED', 'No package found matching "chat"'), - ); - assert.match( - normalized.hint ?? '', - /Run apps to discover the exact installed package or bundle id/i, - ); -}); - test('normalizeError lifts details.retriable to the top level and strips it from details', () => { const normalized = normalizeError( new AppError('COMMAND_FAILED', 'adb exited with code 1', { @@ -104,16 +94,6 @@ test('normalizeError preserves details.divergence verbatim through redaction/str assert.equal(normalized.details?.step, 2); }); -test('daemon-originated REPLAY_DIVERGENCE gets a default hint pointing at the report', () => { - const normalized = normalizeError( - new AppError('REPLAY_DIVERGENCE', 'Replay failed at step 1', {}), - ); - assert.match(normalized.hint ?? '', /details\.divergence/); -}); - -// --- stringDetail/booleanDetail/stripDiagnosticMeta: type-guard and -// empty-after-stripping contracts normalizeError relies on --- - test('normalizeError ignores a non-string details.diagnosticId/logPath/hint rather than surfacing the wrong-typed value', () => { const normalized = normalizeError( new AppError('COMMAND_FAILED', 'runner failed', { diff --git a/src/daemon/__tests__/interaction-surface-signature.test.ts b/src/daemon/__tests__/interaction-surface-signature.test.ts index bf0d5677b9..e2100079c3 100644 --- a/src/daemon/__tests__/interaction-surface-signature.test.ts +++ b/src/daemon/__tests__/interaction-surface-signature.test.ts @@ -8,13 +8,6 @@ import { discriminatingSurfaceChangedWithinRect, } from '../interaction-surface-signature.ts'; -test('areInteractionSurfaceSignaturesStable treats identical surfaces as stable', () => { - const before = buildInteractionSurfaceSignature(makeSnapshot('Inbox').nodes); - const after = buildInteractionSurfaceSignature(makeSnapshot('Inbox').nodes); - - assert.equal(areInteractionSurfaceSignaturesStable(before, after), true); -}); - test('areInteractionSurfaceSignaturesStable tolerates tiny rect drift', () => { const before = buildInteractionSurfaceSignature(makeSnapshot('Inbox', 100).nodes); const after = buildInteractionSurfaceSignature(makeSnapshot('Inbox', 100.4).nodes); diff --git a/src/daemon/interaction/internal/__tests__/interaction-touch-targets.test.ts b/src/daemon/interaction/internal/__tests__/interaction-touch-targets.test.ts index 2e4138db01..ead2ca11dd 100644 --- a/src/daemon/interaction/internal/__tests__/interaction-touch-targets.test.ts +++ b/src/daemon/interaction/internal/__tests__/interaction-touch-targets.test.ts @@ -5,19 +5,6 @@ import { parseTouchTarget, } from '../interaction-touch-targets.ts'; -test('parseTouchTarget preserves ref fallback label through shared grammar', () => { - const parsed = parseTouchTarget(['@e4', 'Email field'], 'press'); - - expect(parsed).toEqual({ - ok: true, - target: { - kind: 'ref', - ref: '@e4', - fallbackLabel: 'Email field', - }, - }); -}); - test('parseTouchTarget trims ref fallback label', () => { const parsed = parseTouchTarget(['@e4', ' Email field '], 'press'); diff --git a/src/daemon/server/daemon-idle-reap.test.ts b/src/daemon/server/daemon-idle-reap.test.ts index 9e7606dfb5..7bf2a18336 100644 --- a/src/daemon/server/daemon-idle-reap.test.ts +++ b/src/daemon/server/daemon-idle-reap.test.ts @@ -119,12 +119,6 @@ test('a repair-armed session that has already COMMITTED still blocks idle-reap ( assert.equal(isDaemonIdle({ sessionStore, inFlightRequestCount: 0 }), false); }); -test('an ordinary (non-repair) open session still blocks idle-reap', () => { - sessionStore.publish('default', makeSession()); - assert.equal(hasReapBlockingOpenSessions(sessionStore), true); - assert.equal(isDaemonIdle({ sessionStore, inFlightRequestCount: 0 }), false); -}); - test('a normal session alongside a reapable repair session still blocks idle-reap', () => { sessionStore.publish( 'default', From 12b9b97ca1a001e99f2943f85f50c343ba696f1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Wed, 7 Oct 2026 23:03:07 +0200 Subject: [PATCH 3/5] chore(gates): ack the DaemonError alias in the wire ledger DaemonError is now declared as an alias of NormalizedError; the members were already field-for-field identical, so no wire byte changes and the ack records the moved declaration text. Co-Authored-By: Claude Fable 5.1 --- test/wire-compat/ledger.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/wire-compat/ledger.json b/test/wire-compat/ledger.json index cae40d9ab3..e1c1b0cffa 100644 --- a/test/wire-compat/ledger.json +++ b/test/wire-compat/ledger.json @@ -41,7 +41,7 @@ "packages/kernel/src/contracts.ts#jsonRpcRequestSchema": "sha256:67e6b8a28b39a3883424a565ae7033c336dc2ea6b193e9b32bb98eb3e18dca7c", "packages/kernel/src/device.ts#PLATFORM_SELECTORS": "sha256:36e9da1cc660c0ddfb4cf52521f7f230341ff20e3ebda3405ba7c1799476c42d", "packages/kernel/src/device.ts#PlatformSelector": "sha256:61de3f003507ea2f53b396b0146c17670bf674a2db2132e19c462ca6c10cc8fd", - "packages/kernel/src/errors.ts#DaemonError": "sha256:d6cb31f516102d147bd4e4058ea4ace1d48584280f5b49205234c6dd5ada6e1c", + "packages/kernel/src/errors.ts#DaemonError": "sha256:b392b10f461b774f82252f8a5958781450b101329d43827abca10b6511b6d588", "packages/kernel/src/errors.ts#DiagnosticsRecordRef": "sha256:c5ef4185d01814fbfddcdcf797ce892f6ebfe1fd3c3fa73c7560ce2d3b6fce60", "packages/kernel/src/errors.ts#DispatchDisclosure": "sha256:60ec96fe63fb8756d7936187bef6226df1ed56d48ff03f254581b3685e3aa4ce", "packages/kernel/src/errors.ts#ErrorCause": "sha256:2f38679b0e9ec997fe8d94b5d5025404fd6ebb21f2c91ed2ab5d4133e578dfc4", @@ -231,8 +231,8 @@ }, { "declaration": "packages/kernel/src/errors.ts#DaemonError", - "digest": "sha256:d6cb31f516102d147bd4e4058ea4ace1d48584280f5b49205234c6dd5ada6e1c", - "rationale": "details.dispatched is a new OPTIONAL field inside details, which was already an open record: an older peer ignores the key and every existing field keeps its shape. It carries no | unknown for whether a failed interaction reached the device (the dispatch-disclosure golden table)." + "digest": "sha256:b392b10f461b774f82252f8a5958781450b101329d43827abca10b6511b6d588", + "rationale": "DaemonError is now declared as an alias of NormalizedError, whose members were already field-for-field identical (code, message, cause, hint, diagnosticId, logPath, logPathUnavailable, diagnosticsRecord, retriable, supportedOn, details). No wire byte changes; only the declaration text moved." }, { "declaration": "packages/kernel/src/errors.ts#NormalizedError", From fea10e96a1231202209eef8b4108c3087369d601 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Thu, 8 Oct 2026 10:19:12 +0200 Subject: [PATCH 4/5] chore(gates): run the integration-progress model test under its own gate Co-Authored-By: Claude Fable 5.1 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index ac0d9f71ee..10195a80e6 100644 --- a/package.json +++ b/package.json @@ -148,7 +148,7 @@ "fallow:baseline": "fallow dead-code --save-baseline fallow-baselines/dead-code.json --summary && fallow health --report-only --save-baseline fallow-baselines/health.json --summary", "check:fallow": "fallow audit", "check:affected": "node --experimental-strip-types scripts/check-affected/run.ts", - "check:affected:test": "node --experimental-strip-types scripts/node-test-tmpdir.ts --experimental-strip-types --test scripts/check-affected/model.test.ts scripts/integration-progress-model.test.ts scripts/check-affected/platform-packages.test.ts scripts/check-affected/device-lanes.test.ts scripts/check-affected/lockfile-install-sync.test.ts scripts/check-affected/run.test.ts", + "check:affected:test": "node --experimental-strip-types scripts/node-test-tmpdir.ts --experimental-strip-types --test scripts/check-affected/model.test.ts scripts/check-affected/platform-packages.test.ts scripts/check-affected/device-lanes.test.ts scripts/check-affected/lockfile-install-sync.test.ts scripts/check-affected/run.test.ts", "gate": "node --experimental-strip-types scripts/gate/run.ts", "check:gate-manifest": "node --experimental-strip-types scripts/gate/check.ts", "check:gate-manifest:test": "node --experimental-strip-types scripts/node-test-tmpdir.ts --experimental-strip-types --test scripts/gate/*.test.ts", @@ -199,7 +199,7 @@ "test:fuzz-worker": "vitest run --project fuzz-worker", "test:integration:provider": "vitest run --project provider-integration", "test:integration:progress": "node --experimental-strip-types scripts/integration-progress.ts", - "test:integration:progress:check": "node --experimental-strip-types scripts/integration-progress.ts --check", + "test:integration:progress:check": "node --experimental-strip-types scripts/node-test-tmpdir.ts --experimental-strip-types --test scripts/integration-progress-model.test.ts && node --experimental-strip-types scripts/integration-progress.ts --check", "test:fixture-cache": "node --experimental-strip-types scripts/node-test-tmpdir.ts --test test/ci/trusted-fixture-artifact.test.mjs", "test:fixture-fallback": "sh ./test/scripts/setup-fixture-app-fallback-smoke.sh", "test:output-economy": "vitest run --project output-economy", From 70aa643aa23a13f9cd8117d9ec98edc99720304f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Thu, 8 Oct 2026 13:02:07 +0200 Subject: [PATCH 5/5] test(mutation): keep the config hash self-comparison line Touching scripts/mutation/ classifies a PR as a harness change and runs the full mutant sweep, which the single-file kernel-errors shard no longer finishes inside its 30-minute budget. The one-line cleanup is not worth that lane. Co-Authored-By: Claude Fable 5.1 --- scripts/mutation/config.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/mutation/config.test.ts b/scripts/mutation/config.test.ts index c61e60a9c9..c6896a5735 100644 --- a/scripts/mutation/config.test.ts +++ b/scripts/mutation/config.test.ts @@ -35,6 +35,7 @@ test('stryker owns no pass/fail threshold — the lane never gates on a score', }); test('the config content hash is stable and content-addressed', () => { + assert.equal(configHash('a'), configHash('a')); assert.notEqual(configHash('a'), configHash('b')); assert.match(configHash('a'), /^sha256:[0-9a-f]{12}$/); });