@deeeed/metamask-harness 0.84.0 → 0.86.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 (75) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +5 -3
  3. package/adapters/extension/console-tail.mjs +11 -7
  4. package/adapters/extension/ensure-browser.sh +17 -18
  5. package/adapters/extension/refresh-build.sh +1 -1
  6. package/adapters/extension/start-watch.sh +17 -8
  7. package/adapters/manifest.json +1 -1
  8. package/adapters/mobile/reload-app.mjs +2 -1
  9. package/adapters/mobile/start-console-forwarder.sh +8 -0
  10. package/adapters/shared/resolve-slot-ports-core.mjs +48 -6
  11. package/dist/adapters/console-capture.js +3 -1
  12. package/dist/adapters/extension/actions.js +5 -0
  13. package/dist/adapters/extension/console-capture.js +18 -0
  14. package/dist/adapters/extension/fixtures.js +2 -0
  15. package/dist/adapters/extension/overlay.js +5 -4
  16. package/dist/adapters/extension/runtime.js +2 -0
  17. package/dist/adapters/mobile/launch.js +19 -2
  18. package/dist/adapters/mobile/provision.js +4 -2
  19. package/dist/adapters/mobile/run.js +3 -2
  20. package/dist/adapters/mobile/verify.js +15 -7
  21. package/dist/adapters/web-dapp/actions.js +7 -5
  22. package/dist/adapters.js +5 -3
  23. package/dist/command-contract.js +32 -20
  24. package/dist/commands/check.js +5 -7
  25. package/dist/commands/debug.js +2 -2
  26. package/dist/commands/doctor.js +2 -3
  27. package/dist/commands/domain.js +4 -3
  28. package/dist/commands/fixtures.js +3 -3
  29. package/dist/commands/help.js +6 -5
  30. package/dist/commands/launch/extension.js +9 -7
  31. package/dist/commands/logs.js +2 -2
  32. package/dist/commands/prepare.js +4 -4
  33. package/dist/commands/provision.js +2 -2
  34. package/dist/commands/review.js +8 -10
  35. package/dist/commands/runtime-launch.js +2 -0
  36. package/dist/commands/status-probe.js +8 -1
  37. package/dist/commands/status.js +4 -2
  38. package/dist/commands/task-init.js +2 -3
  39. package/dist/console-classifier.js +3 -2
  40. package/dist/doctor.js +6 -2
  41. package/dist/mm-harness-cli.js +19 -6
  42. package/dist/paths.js +9 -4
  43. package/dist/recipe-security.js +1 -1
  44. package/dist/review/knowledge.js +16 -4
  45. package/dist/runner.js +18 -3
  46. package/docs/CONTRIBUTING.md +11 -0
  47. package/docs/QA.md +1 -0
  48. package/docs/RECIPES.md +18 -2
  49. package/docs/cli-contract.md +8 -6
  50. package/library/actions/core/perps/_controller.mjs +2 -2
  51. package/library/actions/extension/app/assert_no_console_errors.mjs +95 -0
  52. package/library/actions/extension/platform/cdp.mjs +72 -2
  53. package/library/actions/extension/ui/navigate.mjs +3 -1
  54. package/library/actions/extension/wallet/select_account.mjs +17 -17
  55. package/library/actions/extension/wallet/state.mjs +75 -21
  56. package/library/actions/mobile/app/assert_no_console_errors.mjs +129 -0
  57. package/library/actions/mobile/wallet/select_account.mjs +34 -18
  58. package/library/actions/shared/console-assert.mjs +174 -0
  59. package/library/actions/shared/console-collector.mjs +10 -3
  60. package/library/actions/shared/console-findings.mjs +108 -7
  61. package/library/actions/web-dapp/app/assert_no_console_errors.mjs +10 -54
  62. package/library/actions/web-dapp/platform/hud.mjs +2 -9
  63. package/library/actions/web-dapp/platform/page.mjs +62 -16
  64. package/library/actions/web-dapp/platform/runtime.mjs +1 -1
  65. package/library/diagnostics/console-allowlist.json +13 -0
  66. package/library/manifests/core.action-manifest.json +6 -6
  67. package/library/manifests/extension.action-manifest.json +37 -1
  68. package/library/manifests/mobile.action-manifest.json +40 -4
  69. package/library/manifests/web-dapp.action-manifest.json +1 -1
  70. package/library/recipes/core/health/check.recipe.json +40 -0
  71. package/library/recipes/extension/health/check.recipe.json +71 -0
  72. package/library/recipes/mobile/health/check.recipe.json +71 -0
  73. package/package.json +9 -9
  74. package/scripts/completions.sh +2 -2
  75. package/site/assets/help-recipes.json +3 -2
