Skip to content

Commit 3d92f85

Browse files
committed
refactor(contracts): route the keyboard binder through the shared optional-member guard
`requireKeyboardMethod` was a verbatim copy of `requireInteractorMethod`. The keyboard binder now uses the shared guard, loaded on the call rather than at module evaluation: the `keyboard-runtime` facade's eager closure is held at its merge-base size by `eager-closure-budgets`, and a static edge to the binding module would grow it by one. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014x96iYiFTbAwD4YEKU3PQ1
1 parent e0dba23 commit 3d92f85

9 files changed

Lines changed: 33 additions & 48 deletions

File tree

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
{"type":"replay_test_attempt_start","ts":"2026-09-20T14:13:02.623Z","replayPath":"/tmp/agent-device-test-run-5234-CnUBwE/agent-device-maestro-remote-test-Kcz7lm/01-flow.yaml","session":"default:test:req-maestro-test-wire-true:1-01-flow:attempt-1","requestId":"req-maestro-test-wire-true:test:1:01-flow.yaml:attempt:1"}
2-
{"type":"replay_test_attempt_stop","ts":"2026-09-20T14:13:02.625Z","session":"default:test:req-maestro-test-wire-true:1-01-flow:attempt-1","ok":true,"timedOut":false,"durationMs":2}
3-
{"type":"replay_test_finalize_start","ts":"2026-09-20T14:13:02.626Z","session":"default:test:req-maestro-test-wire-true:1-01-flow:attempt-1"}
4-
{"type":"replay_test_finalize_stop","ts":"2026-09-20T14:13:02.626Z","session":"default:test:req-maestro-test-wire-true:1-01-flow:attempt-1","ok":true,"durationMs":0}
5-
{"type":"replay_test_cleanup_start","ts":"2026-09-20T14:13:02.626Z","session":"default:test:req-maestro-test-wire-true:1-01-flow:attempt-1"}
6-
{"type":"replay_test_cleanup_stop","ts":"2026-09-20T14:13:02.626Z","session":"default:test:req-maestro-test-wire-true:1-01-flow:attempt-1","ok":true,"durationMs":0}
1+
{"type":"replay_test_attempt_start","ts":"2026-09-19T14:32:46.818Z","replayPath":"/tmp/agent-device-test-run-75313-0YH0p7/agent-device-maestro-remote-test-pzCEKx/01-flow.yaml","session":"default:test:req-maestro-test-wire-true:1-01-flow:attempt-1","requestId":"req-maestro-test-wire-true:test:1:01-flow.yaml:attempt:1"}
2+
{"type":"replay_test_attempt_stop","ts":"2026-09-19T14:32:46.819Z","session":"default:test:req-maestro-test-wire-true:1-01-flow:attempt-1","ok":true,"timedOut":false,"durationMs":2}
3+
{"type":"replay_test_finalize_start","ts":"2026-09-19T14:32:46.819Z","session":"default:test:req-maestro-test-wire-true:1-01-flow:attempt-1"}
4+
{"type":"replay_test_finalize_stop","ts":"2026-09-19T14:32:46.820Z","session":"default:test:req-maestro-test-wire-true:1-01-flow:attempt-1","ok":true,"durationMs":1}
5+
{"type":"replay_test_cleanup_start","ts":"2026-09-19T14:32:46.820Z","session":"default:test:req-maestro-test-wire-true:1-01-flow:attempt-1"}
6+
{"type":"replay_test_cleanup_stop","ts":"2026-09-19T14:32:46.820Z","session":"default:test:req-maestro-test-wire-true:1-01-flow:attempt-1","ok":true,"durationMs":0}

