@deeeed/metamask-harness 0.19.1 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/adapters/core/inject.sh +1 -3
  3. package/adapters/extension/ensure-browser.sh +6 -4
  4. package/adapters/extension/inject.mjs +1 -1
  5. package/adapters/extension/launch-browser.cjs +6 -1
  6. package/adapters/extension/lib/chrome-args.cjs +11 -1
  7. package/adapters/extension/start-watch.sh +1 -1
  8. package/adapters/extension/verify.sh +17 -0
  9. package/adapters/mobile/inject.sh +1 -1
  10. package/adapters/mobile/open-device.sh +12 -1
  11. package/adapters/mobile/wait-for-bridge.sh +1 -1
  12. package/dist/adapters/extension/ensure-ready.js +7 -2
  13. package/dist/adapters/extension/runtime-decision.js +4 -6
  14. package/dist/adapters/extension/runtime.js +114 -17
  15. package/dist/adapters/mobile/prepare.js +40 -6
  16. package/dist/command-contract.js +58 -5
  17. package/dist/commands/call.js +7 -4
  18. package/dist/commands/check.js +9 -2
  19. package/dist/commands/checklist.js +117 -14
  20. package/dist/commands/launch/extension.js +8 -3
  21. package/dist/commands/launch/index.js +38 -25
  22. package/dist/commands/launch/mobile.js +2 -2
  23. package/dist/commands/manifest.js +86 -25
  24. package/dist/commands/run-engine.js +7 -2
  25. package/dist/heal-bounds.js +1 -1
  26. package/dist/manifest.js +85 -101
  27. package/dist/metamask-action-validation.js +45 -0
  28. package/dist/mm-harness-cli.js +7 -4
  29. package/dist/runner.js +9 -4
  30. package/docs/CONTRIBUTING.md +8 -0
  31. package/docs/RECIPES.md +2 -11
  32. package/library/actions/extension/platform/cdp.mjs +8 -3
  33. package/library/actions/extension/ui/navigate.mjs +239 -16
  34. package/library/actions/mobile/ui/navigate.mjs +1 -1
  35. package/library/manifests/core.action-manifest.json +357 -489
  36. package/library/manifests/extension.action-manifest.json +616 -885
  37. package/library/manifests/mobile.action-manifest.json +647 -931
  38. package/library/recipes/perps/lifecycle.recipe.json +3 -9
  39. package/library/recipes/runner/action-validation.extension.recipe.json +5 -4
  40. package/package.json +5 -4
  41. package/scripts/completions.sh +2 -2
  42. package/library/library.json +0 -7