@@ -1321,7 +1321,7 @@
1321
1321
  ]
1322
1322
  },
1323
1323
  "metamask.perps.close_positions": {
1324
- "description": "core Close selected live Perps positions on HyperLiquid testnet by driving the headless perps controller closePosition() through the full signing/provider path. Testnet only. Pre-existing testnet state on an account the task owns is converged, not refused; mainnet is never mutated.",
1324
+ "description": "core Close selected live Perps positions by driving the headless perps controller closePosition() through the full signing/provider path. Testnet by default; mainnet needs network: mainnet and CORE_PERPS_ALLOW_MAINNET_WRITES=1. Pre-existing testnet state on an account the task owns is converged, not refused.",
1325
1325
  "schema": {
1326
1326
  "type": "object",
1327
1327
  "properties": {
@@ -1591,7 +1591,7 @@
1591
1591
  "execution_capabilities": []
1592
1592
  },
1593
1593
  "metamask.perps.ensure_positions": {
1594
- "description": "core Higher-level wrapper that reads selected positions, places or closes via the headless controller as needed, then asserts the final position state. Testnet only. Pre-existing testnet state on an account the task owns is converged, not refused; mainnet is never mutated.",
1594
+ "description": "core Higher-level wrapper that reads selected positions, places or closes via the headless controller as needed, then asserts the final position state. Testnet by default; mainnet needs network: mainnet and CORE_PERPS_ALLOW_MAINNET_WRITES=1, and placing follows the shared Perps order rule. Pre-existing testnet state on an account the task owns is converged, not refused.",
1595
1595
  "schema": {
1596
1596
  "type": "object",
1597
1597
  "properties": {
@@ -1744,7 +1744,7 @@
1744
1744
  ]
1745
1745
  },
1746
1746
  "metamask.perps.close_orders": {
1747
- "description": "Core Perps cancellation selects current orders by market/side or mode=all, then dispatches and verifies those exact order IDs. Missing or duplicate selected IDs fail before dispatch. Requires authorized signing and task-owned selection; defaults to testnet. Pre-existing testnet state on an account the task owns is converged, not refused; mainnet is never mutated.",
1747
+ "description": "Core Perps cancellation selects current orders by market/side or mode=all, then dispatches and verifies those exact order IDs. Missing or duplicate selected IDs fail before dispatch. Requires authorized signing and task-owned selection. Testnet by default; mainnet needs network: mainnet and CORE_PERPS_ALLOW_MAINNET_WRITES=1. Pre-existing testnet state on an account the task owns is converged, not refused.",
1748
1748
  "schema": {
1749
1749
  "type": "object",
1750
1750
  "properties": {
@@ -2078,7 +2078,7 @@
2078
2078
  "execution_capabilities": []
2079
2079
  },
2080
2080
  "metamask.perps.ensure_orders": {
2081
- "description": "core Default to testnet for Perps mutations; mainnet is read-only unless explicitly requested.Higher-level wrapper that reads selected orders, cancels when needed, then asserts final order state. Pre-existing testnet state on an account the task owns is converged, not refused; mainnet is never mutated.",
2081
+ "description": "core Higher-level wrapper that reads selected orders, cancels when needed, then asserts final order state. Testnet by default; mainnet needs network: mainnet and CORE_PERPS_ALLOW_MAINNET_WRITES=1. An open state is a resting limit order, which the shared Perps order rule refuses on mainnet. Pre-existing testnet state on an account the task owns is converged, not refused.",
2082
2082
  "schema": {
2083
2083
  "type": "object",
2084
2084
  "properties": {
@@ -2233,7 +2233,7 @@
2233
2233
  ]
2234
2234
  },
2235
2235
  "metamask.perps.start_state": {
2236
- "description": "core Default to testnet for Perps mutations; mainnet is read-only unless explicitly requested.Recommended configurable Perps start state that composes reusable domain operations before the proof window. Headless: converges POSITION and ORDER baselines on testnet via the controller; no navigation/page/HUD.",
2236
+ "description": "core Recommended configurable Perps start state that composes reusable domain operations before the proof window. Headless: converges POSITION and ORDER baselines via the controller; no navigation/page/HUD. Testnet by default; mainnet needs network: mainnet and CORE_PERPS_ALLOW_MAINNET_WRITES=1, and orders follow the shared Perps order rule.",
2237
2237
  "schema": {
2238
2238
  "type": "object",
2239
2239
  "properties": {
@@ -2554,7 +2554,7 @@
2554
2554
  ]
2555
2555
  },
2556
2556
  "metamask.perps.teardown_state": {
2557
- "description": "core Recommended configurable Perps teardown state that restores the selected environment after proof. Headless: converges POSITION and ORDER baselines on testnet via the controller; no navigation/page/HUD.",
2557
+ "description": "core Recommended configurable Perps teardown state that restores the selected environment after proof. Headless: converges POSITION and ORDER baselines via the controller; no navigation/page/HUD. Testnet by default; mainnet needs network: mainnet and CORE_PERPS_ALLOW_MAINNET_WRITES=1.",
2558
2558
  "schema": {
2559
2559
  "type": "object",
2560
2560
  "properties": {
@@ -1237,6 +1237,42 @@
1237
1237
  },
1238
1238
  "execution_capabilities": []
1239
1239
  },
1240
+ "metamask.app.assert_no_console_errors": {
1241
+ "description": "Fail when MetaMask (service worker or UI pages) logged an error or uncaught exception that no console allowlist (the bundled library's, then each recipe library's diagnostics/console-allowlist.json) marks as known noise, judging the events since the launch began, by their own timestamps. Reads extension-console.log from the offset mm-harness launch records when it starts, and leaves out records the collector stamps with an event time before the launch (a replaced collector's replay of a browser the launch kept); with no recorded launch it fails unless since is start-of-log. The same classification feeds run diagnostics. Fails as unavailable unless this runtime's console collector runs on the run's CDP port and a control line logged in MetaMask's service worker or page reaches extension-console.log. Allowlist entries are exact signatures (schemaVersion 3).",
1242
+ "execution_capabilities": ["host-read-export"],
1243
+ "schema": {
1244
+ "type": "object",
1245
+ "properties": {
1246
+ "levels": {
1247
+ "type": "array",
1248
+ "items": { "type": "string", "enum": ["error", "exception", "warning"] },
1249
+ "description": "Default [\"error\",\"exception\"]."
1250
+ },
1251
+ "since": {
1252
+ "type": "string",
1253
+ "enum": ["launch", "start-of-log"],
1254
+ "default": "launch"
1255
+ },
1256
+ "allowlist_root": {
1257
+ "type": "string",
1258
+ "description": "An extra directory holding diagnostics/console-allowlist.json."
1259
+ },
1260
+ "timeout_ms": {
1261
+ "type": "number",
1262
+ "minimum": 1,
1263
+ "description": "Maximum time in milliseconds to wait for the capture control line (default 8000)."
1264
+ }
1265
+ },
1266
+ "additionalProperties": false
1267
+ },
1268
+ "examples": [
1269
+ {
1270
+ "action": "metamask.app.assert_no_console_errors",
1271
+ "intent": "Prove MetaMask logged no unexpected error since launch",
1272
+ "next": "done"
1273
+ }
1274
+ ]
1275
+ },
1240
1276
  "app.network_assert": {
1241
1277
  "description": "Assert a previously indexed app.network_capture summary without hiding its diagnostics on failure.",
1242
1278
  "examples": [
@@ -4095,7 +4131,7 @@
4095
4131
  "execution_capabilities": []
4096
4132
  },
4097
4133
  "metamask.perps.close_orders": {
4098
- "description": "extension 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. Extension ensure_* only assert existing state; converge an owned testnet account through an explicitly funded leaf action. Mainnet is never mutated.",
4134
+ "description": "extension Cancel selected live Perps open orders. Use selector/mode params to cancel matching symbols or all orders. On mainnet, cancel is refused in the fixture-policy lane and needs the funded release lane. Extension ensure_* only assert existing state; converge an owned testnet account through an explicitly funded leaf action.",
4099
4135
  "schema": {
4100
4136
  "type": "object",
4101
4137
  "properties": {
@@ -1260,6 +1260,42 @@
1260
1260
  },
1261
1261
  "execution_capabilities": []
1262
1262
  },
1263
+ "metamask.app.assert_no_console_errors": {
1264
+ "description": "Fail when the app logged an error or uncaught exception that no console allowlist (the bundled library's, then each recipe library's diagnostics/console-allowlist.json) marks as known noise, judging the events since the launch began, by their own timestamps. Reads app-console.log from the adapter-rn console forwarder, which stamps each line with the event's own time: mm-harness launch records when it starts, and lines a reused app runtime replays from before then are left out; with no recorded launch it fails unless since is start-of-log. Only the device the bridge selects for the run (the one the control line comes back from) is judged; when that line names no device, every device is, reported as devices: \"all\". The same classification feeds run diagnostics. Fails as unavailable unless this checkout's forwarder is running and a control line logged inside the app through the debug bridge reaches app-console.log; the opaque release runtime has no bridge and always fails as unavailable. Allowlist entries are exact signatures (schemaVersion 3).",
1265
+ "execution_capabilities": ["host-read-export"],
1266
+ "schema": {
1267
+ "type": "object",
1268
+ "properties": {
1269
+ "levels": {
1270
+ "type": "array",
1271
+ "items": { "type": "string", "enum": ["error", "exception", "warning"] },
1272
+ "description": "Default [\"error\",\"exception\"]."
1273
+ },
1274
+ "since": {
1275
+ "type": "string",
1276
+ "enum": ["launch", "start-of-log"],
1277
+ "default": "launch"
1278
+ },
1279
+ "allowlist_root": {
1280
+ "type": "string",
1281
+ "description": "An extra directory holding diagnostics/console-allowlist.json."
1282
+ },
1283
+ "timeout_ms": {
1284
+ "type": "number",
1285
+ "minimum": 1,
1286
+ "description": "Maximum time in milliseconds to wait for the capture control line (default 8000)."
1287
+ }
1288
+ },
1289
+ "additionalProperties": false
1290
+ },
1291
+ "examples": [
1292
+ {
1293
+ "action": "metamask.app.assert_no_console_errors",
1294
+ "intent": "Prove the app logged no unexpected error since launch",
1295
+ "next": "done"
1296
+ }
1297
+ ]
1298
+ },
1263
1299
  "app.network_assert": {
1264
1300
  "description": "Assert a previously indexed app.network_capture summary without hiding its diagnostics on failure.",
1265
1301
  "examples": [
@@ -2486,7 +2522,7 @@
2486
2522
  "execution_capabilities": []
2487
2523
  },
2488
2524
  "metamask.perps.close_positions": {
2489
- "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. Pre-existing testnet state on an account the task owns is converged, not refused; mainnet is never mutated.",
2525
+ "description": "mobile Close selected live Perps positions on the app's current Perps network, with no mainnet opt-in. Use selector/mode params to close matching symbols or all positions. Pre-existing testnet state on an account the task owns is converged, not refused.",
2490
2526
  "schema": {
2491
2527
  "type": "object",
2492
2528
  "properties": {
@@ -2577,7 +2613,7 @@
2577
2613
  "execution_capabilities": ["app-mutation", "external-mutation"]
2578
2614
  },
2579
2615
  "metamask.perps.close_orders": {
2580
- "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. Pre-existing testnet state on an account the task owns is converged, not refused; mainnet is never mutated.",
2616
+ "description": "mobile Cancel selected live Perps open orders on the app's current Perps network, with no mainnet opt-in. Use selector/mode params to cancel matching symbols or all orders. Pre-existing testnet state on an account the task owns is converged, not refused.",
2581
2617
  "schema": {
2582
2618
  "type": "object",
2583
2619
  "properties": {
@@ -2989,7 +3025,7 @@
2989
3025
  "execution_capabilities": []
2990
3026
  },
2991
3027
  "metamask.perps.ensure_positions": {
2992
- "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. Pre-existing testnet state on an account the task owns is converged, not refused; mainnet is never mutated.",
3028
+ "description": "mobile Higher-level wrapper that reads selected positions, closes or places when needed, then asserts final position state. Acts on the app's current Perps network: closing has no mainnet opt-in, and placing follows the shared Perps order rule, so it cannot leave a position open on mainnet. Pre-existing testnet state on an account the task owns is converged, not refused.",
2993
3029
  "schema": {
2994
3030
  "type": "object",
2995
3031
  "properties": {
@@ -3105,7 +3141,7 @@
3105
3141
  "execution_capabilities": ["app-mutation", "external-mutation"]
3106
3142
  },
3107
3143
  "metamask.perps.ensure_orders": {
3108
- "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. Pre-existing testnet state on an account the task owns is converged, not refused; mainnet is never mutated.",
3144
+ "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, which the shared Perps order rule refuses on mainnet; cancelling acts on the app's current Perps network with no mainnet opt-in. Pre-existing testnet state on an account the task owns is converged, not refused.",
3109
3145
  "schema": {
3110
3146
  "type": "object",
3111
3147
  "properties": {
@@ -17,7 +17,7 @@
17
17
  }
18
18
  },
19
19
  "app.hud": {
20
- "description": "Display recipe progress in an overlay on the app tab: recipe, step N/M, the current node intent and the last pass/fail. Never drawn in MetaMask's own pages; ignored by clicks and page text. --hud auto shows it only when the browser is headful.",
20
+ "description": "Display recipe progress in an overlay on the app tab: recipe, step N/M, the current node intent and the last pass/fail. Drawn on a MetaMask wallet page only for the length of a surface=wallet ui.screenshot; ignored by clicks and page text; kept in every ui.screenshot. Drawn in a headless or headful browser unless --hud hide, which also clears the HUD an earlier run left.",
21
21
  "examples": [
22
22
  {
23
23
  "action": "app.hud",
@@ -0,0 +1,40 @@
1
+ {
2
+ "$schema": "https://farmslot.io/schemas/recipe-v1.schema.json",
3
+ "title": "MetaMask Core health check",
4
+ "description": "Read-only check to run before ticket work: the headless Perps controller imports and answers one bounded HyperLiquid testnet account read. A failed precondition stops at the node named after its error; that node's intent gives the recovery. Places no orders.",
5
+ "paramsSchema": {
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "properties": {
9
+ "timeout_ms": { "type": "integer", "minimum": 1000, "default": 60000 }
10
+ }
11
+ },
12
+ "workflow": {
13
+ "entry": "status",
14
+ "nodes": {
15
+ "status": {
16
+ "action": "app.status",
17
+ "next": "CONTROLLER_UNAVAILABLE",
18
+ "intent": "Record the resolved Core checkout"
19
+ },
20
+ "CONTROLLER_UNAVAILABLE": {
21
+ "action": "metamask.perps.read_account",
22
+ "network": "testnet",
23
+ "timeout_ms": "{{params.timeout_ms}}",
24
+ "next": "ACCOUNT_READ_FAILED",
25
+ "intent": "CONTROLLER_UNAVAILABLE unless the Perps controller imports and reads the testnet account in time. Recovery: mm-harness doctor --fix"
26
+ },
27
+ "ACCOUNT_READ_FAILED": {
28
+ "action": "assert_output",
29
+ "source": "CONTROLLER_UNAVAILABLE",
30
+ "assert": { "path": "$.accountState.totalBalance", "operator": "matches", "value": "^-?[0-9]+(\\.[0-9]+)?$" },
31
+ "next": "done",
32
+ "intent": "ACCOUNT_READ_FAILED unless the read returned a numeric balance; the controller reports an unreachable venue as a placeholder. Recovery: check network access to HyperLiquid testnet, then rerun"
33
+ },
34
+ "done": {
35
+ "action": "end",
36
+ "status": "pass"
37
+ }
38
+ }
39
+ }
40
+ }
@@ -0,0 +1,71 @@
1
+ {
2
+ "$schema": "https://farmslot.io/schemas/recipe-v1.schema.json",
3
+ "title": "MetaMask Extension health check",
4
+ "description": "Read-only check to run before ticket work: the extension answers over CDP, onboarding is done, and the wallet is unlocked on a selected account at wallet home. A failed precondition stops at the node named after its error; that node's intent gives the recovery. Changes no app state.",
5
+ "paramsSchema": {
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "properties": {
9
+ "capture_path": { "type": "string", "default": "screenshots/health-check.png" }
10
+ }
11
+ },
12
+ "workflow": {
13
+ "entry": "APP_NOT_RUNNING",
14
+ "nodes": {
15
+ "APP_NOT_RUNNING": {
16
+ "action": "cdp.target",
17
+ "require_reachable": true,
18
+ "next": "state",
19
+ "intent": "APP_NOT_RUNNING unless the extension answers over CDP. Recovery: mm-harness launch --verify"
20
+ },
21
+ "state": {
22
+ "action": "metamask.wallet.read_state",
23
+ "next": "ONBOARDING",
24
+ "intent": "Read the selected account, lock state and page without changing them"
25
+ },
26
+ "ONBOARDING": {
27
+ "action": "assert_output",
28
+ "source": "state",
29
+ "assert": { "path": "$.state.completedOnboarding", "operator": "eq", "value": true },
30
+ "next": "WALLET_LOCKED",
31
+ "intent": "ONBOARDING if onboarding is not complete. Recovery: mm-harness run wallet.import"
32
+ },
33
+ "WALLET_LOCKED": {
34
+ "action": "assert_output",
35
+ "source": "state",
36
+ "assert": { "path": "$.state.passwordInput", "operator": "falsy" },
37
+ "next": "NO_SELECTED_ACCOUNT",
38
+ "intent": "WALLET_LOCKED if the unlock page is showing. Recovery: mm-harness run wallet.smoke"
39
+ },
40
+ "NO_SELECTED_ACCOUNT": {
41
+ "action": "assert_output",
42
+ "source": "state",
43
+ "assert": { "path": "$.state.selectedAccount.address", "operator": "not_null" },
44
+ "next": "NOT_ON_HOME",
45
+ "intent": "NO_SELECTED_ACCOUNT if the wallet reports no selected account. Recovery: mm-harness run wallet.import"
46
+ },
47
+ "NOT_ON_HOME": {
48
+ "action": "assert_output",
49
+ "source": "state",
50
+ "assert": { "path": "$.state.href", "operator": "matches", "value": "/(home|sidepanel)\\.html#/?$" },
51
+ "next": "CONSOLE_ERRORS",
52
+ "intent": "NOT_ON_HOME unless wallet home is showing. Recovery: a ui.navigate node with page home, then rerun"
53
+ },
54
+ "CONSOLE_ERRORS": {
55
+ "action": "metamask.app.assert_no_console_errors",
56
+ "next": "screenshot",
57
+ "intent": "CONSOLE_ERRORS if the app logged an error outside the console allowlist since launch. Recovery: fix the error, or allowlist known noise with a reason in a library's diagnostics/console-allowlist.json"
58
+ },
59
+ "screenshot": {
60
+ "action": "ui.screenshot",
61
+ "path": "{{params.capture_path}}",
62
+ "next": "done",
63
+ "intent": "Record the ready wallet home as evidence"
64
+ },
65
+ "done": {
66
+ "action": "end",
67
+ "status": "pass"
68
+ }
69
+ }
70
+ }
71
+ }
@@ -0,0 +1,71 @@
1
+ {
2
+ "$schema": "https://farmslot.io/schemas/recipe-v1.schema.json",
3
+ "title": "MetaMask Mobile health check",
4
+ "description": "Read-only check to run before ticket work: the app answers, onboarding is done, and the wallet is unlocked on a selected account at wallet home. A failed precondition stops at the node named after its error; that node's intent gives the recovery. Changes no app state.",
5
+ "paramsSchema": {
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "properties": {
9
+ "capture_path": { "type": "string", "default": "screenshots/health-check.png" }
10
+ }
11
+ },
12
+ "workflow": {
13
+ "entry": "APP_NOT_RUNNING",
14
+ "nodes": {
15
+ "APP_NOT_RUNNING": {
16
+ "action": "cdp.target",
17
+ "require_reachable": true,
18
+ "next": "state",
19
+ "intent": "APP_NOT_RUNNING unless the app's debug bridge answers. Recovery: mm-harness launch ios|android --verify"
20
+ },
21
+ "state": {
22
+ "action": "metamask.wallet.read_state",
23
+ "next": "ONBOARDING",
24
+ "intent": "Read the selected account and route without changing them"
25
+ },
26
+ "ONBOARDING": {
27
+ "action": "assert_output",
28
+ "source": "state",
29
+ "assert": { "path": "$.route.name", "operator": "matches", "value": "^(?!Onboarding|OptinMetrics)" },
30
+ "next": "WALLET_LOCKED",
31
+ "intent": "ONBOARDING if the app is still in onboarding. Recovery: mm-harness run wallet.import"
32
+ },
33
+ "WALLET_LOCKED": {
34
+ "action": "assert_output",
35
+ "source": "state",
36
+ "assert": { "path": "$.route.name", "operator": "ne", "value": "Login" },
37
+ "next": "NO_SELECTED_ACCOUNT",
38
+ "intent": "WALLET_LOCKED if the unlock screen is showing. Recovery: mm-harness run wallet.smoke"
39
+ },
40
+ "NO_SELECTED_ACCOUNT": {
41
+ "action": "assert_output",
42
+ "source": "state",
43
+ "assert": { "path": "$.account.address", "operator": "not_null" },
44
+ "next": "NOT_ON_HOME",
45
+ "intent": "NO_SELECTED_ACCOUNT if the wallet reports no selected account. Recovery: mm-harness run wallet.import"
46
+ },
47
+ "NOT_ON_HOME": {
48
+ "action": "assert_output",
49
+ "source": "state",
50
+ "assert": { "path": "$.route.name", "operator": "eq", "value": "WalletView" },
51
+ "next": "CONSOLE_ERRORS",
52
+ "intent": "NOT_ON_HOME unless wallet home is showing. Recovery: a ui.navigate node with page home, then rerun"
53
+ },
54
+ "CONSOLE_ERRORS": {
55
+ "action": "metamask.app.assert_no_console_errors",
56
+ "next": "screenshot",
57
+ "intent": "CONSOLE_ERRORS if the app logged an error outside the console allowlist since launch. Recovery: fix the error, or allowlist known noise with a reason in a library's diagnostics/console-allowlist.json"
58
+ },
59
+ "screenshot": {
60
+ "action": "ui.screenshot",
61
+ "path": "{{params.capture_path}}",
62
+ "next": "done",
63
+ "intent": "Record the ready wallet home as evidence"
64
+ },
65
+ "done": {
66
+ "action": "end",
67
+ "status": "pass"
68
+ }
69
+ }
70
+ }
71
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deeeed/metamask-harness",
3
- "version": "0.84.0",
3
+ "version": "0.86.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mm-harness": "bin/mm-harness"
@@ -21,15 +21,15 @@
21
21
  "check:syntax": "find . -name '*.mjs' -print0 | xargs -0 -n1 node --check"
22
22
  },
23
23
  "dependencies": {
24
- "@farmslot/adapter-node": "0.1.4",
25
- "@farmslot/adapter-rn": "0.19.1",
26
- "@farmslot/adapter-sdk": "0.7.1",
27
- "@farmslot/adapter-web": "0.6.2",
28
- "@farmslot/agent-runtime": "0.17.0",
24
+ "@farmslot/adapter-node": "0.1.7",
25
+ "@farmslot/adapter-rn": "0.19.4",
26
+ "@farmslot/adapter-sdk": "0.8.1",
27
+ "@farmslot/adapter-web": "0.6.5",
28
+ "@farmslot/agent-runtime": "0.17.1",
29
29
  "@farmslot/handoff": "^0.3.1",
30
- "@farmslot/protocol": "0.34.0",
31
- "@farmslot/recipe-cli": "0.10.0",
32
- "@farmslot/recipe-runner": "0.27.0",
30
+ "@farmslot/protocol": "0.35.0",
31
+ "@farmslot/recipe-cli": "0.11.1",
32
+ "@farmslot/recipe-runner": "0.28.1",
33
33
  "agent-device": "0.19.3",
34
34
  "commander": "^12.0.0",
35
35
  "esbuild": "0.28.1",
@@ -45,8 +45,8 @@ _mmh_flags_for() {
45
45
  execution-template) printf '%s' "--dir --domain-dir --project-worker --project-name --package-templates --package-id --flow --run-mode --platform --domain --id --provenance --include-shadowed --no-include-shadowed --title --force --json" ;;
46
46
  recipe-quality) printf '%s' "--input --output --recipe --task --evidence --params --adapter --library --advisor --advisor-model --advisor-stages --json" ;;
47
47
  actions) printf '%s' "--adapter --action --kind --raw --action-manifest --target --json" ;;
48
- call) printf '%s' "--list --domain --source --sort --adapter --target --artifacts-dir --action-manifest --heal --json" ;;
49
- run) printf '%s' "--list --domain --source --sort --describe --plan --adapter --artifacts-dir --target --library --cdp-port --heal --record-video --json" ;;
48
+ call) printf '%s' "--list --domain --source --sort --adapter --target --artifacts-dir --action-manifest --heal --hud --json" ;;
49
+ run) printf '%s' "--list --domain --source --sort --describe --plan --adapter --artifacts-dir --target --library --cdp-port --heal --record-video --hud --json" ;;
50
50
  doctor) printf '%s' "--adapter --target --fix --json" ;;
51
51
  install|verify|cleanup) printf '%s' "--adapter --target --json" ;;
52
52
  *) printf '%s' "" ;;
@@ -30,7 +30,8 @@
30
30
  ],
31
31
  "commands": [
32
32
  "mm-harness doctor --fix",
33
- "mm-harness status --json"
33
+ "mm-harness status --json",
34
+ "mm-harness run health.check"
34
35
  ]
35
36
  },
36
37
  {
@@ -200,7 +201,7 @@
200
201
  "Analytics: verify the collection destination and existing consent before capture. Changing participation or marketing consent requires operator approval and restoration of the prior settings; a recipe's hardcoded opt-in is not permission. Missing collection prerequisites are not missing product events.",
201
202
  "Flake risk: wait on observable state instead of sleeping, keep device and runtime identity explicit, and never overwrite a prior run's artifacts.",
202
203
  "Mutation: require an independent state assertion and identity-bound receipt. Bind activity to that operation's accepted ID; an old row or nonzero count is insufficient. Opening and closing orders have different IDs. Schedule authorized cleanup in teardown, verify its result, and report any residue. Testnet cleanup closes what the task opened and never silently undoes another task's live proof on a reserved account or market.",
203
- "Testnet account state: on a testnet account or market the task owns for its run (the slot's fixture account, or one reserved with exclusive_testnet_market=true), converging is an explicit, ownership-scoped setup choice, not a violation. Use the team library's perps.ensure-market-state or perps.clean-market-testnet, or on Mobile and Core metamask.perps.ensure_positions / metamask.perps.ensure_orders (mode=all for strays), record what was closed in the trace, then prove. A pre-existing position or order on an account the task owns is never a reason to block, skip, or refuse, and a fixture that demands an empty account must converge rather than refuse. Extension ensure_* and teardown_state only assert; converge there through an explicitly funded leaf action. Never broaden cleanup onto an account or market the task does not own; obtain an owned one instead of blocking. Mainnet is read-only.",
204
+ "Testnet account state: on a testnet account or market the task owns for its run (the slot's fixture account, or one reserved with exclusive_testnet_market=true), converging is an explicit, ownership-scoped setup choice, not a violation. Use the team library's perps.ensure-market-state or perps.clean-market-testnet, or on Mobile and Core metamask.perps.ensure_positions / metamask.perps.ensure_orders (mode=all for strays), record what was closed in the trace, then prove. A pre-existing position or order on an account the task owns is never a reason to block, skip, or refuse, and a fixture that demands an empty account must converge rather than refuse. Extension ensure_* and teardown_state only assert; converge there through an explicitly funded leaf action. Never broaden cleanup onto an account or market the task does not own; obtain an owned one instead of blocking. On mainnet, orders follow the shared Perps order rule (PERPS_MAINNET_ORDER_REFUSED), Core needs CORE_PERPS_ALLOW_MAINNET_WRITES=1 for any write, Mobile cleanup acts on whatever network the app is on, and Extension refuses mainnet writes other than orders and `close_visible_position` in the fixture-policy lane.",
204
205
  {
205
206
  "text": "An accepted order can fill before the UI updates. Preserve its receipt even when the expected resting-order assertion fails; one empty read does not prove cleanup. Check the read API's cache and freshness options before adding polling. Reconcile returned margin against the pre-placement balance. A successful close does not turn the original failed proof into a pass.",
206
207
  "adapters": [