‎.agent-device/test-artifacts/req-maestro-test-wire-true/01-flow.yaml/attempt-1/result.txt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
file: /tmp/agent-device-test-run-5234-CnUBwE/agent-device-maestro-remote-test-Kcz7lm/01-flow.yaml
1+
file: /tmp/agent-device-test-run-75313-0YH0p7/agent-device-maestro-remote-test-pzCEKx/01-flow.yaml
22
session: default:test:req-maestro-test-wire-true:1-01-flow:attempt-1
33
attempt: 1/1
44
status: passed
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
{"type":"replay_test_attempt_start","ts":"2026-09-20T14:13:02.628Z","replayPath":"/tmp/agent-device-test-run-5234-CnUBwE/agent-device-maestro-remote-test-Kcz7lm/02-flow.yaml","session":"default:test:req-maestro-test-wire-true:2-02-flow:attempt-1","requestId":"req-maestro-test-wire-true:test:2:02-flow.yaml:attempt:1"}
2-
{"type":"replay_test_attempt_stop","ts":"2026-09-20T14:13:02.629Z","session":"default:test:req-maestro-test-wire-true:2-02-flow:attempt-1","ok":true,"timedOut":false,"durationMs":1}
3-
{"type":"replay_test_finalize_start","ts":"2026-09-20T14:13:02.629Z","session":"default:test:req-maestro-test-wire-true:2-02-flow:attempt-1"}
4-
{"type":"replay_test_finalize_stop","ts":"2026-09-20T14:13:02.629Z","session":"default:test:req-maestro-test-wire-true:2-02-flow:attempt-1","ok":true,"durationMs":0}
5-
{"type":"replay_test_cleanup_start","ts":"2026-09-20T14:13:02.629Z","session":"default:test:req-maestro-test-wire-true:2-02-flow:attempt-1"}
6-
{"type":"replay_test_cleanup_stop","ts":"2026-09-20T14:13:02.629Z","session":"default:test:req-maestro-test-wire-true:2-02-flow:attempt-1","ok":true,"durationMs":0}
1+
{"type":"replay_test_attempt_start","ts":"2026-09-19T14:32:46.820Z","replayPath":"/tmp/agent-device-test-run-75313-0YH0p7/agent-device-maestro-remote-test-pzCEKx/02-flow.yaml","session":"default:test:req-maestro-test-wire-true:2-02-flow:attempt-1","requestId":"req-maestro-test-wire-true:test:2:02-flow.yaml:attempt:1"}
2+
{"type":"replay_test_attempt_stop","ts":"2026-09-19T14:32:46.821Z","session":"default:test:req-maestro-test-wire-true:2-02-flow:attempt-1","ok":true,"timedOut":false,"durationMs":1}
3+
{"type":"replay_test_finalize_start","ts":"2026-09-19T14:32:46.821Z","session":"default:test:req-maestro-test-wire-true:2-02-flow:attempt-1"}
4+
{"type":"replay_test_finalize_stop","ts":"2026-09-19T14:32:46.821Z","session":"default:test:req-maestro-test-wire-true:2-02-flow:attempt-1","ok":true,"durationMs":0}
5+
{"type":"replay_test_cleanup_start","ts":"2026-09-19T14:32:46.821Z","session":"default:test:req-maestro-test-wire-true:2-02-flow:attempt-1"}
6+
{"type":"replay_test_cleanup_stop","ts":"2026-09-19T14:32:46.821Z","session":"default:test:req-maestro-test-wire-true:2-02-flow:attempt-1","ok":true,"durationMs":0}

