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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,4 @@ test('apps declares the complete request-scoped runtime use', () => {
});
expect(appsRuntimeUse.required).toEqual(['ensureReady', 'listApps']);
expect(appsRuntimeUse.preferred).toEqual([]);
expect(descriptor).not.toHaveProperty('capability');
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { commandDescriptors } from '../registry.ts';
test('appstate descriptor declares the complete readiness and foreground-state use', () => {
const appstate = commandDescriptors.find(({ name }) => name === 'appstate');

expect(appstate).not.toHaveProperty('capability');
expect(appstate?.platformExecution).toEqual({
kind: 'device-runtime',
uses: appStateRuntimeUses,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@ import {
import { expect, test } from 'vitest';
import { commandDescriptors } from '../registry.ts';

test('boot descriptor declares both readiness uses instead of a capability bucket', () => {
test('boot descriptor declares both readiness uses', () => {
const boot = commandDescriptors.find(({ name }) => name === 'boot');

expect(boot).not.toHaveProperty('capability');
expect(boot?.platformExecution).toEqual({
kind: 'device-runtime',
uses: deviceBootRuntimeUses,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,10 @@ import { commandDescriptors } from '../registry.ts';
// operation ("press shares the admitted tapPoint fact that live click and press both require").
// That claim rests on `pressRuntimeUses` and the `press` descriptor literally being click's, not
// merely looking similar — pin both here so a future divergence fails this test, not silently.
test('press descriptor reuses the complete click plan uses with no legacy projection', () => {
test('press descriptor reuses the complete click plan uses', () => {
const click = commandDescriptors.find(({ name }) => name === 'click');
const press = commandDescriptors.find(({ name }) => name === 'press');

expect(click).not.toHaveProperty('capability');
expect(click).not.toHaveProperty('dispatch');
expect(press).not.toHaveProperty('capability');
expect(press).not.toHaveProperty('dispatch');
expect(click?.platformExecution).toEqual({ kind: 'device-runtime', uses: clickRuntimeUses });
expect(press?.platformExecution).toEqual({ kind: 'device-runtime', uses: clickRuntimeUses });
expect(pressRuntimeUses).toBe(clickRuntimeUses);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ import { commandDescriptors, selectFindStepUses } from '../registry.ts';
import { expect, test } from 'vitest';
import { findRuntimePlanUses } from '@agent-device/contracts/platform-runtime-operations';

test('find descriptor declares its complete runtime uses with no legacy projection', () => {
test('find descriptor declares its complete runtime uses', () => {
const find = commandDescriptors.find(({ name }) => name === 'find');

expect(find).not.toHaveProperty('capability');
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
// Plan-time consumers select the alternative from the step input the way the handler does.
expect(find?.platformExecution).toEqual({
kind: 'device-runtime',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import { commandDescriptors, listRuntimeFactCommands } from '../registry.ts';
test('focus descriptor declares its complete runtime use', () => {
const focus = commandDescriptors.find(({ name }) => name === 'focus');

expect(focus).not.toHaveProperty('capability');
expect(focus).not.toHaveProperty('dispatch');
expect(focus?.platformExecution).toEqual({
kind: 'device-runtime',
uses: [focusRuntimeUse],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { expect, test } from 'vitest';
import { commandDescriptors, RAW_COMMAND_DESCRIPTORS } from '../registry.ts';

// A retired projection written in a descriptor literal fails `tsc`, but one smuggled through a
// conditional spread — the shape `ownerFiles` uses — slips past the excess-property check, and no
// `toEqual` on `platformExecution` sees a sibling key. This sweep is what actually closes it.
const RETIRED_DESCRIPTOR_KEYS = ['capability', 'dispatch'] as const;

test('no registered descriptor carries a retired capability or dispatch projection', () => {
for (const descriptor of RAW_COMMAND_DESCRIPTORS) {
for (const key of RETIRED_DESCRIPTOR_KEYS) {
expect(descriptor, `${descriptor.name} raw ${key}`).not.toHaveProperty(key);
}
}
for (const descriptor of commandDescriptors) {
for (const key of RETIRED_DESCRIPTOR_KEYS) {
expect(descriptor, `${descriptor.name} runtime ${key}`).not.toHaveProperty(key);
}
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ import { expect, test } from 'vitest';
import { screenshotRuntimePlanUses } from '@agent-device/contracts/platform-runtime-operations';
import { commandDescriptors } from '../registry.ts';

test('screenshot descriptor declares its complete runtime uses with no legacy projection', () => {
test('screenshot descriptor declares its complete runtime uses', () => {
const screenshot = commandDescriptors.find(({ name }) => name === 'screenshot');

expect(screenshot).not.toHaveProperty('capability');
expect(screenshot).not.toHaveProperty('dispatch');
expect(screenshot?.platformExecution).toEqual({
kind: 'device-runtime',
uses: screenshotRuntimePlanUses,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ import { commandDescriptors, selectSnapshotStepUses } from '../registry.ts';
import { snapshotRuntimePlanUses } from '@agent-device/contracts/platform-runtime-operations';
import { expect, test } from 'vitest';

test('snapshot descriptor declares its complete planned capture uses with no legacy projection', () => {
test('snapshot descriptor declares its complete planned capture uses', () => {
const snapshot = commandDescriptors.find(({ name }) => name === 'snapshot');

expect(snapshot).not.toHaveProperty('capability');
expect(snapshot).not.toHaveProperty('dispatch');
// Plan-time consumers select the alternative from the step input the way the handler does.
expect(snapshot?.platformExecution).toEqual({
kind: 'device-runtime',
Expand All @@ -21,11 +19,9 @@ test('snapshot descriptor declares its complete planned capture uses with no leg
]);
});

test('diff descriptor reuses the complete snapshot plan uses with no legacy projection', () => {
test('diff descriptor reuses the complete snapshot plan uses', () => {
const diff = commandDescriptors.find(({ name }) => name === 'diff');

expect(diff).not.toHaveProperty('capability');
expect(diff).not.toHaveProperty('dispatch');
expect(diff?.platformExecution).toEqual({
kind: 'device-runtime',
uses: snapshotRuntimePlanUses,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ import { expect, test } from 'vitest';
import { typeTextRuntimeUse } from '@agent-device/contracts/platform-runtime-operations';
import { commandDescriptors } from '../registry.ts';

test('type descriptor declares its complete runtime use with no legacy projection', () => {
test('type descriptor declares its complete runtime use', () => {
const type = commandDescriptors.find(({ name }) => name === 'type');

expect(type).not.toHaveProperty('capability');
expect(type).not.toHaveProperty('dispatch');
expect(type?.platformExecution).toEqual({
kind: 'device-runtime',
uses: [typeTextRuntimeUse],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ import { expect, test } from 'vitest';
import { viewportRuntimeUse } from '@agent-device/contracts/platform-runtime-operations';
import { commandDescriptors } from '../registry.ts';

test('viewport descriptor declares its complete runtime use with no legacy projection', () => {
test('viewport descriptor declares its complete runtime use', () => {
const viewport = commandDescriptors.find(({ name }) => name === 'viewport');

expect(viewport).not.toHaveProperty('capability');
expect(viewport).not.toHaveProperty('dispatch');
expect(viewport?.platformExecution).toEqual({
kind: 'device-runtime',
uses: [viewportRuntimeUse],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@ import { waitSelectorCaptureRuntimePlanUses } from '@agent-device/contracts/plat
import { waitObservesDevice } from '@agent-device/contracts/wait-runtime-plan';
import { commandDescriptors } from '../registry.ts';

test('wait descriptor declares its complete runtime use with no capability bucket', () => {
test('wait descriptor declares its complete runtime use', () => {
const wait = commandDescriptors.find(({ name }) => name === 'wait');

expect(wait).not.toHaveProperty('capability');
expect(wait).not.toHaveProperty('dispatch');
expect(wait?.platformExecution).toEqual({
kind: 'device-runtime',
uses: waitSelectorCaptureRuntimePlanUses,
Expand Down
85 changes: 2 additions & 83 deletions packages/platform-apple/src/runner/__tests__/runner-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ vi.mock('../runner-macos-products.ts', async () => {
});

import type { DeviceInfo } from '@agent-device/kernel/device';
import { RUNNER_COMMAND_TRAITS, isReadOnlyRunnerCommand } from '../runner-command-traits.ts';
import { withRunnerCommandId, type RunnerCommand } from '../runner-contract.ts';
import { isReadOnlyRunnerCommand } from '../runner-command-traits.ts';
import { withRunnerCommandId } from '../runner-contract.ts';
import {
resolveRunnerBuildDestination,
resolveRunnerDestination,
Expand Down Expand Up @@ -94,66 +94,6 @@ const macOsDevice: DeviceInfo = {
booted: true,
};

const runnerProtocolCommandFixtures: Record<RunnerCommand['command'], RunnerCommand> = {
tap: { command: 'tap', x: 120, y: 240 },
mouseClick: { command: 'mouseClick', x: 120, y: 240, button: 'secondary' },
longPress: { command: 'longPress', x: 120, y: 240, durationMs: 750 },
drag: { command: 'drag', x: 120, y: 240, x2: 300, y2: 420, durationMs: 400 },
remotePress: { command: 'remotePress', remoteButton: 'down', durationMs: 250 },
type: { command: 'type', text: 'hello', delayMs: 20, textEntryMode: 'replace' },
swipe: { command: 'swipe', direction: 'down', durationMs: 250 },
scroll: { command: 'scroll', direction: 'down', amount: 0.6, pixels: 240 },
desktopScroll: {
command: 'desktopScroll',
direction: 'down',
amount: 0.6,
pixels: 240,
durationMs: 50,
},
findText: { command: 'findText', text: 'Settings' },
querySelector: { command: 'querySelector', selectorKey: 'id', selectorValue: 'submit' },
readText: { command: 'readText' },
appState: { command: 'appState', appBundleId: 'com.demo.app' },
snapshot: {
command: 'snapshot',
interactiveOnly: true,
depth: 2,
scope: 'app',
raw: false,
},
screenshot: { command: 'screenshot', outPath: '/tmp/runner-screenshot.png', fullscreen: true },
backInApp: { command: 'backInApp' },
backSystem: { command: 'backSystem' },
home: { command: 'home' },
rotate: { command: 'rotate', orientation: 'landscape-left' },
appSwitcher: { command: 'appSwitcher' },
actionButton: { command: 'actionButton' },
keyboardDismiss: { command: 'keyboardDismiss' },
keyboardReturn: { command: 'keyboardReturn' },
alert: { command: 'alert', action: 'accept' },
sequence: {
command: 'sequence',
steps: [
{ kind: 'tap', x: 120, y: 240 },
{ kind: 'longPress', x: 120, y: 240, durationMs: 300 },
{ kind: 'doubleTap', x: 10, y: 600, pauseMs: 50 },
],
},
gesture: { command: 'gesture' },
gestureViewport: { command: 'gestureViewport' },
recordStart: {
command: 'recordStart',
outPath: '/tmp/runner-recording.mp4',
fps: 30,
},
recordStop: { command: 'recordStop' },
status: { command: 'status', statusCommandId: 'runner-command-1' },
uptime: { command: 'uptime' },
activate: { command: 'activate', appBundleId: 'com.example.app' },
terminate: { command: 'terminate', appBundleId: 'com.example.app' },
targetReset: { command: 'targetReset' },
shutdown: { command: 'shutdown' },
};
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../../..');

async function makeTmpDir(): Promise<string> {
Expand Down Expand Up @@ -301,27 +241,6 @@ test('resolveRunnerDestination uses simulator destination for simulators', () =>
assert.equal(resolveRunnerDestination(iosSimulator), 'platform=iOS Simulator,id=sim-1');
});

test('runner protocol fixtures cover every runner command with JSON-safe samples', () => {
// The trait table is the exhaustive runner-command enumeration — it is `satisfies
// Record<RunnerCommand['command'], …>` — so the fixture set is checked against that declaration
// instead of against a second hand-maintained list that a new command has to remember to update.
assert.deepEqual(
Object.keys(runnerProtocolCommandFixtures).sort(),
Object.keys(RUNNER_COMMAND_TRAITS).sort(),
);

const roundTrip = JSON.parse(JSON.stringify(runnerProtocolCommandFixtures)) as Record<
string,
Record<string, unknown>
>;
assert.equal(roundTrip.tap!.command, 'tap');
assert.equal(roundTrip.mouseClick!.button, 'secondary');
assert.equal(roundTrip.snapshot!.scope, 'app');
assert.equal(roundTrip.screenshot!.fullscreen, true);
assert.equal(roundTrip.rotate!.orientation, 'landscape-left');
assert.equal(roundTrip.recordStart!.fps, 30);
});

test('withRunnerCommandId replaces blank command ids', () => {
const command = withRunnerCommandId({ command: 'uptime', commandId: ' ' });

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@ import { isDeepStrictEqual } from 'node:util';
import { test } from 'vitest';
import type { RunnerCommand } from '../runner-contract.ts';
import {
canSkipRunnerReadinessPreflightAfterHealthyMutation,
isReadOnlyRunnerCommand,
isRunnerReadinessPreflightExempt,
isRunnerReadinessProbeCommand,
readRunnerCommandTraits,
RUNNER_COMMAND_TRAITS,
type RunnerCommandTraits,
Expand Down Expand Up @@ -67,24 +64,6 @@ test('runner command trait table pins lifecycle-sensitive command groups', () =>
assert.deepEqual(Object.values(groups).flat().sort(), [...RUNNER_COMMANDS].sort());
});

test('runner command trait helpers read from the shared trait table', () => {
for (const command of RUNNER_COMMANDS) {
const traits = readRunnerCommandTraits({ command });
assert.equal(isReadOnlyRunnerCommand({ command }), traits.readOnly, command);
assert.equal(isRunnerReadinessProbeCommand({ command }), traits.readinessProbe, command);
assert.equal(
isRunnerReadinessPreflightExempt({ command }),
traits.readinessPreflightExempt,
command,
);
assert.equal(
canSkipRunnerReadinessPreflightAfterHealthyMutation({ command }),
traits.readinessPreflightSkipEligibleAfterHealthyMutation,
command,
);
}
});

test('alert actions match the native read-only golden table', () => {
// The fixture's `query` column records whether the alert request changes anything — `get` is the
// one action that is side-effect-free — and each side consumes it under its own name: `readOnly`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,24 +234,23 @@ test('a runner payload with the hittable bit absent presents without declaring i
}
});

// The keyboard band the runner measured for a capture (#2660). The reader is the only place a wire
// fact becomes a daemon fact, so it owns the whole strictness budget: what cannot be placed is
// restated as `unmeasurable` with a reason, never as a band and never as silence.
// The keyboard band the runner measured for a capture (#2660). The strictness budget that decides
// what cannot be placed belongs to `readSnapshotKeyboardBandFact` in @agent-device/kernel, which owns
// that table; this seam owns forwarding AND the proof that it forwards through that reader. The
// same-process capture path feeds `result.keyboard` straight into the tap/click occlusion guard in
// src/commands/interaction/runtime/keyboard-occlusion.ts before any serialization, so a seam that
// passed the raw wire value through would hand the guard an unvalidated shape with nothing red.
// The malformed cases below are what prove the routing; kernel/src/record.test.ts owns the table.

test('a measured keyboard band is read as the band the guard will measure against', () => {
test('the reader forwards each published keyboard band shape unchanged', () => {
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
// The landscape band #2653 confirmed on iPhone 17 Pro: the runner answers `app.keyboards` in the
// app's own orientation space, so the daemon reads these numbers beside node rects unchanged.
const result = readAppleSnapshotResult({
keyboard: { kind: 'visible', frame: { x: 0, y: 198, width: 874, height: 204 } },
});

assert.deepEqual(result.keyboard, {
kind: 'visible',
frame: { x: 0, y: 198, width: 874, height: 204 },
});
});

test('a proven absence and a stated failure both survive the wire as themselves', () => {
assert.deepEqual(
readAppleSnapshotResult({
keyboard: { kind: 'visible', frame: { x: 0, y: 198, width: 874, height: 204 } },
}).keyboard,
{ kind: 'visible', frame: { x: 0, y: 198, width: 874, height: 204 } },
);
assert.deepEqual(readAppleSnapshotResult({ keyboard: { kind: 'absent' } }).keyboard, {
kind: 'absent',
});
Expand All @@ -269,14 +268,15 @@ test('a capture from a tier that never reads the keyboard publishes no fact at a
assert.equal(readAppleSnapshotResult({ nodes: [] }).keyboard, undefined);
});

test('a band that cannot be placed is restated as unmeasurable rather than dropped or trusted', () => {
// Routing proof: every reason below is emitted only by the kernel reader, never by a wire producer.
// One case per reason code — enough that a seam forwarding the raw payload fails on each branch,
// without re-owning the shape table kernel/src/record.test.ts already pins exhaustively.
test('a malformed keyboard payload is restated by the kernel reader, never forwarded raw', () => {
const cases: ReadonlyArray<readonly [unknown, string]> = [
[{ kind: 'visible' }, 'invalid-visible-frame'],
[{ kind: 'visible', frame: { x: 0, y: 198, width: 0, height: 204 } }, 'invalid-visible-frame'],
[{ kind: 'visible', frame: { x: 0, y: 198 } }, 'invalid-visible-frame'],
[{ kind: 'unmeasurable' }, 'unreported-reason'],
[{ kind: 'measured' }, 'unrecognized-kind'],
['visible', 'malformed-fact'],
[{ kind: 'measured' }, 'unrecognized-kind'],
[{ kind: 'unmeasurable' }, 'unreported-reason'],
[{ kind: 'visible', frame: { x: 0, y: 198, width: 0, height: 204 } }, 'invalid-visible-frame'],
];

for (const [payload, reason] of cases) {
Expand Down
Loading
Loading