@@ -1,40 +1,14 @@
1
1
  {
2
- "runner_protocol_version": 1,
3
- "action_registry_version": 1,
4
- "supported_official_actions": [
5
- "command",
6
- "wait",
7
- "assert_file",
8
- "assert_json",
9
- "assert_exit_code",
10
- "assert_output",
11
- "watch_logs",
12
- "index_artifacts",
13
- "end",
14
- "call",
15
- "switch",
16
- "ui.navigate",
17
- "ui.press",
18
- "ui.key_press",
19
- "ui.set_input",
20
- "ui.scroll",
21
- "ui.wait_for",
22
- "ui.screenshot",
23
- "app.lifecycle",
24
- "app.status",
25
- "app.hud",
26
- "cdp.target"
27
- ],
28
- "action_metadata": {
2
+ "$schema": "https://farmslot.io/schemas/action-manifest-v1.schema.json",
3
+ "actions": {
29
4
  "command": {
30
5
  "description": "Run a shell command from the project root.",
31
6
  "examples": [
32
7
  {
33
- "node": {
34
- "action": "command",
35
- "cmd": "pwd",
36
- "intent": "Run a shell command from the project root"
37
- }
8
+ "action": "command",
9
+ "cmd": "pwd",
10
+ "intent": "Prepare the project state needed for this proof.",
11
+ "next": "done"
38
12
  }
39
13
  ],
40
14
  "schema": {
@@ -63,11 +37,10 @@
63
37
  "description": "Base wait action.",
64
38
  "examples": [
65
39
  {
66
- "node": {
67
- "action": "wait",
68
- "duration_ms": 1,
69
- "intent": "Base wait action"
70
- }
40
+ "action": "wait",
41
+ "duration_ms": 1,
42
+ "intent": "Allow the requested asynchronous state to settle.",
43
+ "next": "done"
71
44
  }
72
45
  ],
73
46
  "schema": {
@@ -83,40 +56,229 @@
83
56
  "additionalProperties": false
84
57
  }
85
58
  },
86
- "switch": {
87
- "description": "Choose a graph branch when one resolved string equals another.",
59
+ "assert_file": {
60
+ "description": "Assert that a project file exists and optionally contains text.",
61
+ "examples": [
62
+ {
63
+ "action": "assert_file",
64
+ "path": "reports/result.txt",
65
+ "contains": "ok",
66
+ "intent": "Confirm the requested project file state.",
67
+ "next": "done"
68
+ }
69
+ ],
70
+ "schema": {
71
+ "type": "object",
72
+ "properties": {
73
+ "path": {
74
+ "type": "string"
75
+ },
76
+ "contains": {
77
+ "type": "string"
78
+ }
79
+ },
80
+ "required": [
81
+ "path"
82
+ ],
83
+ "additionalProperties": false
84
+ }
85
+ },
86
+ "assert_json": {
87
+ "description": "Assert a JSON file value with a JSONPath-style selector.",
88
+ "examples": [
89
+ {
90
+ "action": "assert_json",
91
+ "path": "reports/result.json",
92
+ "assert": {
93
+ "path": "$.status",
94
+ "operator": "eq",
95
+ "value": "ok"
96
+ },
97
+ "intent": "Confirm the recorded data satisfies the requested condition.",
98
+ "next": "done"
99
+ }
100
+ ],
101
+ "schema": {
102
+ "type": "object",
103
+ "properties": {
104
+ "path": {
105
+ "type": "string"
106
+ },
107
+ "assert": {
108
+ "type": "object",
109
+ "properties": {
110
+ "path": {
111
+ "type": "string"
112
+ },
113
+ "operator": {
114
+ "type": "string"
115
+ },
116
+ "value": {
117
+ "type": [
118
+ "string",
119
+ "number",
120
+ "integer",
121
+ "boolean",
122
+ "object",
123
+ "array"
124
+ ],
125
+ "items": {
126
+ "type": [
127
+ "string",
128
+ "number",
129
+ "integer",
130
+ "boolean",
131
+ "object"
132
+ ]
133
+ }
134
+ },
135
+ "all": {
136
+ "type": "array",
137
+ "items": {
138
+ "type": "object"
139
+ }
140
+ },
141
+ "any": {
142
+ "type": "array",
143
+ "items": {
144
+ "type": "object"
145
+ }
146
+ },
147
+ "none": {
148
+ "type": "array",
149
+ "items": {
150
+ "type": "object"
151
+ }
152
+ }
153
+ },
154
+ "additionalProperties": false
155
+ }
156
+ },
157
+ "required": [
158
+ "path",
159
+ "assert"
160
+ ],
161
+ "additionalProperties": false
162
+ }
163
+ },
164
+ "assert_exit_code": {
165
+ "description": "Assert the exit code captured from a command node output.",
88
166
  "examples": [
89
167
  {
168
+ "action": "assert_exit_code",
169
+ "source": "run-tests",
170
+ "expected": 0,
171
+ "intent": "Confirm the project command completed with the expected result.",
172
+ "next": "done"
173
+ }
174
+ ],
175
+ "schema": {
176
+ "type": "object",
177
+ "properties": {
178
+ "source": {
179
+ "type": "string"
180
+ },
90
181
  "node": {
91
- "action": "switch",
92
- "value": "{{params.mode}}",
93
- "equals": "background_resume",
94
- "cases": { "match": "background" },
95
- "default": "warm",
96
- "intent": "Choose the requested lifecycle preparation path"
182
+ "type": "string"
183
+ },
184
+ "expected": {
185
+ "type": "number"
97
186
  }
187
+ },
188
+ "additionalProperties": false
189
+ }
190
+ },
191
+ "assert_output": {
192
+ "description": "Assert stdout or stderr captured from a command node output.",
193
+ "examples": [
194
+ {
195
+ "action": "assert_output",
196
+ "source": "run-tests",
197
+ "stream": "stdout",
198
+ "contains": "PASS",
199
+ "intent": "Confirm the recorded output satisfies the requested condition.",
200
+ "next": "done"
98
201
  }
99
202
  ],
100
203
  "schema": {
101
204
  "type": "object",
102
205
  "properties": {
103
- "value": { "type": "string" },
104
- "equals": { "type": "string" }
206
+ "source": {
207
+ "type": "string"
208
+ },
209
+ "node": {
210
+ "type": "string"
211
+ },
212
+ "stream": {
213
+ "type": "string"
214
+ },
215
+ "contains": {
216
+ "type": "string"
217
+ },
218
+ "match": {
219
+ "type": "string"
220
+ },
221
+ "assert": {
222
+ "type": "object",
223
+ "properties": {
224
+ "path": {
225
+ "type": "string"
226
+ },
227
+ "operator": {
228
+ "type": "string"
229
+ },
230
+ "value": {
231
+ "type": [
232
+ "string",
233
+ "number",
234
+ "integer",
235
+ "boolean",
236
+ "object",
237
+ "array"
238
+ ],
239
+ "items": {
240
+ "type": [
241
+ "string",
242
+ "number",
243
+ "integer",
244
+ "boolean",
245
+ "object"
246
+ ]
247
+ }
248
+ },
249
+ "all": {
250
+ "type": "array",
251
+ "items": {
252
+ "type": "object"
253
+ }
254
+ },
255
+ "any": {
256
+ "type": "array",
257
+ "items": {
258
+ "type": "object"
259
+ }
260
+ },
261
+ "none": {
262
+ "type": "array",
263
+ "items": {
264
+ "type": "object"
265
+ }
266
+ }
267
+ },
268
+ "additionalProperties": false
269
+ }
105
270
  },
106
- "required": ["value", "equals"],
107
271
  "additionalProperties": false
108
- },
109
- "result_cases": ["match"]
272
+ }
110
273
  },
111
274
  "watch_logs": {
112
275
  "description": "Base log assertion/capture action.",
113
276
  "examples": [
114
277
  {
115
- "node": {
116
- "action": "watch_logs",
117
- "path": "logs/app.log",
118
- "intent": "Base log assertion/capture action"
119
- }
278
+ "action": "watch_logs",
279
+ "path": "logs/app.log",
280
+ "intent": "Observe the runtime behavior needed for this proof.",
281
+ "next": "done"
120
282
  }
121
283
  ],
122
284
  "schema": {
@@ -146,18 +308,17 @@
146
308
  "description": "Index extra recipe-authored artifacts that already exist on disk. Do not use after ui.screenshot; screenshots are registered by the runner.",
147
309
  "examples": [
148
310
  {
149
- "node": {
150
- "action": "index_artifacts",
151
- "artifacts": [
152
- {
153
- "path": "reports/result.json",
154
- "type": "report",
155
- "category": "validation",
156
- "label": "Validation report"
157
- }
158
- ],
159
- "intent": "Register a pre-existing report artifact"
160
- }
311
+ "action": "index_artifacts",
312
+ "artifacts": [
313
+ {
314
+ "path": "reports/result.json",
315
+ "type": "report",
316
+ "category": "validation",
317
+ "label": "Validation report"
318
+ }
319
+ ],
320
+ "intent": "Preserve generated evidence in the review package.",
321
+ "next": "done"
161
322
  }
162
323
  ],
163
324
  "schema": {
@@ -213,15 +374,58 @@
213
374
  "description": "Base terminal action.",
214
375
  "examples": [
215
376
  {
216
- "node": {
217
- "action": "end",
218
- "status": "pass"
219
- }
377
+ "action": "end",
378
+ "status": "pass"
220
379
  }
221
380
  ]
222
381
  },
382
+ "call": {
383
+ "description": "Invoke a named recipe by ref and run its steps inline.",
384
+ "examples": [
385
+ {
386
+ "action": "call",
387
+ "ref": "mydev.open_perps_setup",
388
+ "intent": "Run a personal Perps setup recipe",
389
+ "next": "done"
390
+ }
391
+ ]
392
+ },
393
+ "switch": {
394
+ "description": "Choose a graph branch when one resolved string equals another.",
395
+ "examples": [
396
+ {
397
+ "action": "switch",
398
+ "value": "{{params.mode}}",
399
+ "equals": "background_resume",
400
+ "cases": {
401
+ "match": "background"
402
+ },
403
+ "default": "warm",
404
+ "intent": "Choose the graph path matching the observed state."
405
+ }
406
+ ],
407
+ "schema": {
408
+ "type": "object",
409
+ "properties": {
410
+ "value": {
411
+ "type": "string"
412
+ },
413
+ "equals": {
414
+ "type": "string"
415
+ }
416
+ },
417
+ "required": [
418
+ "value",
419
+ "equals"
420
+ ],
421
+ "additionalProperties": false
422
+ },
423
+ "result_cases": [
424
+ "match"
425
+ ]
426
+ },
223
427
  "ui.navigate": {
224
- "description": "Navigate with either raw React Navigation route/screen params or a small stable page alias. Discover supported aliases here before hardcoding routes.",
428
+ "description": "Navigate by semantic page intent through the adapter-owned current route mapping, then verify the live destination. Route drift fails loudly; raw route/screen params must be verified against the current app first.",
225
429
  "schema": {
226
430
  "type": "object",
227
431
  "properties": {
@@ -265,34 +469,112 @@
265
469
  },
266
470
  "examples": [
267
471
  {
268
- "description": "Open Perps through the stable alias",
269
- "node": {
270
- "action": "ui.navigate",
271
- "page": "perps",
272
- "intent": "Open Perps"
273
- }
274
- },
275
- {
276
- "description": "Open a Perps market through the stable alias",
277
- "node": {
278
- "action": "ui.navigate",
279
- "page": "perps-market",
280
- "market": "ETH",
281
- "intent": "Open ETH Perps market"
282
- }
283
- },
284
- {
285
- "description": "Fallback to a raw React Navigation route when no alias exists",
286
- "node": {
287
- "action": "ui.navigate",
288
- "route": "PerpsMarketListView",
289
- "intent": "Open a raw app route"
290
- }
472
+ "action": "ui.navigate",
473
+ "page": "perps",
474
+ "intent": "Open the requested application surface.",
475
+ "next": "done"
476
+ },
477
+ {
478
+ "action": "ui.navigate",
479
+ "page": "perps-market",
480
+ "market": "ETH",
481
+ "intent": "Open the requested application surface.",
482
+ "next": "done"
483
+ },
484
+ {
485
+ "action": "ui.navigate",
486
+ "route": "PerpsMarketListView",
487
+ "intent": "Open the requested application surface.",
488
+ "next": "done"
489
+ }
490
+ ]
491
+ },
492
+ "ui.press": {
493
+ "description": "Press a React Native component by exact testID/test_id, selector identifier, or contained visible text.",
494
+ "schema": {
495
+ "type": "object",
496
+ "properties": {
497
+ "selector": {
498
+ "type": "string"
499
+ },
500
+ "test_id": {
501
+ "type": "string"
502
+ },
503
+ "testID": {
504
+ "type": "string"
505
+ },
506
+ "text": {
507
+ "type": "string"
508
+ },
509
+ "label": {
510
+ "type": "string"
511
+ },
512
+ "timeout_ms": {
513
+ "type": "number"
514
+ },
515
+ "settle": {
516
+ "type": "boolean"
517
+ },
518
+ "long_press": {
519
+ "type": "boolean",
520
+ "description": "Invoke the target component's long-press gesture. Requires identifier targeting."
521
+ }
522
+ },
523
+ "additionalProperties": false
524
+ },
525
+ "examples": [
526
+ {
527
+ "action": "ui.press",
528
+ "test_id": "perps-tab",
529
+ "intent": "Activate the requested visible control.",
530
+ "next": "done"
531
+ },
532
+ {
533
+ "action": "ui.press",
534
+ "text": "Ethereum",
535
+ "intent": "Activate the requested visible control.",
536
+ "next": "done"
537
+ },
538
+ {
539
+ "action": "ui.press",
540
+ "test_id": "keypad-delete-button",
541
+ "long_press": true,
542
+ "intent": "Activate the requested visible control.",
543
+ "next": "done"
544
+ }
545
+ ]
546
+ },
547
+ "ui.key_press": {
548
+ "description": "Send a trusted keyboard press to the currently focused UI element through the UI transport.",
549
+ "schema": {
550
+ "type": "object",
551
+ "properties": {
552
+ "key": {
553
+ "type": "string"
554
+ },
555
+ "timeout_ms": {
556
+ "type": "number"
557
+ },
558
+ "settle": {
559
+ "type": "boolean"
560
+ }
561
+ },
562
+ "required": [
563
+ "key"
564
+ ],
565
+ "additionalProperties": false
566
+ },
567
+ "examples": [
568
+ {
569
+ "action": "ui.key_press",
570
+ "key": "Enter",
571
+ "intent": "Continue the requested keyboard interaction.",
572
+ "next": "done"
291
573
  }
292
574
  ]
293
575
  },
294
- "ui.press": {
295
- "description": "Press a React Native component by exact testID/test_id, selector identifier, or contained visible text.",
576
+ "ui.set_input": {
577
+ "description": "Set text on a React Native TextInput by testID through the app control bridge.",
296
578
  "schema": {
297
579
  "type": "object",
298
580
  "properties": {
@@ -305,10 +587,10 @@
305
587
  "testID": {
306
588
  "type": "string"
307
589
  },
308
- "text": {
590
+ "value": {
309
591
  "type": "string"
310
592
  },
311
- "label": {
593
+ "text": {
312
594
  "type": "string"
313
595
  },
314
596
  "timeout_ms": {
@@ -316,39 +598,17 @@
316
598
  },
317
599
  "settle": {
318
600
  "type": "boolean"
319
- },
320
- "long_press": {
321
- "type": "boolean",
322
- "description": "Invoke the target component's long-press gesture. Requires identifier targeting."
323
601
  }
324
602
  },
325
603
  "additionalProperties": false
326
604
  },
327
605
  "examples": [
328
606
  {
329
- "description": "Press by stable React Native testID",
330
- "node": {
331
- "action": "ui.press",
332
- "test_id": "perps-tab",
333
- "intent": "Open Perps through its stable testID"
334
- }
335
- },
336
- {
337
- "description": "Press a component that is reachable only by visible text",
338
- "node": {
339
- "action": "ui.press",
340
- "text": "Ethereum",
341
- "intent": "Press the component containing the visible Ethereum label"
342
- }
343
- },
344
- {
345
- "description": "Long-press a component by stable React Native testID",
346
- "node": {
347
- "action": "ui.press",
348
- "test_id": "keypad-delete-button",
349
- "long_press": true,
350
- "intent": "Clear the current keypad value through its human reset gesture"
351
- }
607
+ "action": "ui.set_input",
608
+ "test_id": "network-selector-network-search-input",
609
+ "value": "Sepolia",
610
+ "intent": "Provide the requested value in the visible form.",
611
+ "next": "done"
352
612
  }
353
613
  ]
354
614
  },
@@ -356,23 +616,19 @@
356
616
  "description": "Scroll through the UI through the Farmslot base platform adapter. Supports deltas and scroll_into_view for selector/test-id proof positioning.",
357
617
  "examples": [
358
618
  {
359
- "node": {
360
- "action": "ui.scroll",
361
- "delta_y": 300,
362
- "intent": "Scroll through the UI through the Farmslot base platform adapter. Supports deltas and scroll_into_view for selector/test-id proof positioning"
363
- }
619
+ "action": "ui.scroll",
620
+ "delta_y": 300,
621
+ "intent": "Expose the requested content for visible review.",
622
+ "next": "done"
364
623
  },
365
624
  {
366
- "description": "Bring a React Native testID into view before screenshot proof",
367
- "node": {
368
- "action": "ui.scroll",
369
- "test_id": "wallet-screen",
370
- "scroll_into_view": true,
371
- "intent": "Bring a React Native testID into view before screenshot proof"
372
- }
625
+ "action": "ui.scroll",
626
+ "test_id": "wallet-screen",
627
+ "scroll_into_view": true,
628
+ "intent": "Expose the requested content for visible review.",
629
+ "next": "done"
373
630
  }
374
631
  ],
375
- "proof_effect": "E2E validation must include a passing ui-scroll-into-view trace event with output.intoView=true.",
376
632
  "schema": {
377
633
  "type": "object",
378
634
  "properties": {
@@ -463,20 +719,18 @@
463
719
  },
464
720
  "examples": [
465
721
  {
466
- "node": {
467
- "action": "ui.wait_for",
468
- "test_id": "perps-home-heading",
469
- "expected": "present",
470
- "intent": "Confirm the requested mobile content is visible"
471
- }
722
+ "action": "ui.wait_for",
723
+ "test_id": "perps-home-heading",
724
+ "expected": "present",
725
+ "intent": "Confirm the requested application state is visible.",
726
+ "next": "done"
472
727
  },
473
728
  {
474
- "node": {
475
- "action": "ui.wait_for",
476
- "test_id": "conditional-banner",
477
- "expected": "absent",
478
- "intent": "Confirm the conditional mobile content is absent"
479
- }
729
+ "action": "ui.wait_for",
730
+ "test_id": "conditional-banner",
731
+ "expected": "absent",
732
+ "intent": "Confirm the requested application state is visible.",
733
+ "next": "done"
480
734
  }
481
735
  ]
482
736
  },
@@ -484,11 +738,10 @@
484
738
  "description": "Capture registered PNG evidence with xcrun simctl on iOS or adb screencap on Android. Artifact metadata records the native provider, command mode, and selected device; invalid or empty PNG output fails before registration.",
485
739
  "examples": [
486
740
  {
487
- "node": {
488
- "action": "ui.screenshot",
489
- "path": "screenshots/example.png",
490
- "intent": "Capture visual evidence after a wait/assert step"
491
- }
741
+ "action": "ui.screenshot",
742
+ "path": "screenshots/example.png",
743
+ "intent": "Preserve the visible application state as review evidence.",
744
+ "next": "done"
492
745
  }
493
746
  ],
494
747
  "schema": {
@@ -514,12 +767,11 @@
514
767
  "description": "Drive outer mobile app lifecycle for deterministic performance start states without rebuilding: foreground/launch, background, terminate, or restart the installed app.",
515
768
  "examples": [
516
769
  {
517
- "node": {
518
- "action": "app.lifecycle",
519
- "command": "background",
520
- "settle_ms": 1000,
521
- "intent": "Send the selected Android device to the launcher before a warm-resume measurement"
522
- }
770
+ "action": "app.lifecycle",
771
+ "command": "background",
772
+ "settle_ms": 1000,
773
+ "intent": "Converge the application to the requested lifecycle state.",
774
+ "next": "done"
523
775
  }
524
776
  ],
525
777
  "schema": {
@@ -599,260 +851,53 @@
599
851
  "type": "string"
600
852
  },
601
853
  "app_id": {
602
- "type": "string"
603
- },
604
- "simulator": {
605
- "type": "string"
606
- },
607
- "ios_simulator": {
608
- "type": "string"
609
- },
610
- "bundle_id": {
611
- "type": "string"
612
- },
613
- "bundleId": {
614
- "type": "string"
615
- }
616
- },
617
- "additionalProperties": false
618
- }
619
- },
620
- "assert_file": {
621
- "description": "Assert that a project file exists and optionally contains text.",
622
- "examples": [
623
- {
624
- "node": {
625
- "action": "assert_file",
626
- "path": "reports/result.txt",
627
- "contains": "ok",
628
- "intent": "Assert that a project file exists and optionally contains text"
629
- }
630
- }
631
- ],
632
- "schema": {
633
- "type": "object",
634
- "properties": {
635
- "path": {
636
- "type": "string"
637
- },
638
- "contains": {
639
- "type": "string"
640
- }
641
- },
642
- "required": [
643
- "path"
644
- ],
645
- "additionalProperties": false
646
- }
647
- },
648
- "assert_json": {
649
- "description": "Assert a JSON file value with a JSONPath-style selector.",
650
- "examples": [
651
- {
652
- "node": {
653
- "action": "assert_json",
654
- "path": "reports/result.json",
655
- "assert": {
656
- "path": "$.status",
657
- "operator": "eq",
658
- "value": "ok"
659
- },
660
- "intent": "Assert a JSON file value with a JSONPath-style selector"
661
- }
662
- }
663
- ],
664
- "schema": {
665
- "type": "object",
666
- "properties": {
667
- "path": {
668
- "type": "string"
669
- },
670
- "assert": {
671
- "type": "object",
672
- "properties": {
673
- "path": {
674
- "type": "string"
675
- },
676
- "operator": {
677
- "type": "string"
678
- },
679
- "value": {
680
- "type": [
681
- "string",
682
- "number",
683
- "integer",
684
- "boolean",
685
- "object",
686
- "array"
687
- ],
688
- "items": {
689
- "type": [
690
- "string",
691
- "number",
692
- "integer",
693
- "boolean",
694
- "object"
695
- ]
696
- }
697
- },
698
- "all": {
699
- "type": "array",
700
- "items": {
701
- "type": "object"
702
- }
703
- },
704
- "any": {
705
- "type": "array",
706
- "items": {
707
- "type": "object"
708
- }
709
- },
710
- "none": {
711
- "type": "array",
712
- "items": {
713
- "type": "object"
714
- }
715
- }
716
- },
717
- "additionalProperties": false
718
- }
719
- },
720
- "required": [
721
- "path",
722
- "assert"
723
- ],
724
- "additionalProperties": false
725
- }
726
- },
727
- "assert_exit_code": {
728
- "description": "Assert the exit code captured from a command node output.",
729
- "examples": [
730
- {
731
- "node": {
732
- "action": "assert_exit_code",
733
- "source": "run-tests",
734
- "expected": 0,
735
- "intent": "Assert the exit code captured from a command node output"
736
- }
737
- }
738
- ],
739
- "schema": {
740
- "type": "object",
741
- "properties": {
742
- "source": {
743
- "type": "string"
744
- },
745
- "node": {
746
- "type": "string"
747
- },
748
- "expected": {
749
- "type": "number"
750
- }
751
- },
752
- "additionalProperties": false
753
- }
754
- },
755
- "assert_output": {
756
- "description": "Assert stdout or stderr captured from a command node output.",
757
- "examples": [
758
- {
759
- "node": {
760
- "action": "assert_output",
761
- "source": "run-tests",
762
- "stream": "stdout",
763
- "contains": "PASS",
764
- "intent": "Assert stdout or stderr captured from a command node output"
765
- }
766
- }
767
- ],
768
- "schema": {
769
- "type": "object",
770
- "properties": {
771
- "source": {
772
- "type": "string"
773
- },
774
- "node": {
775
- "type": "string"
776
- },
777
- "stream": {
778
- "type": "string"
779
- },
780
- "contains": {
781
- "type": "string"
782
- },
783
- "match": {
784
- "type": "string"
785
- },
786
- "assert": {
787
- "type": "object",
788
- "properties": {
789
- "path": {
790
- "type": "string"
791
- },
792
- "operator": {
793
- "type": "string"
794
- },
795
- "value": {
796
- "type": [
797
- "string",
798
- "number",
799
- "integer",
800
- "boolean",
801
- "object",
802
- "array"
803
- ],
804
- "items": {
805
- "type": [
806
- "string",
807
- "number",
808
- "integer",
809
- "boolean",
810
- "object"
811
- ]
812
- }
813
- },
814
- "all": {
815
- "type": "array",
816
- "items": {
817
- "type": "object"
818
- }
819
- },
820
- "any": {
821
- "type": "array",
822
- "items": {
823
- "type": "object"
824
- }
825
- },
826
- "none": {
827
- "type": "array",
828
- "items": {
829
- "type": "object"
830
- }
831
- }
832
- },
833
- "additionalProperties": false
854
+ "type": "string"
855
+ },
856
+ "simulator": {
857
+ "type": "string"
858
+ },
859
+ "ios_simulator": {
860
+ "type": "string"
861
+ },
862
+ "bundle_id": {
863
+ "type": "string"
864
+ },
865
+ "bundleId": {
866
+ "type": "string"
834
867
  }
835
868
  },
836
869
  "additionalProperties": false
837
870
  }
838
871
  },
872
+ "app.status": {
873
+ "description": "Report the adapter's static status — platform, project root, resolved checkout shape, and headless compatibility mode (no live route or account).",
874
+ "examples": [
875
+ {
876
+ "action": "app.status",
877
+ "intent": "Record the resolved application state before continuing.",
878
+ "next": "done"
879
+ }
880
+ ],
881
+ "schema": {
882
+ "type": "object",
883
+ "properties": {},
884
+ "additionalProperties": false
885
+ }
886
+ },
839
887
  "app.hud": {
840
888
  "description": "Display minimal human-readable recipe progress. The intent must explain the current agent goal; debug metadata and grouping labels are hidden unless explicitly opted in.",
841
889
  "examples": [
842
890
  {
843
- "description": "Default one-intent progress HUD",
844
- "node": {
845
- "action": "app.hud",
846
- "status": "running",
847
- "intent": "Open a small ETH long position",
848
- "progress": {
849
- "current": 12,
850
- "total": 19
851
- }
852
- }
891
+ "action": "app.hud",
892
+ "status": "running",
893
+ "intent": "Show the current goal to the supervising human.",
894
+ "progress": {
895
+ "current": 12,
896
+ "total": 19
897
+ },
898
+ "next": "done"
853
899
  }
854
900
  ],
855
- "safety_notes": "HUD is reviewer feedback only. It must not mutate product state or carry task assertions; trace/artifacts remain authoritative.",
856
901
  "schema": {
857
902
  "type": "object",
858
903
  "properties": {
@@ -904,101 +949,14 @@
904
949
  "additionalProperties": false
905
950
  }
906
951
  },
907
- "ui.set_input": {
908
- "description": "Set text on a React Native TextInput by testID through the app control bridge.",
909
- "schema": {
910
- "type": "object",
911
- "properties": {
912
- "selector": {
913
- "type": "string"
914
- },
915
- "test_id": {
916
- "type": "string"
917
- },
918
- "testID": {
919
- "type": "string"
920
- },
921
- "value": {
922
- "type": "string"
923
- },
924
- "text": {
925
- "type": "string"
926
- },
927
- "timeout_ms": {
928
- "type": "number"
929
- },
930
- "settle": {
931
- "type": "boolean"
932
- }
933
- },
934
- "additionalProperties": false
935
- },
936
- "examples": [
937
- {
938
- "node": {
939
- "action": "ui.set_input",
940
- "test_id": "network-selector-network-search-input",
941
- "value": "Sepolia",
942
- "intent": "Set text on a React Native TextInput by testID through the app control bridge"
943
- }
944
- }
945
- ]
946
- },
947
- "ui.key_press": {
948
- "description": "Send a trusted keyboard press to the currently focused UI element through the UI transport.",
949
- "schema": {
950
- "type": "object",
951
- "properties": {
952
- "key": {
953
- "type": "string"
954
- },
955
- "timeout_ms": {
956
- "type": "number"
957
- },
958
- "settle": {
959
- "type": "boolean"
960
- }
961
- },
962
- "required": [
963
- "key"
964
- ],
965
- "additionalProperties": false
966
- },
967
- "examples": [
968
- {
969
- "node": {
970
- "action": "ui.key_press",
971
- "key": "Enter",
972
- "intent": "Send a trusted Enter key press to the focused UI element"
973
- }
974
- }
975
- ]
976
- },
977
- "app.status": {
978
- "description": "Report the adapter's static status — platform, project root, resolved checkout shape, and headless compatibility mode (no live route or account).",
979
- "examples": [
980
- {
981
- "node": {
982
- "action": "app.status",
983
- "intent": "Report the resolved checkout status"
984
- }
985
- }
986
- ],
987
- "schema": {
988
- "type": "object",
989
- "properties": {},
990
- "additionalProperties": false
991
- }
992
- },
993
952
  "cdp.target": {
994
953
  "description": "Probe the platform debug transport — Chrome CDP for extension, the React Native debug bridge for mobile — and report whether it is reachable.",
995
954
  "examples": [
996
955
  {
997
- "node": {
998
- "action": "cdp.target",
999
- "require_reachable": true,
1000
- "intent": "Confirm the mobile React Native debug bridge is reachable"
1001
- }
956
+ "action": "cdp.target",
957
+ "require_reachable": true,
958
+ "intent": "Confirm the requested application target is available.",
959
+ "next": "done"
1002
960
  }
1003
961
  ],
1004
962
  "schema": {
@@ -1032,23 +990,7 @@
1032
990
  "additionalProperties": false
1033
991
  }
1034
992
  },
1035
- "call": {
1036
- "description": "Invoke a named recipe by ref and run its steps inline.",
1037
- "examples": [
1038
- {
1039
- "node": {
1040
- "action": "call",
1041
- "ref": "mydev.open_perps_setup",
1042
- "intent": "Run a personal Perps setup recipe"
1043
- }
1044
- }
1045
- ]
1046
- }
1047
- },
1048
- "custom_actions": [
1049
- {
1050
- "name": "metamask.wallet.fixture_status",
1051
- "owner": "metamask",
993
+ "metamask.wallet.fixture_status": {
1052
994
  "description": "Report the on-disk wallet fixture status (accounts and password presence) without launching or mutating the app.",
1053
995
  "schema": {
1054
996
  "type": "object",
@@ -1057,20 +999,14 @@
1057
999
  },
1058
1000
  "examples": [
1059
1001
  {
1060
- "description": "Minimal node",
1061
- "node": {
1062
- "action": "metamask.wallet.fixture_status",
1063
- "intent": "Check wallet fixture status"
1064
- }
1002
+ "action": "metamask.wallet.fixture_status",
1003
+ "intent": "Check wallet fixture status",
1004
+ "next": "done"
1065
1005
  }
1066
1006
  ],
1067
- "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or target runtime output.",
1068
- "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof.",
1069
1007
  "execution_capabilities": []
1070
1008
  },
1071
- {
1072
- "name": "metamask.wallet.setup",
1073
- "owner": "metamask",
1009
+ "metamask.wallet.setup": {
1074
1010
  "description": "Import the wallet fixture accounts and password so the app starts from a known signed-in state.",
1075
1011
  "schema": {
1076
1012
  "type": "object",
@@ -1079,22 +1015,16 @@
1079
1015
  },
1080
1016
  "examples": [
1081
1017
  {
1082
- "description": "Minimal node",
1083
- "node": {
1084
- "action": "metamask.wallet.setup",
1085
- "intent": "Prepare the wallet fixture for recipe execution"
1086
- }
1018
+ "action": "metamask.wallet.setup",
1019
+ "intent": "Prepare the wallet fixture for recipe execution",
1020
+ "next": "done"
1087
1021
  }
1088
1022
  ],
1089
- "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or target runtime output.",
1090
- "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof.",
1091
1023
  "execution_capabilities": [
1092
1024
  "app-mutation"
1093
1025
  ]
1094
1026
  },
1095
- {
1096
- "name": "metamask.wallet.ensure_unlocked",
1097
- "owner": "metamask",
1027
+ "metamask.wallet.ensure_unlocked": {
1098
1028
  "description": "Unlock the wallet with the fixture password when it is currently locked.",
1099
1029
  "schema": {
1100
1030
  "type": "object",
@@ -1110,22 +1040,16 @@
1110
1040
  },
1111
1041
  "examples": [
1112
1042
  {
1113
- "description": "Minimal node",
1114
- "node": {
1115
- "action": "metamask.wallet.ensure_unlocked",
1116
- "intent": "Ensure the wallet is unlocked before proof steps"
1117
- }
1043
+ "action": "metamask.wallet.ensure_unlocked",
1044
+ "intent": "Ensure the wallet is unlocked before proof steps",
1045
+ "next": "done"
1118
1046
  }
1119
1047
  ],
1120
- "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or target runtime output.",
1121
- "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof.",
1122
1048
  "execution_capabilities": [
1123
1049
  "app-mutation"
1124
1050
  ]
1125
1051
  },
1126
- {
1127
- "name": "metamask.wallet.select_account",
1128
- "owner": "metamask",
1052
+ "metamask.wallet.select_account": {
1129
1053
  "description": "mobile action to select a wallet account by address, id, or display name.",
1130
1054
  "schema": {
1131
1055
  "type": "object",
@@ -1147,31 +1071,23 @@
1147
1071
  },
1148
1072
  "examples": [
1149
1073
  {
1150
- "description": "Select by deterministic fixture address",
1151
- "node": {
1152
- "action": "metamask.wallet.select_account",
1153
- "address": "0x8dc623e964475d4d669da601fd15ea9125469003",
1154
- "intent": "Select the requested wallet account"
1155
- }
1074
+ "action": "metamask.wallet.select_account",
1075
+ "address": "0x8dc623e964475d4d669da601fd15ea9125469003",
1076
+ "intent": "Select the requested wallet account",
1077
+ "next": "done"
1156
1078
  },
1157
1079
  {
1158
- "description": "Select by fixture display name",
1159
- "node": {
1160
- "action": "metamask.wallet.select_account",
1161
- "name": "dev1",
1162
- "intent": "Select the requested wallet account"
1163
- }
1080
+ "action": "metamask.wallet.select_account",
1081
+ "name": "dev1",
1082
+ "intent": "Select the requested wallet account",
1083
+ "next": "done"
1164
1084
  }
1165
1085
  ],
1166
- "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or target runtime output.",
1167
- "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof.",
1168
1086
  "execution_capabilities": [
1169
1087
  "app-mutation"
1170
1088
  ]
1171
1089
  },
1172
- {
1173
- "name": "metamask.wallet.list_accounts",
1174
- "owner": "metamask",
1090
+ "metamask.wallet.list_accounts": {
1175
1091
  "description": "List redacted wallet accounts and identify the selected account without mutating the app.",
1176
1092
  "schema": {
1177
1093
  "type": "object",
@@ -1192,20 +1108,14 @@
1192
1108
  },
1193
1109
  "examples": [
1194
1110
  {
1195
- "description": "List wallet accounts",
1196
- "node": {
1197
- "action": "metamask.wallet.list_accounts",
1198
- "intent": "Discover the available wallet accounts"
1199
- }
1111
+ "action": "metamask.wallet.list_accounts",
1112
+ "intent": "Discover the available wallet accounts",
1113
+ "next": "done"
1200
1114
  }
1201
1115
  ],
1202
- "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or target runtime output.",
1203
- "safety_notes": "Read-only account metadata; must never expose keys, mnemonics, vault data, or credentials.",
1204
1116
  "execution_capabilities": []
1205
1117
  },
1206
- {
1207
- "name": "metamask.wallet.read_state",
1208
- "owner": "metamask",
1118
+ "metamask.wallet.read_state": {
1209
1119
  "description": "Read the redacted wallet state (selected account, route, and device) from the running app.",
1210
1120
  "schema": {
1211
1121
  "type": "object",
@@ -1214,20 +1124,14 @@
1214
1124
  },
1215
1125
  "examples": [
1216
1126
  {
1217
- "description": "Minimal node",
1218
- "node": {
1219
- "action": "metamask.wallet.read_state",
1220
- "intent": "Read wallet state needed for the proof"
1221
- }
1127
+ "action": "metamask.wallet.read_state",
1128
+ "intent": "Read wallet state needed for the proof",
1129
+ "next": "done"
1222
1130
  }
1223
1131
  ],
1224
- "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or target runtime output.",
1225
- "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof.",
1226
1132
  "execution_capabilities": []
1227
1133
  },
1228
- {
1229
- "name": "metamask.perps.read_positions",
1230
- "owner": "metamask",
1134
+ "metamask.perps.read_positions": {
1231
1135
  "description": "mobile Read live Perps positions; without a selector, return all live positions.",
1232
1136
  "schema": {
1233
1137
  "type": "object",
@@ -1316,21 +1220,15 @@
1316
1220
  },
1317
1221
  "examples": [
1318
1222
  {
1319
- "description": "Read selected live state",
1320
- "node": {
1321
- "action": "metamask.perps.read_positions",
1322
- "market": "BTC",
1323
- "intent": "Read Perps positions needed for the proof"
1324
- }
1223
+ "action": "metamask.perps.read_positions",
1224
+ "market": "BTC",
1225
+ "intent": "Read Perps positions needed for the proof",
1226
+ "next": "done"
1325
1227
  }
1326
1228
  ],
1327
- "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or target runtime output.",
1328
- "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof.",
1329
1229
  "execution_capabilities": []
1330
1230
  },
1331
- {
1332
- "name": "metamask.perps.read_orders",
1333
- "owner": "metamask",
1231
+ "metamask.perps.read_orders": {
1334
1232
  "description": "mobile Read live Perps open orders; without a selector, return all live orders.",
1335
1233
  "schema": {
1336
1234
  "type": "object",
@@ -1419,21 +1317,15 @@
1419
1317
  },
1420
1318
  "examples": [
1421
1319
  {
1422
- "description": "Read selected live state",
1423
- "node": {
1424
- "action": "metamask.perps.read_orders",
1425
- "market": "BTC",
1426
- "intent": "Read Perps open orders needed for the proof"
1427
- }
1320
+ "action": "metamask.perps.read_orders",
1321
+ "market": "BTC",
1322
+ "intent": "Read Perps open orders needed for the proof",
1323
+ "next": "done"
1428
1324
  }
1429
1325
  ],
1430
- "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or target runtime output.",
1431
- "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof.",
1432
1326
  "execution_capabilities": []
1433
1327
  },
1434
- {
1435
- "name": "metamask.perps.close_positions",
1436
- "owner": "metamask",
1328
+ "metamask.perps.close_positions": {
1437
1329
  "description": "mobile Default to testnet for Perps mutations; mainnet is read-only unless explicitly requested. Close selected live Perps positions. Use selector/mode params to close matching symbols or all positions.",
1438
1330
  "schema": {
1439
1331
  "type": "object",
@@ -1522,35 +1414,27 @@
1522
1414
  },
1523
1415
  "examples": [
1524
1416
  {
1525
- "description": "Close every open position",
1526
- "node": {
1527
- "action": "metamask.perps.close_positions",
1528
- "mode": "all",
1529
- "intent": "Close selected Perps positions before continuing"
1530
- }
1417
+ "action": "metamask.perps.close_positions",
1418
+ "mode": "all",
1419
+ "intent": "Close selected Perps positions before continuing",
1420
+ "next": "done"
1531
1421
  },
1532
1422
  {
1533
- "description": "Close selected BTC/ETH positions",
1534
- "node": {
1535
- "action": "metamask.perps.close_positions",
1536
- "markets": [
1537
- "BTC",
1538
- "ETH"
1539
- ],
1540
- "intent": "Close selected Perps positions before continuing"
1541
- }
1423
+ "action": "metamask.perps.close_positions",
1424
+ "markets": [
1425
+ "BTC",
1426
+ "ETH"
1427
+ ],
1428
+ "intent": "Close selected Perps positions before continuing",
1429
+ "next": "done"
1542
1430
  }
1543
1431
  ],
1544
- "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or target runtime output.",
1545
- "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof.",
1546
1432
  "execution_capabilities": [
1547
1433
  "app-mutation",
1548
1434
  "external-mutation"
1549
1435
  ]
1550
1436
  },
1551
- {
1552
- "name": "metamask.perps.close_orders",
1553
- "owner": "metamask",
1437
+ "metamask.perps.close_orders": {
1554
1438
  "description": "mobile Default to testnet for Perps mutations; mainnet is read-only unless explicitly requested. Cancel selected live Perps open orders. Use selector/mode params to cancel matching symbols or all orders.",
1555
1439
  "schema": {
1556
1440
  "type": "object",
@@ -1639,32 +1523,24 @@
1639
1523
  },
1640
1524
  "examples": [
1641
1525
  {
1642
- "description": "Cancel every open order",
1643
- "node": {
1644
- "action": "metamask.perps.close_orders",
1645
- "mode": "all",
1646
- "intent": "Cancel selected Perps orders before continuing"
1647
- }
1526
+ "action": "metamask.perps.close_orders",
1527
+ "mode": "all",
1528
+ "intent": "Cancel selected Perps orders before continuing",
1529
+ "next": "done"
1648
1530
  },
1649
1531
  {
1650
- "description": "Cancel BTC orders only",
1651
- "node": {
1652
- "action": "metamask.perps.close_orders",
1653
- "market": "BTC",
1654
- "intent": "Cancel selected Perps orders before continuing"
1655
- }
1532
+ "action": "metamask.perps.close_orders",
1533
+ "market": "BTC",
1534
+ "intent": "Cancel selected Perps orders before continuing",
1535
+ "next": "done"
1656
1536
  }
1657
1537
  ],
1658
- "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or target runtime output.",
1659
- "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof.",
1660
1538
  "execution_capabilities": [
1661
1539
  "app-mutation",
1662
1540
  "external-mutation"
1663
1541
  ]
1664
1542
  },
1665
- {
1666
- "name": "metamask.perps.place_order",
1667
- "owner": "metamask",
1543
+ "metamask.perps.place_order": {
1668
1544
  "description": "mobile Place a Perps order through a supported app/API path. Default to testnet; mainnet order placement requires an explicit user request.",
1669
1545
  "schema": {
1670
1546
  "type": "object",
@@ -1743,17 +1619,23 @@
1743
1619
  "long",
1744
1620
  "short"
1745
1621
  ],
1746
- "description": "Optional position/order side filter."
1622
+ "description": "Required order direction."
1747
1623
  },
1748
1624
  "timeout_ms": {
1749
1625
  "type": "number"
1750
1626
  },
1751
1627
  "amount": {
1752
- "type": "string",
1628
+ "type": [
1629
+ "string",
1630
+ "number"
1631
+ ],
1753
1632
  "description": "USD amount/notional alias."
1754
1633
  },
1755
1634
  "notional": {
1756
- "type": "string",
1635
+ "type": [
1636
+ "string",
1637
+ "number"
1638
+ ],
1757
1639
  "description": "USD notional alias."
1758
1640
  },
1759
1641
  "size": {
@@ -1769,27 +1651,21 @@
1769
1651
  },
1770
1652
  "examples": [
1771
1653
  {
1772
- "description": "Place BTC market order",
1773
- "node": {
1774
- "action": "metamask.perps.place_order",
1775
- "market": "BTC",
1776
- "side": "long",
1777
- "notional": "10",
1778
- "leverage": 2,
1779
- "intent": "Place the requested Perps order"
1780
- }
1654
+ "action": "metamask.perps.place_order",
1655
+ "market": "BTC",
1656
+ "side": "long",
1657
+ "notional": "10",
1658
+ "leverage": 2,
1659
+ "intent": "Place the requested Perps order",
1660
+ "next": "done"
1781
1661
  }
1782
1662
  ],
1783
- "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or target runtime output.",
1784
- "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof.",
1785
1663
  "execution_capabilities": [
1786
1664
  "app-mutation",
1787
1665
  "external-mutation"
1788
1666
  ]
1789
1667
  },
1790
- {
1791
- "name": "metamask.perps.assert_positions",
1792
- "owner": "metamask",
1668
+ "metamask.perps.assert_positions": {
1793
1669
  "description": "mobile Assert live Perps positions are present or absent for an explicit market selection or mode=all.",
1794
1670
  "schema": {
1795
1671
  "type": "object",
@@ -1892,22 +1768,16 @@
1892
1768
  },
1893
1769
  "examples": [
1894
1770
  {
1895
- "description": "Assert BTC position is open",
1896
- "node": {
1897
- "action": "metamask.perps.assert_positions",
1898
- "state": "open",
1899
- "market": "BTC",
1900
- "intent": "Assert the expected Perps position state"
1901
- }
1771
+ "action": "metamask.perps.assert_positions",
1772
+ "state": "open",
1773
+ "market": "BTC",
1774
+ "intent": "Assert the expected Perps position state",
1775
+ "next": "done"
1902
1776
  }
1903
1777
  ],
1904
- "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or target runtime output.",
1905
- "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof.",
1906
1778
  "execution_capabilities": []
1907
1779
  },
1908
- {
1909
- "name": "metamask.perps.assert_orders",
1910
- "owner": "metamask",
1780
+ "metamask.perps.assert_orders": {
1911
1781
  "description": "mobile Assert live Perps open orders are present or absent for an explicit market selection or mode=all.",
1912
1782
  "schema": {
1913
1783
  "type": "object",
@@ -2010,22 +1880,16 @@
2010
1880
  },
2011
1881
  "examples": [
2012
1882
  {
2013
- "description": "Assert no BTC open orders",
2014
- "node": {
2015
- "action": "metamask.perps.assert_orders",
2016
- "state": "none",
2017
- "market": "BTC",
2018
- "intent": "Assert the expected Perps open order state"
2019
- }
1883
+ "action": "metamask.perps.assert_orders",
1884
+ "state": "none",
1885
+ "market": "BTC",
1886
+ "intent": "Assert the expected Perps open order state",
1887
+ "next": "done"
2020
1888
  }
2021
1889
  ],
2022
- "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or target runtime output.",
2023
- "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof.",
2024
1890
  "execution_capabilities": []
2025
1891
  },
2026
- {
2027
- "name": "metamask.perps.ensure_positions",
2028
- "owner": "metamask",
1892
+ "metamask.perps.ensure_positions": {
2029
1893
  "description": "mobile Default to testnet for Perps mutations; mainnet is read-only unless explicitly requested. Higher-level wrapper that reads selected positions, closes or places when needed, then asserts final position state.",
2030
1894
  "schema": {
2031
1895
  "type": "object",
@@ -2104,11 +1968,25 @@
2104
1968
  "long",
2105
1969
  "short"
2106
1970
  ],
2107
- "description": "Optional position/order side filter."
1971
+ "description": "Required when state=open/present; otherwise filters selected positions."
2108
1972
  },
2109
1973
  "timeout_ms": {
2110
1974
  "type": "number"
2111
1975
  },
1976
+ "amount": {
1977
+ "type": [
1978
+ "string",
1979
+ "number"
1980
+ ],
1981
+ "description": "Explicit USD notional used only when creating a missing open position. Alias: notional."
1982
+ },
1983
+ "notional": {
1984
+ "type": [
1985
+ "string",
1986
+ "number"
1987
+ ],
1988
+ "description": "Alias for amount."
1989
+ },
2112
1990
  "state": {
2113
1991
  "type": "string",
2114
1992
  "enum": [
@@ -2128,34 +2006,35 @@
2128
2006
  },
2129
2007
  "examples": [
2130
2008
  {
2131
- "description": "Ensure no BTC position before proof window",
2132
- "node": {
2133
- "action": "metamask.perps.ensure_positions",
2134
- "state": "none",
2135
- "market": "BTC",
2136
- "intent": "Converge Perps positions to the requested state"
2137
- }
2009
+ "action": "metamask.perps.ensure_positions",
2010
+ "state": "none",
2011
+ "market": "BTC",
2012
+ "intent": "Converge Perps positions to the requested state",
2013
+ "next": "done"
2138
2014
  },
2139
2015
  {
2140
- "description": "Ensure any existing positions are closed",
2141
- "node": {
2142
- "action": "metamask.perps.ensure_positions",
2143
- "state": "none",
2144
- "mode": "all",
2145
- "intent": "Converge Perps positions to the requested state"
2146
- }
2016
+ "action": "metamask.perps.ensure_positions",
2017
+ "state": "none",
2018
+ "mode": "all",
2019
+ "intent": "Converge Perps positions to the requested state",
2020
+ "next": "done"
2021
+ },
2022
+ {
2023
+ "action": "metamask.perps.ensure_positions",
2024
+ "market": "ETH",
2025
+ "side": "long",
2026
+ "state": "open",
2027
+ "notional": "10",
2028
+ "intent": "Converge ETH positions to one explicit testnet precondition",
2029
+ "next": "done"
2147
2030
  }
2148
2031
  ],
2149
- "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or target runtime output.",
2150
- "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof.",
2151
2032
  "execution_capabilities": [
2152
2033
  "app-mutation",
2153
2034
  "external-mutation"
2154
2035
  ]
2155
2036
  },
2156
- {
2157
- "name": "metamask.perps.ensure_orders",
2158
- "owner": "metamask",
2037
+ "metamask.perps.ensure_orders": {
2159
2038
  "description": "mobile Converge selected testnet orders to open or absent, then independently assert the final state. Creating an open state uses a resting limit order.",
2160
2039
  "schema": {
2161
2040
  "type": "object",
@@ -2234,15 +2113,24 @@
2234
2113
  "long",
2235
2114
  "short"
2236
2115
  ],
2237
- "description": "Optional position/order side filter."
2116
+ "description": "Required when state=open/present; otherwise filters selected orders."
2238
2117
  },
2239
2118
  "timeout_ms": {
2240
2119
  "type": "number"
2241
2120
  },
2121
+ "amount": {
2122
+ "type": [
2123
+ "string",
2124
+ "number"
2125
+ ],
2126
+ "description": "Explicit USD notional used only when creating a missing open order. Alias: notional."
2127
+ },
2242
2128
  "notional": {
2243
- "type": "number",
2244
- "default": 10,
2245
- "description": "Small testnet USD notional used only when creating a missing open order."
2129
+ "type": [
2130
+ "string",
2131
+ "number"
2132
+ ],
2133
+ "description": "Alias for amount."
2246
2134
  },
2247
2135
  "leverage": {
2248
2136
  "type": "number",
@@ -2268,37 +2156,29 @@
2268
2156
  },
2269
2157
  "examples": [
2270
2158
  {
2271
- "description": "Ensure no open orders before proof window",
2272
- "node": {
2273
- "action": "metamask.perps.ensure_orders",
2274
- "state": "none",
2275
- "mode": "all",
2276
- "intent": "Converge Perps orders to the requested state"
2277
- }
2159
+ "action": "metamask.perps.ensure_orders",
2160
+ "state": "none",
2161
+ "mode": "all",
2162
+ "intent": "Converge Perps orders to the requested state",
2163
+ "next": "done"
2278
2164
  },
2279
2165
  {
2280
- "description": "Ensure one small resting ETH short order exists",
2281
- "node": {
2282
- "action": "metamask.perps.ensure_orders",
2283
- "market": "ETH",
2284
- "side": "short",
2285
- "state": "open",
2286
- "notional": 10,
2287
- "leverage": 2,
2288
- "intent": "Converge ETH open orders to one deterministic testnet precondition"
2289
- }
2166
+ "action": "metamask.perps.ensure_orders",
2167
+ "market": "ETH",
2168
+ "side": "short",
2169
+ "state": "open",
2170
+ "notional": 10,
2171
+ "leverage": 2,
2172
+ "intent": "Converge ETH open orders to one deterministic testnet precondition",
2173
+ "next": "done"
2290
2174
  }
2291
2175
  ],
2292
- "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or target runtime output.",
2293
- "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof.",
2294
2176
  "execution_capabilities": [
2295
2177
  "app-mutation",
2296
2178
  "external-mutation"
2297
2179
  ]
2298
2180
  },
2299
- {
2300
- "name": "metamask.perps.start_state",
2301
- "owner": "metamask",
2181
+ "metamask.perps.start_state": {
2302
2182
  "description": "mobile Default to testnet for Perps mutations; mainnet is read-only unless explicitly requested. Recommended configurable Perps start state that first restores the fixture-backed unlocked wallet, then composes reusable domain operations before the proof window.",
2303
2183
  "schema": {
2304
2184
  "type": "object",
@@ -2620,48 +2500,40 @@
2620
2500
  },
2621
2501
  "examples": [
2622
2502
  {
2623
- "description": "Clean BTC testnet market state",
2624
- "node": {
2625
- "action": "metamask.perps.start_state",
2626
- "profile": "clean_market_testnet",
2627
- "market": "BTC",
2628
- "positions": {
2629
- "state": "none"
2630
- },
2631
- "orders": {
2632
- "state": "none"
2633
- },
2634
- "intent": "Prepare the Perps start state for the proof",
2635
- "network": "testnet"
2636
- }
2503
+ "action": "metamask.perps.start_state",
2504
+ "profile": "clean_market_testnet",
2505
+ "market": "BTC",
2506
+ "positions": {
2507
+ "state": "none"
2508
+ },
2509
+ "orders": {
2510
+ "state": "none"
2511
+ },
2512
+ "intent": "Prepare the Perps start state for the proof",
2513
+ "network": "testnet",
2514
+ "next": "done"
2637
2515
  },
2638
2516
  {
2639
- "description": "Start with an open ETH long position",
2640
- "node": {
2641
- "action": "metamask.perps.start_state",
2642
- "profile": "open_position_testnet",
2643
- "market": "ETH",
2644
- "side": "long",
2645
- "positions": {
2646
- "state": "open",
2647
- "notional": "10",
2648
- "leverage": 2
2649
- },
2650
- "intent": "Prepare the Perps start state for the proof",
2651
- "network": "testnet"
2652
- }
2517
+ "action": "metamask.perps.start_state",
2518
+ "profile": "open_position_testnet",
2519
+ "market": "ETH",
2520
+ "side": "long",
2521
+ "positions": {
2522
+ "state": "open",
2523
+ "notional": "10",
2524
+ "leverage": 2
2525
+ },
2526
+ "intent": "Prepare the Perps start state for the proof",
2527
+ "network": "testnet",
2528
+ "next": "done"
2653
2529
  }
2654
2530
  ],
2655
- "proof_effect": "Runs in setup/teardown phase and must be visible in trace.json; proof videos can keep this as trace-only setup.",
2656
- "safety_notes": "Composes supported app/API paths and must not mutate React/Redux/MobX/DOM/local storage to fabricate proof.",
2657
2531
  "execution_capabilities": [
2658
2532
  "app-mutation",
2659
2533
  "external-mutation"
2660
2534
  ]
2661
2535
  },
2662
- {
2663
- "name": "metamask.perps.teardown_state",
2664
- "owner": "metamask",
2536
+ "metamask.perps.teardown_state": {
2665
2537
  "description": "mobile Recommended configurable Perps teardown state that restores the selected environment after proof.",
2666
2538
  "schema": {
2667
2539
  "type": "object",
@@ -2987,185 +2859,29 @@
2987
2859
  },
2988
2860
  "examples": [
2989
2861
  {
2990
- "description": "Return BTC test account to clean state",
2991
- "node": {
2992
- "action": "metamask.perps.teardown_state",
2993
- "market": "BTC",
2994
- "positions": {
2995
- "state": "none"
2996
- },
2997
- "orders": {
2998
- "state": "none"
2999
- },
3000
- "page": "home",
3001
- "intent": "Restore Perps state after the proof"
3002
- }
2862
+ "action": "metamask.perps.teardown_state",
2863
+ "market": "BTC",
2864
+ "positions": {
2865
+ "state": "none"
2866
+ },
2867
+ "orders": {
2868
+ "state": "none"
2869
+ },
2870
+ "page": "home",
2871
+ "intent": "Restore Perps state after the proof",
2872
+ "next": "done"
3003
2873
  },
3004
2874
  {
3005
- "description": "Teardown only state created by selected market",
3006
- "node": {
3007
- "action": "metamask.perps.teardown_state",
3008
- "market": "BTC",
3009
- "intent": "Restore Perps state after the proof"
3010
- }
2875
+ "action": "metamask.perps.teardown_state",
2876
+ "market": "BTC",
2877
+ "intent": "Restore Perps state after the proof",
2878
+ "next": "done"
3011
2879
  }
3012
2880
  ],
3013
- "proof_effect": "Runs in setup/teardown phase and must be visible in trace.json; proof videos can keep this as trace-only setup.",
3014
- "safety_notes": "Composes supported app/API paths and must not mutate React/Redux/MobX/DOM/local storage to fabricate proof.",
3015
2881
  "execution_capabilities": [
3016
2882
  "app-mutation",
3017
2883
  "external-mutation"
3018
2884
  ]
3019
2885
  }
3020
- ],
3021
- "native_bindings": [
3022
- {
3023
- "action": "command",
3024
- "implementation": "@farmslot/recipe-harness/adapters/core"
3025
- },
3026
- {
3027
- "action": "wait",
3028
- "implementation": "@farmslot/recipe-harness/adapters/core"
3029
- },
3030
- {
3031
- "action": "assert_file",
3032
- "implementation": "@farmslot/recipe-harness/adapters/core"
3033
- },
3034
- {
3035
- "action": "assert_json",
3036
- "implementation": "@farmslot/recipe-harness/adapters/core"
3037
- },
3038
- {
3039
- "action": "assert_exit_code",
3040
- "implementation": "@farmslot/recipe-harness/adapters/core"
3041
- },
3042
- {
3043
- "action": "assert_output",
3044
- "implementation": "@farmslot/recipe-harness/adapters/core"
3045
- },
3046
- {
3047
- "action": "watch_logs",
3048
- "implementation": "@farmslot/recipe-harness/adapters/core"
3049
- },
3050
- {
3051
- "action": "index_artifacts",
3052
- "implementation": "@farmslot/recipe-harness/adapters/core"
3053
- },
3054
- {
3055
- "action": "end",
3056
- "implementation": "@farmslot/recipe-harness/adapters/core"
3057
- },
3058
- {
3059
- "action": "ui.navigate",
3060
- "implementation": "@farmslot/recipe-harness/runtime/react-native-bridge"
3061
- },
3062
- {
3063
- "action": "ui.press",
3064
- "implementation": "@farmslot/recipe-harness/runtime/react-native-bridge"
3065
- },
3066
- {
3067
- "action": "ui.key_press",
3068
- "implementation": "@farmslot/recipe-harness/runtime/react-native-bridge"
3069
- },
3070
- {
3071
- "action": "ui.set_input",
3072
- "implementation": "@metamask/recipe-runner/mobile/react-native-set-input"
3073
- },
3074
- {
3075
- "action": "ui.scroll",
3076
- "implementation": "@farmslot/recipe-harness/runtime/react-native-bridge"
3077
- },
3078
- {
3079
- "action": "ui.wait_for",
3080
- "implementation": "@farmslot/recipe-harness/runtime/react-native-bridge"
3081
- },
3082
- {
3083
- "action": "ui.screenshot",
3084
- "implementation": "@farmslot/recipe-harness/runtime/react-native-bridge"
3085
- },
3086
- {
3087
- "action": "app.lifecycle",
3088
- "implementation": "@farmslot/recipe-harness/adapters/app-lifecycle"
3089
- },
3090
- {
3091
- "action": "app.status",
3092
- "implementation": "@metamask/recipe-runner/platform-status"
3093
- },
3094
- {
3095
- "action": "app.hud",
3096
- "implementation": "@farmslot/recipe-harness/runtime/react-native-bridge"
3097
- },
3098
- {
3099
- "action": "cdp.target",
3100
- "implementation": "@metamask/recipe-runner/platform-cdp-target"
3101
- },
3102
- {
3103
- "action": "metamask.wallet.fixture_status",
3104
- "implementation": "@metamask/recipe-runner/mobile/wallet"
3105
- },
3106
- {
3107
- "action": "metamask.wallet.setup",
3108
- "implementation": "@metamask/recipe-runner/mobile/wallet"
3109
- },
3110
- {
3111
- "action": "metamask.wallet.ensure_unlocked",
3112
- "implementation": "@metamask/recipe-runner/mobile/wallet"
3113
- },
3114
- {
3115
- "action": "metamask.wallet.select_account",
3116
- "implementation": "@metamask/recipe-runner/mobile/wallet"
3117
- },
3118
- {
3119
- "action": "metamask.wallet.list_accounts",
3120
- "implementation": "@metamask/recipe-runner/mobile/wallet"
3121
- },
3122
- {
3123
- "action": "metamask.wallet.read_state",
3124
- "implementation": "@metamask/recipe-runner/mobile/wallet"
3125
- },
3126
- {
3127
- "action": "metamask.perps.read_positions",
3128
- "implementation": "@metamask/recipe-runner/mobile/perps"
3129
- },
3130
- {
3131
- "action": "metamask.perps.read_orders",
3132
- "implementation": "@metamask/recipe-runner/mobile/perps"
3133
- },
3134
- {
3135
- "action": "metamask.perps.close_positions",
3136
- "implementation": "@metamask/recipe-runner/mobile/perps"
3137
- },
3138
- {
3139
- "action": "metamask.perps.close_orders",
3140
- "implementation": "@metamask/recipe-runner/mobile/perps"
3141
- },
3142
- {
3143
- "action": "metamask.perps.place_order",
3144
- "implementation": "@metamask/recipe-runner/mobile/perps"
3145
- },
3146
- {
3147
- "action": "metamask.perps.assert_positions",
3148
- "implementation": "@metamask/recipe-runner/mobile/perps"
3149
- },
3150
- {
3151
- "action": "metamask.perps.assert_orders",
3152
- "implementation": "@metamask/recipe-runner/mobile/perps"
3153
- },
3154
- {
3155
- "action": "metamask.perps.ensure_positions",
3156
- "implementation": "@metamask/recipe-runner/mobile/perps"
3157
- },
3158
- {
3159
- "action": "metamask.perps.ensure_orders",
3160
- "implementation": "@metamask/recipe-runner/mobile/perps"
3161
- },
3162
- {
3163
- "action": "metamask.perps.start_state",
3164
- "implementation": "@metamask/recipe-runner/mobile/perps-domain-dispatcher"
3165
- },
3166
- {
3167
- "action": "metamask.perps.teardown_state",
3168
- "implementation": "@metamask/recipe-runner/mobile/perps-domain-dispatcher"
3169
- }
3170
- ]
2886
+ }
3171
2887
  }