‎.agent-device/test-artifacts/req-maestro-test-wire-true/02-flow.yaml/attempt-1/result.txt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
file: /tmp/agent-device-test-run-5234-CnUBwE/agent-device-maestro-remote-test-Kcz7lm/02-flow.yaml
1+
file: /tmp/agent-device-test-run-75313-0YH0p7/agent-device-maestro-remote-test-pzCEKx/02-flow.yaml
22
session: default:test:req-maestro-test-wire-true:2-02-flow:attempt-1
33
attempt: 1/1
44
status: passed
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
{"type":"replay_test_attempt_start","ts":"2026-09-20T14:13:02.633Z","replayPath":"/tmp/agent-device-test-run-5234-CnUBwE/agent-device-maestro-remote-test-YbCFeL/01-flow.yaml","session":"default:test:req-maestro-test-wire-undefined:1-01-flow:attempt-1","requestId":"req-maestro-test-wire-undefined:test:1:01-flow.yaml:attempt:1"}
2-
{"type":"replay_test_attempt_stop","ts":"2026-09-20T14:13:02.634Z","session":"default:test:req-maestro-test-wire-undefined:1-01-flow:attempt-1","ok":true,"timedOut":false,"durationMs":1}
3-
{"type":"replay_test_finalize_start","ts":"2026-09-20T14:13:02.634Z","session":"default:test:req-maestro-test-wire-undefined:1-01-flow:attempt-1"}
4-
{"type":"replay_test_finalize_stop","ts":"2026-09-20T14:13:02.634Z","session":"default:test:req-maestro-test-wire-undefined:1-01-flow:attempt-1","ok":true,"durationMs":0}
5-
{"type":"replay_test_cleanup_start","ts":"2026-09-20T14:13:02.634Z","session":"default:test:req-maestro-test-wire-undefined:1-01-flow:attempt-1"}
6-
{"type":"replay_test_cleanup_stop","ts":"2026-09-20T14:13:02.634Z","session":"default:test:req-maestro-test-wire-undefined:1-01-flow:attempt-1","ok":true,"durationMs":0}
1+
{"type":"replay_test_attempt_start","ts":"2026-09-19T14:32:46.823Z","replayPath":"/tmp/agent-device-test-run-75313-0YH0p7/agent-device-maestro-remote-test-Wnbhbp/01-flow.yaml","session":"default:test:req-maestro-test-wire-undefined:1-01-flow:attempt-1","requestId":"req-maestro-test-wire-undefined:test:1:01-flow.yaml:attempt:1"}
2+
{"type":"replay_test_attempt_stop","ts":"2026-09-19T14:32:46.823Z","session":"default:test:req-maestro-test-wire-undefined:1-01-flow:attempt-1","ok":true,"timedOut":false,"durationMs":0}
3+
{"type":"replay_test_finalize_start","ts":"2026-09-19T14:32:46.823Z","session":"default:test:req-maestro-test-wire-undefined:1-01-flow:attempt-1"}
4+
{"type":"replay_test_finalize_stop","ts":"2026-09-19T14:32:46.823Z","session":"default:test:req-maestro-test-wire-undefined:1-01-flow:attempt-1","ok":true,"durationMs":0}
5+
{"type":"replay_test_cleanup_start","ts":"2026-09-19T14:32:46.823Z","session":"default:test:req-maestro-test-wire-undefined:1-01-flow:attempt-1"}
6+
{"type":"replay_test_cleanup_stop","ts":"2026-09-19T14:32:46.823Z","session":"default:test:req-maestro-test-wire-undefined:1-01-flow:attempt-1","ok":true,"durationMs":0}

‎.agent-device/test-artifacts/req-maestro-test-wire-undefined/01-flow.yaml/attempt-1/result.txt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
file: /tmp/agent-device-test-run-5234-CnUBwE/agent-device-maestro-remote-test-YbCFeL/01-flow.yaml
1+
file: /tmp/agent-device-test-run-75313-0YH0p7/agent-device-maestro-remote-test-Wnbhbp/01-flow.yaml
22
session: default:test:req-maestro-test-wire-undefined:1-01-flow:attempt-1
33
attempt: 1/1
44
status: passed
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
{"type":"replay_test_attempt_start","ts":"2026-09-20T14:13:02.635Z","replayPath":"/tmp/agent-device-test-run-5234-CnUBwE/agent-device-maestro-remote-test-YbCFeL/02-flow.yaml","session":"default:test:req-maestro-test-wire-undefined:2-02-flow:attempt-1","requestId":"req-maestro-test-wire-undefined:test:2:02-flow.yaml:attempt:1"}
2-
{"type":"replay_test_attempt_stop","ts":"2026-09-20T14:13:02.636Z","session":"default:test:req-maestro-test-wire-undefined:2-02-flow:attempt-1","ok":true,"timedOut":false,"durationMs":1}
3-
{"type":"replay_test_finalize_start","ts":"2026-09-20T14:13:02.636Z","session":"default:test:req-maestro-test-wire-undefined:2-02-flow:attempt-1"}
4-
{"type":"replay_test_finalize_stop","ts":"2026-09-20T14:13:02.636Z","session":"default:test:req-maestro-test-wire-undefined:2-02-flow:attempt-1","ok":true,"durationMs":0}
5-
{"type":"replay_test_cleanup_start","ts":"2026-09-20T14:13:02.636Z","session":"default:test:req-maestro-test-wire-undefined:2-02-flow:attempt-1"}
6-
{"type":"replay_test_cleanup_stop","ts":"2026-09-20T14:13:02.636Z","session":"default:test:req-maestro-test-wire-undefined:2-02-flow:attempt-1","ok":true,"durationMs":0}
1+
{"type":"replay_test_attempt_start","ts":"2026-09-19T14:32:46.824Z","replayPath":"/tmp/agent-device-test-run-75313-0YH0p7/agent-device-maestro-remote-test-Wnbhbp/02-flow.yaml","session":"default:test:req-maestro-test-wire-undefined:2-02-flow:attempt-1","requestId":"req-maestro-test-wire-undefined:test:2:02-flow.yaml:attempt:1"}
2+
{"type":"replay_test_attempt_stop","ts":"2026-09-19T14:32:46.824Z","session":"default:test:req-maestro-test-wire-undefined:2-02-flow:attempt-1","ok":true,"timedOut":false,"durationMs":0}
3+
{"type":"replay_test_finalize_start","ts":"2026-09-19T14:32:46.824Z","session":"default:test:req-maestro-test-wire-undefined:2-02-flow:attempt-1"}
4+
{"type":"replay_test_finalize_stop","ts":"2026-09-19T14:32:46.824Z","session":"default:test:req-maestro-test-wire-undefined:2-02-flow:attempt-1","ok":true,"durationMs":0}
5+
{"type":"replay_test_cleanup_start","ts":"2026-09-19T14:32:46.824Z","session":"default:test:req-maestro-test-wire-undefined:2-02-flow:attempt-1"}
6+
{"type":"replay_test_cleanup_stop","ts":"2026-09-19T14:32:46.824Z","session":"default:test:req-maestro-test-wire-undefined:2-02-flow:attempt-1","ok":true,"durationMs":0}

‎.agent-device/test-artifacts/req-maestro-test-wire-undefined/02-flow.yaml/attempt-1/result.txt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
file: /tmp/agent-device-test-run-5234-CnUBwE/agent-device-maestro-remote-test-YbCFeL/02-flow.yaml
1+
file: /tmp/agent-device-test-run-75313-0YH0p7/agent-device-maestro-remote-test-Wnbhbp/02-flow.yaml
22
session: default:test:req-maestro-test-wire-undefined:2-02-flow:attempt-1
33
attempt: 1/1
44
status: passed

‎packages/contracts/src/keyboard-runtime.ts‎

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { AppError } from '@agent-device/kernel/errors';
21
import type {
32
Interactor,
43
KeyboardDismissResult,
@@ -75,24 +74,6 @@ export function keyboardRuntimeOperationFacts(
7574
});
7675
}
7776

78-
/**
79-
* `Interactor.keyboardStatus`/`keyboardDismiss`/`keyboardEnter` are optional (parity with
80-
* `hover`): a platform with no keyboard concept for that action leaves it undefined. Facts admit
81-
* an operation only for owners whose interactor implements it, so a missing method at bind time
82-
* is a runtime-contract error, not a normal refusal.
83-
*/
84-
function requireKeyboardMethod<Method>(
85-
method: Method | undefined,
86-
operation: string,
87-
): NonNullable<Method> {
88-
if (method) return method as NonNullable<Method>;
89-
throw new AppError(
90-
'COMMAND_FAILED',
91-
`${operation} was admitted but its bound interactor has no implementation.`,
92-
{ reason: 'interactor-method-missing' },
93-
);
94-
}
95-
9677
/**
9778
* Captures one selected owner's interactor authority for the lifetime of a request binding. The
9879
* owner is already chosen by the time a binder is called, so each entry point supplies its own
@@ -129,7 +110,11 @@ export function bindKeyboardAction<Key extends keyof KeyboardRuntimeOperations>(
129110
): Pick<KeyboardRuntimeOperations, Key> {
130111
const action = async (input: KeyboardActionInput) => {
131112
const interactor = await resolveKeyboardInteractor(signal, resolveInteractor, input);
132-
const method = requireKeyboardMethod(interactor[key], KEYBOARD_ACTION_LABELS[key]);
113+
// The guard every optional-member binder shares. Loaded on the call rather than at module
114+
// evaluation because this facade's eager closure is held at its merge-base size
115+
// (`eager-closure-budgets`); a static edge would grow it by one module.
116+
const { requireInteractorMethod } = await import('./interactor-operation-binding.ts');
117+
const method = requireInteractorMethod(interactor[key], KEYBOARD_ACTION_LABELS[key]);
133118
return await (method as () => Promise<unknown>).call(interactor);
134119
};
135120
return Object.freeze({ [key]: action }) as Pick<KeyboardRuntimeOperations, Key>;

0 commit comments

Comments
 (0)