@deeeed/metamask-harness 0.47.4 → 0.49.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 (261) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +3 -2
  3. package/adapters/extension/lib/macos-focus.cjs +2 -2
  4. package/adapters/extension/live.sh +6 -8
  5. package/adapters/mobile/bridge-runtime/lib/bridge-errors.cjs +1 -0
  6. package/adapters/shared/ensure-runner-deps.sh +6 -0
  7. package/dist/adapters/extension/runtime-decision.js +18 -1
  8. package/dist/adapters/extension/runtime.js +5 -5
  9. package/dist/adapters/extension/surface.js +1 -0
  10. package/dist/app-lifecycle.js +3 -1
  11. package/dist/command-contract.js +2 -0
  12. package/dist/commands/call.js +1 -8
  13. package/dist/commands/device-target.js +12 -3
  14. package/dist/commands/doctor.js +7 -5
  15. package/dist/commands/fixtures.js +1 -1
  16. package/dist/commands/help.js +17 -5
  17. package/dist/commands/launch/index.js +10 -0
  18. package/dist/commands/manifest.js +3 -1
  19. package/dist/commands/mobile-device-view.js +13 -3
  20. package/dist/commands/parse-args.js +2 -0
  21. package/dist/commands/run-engine.js +29 -4
  22. package/dist/commands/run.js +1 -1
  23. package/dist/commands/runtime-launch.js +10 -1
  24. package/dist/commands/status.js +8 -4
  25. package/dist/devices.js +31 -9
  26. package/dist/execution-provenance.js +8 -7
  27. package/dist/funding-execution-context.js +62 -17
  28. package/dist/heal-bounds.js +3 -3
  29. package/dist/live-adapter-contract.js +4 -0
  30. package/dist/manifest.js +2 -13
  31. package/dist/metamask-action-validation.js +3 -2
  32. package/dist/mm-harness-cli.js +4 -2
  33. package/dist/runner.js +14 -3
  34. package/docs/RECIPES.md +135 -24
  35. package/docs/RELEASE-QA-CAPABILITY-MAP.md +3 -2
  36. package/library/actions/core/perps/_controller.mjs +24 -24
  37. package/library/actions/core/perps/assert_orders.mjs +3 -5
  38. package/library/actions/core/perps/assert_positions.mjs +3 -5
  39. package/library/actions/core/perps/close_orders.mjs +39 -19
  40. package/library/actions/core/perps/close_positions.mjs +53 -12
  41. package/library/actions/core/perps/edit_order.mjs +2 -0
  42. package/library/actions/core/perps/ensure_orders.mjs +3 -4
  43. package/library/actions/core/perps/ensure_positions.mjs +4 -5
  44. package/library/actions/core/perps/place_order.mjs +7 -14
  45. package/library/actions/core/perps/read_account.mjs +2 -0
  46. package/library/actions/core/perps/read_orders.mjs +2 -0
  47. package/library/actions/core/perps/read_positions.mjs +3 -1
  48. package/library/actions/core/perps/read_snapshot.mjs +3 -0
  49. package/library/actions/core/perps/start_state.mjs +3 -14
  50. package/library/actions/core/perps/teardown_state.mjs +3 -14
  51. package/library/actions/core/perps/update_position_tpsl.mjs +13 -19
  52. package/library/actions/core/wallet/list_accounts.mjs +3 -0
  53. package/library/actions/extension/analytics/consent.mjs +2 -0
  54. package/library/actions/extension/analytics/set_consent.mjs +2 -0
  55. package/library/actions/extension/assets/home-token-identity.mjs +31 -0
  56. package/library/actions/extension/assets/import_custom_token.mjs +63 -79
  57. package/library/actions/extension/assets/open_details.mjs +19 -7
  58. package/library/actions/extension/assets/prepare_send.mjs +1 -0
  59. package/library/actions/extension/assets/read_details.mjs +62 -29
  60. package/library/actions/extension/assets/read_visible_state.mjs +5 -1
  61. package/library/actions/extension/assets/review_send.mjs +1 -0
  62. package/library/actions/extension/assets/select_network_scope.mjs +17 -9
  63. package/library/actions/extension/assets/send.mjs +2 -0
  64. package/library/actions/extension/assets/set_custom_gas.mjs +1 -0
  65. package/library/actions/extension/assets/set_token_visibility.mjs +86 -17
  66. package/library/actions/extension/assets/verify_sorting.mjs +63 -14
  67. package/library/actions/extension/deeplink/open.mjs +1 -0
  68. package/library/actions/extension/networks/add_chainlist.mjs +2 -0
  69. package/library/actions/extension/networks/add_custom.mjs +21 -17
  70. package/library/actions/extension/networks/custom_network.mjs +10 -3
  71. package/library/actions/extension/networks/read_visible_state.mjs +2 -0
  72. package/library/actions/extension/networks/remove_custom.mjs +1 -0
  73. package/library/actions/extension/performance/_navigation-memory.mjs +37 -6
  74. package/library/actions/extension/performance/compare_idle_navigation_memory.mjs +5 -2
  75. package/library/actions/extension/performance/measure_detached_dom.mjs +5 -2
  76. package/library/actions/extension/performance/measure_navigation_memory.mjs +5 -2
  77. package/library/actions/extension/perps/assert_orders.mjs +1 -0
  78. package/library/actions/extension/perps/assert_positions.mjs +1 -0
  79. package/library/actions/extension/perps/assert_visible_consistency.mjs +11 -4
  80. package/library/actions/extension/perps/close_orders.mjs +4 -1
  81. package/library/actions/extension/perps/close_positions.mjs +1 -0
  82. package/library/actions/extension/perps/close_visible_position.mjs +160 -11
  83. package/library/actions/extension/perps/compare_provider_market.mjs +2 -0
  84. package/library/actions/extension/perps/edit_margin.mjs +3 -1
  85. package/library/actions/extension/perps/ensure_orders.mjs +1 -0
  86. package/library/actions/extension/perps/ensure_positions.mjs +1 -0
  87. package/library/actions/extension/perps/mutation-receipt.mjs +54 -3
  88. package/library/actions/extension/perps/open_balance_action.mjs +17 -7
  89. package/library/actions/extension/perps/open_position_action.mjs +1 -0
  90. package/library/actions/extension/perps/perps.mjs +550 -69
  91. package/library/actions/extension/perps/place_order.mjs +1 -0
  92. package/library/actions/extension/perps/read_eligibility.mjs +1 -0
  93. package/library/actions/extension/perps/read_funds_confirmation.mjs +10 -0
  94. package/library/actions/extension/perps/read_orders.mjs +1 -0
  95. package/library/actions/extension/perps/read_positions.mjs +1 -0
  96. package/library/actions/extension/perps/read_snapshot.mjs +4 -1
  97. package/library/actions/extension/perps/read_visible_state.mjs +12 -6
  98. package/library/actions/extension/perps/search_markets.mjs +8 -2
  99. package/library/actions/extension/perps/select_activity_filter.mjs +2 -1
  100. package/library/actions/extension/perps/select_market_filter.mjs +75 -45
  101. package/library/actions/extension/perps/set_market_favorite.mjs +7 -3
  102. package/library/actions/extension/perps/state.mjs +2 -0
  103. package/library/actions/extension/perps/update_position_tpsl.mjs +3 -1
  104. package/library/actions/extension/perps/visible-mutation-identity.mjs +6 -2
  105. package/library/actions/extension/platform/cdp.mjs +50 -142
  106. package/library/actions/extension/settings/set_basic_functionality.mjs +1 -0
  107. package/library/actions/extension/swap_bridge/quote-state.mjs +56 -0
  108. package/library/actions/extension/swap_bridge/read_visible_state.mjs +5 -1
  109. package/library/actions/extension/swap_bridge/select_assets.mjs +12 -1
  110. package/library/actions/extension/swap_bridge/set_amount.mjs +11 -8
  111. package/library/actions/extension/swap_bridge/set_max_amount.mjs +18 -18
  112. package/library/actions/extension/swap_bridge/set_slippage.mjs +1 -9
  113. package/library/actions/extension/ui/locators.mjs +1 -0
  114. package/library/actions/extension/ui/navigate.mjs +60 -14
  115. package/library/actions/extension/wallet/ensure_unlocked.mjs +2 -0
  116. package/library/actions/extension/wallet/import.mjs +2 -0
  117. package/library/actions/extension/wallet/list_accounts.mjs +1 -0
  118. package/library/actions/extension/wallet/lock.mjs +4 -1
  119. package/library/actions/extension/wallet/read_state.mjs +1 -0
  120. package/library/actions/extension/wallet/reset.mjs +2 -0
  121. package/library/actions/extension/wallet/secret-input.mjs +2 -0
  122. package/library/actions/extension/wallet/select_account.mjs +2 -0
  123. package/library/actions/extension/wallet/setup.mjs +1 -0
  124. package/library/actions/extension/wallet/state.mjs +2 -0
  125. package/library/actions/extension/wallet/visible-session.mjs +2 -0
  126. package/library/actions/mobile/analytics/consent-settings.mjs +1 -0
  127. package/library/actions/mobile/analytics/set_consent.mjs +1 -0
  128. package/library/actions/mobile/app/network-control.mjs +2 -0
  129. package/library/actions/mobile/app/network.mjs +1 -0
  130. package/library/actions/mobile/app/network_assert.mjs +1 -0
  131. package/library/actions/mobile/app/network_capture.mjs +1 -0
  132. package/library/actions/mobile/assets/import_custom_token.mjs +3 -0
  133. package/library/actions/mobile/assets/open_details.mjs +1 -0
  134. package/library/actions/mobile/assets/read_details.mjs +1 -0
  135. package/library/actions/mobile/assets/read_visible_state.mjs +4 -1
  136. package/library/actions/mobile/assets/set_token_visibility.mjs +15 -9
  137. package/library/actions/mobile/assets/verify_sorting.mjs +90 -7
  138. package/library/actions/mobile/deeplink/open.mjs +1 -0
  139. package/library/actions/mobile/networks/add_custom.mjs +1 -0
  140. package/library/actions/mobile/networks/network-management.mjs +23 -15
  141. package/library/actions/mobile/networks/read_visible_state.mjs +1 -0
  142. package/library/actions/mobile/networks/remove_custom.mjs +7 -2
  143. package/library/actions/mobile/perps/assert_orders.mjs +1 -0
  144. package/library/actions/mobile/perps/assert_positions.mjs +1 -0
  145. package/library/actions/mobile/perps/capture_performance.mjs +1 -0
  146. package/library/actions/mobile/perps/close_orders.mjs +1 -0
  147. package/library/actions/mobile/perps/close_positions.mjs +1 -0
  148. package/library/actions/mobile/perps/ensure_orders.mjs +1 -0
  149. package/library/actions/mobile/perps/ensure_positions.mjs +1 -0
  150. package/library/actions/mobile/perps/measure_homepage_visible.mjs +1 -0
  151. package/library/actions/mobile/perps/performance-capture.mjs +2 -0
  152. package/library/actions/mobile/perps/perps.mjs +8 -1
  153. package/library/actions/mobile/perps/place_order.mjs +1 -0
  154. package/library/actions/mobile/perps/prepare_local_snapshot_endpoint.mjs +2 -0
  155. package/library/actions/mobile/perps/read-visible-state-loop.mjs +3 -0
  156. package/library/actions/mobile/perps/read_orders.mjs +1 -0
  157. package/library/actions/mobile/perps/read_positions.mjs +1 -0
  158. package/library/actions/mobile/perps/read_visible_state.mjs +1 -0
  159. package/library/actions/mobile/perps/search_markets.mjs +1 -0
  160. package/library/actions/mobile/perps/set_market_favorite.mjs +17 -19
  161. package/library/actions/mobile/platform/bridge.mjs +16 -4
  162. package/library/actions/mobile/platform/native-session-name.mjs +1 -0
  163. package/library/actions/mobile/platform/native-session.mjs +28 -17
  164. package/library/actions/mobile/platform/observe-ui.mjs +4 -2
  165. package/library/actions/mobile/platform/tool-paths.mjs +1 -0
  166. package/library/actions/mobile/swap_bridge/native-session.mjs +1 -0
  167. package/library/actions/mobile/swap_bridge/read_visible_state.mjs +1 -0
  168. package/library/actions/mobile/swap_bridge/select_assets.mjs +2 -0
  169. package/library/actions/mobile/swap_bridge/set_amount.mjs +1 -0
  170. package/library/actions/mobile/swap_bridge/set_max_amount.mjs +1 -0
  171. package/library/actions/mobile/swap_bridge/set_slippage.mjs +1 -0
  172. package/library/actions/mobile/swap_bridge/submit_transaction.mjs +2 -0
  173. package/library/actions/mobile/ui/locators.mjs +1 -0
  174. package/library/actions/mobile/ui/native-navigation.mjs +2 -0
  175. package/library/actions/mobile/ui/navigate.mjs +3 -1
  176. package/library/actions/mobile/wallet/ensure_unlocked.mjs +2 -0
  177. package/library/actions/mobile/wallet/home.mjs +1 -0
  178. package/library/actions/mobile/wallet/import.mjs +2 -0
  179. package/library/actions/mobile/wallet/list_accounts.mjs +1 -0
  180. package/library/actions/mobile/wallet/lock.mjs +1 -0
  181. package/library/actions/mobile/wallet/native-ui.mjs +2 -0
  182. package/library/actions/mobile/wallet/read_state.mjs +2 -0
  183. package/library/actions/mobile/wallet/reset-helper.mjs +1 -0
  184. package/library/actions/mobile/wallet/reset.mjs +1 -0
  185. package/library/actions/mobile/wallet/select_account.mjs +2 -0
  186. package/library/actions/mobile/wallet/setup.mjs +2 -0
  187. package/library/actions/shared/analytics/assert_events.mjs +3 -0
  188. package/library/actions/shared/analytics/consent.mjs +3 -0
  189. package/library/actions/shared/analytics/read_events.mjs +2 -0
  190. package/library/actions/shared/analytics/start_capture.mjs +3 -0
  191. package/library/actions/shared/app/network-artifact.mjs +2 -0
  192. package/library/actions/shared/app/network-assert.mjs +2 -0
  193. package/library/actions/shared/assets/visible-state.mjs +2 -0
  194. package/library/actions/shared/deeplink/url.mjs +3 -0
  195. package/library/actions/shared/networks/visible-state.mjs +2 -0
  196. package/library/actions/shared/perps/visible-state.mjs +16 -3
  197. package/library/actions/shared/swap-bridge/transaction.mjs +3 -0
  198. package/library/actions/shared/swap-bridge/visible-state.mjs +2 -0
  199. package/library/actions/shared/ui/locators.mjs +2 -0
  200. package/library/actions/shared/wallet/import-source.mjs +3 -0
  201. package/library/manifests/core.action-manifest.json +34 -2
  202. package/library/manifests/extension.action-manifest.json +50 -17
  203. package/library/manifests/mobile.action-manifest.json +11 -10
  204. package/library/recipes/core/perps/smoke.recipe.json +23 -0
  205. package/library/recipes/core/perps/snapshot.recipe.json +26 -1
  206. package/library/recipes/extension/assets/release-custom-network-token-persistence.recipe.json +25 -8
  207. package/library/recipes/extension/performance/navigation-memory-suite.recipe.json +219 -0
  208. package/library/recipes/extension/performance/navigation-memory.recipe.json +61 -156
  209. package/library/recipes/extension/wallet/smoke.recipe.json +17 -8
  210. package/library/recipes/mobile/app/lifecycle-smoke.recipe.json +89 -0
  211. package/library/recipes/mobile/networks/release-custom-network-persistence.recipe.json +31 -6
  212. package/library/recipes/mobile/networks/release-multichain-visibility.recipe.json +5 -4
  213. package/library/recipes/mobile/wallet/smoke.recipe.json +17 -8
  214. package/library/recipes/{assets → shared/assets}/release-token-address-import.recipe.json +7 -0
  215. package/library/recipes/{assets → shared/assets}/release-token-details.recipe.json +2 -2
  216. package/library/recipes/shared/assets/release-token-hide-readd.recipe.json +91 -0
  217. package/library/recipes/{assets → shared/assets}/release-token-persistence.recipe.json +3 -0
  218. package/library/recipes/{assets → shared/assets}/release-token-search-import.recipe.json +4 -1
  219. package/library/recipes/{assets → shared/assets}/release-token-sorting.recipe.json +13 -2
  220. package/library/recipes/{swap-bridge → shared/swap-bridge}/release-quote.recipe.json +29 -3
  221. package/package.json +3 -3
  222. package/site/assets/help-recipes.json +220 -27
  223. package/library/recipes/assets/release-token-hide-readd.recipe.json +0 -57
  224. package/library/recipes/core/perps/clean-market-testnet.recipe.json +0 -44
  225. package/library/recipes/core/perps/order-lifecycle.recipe.json +0 -82
  226. package/library/recipes/core/perps/read-markets.recipe.json +0 -36
  227. package/library/recipes/core/perps/trading-lifecycle.recipe.json +0 -80
  228. package/library/recipes/extension/perps/release-activity-filters.recipe.json +0 -51
  229. package/library/recipes/extension/perps/release-funds-flows.recipe.json +0 -76
  230. package/library/recipes/extension/perps/release-live-limit-order.recipe.json +0 -109
  231. package/library/recipes/extension/perps/release-market-filters.recipe.json +0 -97
  232. package/library/recipes/extension/perps/release-order-entry.recipe.json +0 -158
  233. package/library/recipes/extension/perps/release-order-validation.recipe.json +0 -259
  234. package/library/recipes/extension/perps/release-visible-consistency.recipe.json +0 -47
  235. package/library/recipes/extension/perps/source-dev-snapshot-consistency.recipe.json +0 -76
  236. package/library/recipes/mobile/app/lifecycle.android-smoke.recipe.json +0 -69
  237. package/library/recipes/mobile/perps/chase-assert-running.recipe.json +0 -110
  238. package/library/recipes/mobile/perps/chase-place.recipe.json +0 -145
  239. package/library/recipes/mobile/perps/chase-terminate.recipe.json +0 -98
  240. package/library/recipes/mobile/perps/performance.recipe.json +0 -722
  241. package/library/recipes/mobile/perps/pro-order-setup.recipe.json +0 -111
  242. package/library/recipes/mobile/perps/pro-order-start-state.recipe.json +0 -53
  243. package/library/recipes/mobile/perps/scale-assert-orders.recipe.json +0 -126
  244. package/library/recipes/mobile/perps/scale-place.recipe.json +0 -186
  245. package/library/recipes/mobile/perps/twap-assert-active.recipe.json +0 -97
  246. package/library/recipes/mobile/perps/twap-place.recipe.json +0 -146
  247. package/library/recipes/mobile/runner/action-validation.recipe.json +0 -336
  248. package/library/recipes/perps/clean-market-testnet.recipe.json +0 -49
  249. package/library/recipes/perps/lifecycle.recipe.json +0 -144
  250. package/library/recipes/perps/release-live-market-order.recipe.json +0 -139
  251. package/library/recipes/perps/release-watchlist.recipe.json +0 -111
  252. package/library/recipes/swap-bridge/release-max-quote.recipe.json +0 -72
  253. /package/library/recipes/{assets → shared/assets}/release-token-list.recipe.json +0 -0
  254. /package/library/recipes/{perps → shared/perps}/release-market-details.recipe.json +0 -0
  255. /package/library/recipes/{perps → shared/perps}/release-market-search.recipe.json +0 -0
  256. /package/library/recipes/{swap-bridge → shared/swap-bridge}/release-custom-slippage.recipe.json +0 -0
  257. /package/library/recipes/{swap-bridge → shared/swap-bridge}/release-live-max-swap.recipe.json +0 -0
  258. /package/library/recipes/{swap-bridge → shared/swap-bridge}/release-live-swap.recipe.json +0 -0
  259. /package/library/recipes/{swap-bridge → shared/swap-bridge}/release-surface.recipe.json +0 -0
  260. /package/library/recipes/{wallet → shared/wallet}/import.recipe.json +0 -0
  261. /package/library/recipes/{wallet → shared/wallet}/reset-import.recipe.json +0 -0
@@ -37,6 +37,8 @@ async function handleStatus({ options }) {
37
37
  const next = surface.hints.relaunch;
38
38
  const deviceView = adapter === "mobile" ? mobileDeviceView(allDevices) : null;
39
39
  const devices = deviceView?.devices ?? [];
40
+ const discovery = deviceView?.deviceDiscoveryErrors.length ? { deviceDiscoveryErrors: deviceView.deviceDiscoveryErrors } : {};
41
+ const discoveryNext = discovery.deviceDiscoveryErrors?.[0]?.userAction;
40
42
  if (json) {
41
43
  const doProbe = !fast && adapter === "mobile" && deviceView !== null && deviceView.allConnectedDevices.length > 0;
42
44
  if (doProbe) {
@@ -49,9 +51,10 @@ async function handleStatus({ options }) {
49
51
  adapter,
50
52
  target,
51
53
  devices: liveView.devicesWithLive,
54
+ ...discovery,
52
55
  ...liveView.additionalReachableDevices.length > 0 ? { additionalReachableDevices: liveView.additionalReachableDevices } : {},
53
56
  ...liveView.androidPortHints.length > 0 ? { androidPortHints: liveView.androidPortHints } : {},
54
- next: nextForLive(next, liveView.liveMap)
57
+ next: discoveryNext ?? nextForLive(next, liveView.liveMap)
55
58
  },
56
59
  null,
57
60
  2
@@ -67,8 +70,9 @@ async function handleStatus({ options }) {
67
70
  adapter,
68
71
  target,
69
72
  devices,
73
+ ...discovery,
70
74
  ...runtime ? { runtime } : {},
71
- next: runtime ? nextForRuntime(surface.hints.launch, surface.hints.relaunch, runtime) : next
75
+ next: discoveryNext ?? (runtime ? nextForRuntime(surface.hints.launch, surface.hints.relaunch, runtime) : next)
72
76
  },
73
77
  null,
74
78
  2
@@ -85,13 +89,13 @@ async function handleStatus({ options }) {
85
89
  if (liveView.additionalReachableDevices.length > 0) renderAdditionalReachableDevices(liveView.additionalReachableDevices, out);
86
90
  renderMobileLiveBlock(devices, liveView.liveMap, out);
87
91
  renderAndroidPortHints(liveView.androidPortHints, out);
88
- console.log(`${out("label", "Next:")} ${out("cmd", nextForLive(next, liveView.liveMap))}`);
92
+ console.log(`${out("label", "Next:")} ${out("cmd", discoveryNext ?? nextForLive(next, liveView.liveMap))}`);
89
93
  } else if (!fast && adapter === "extension") {
90
94
  const runtime = await surface.runtimeStatus(target);
91
95
  renderRuntimeStatus(runtime, out);
92
96
  console.log(`${out("label", "Next:")} ${out("cmd", nextForRuntime(surface.hints.launch, surface.hints.relaunch, runtime))}`);
93
97
  } else {
94
- console.log(`${out("label", "Next:")} ${out("cmd", next)}`);
98
+ console.log(`${out("label", "Next:")} ${out("cmd", discoveryNext ?? next)}`);
95
99
  }
96
100
  return EXIT.ok;
97
101
  }
package/dist/devices.js CHANGED
@@ -1,18 +1,30 @@
1
1
  import { execFileSync } from "node:child_process";
2
2
  import { resolveMobileToolPath } from "../library/actions/mobile/platform/tool-paths.mjs";
3
- function listConnectedDevices(platform) {
3
+ function reportProbeFailure(errors, platform, error) {
4
+ const code = error?.code;
5
+ if (code === "ENOENT") return;
6
+ const reason = code === "ETIMEDOUT" ? "timed out after 5 seconds" : "failed";
7
+ const probeCode = code ?? error?.status;
8
+ errors.push({
9
+ platform,
10
+ message: `${platform} device discovery ${reason}; device availability is unknown.`,
11
+ userAction: platform === "ios" ? "xcrun simctl list devices booted -j" : "adb devices -l",
12
+ ...probeCode !== void 0 ? { probeCode } : {}
13
+ });
14
+ }
15
+ function listConnectedDevices(platform, errors = []) {
4
16
  const devices = [];
5
- if (!platform || platform === "android") devices.push(...listAndroidDevices());
6
- if (!platform || platform === "ios") devices.push(...listIosSimulators());
17
+ if (!platform || platform === "android") devices.push(...listAndroidDevices(errors));
18
+ if (!platform || platform === "ios") devices.push(...listIosSimulators("booted", errors));
7
19
  return devices;
8
20
  }
9
- function findAvailableIosSimulatorById(id) {
10
- for (const simulator of listIosSimulators("available")) {
21
+ function findAvailableIosSimulatorById(id, errors = []) {
22
+ for (const simulator of listIosSimulators("available", errors)) {
11
23
  if (simulator.id === id) return simulator;
12
24
  }
13
25
  return void 0;
14
26
  }
15
- function listAndroidDevices() {
27
+ function listAndroidDevices(errors) {
16
28
  const adbPath = resolveMobileToolPath("adb");
17
29
  if (!adbPath) return [];
18
30
  let output;
@@ -22,7 +34,12 @@ function listAndroidDevices() {
22
34
  timeout: 5e3,
23
35
  stdio: ["ignore", "pipe", "ignore"]
24
36
  });
25
- } catch {
37
+ } catch (error) {
38
+ reportProbeFailure(errors, "android", error);
39
+ return [];
40
+ }
41
+ if (!output.trimStart().startsWith("List of devices attached")) {
42
+ errors.push({ platform: "android", message: "Android device discovery returned an invalid response; device availability is unknown.", userAction: "adb devices -l" });
26
43
  return [];
27
44
  }
28
45
  const devices = [];
@@ -39,7 +56,7 @@ function listAndroidDevices() {
39
56
  }
40
57
  return devices;
41
58
  }
42
- function listIosSimulators(scope = "booted") {
59
+ function listIosSimulators(scope, errors) {
43
60
  let output;
44
61
  try {
45
62
  output = execFileSync("xcrun", ["simctl", "list", "devices", scope, "-j"], {
@@ -47,13 +64,18 @@ function listIosSimulators(scope = "booted") {
47
64
  timeout: 5e3,
48
65
  stdio: ["ignore", "pipe", "ignore"]
49
66
  });
50
- } catch {
67
+ } catch (error) {
68
+ reportProbeFailure(errors, "ios", error);
51
69
  return [];
52
70
  }
53
71
  let parsed;
54
72
  try {
55
73
  parsed = JSON.parse(output);
74
+ if (!parsed?.devices || typeof parsed.devices !== "object" || Array.isArray(parsed.devices) || Object.values(parsed.devices).some((devices2) => !Array.isArray(devices2) || devices2.some((device) => !device || typeof device.udid !== "string"))) {
75
+ throw new Error("Invalid simctl device inventory");
76
+ }
56
77
  } catch {
78
+ errors.push({ platform: "ios", message: "iOS device discovery returned an invalid response; device availability is unknown.", userAction: `xcrun simctl list devices ${scope} -j` });
57
79
  return [];
58
80
  }
59
81
  const devices = [];
@@ -141,14 +141,15 @@ function sourceSnapshot(root, adapter, excludedRoots = [], includedRoots = []) {
141
141
  "--",
142
142
  ...pathspecs
143
143
  ]);
144
+ const head = gitBuffer(git.topLevel, ["rev-parse", "HEAD"]);
144
145
  return {
145
- head: gitText(git.topLevel, ["rev-parse", "HEAD"]).trim() || null,
146
+ head: head.toString("utf8").trim() || null,
146
147
  status,
147
- sourceFingerprint: adapter === "mobile" && path.resolve(root) === git.topLevel ? mobileSourceFingerprint(root) : nestedGitSourceFingerprint(root, git, pathspecs, excludedRoots, includedRoots)
148
+ sourceFingerprint: adapter === "mobile" && path.resolve(root) === git.topLevel ? mobileSourceFingerprint(root) : nestedGitSourceFingerprint(root, git, pathspecs, excludedRoots, includedRoots, head)
148
149
  };
149
150
  }
150
- function nestedGitSourceFingerprint(root, git, pathspecs, excludedRoots, includedRoots) {
151
- const gitFingerprint = gitSourceFingerprint(git.topLevel, pathspecs);
151
+ function nestedGitSourceFingerprint(root, git, pathspecs, excludedRoots, includedRoots, head) {
152
+ const gitFingerprint = gitSourceFingerprint(git.topLevel, pathspecs, head);
152
153
  if (git.pathspec === "." && includedRoots.length === 0) return gitFingerprint;
153
154
  return createHash("sha256").update(gitFingerprint).update("\0nested-source\0").update(directoryFingerprint(root, excludedRoots, includedRoots)).digest("hex");
154
155
  }
@@ -169,16 +170,16 @@ function gitContext(root) {
169
170
  return null;
170
171
  }
171
172
  }
172
- function gitSourceFingerprint(topLevel, pathspecs) {
173
+ function gitSourceFingerprint(topLevel, pathspecs, head) {
173
174
  const hash = createHash("sha256");
174
- hash.update(gitBuffer(topLevel, ["rev-parse", "HEAD"]));
175
+ hash.update(head);
175
176
  hash.update("\0diff\0");
176
177
  hash.update(
177
178
  gitBuffer(topLevel, [
178
179
  "diff",
179
180
  "--no-ext-diff",
180
181
  "--binary",
181
- "HEAD",
182
+ head.toString("utf8").trim(),
182
183
  "--",
183
184
  ...pathspecs
184
185
  ])
@@ -4,6 +4,7 @@ import { chmod, lstat, mkdir, open, readFile, readdir, realpath } from "node:fs/
4
4
  import os from "node:os";
5
5
  import path from "node:path";
6
6
  import { digestRecipeDocument } from "@farmslot/protocol";
7
+ import { recipeRuntimeDir } from "./paths.js";
7
8
  const SHA256 = /^[a-f0-9]{64}$/u;
8
9
  const INTEGER = /^(0|[1-9][0-9]{0,39})$/u;
9
10
  const IDENTIFIER = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
@@ -35,7 +36,8 @@ const CLAIM_DIFF_FIELDS = [
35
36
  ];
36
37
  const trustedActionContexts = /* @__PURE__ */ new WeakSet();
37
38
  const fundingSources = /* @__PURE__ */ new WeakMap();
38
- const CURRENT_FUNDING_REGISTRY_SHA256 = "7e79ee2a5f7bdde0a5548208cb2eab55881575cc7d826ee5f7acdbf15d49609c";
39
+ const CURRENT_FUNDING_REGISTRY_SHA256 = "d4416e0e8a812a0e2f1798ae6f901a5820f7ee0b9783cfa5d515d0c7863ea80e";
40
+ const PREVIOUS_FUNDING_REGISTRY_SHA256 = "7e79ee2a5f7bdde0a5548208cb2eab55881575cc7d826ee5f7acdbf15d49609c";
39
41
  const FROZEN_G005_FUNDING_REGISTRY_SHA256 = "c2230bc02fa00d6902915d1cb81bcc08389ea7e399ee9618f26fb0d9fc00b291";
40
42
  const LIVE_PERPS_TINY_REGISTRY = {
41
43
  "live-perps-tiny": {
@@ -57,8 +59,15 @@ const LIVE_PERPS_TINY_REGISTRY = {
57
59
  };
58
60
  const TRUSTED_FUNDING_REGISTRIES = {
59
61
  [CURRENT_FUNDING_REGISTRY_SHA256]: LIVE_PERPS_TINY_REGISTRY,
62
+ [PREVIOUS_FUNDING_REGISTRY_SHA256]: LIVE_PERPS_TINY_REGISTRY,
60
63
  [FROZEN_G005_FUNDING_REGISTRY_SHA256]: LIVE_PERPS_TINY_REGISTRY
61
64
  };
65
+ const SOURCE_DEV_TESTNET_BUDGETS = /* @__PURE__ */ new Set([
66
+ "perps-market-order:usdc-base-units",
67
+ "perps-limit-order:usdc-base-units",
68
+ "perps-cancel-order:usdc-base-units",
69
+ "perps-close-position:usdc-base-units"
70
+ ]);
62
71
  const OPERATION_ACTION = /* @__PURE__ */ new Map([
63
72
  ["perps-market-order", "metamask.perps.place_order"],
64
73
  ["perps-limit-order", "metamask.perps.place_order"],
@@ -99,7 +108,7 @@ async function loadTrustedMutationBase(options) {
99
108
  fundingBalanceProof
100
109
  );
101
110
  const identity = await validateFundingDocuments(documents);
102
- const currentReleaseRuntime = options.testIo?.currentReleaseRuntime ?? releaseRuntimeValidator(path.resolve(options.projectRoot));
111
+ const currentReleaseRuntime = options.testIo?.currentReleaseRuntime ?? releaseRuntimeValidator(path.resolve(options.projectRoot), identity.proofLane);
103
112
  const currentRelease = await currentReleaseRuntime(false, identity.cdpPort);
104
113
  if (currentRelease.artifactSha256 !== identity.subjectDigest || currentRelease.treeSha256 !== identity.releaseTreeSha256) {
105
114
  throw new Error("Runner-owned current release artifact does not match the funding release proof.");
@@ -162,6 +171,7 @@ async function authorizeTrustedMutationPlan(base, plan, testIo = {}) {
162
171
  }
163
172
  }
164
173
  if (actual.size === 0) throw new Error("Funding documents require a resolved external-mutation plan.");
174
+ await mkdir(source.artifactsDir, { recursive: true, mode: 448 });
165
175
  const artifactsIdentitySha256 = await directoryIdentity(source.artifactsDir);
166
176
  const operationCounts = [...expected.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([action, count]) => ({ action, count }));
167
177
  const runId = sha256(canonical({
@@ -719,14 +729,27 @@ async function validateFundingEvidenceChain(documents, identity) {
719
729
  if (policy.required !== true || policy.registrySha256 !== identity.registrySha256 || policy.profileId !== identity.profileId || policy.domain !== identity.domain) {
720
730
  throw new Error("Funding release proof does not bind the runner-trusted funding policy.");
721
731
  }
722
- const artifact = requireRecord(releaseProof.artifact, "funding release proof artifact");
723
- if (artifact.sha256 !== identity.subjectDigest || !SHA256.test(String(artifact.treeSha256 ?? ""))) {
724
- throw new Error("Funding release proof artifact identity is invalid.");
732
+ const sourceDev = identity.proofLane === "source-dev";
733
+ let releaseTreeSha256;
734
+ if (sourceDev) {
735
+ const source = requireRecord(releaseProof.source, "funding source-dev proof source");
736
+ const workingTree = requireRecord(source.workingTree, "funding source-dev working tree");
737
+ const build = requireRecord(source.build, "funding source-dev build");
738
+ if (identity.profileId !== "live-perps-tiny" || identity.domain !== "perps" || "artifact" in releaseProof || !/^[a-f0-9]{40}$/u.test(String(source.commit ?? "")) || !["clean", "dirty"].includes(String(workingTree.state)) || !SHA256.test(String(workingTree.statusSha256 ?? "")) || (workingTree.state === "clean" ? workingTree.statusSha256 !== sha256("") || "diffSha256" in workingTree : !SHA256.test(String(workingTree.diffSha256 ?? ""))) || build.outputSha256 !== identity.subjectDigest || !Array.isArray(build.instrumentation) || build.instrumentation.some((item) => typeof item !== "string" || !item.trim()) || releaseProof.compatibilityOnly !== true || releaseProof.releaseRecommendationEligible !== false) {
739
+ throw new Error("Funding source-dev proof requires bound build provenance and compatibility-only evidence.");
740
+ }
741
+ releaseTreeSha256 = String(build.outputSha256);
742
+ } else {
743
+ const artifact = requireRecord(releaseProof.artifact, "funding release proof artifact");
744
+ if (artifact.sha256 !== identity.subjectDigest || !SHA256.test(String(artifact.treeSha256 ?? ""))) {
745
+ throw new Error("Funding release proof artifact identity is invalid.");
746
+ }
747
+ releaseTreeSha256 = String(artifact.treeSha256);
725
748
  }
726
749
  const runtime = requireRecord(releaseProof.runtime, "funding release proof runtime");
727
750
  const extensionRuntime = requireRecord(runtime.extension, "funding release proof Extension runtime");
728
- if (identity.client !== "extension" || identity.proofLane !== "official-unmodified" || runtime.fixtures !== false || runtime.instrumentation !== false || extensionRuntime.cleanProfile !== true || extensionRuntime.runtimeTreeSha256 !== artifact.treeSha256) {
729
- throw new Error("Funding release proof is not an unmodified clean Extension runtime.");
751
+ if (identity.client !== "extension" || extensionRuntime.runtimeTreeSha256 !== releaseTreeSha256 || (sourceDev ? typeof runtime.fixtures !== "boolean" || typeof runtime.instrumentation !== "boolean" || typeof extensionRuntime.cleanProfile !== "boolean" || typeof extensionRuntime.profileId !== "string" || !extensionRuntime.profileId.trim() : identity.proofLane !== "official-unmodified" || runtime.fixtures !== false || runtime.instrumentation !== false || extensionRuntime.cleanProfile !== true)) {
752
+ throw new Error("Funding proof does not match its permitted Extension runtime lane.");
730
753
  }
731
754
  const onboarding = requireRecord(releaseProof.walletOnboarding, "funding release proof walletOnboarding");
732
755
  const onboardingRuntime = requireRecord(onboarding.runtime, "funding release proof wallet runtime");
@@ -837,8 +860,8 @@ async function validateFundingEvidenceChain(documents, identity) {
837
860
  const onboardingWallet = successfulWalletOutput(
838
861
  onboardingTrace,
839
862
  "funding release onboarding evidence",
840
- "ui-import",
841
- "extension-visible-onboarding-import"
863
+ sourceDev ? "ui-visible-session" : "ui-import",
864
+ sourceDev ? "extension-visible-wallet-session" : "extension-visible-onboarding-import"
842
865
  );
843
866
  const onboardingOrigin = extensionOrigin(onboardingWallet.targetUrl, "funding release onboarding wallet");
844
867
  if (onboardingWallet.runtimeSessionId !== onboardingRuntime.runtimeSessionId || onboardingWallet.cdpPort !== onboardingRuntime.cdpPort || onboardingWallet.cdpTargetId !== onboardingRuntime.cdpTargetId || extensionOrigin(onboardingRuntime.targetUrl, "funding release onboarding runtime") !== onboardingOrigin) {
@@ -972,7 +995,7 @@ async function validateFundingEvidenceChain(documents, identity) {
972
995
  evidenceDocument.runtimeSessionId,
973
996
  "funding balance evidence runtimeSessionId"
974
997
  );
975
- if (evidenceDocument.selectedIdentitySha256 !== onboardingWallet.selectedIdentitySha256 || wallet.selectedIdentitySha256 !== onboardingWallet.selectedIdentitySha256 || wallet.runtimeSessionId !== runtimeSessionId || wallet.cdpPort !== onboardingWallet.cdpPort || extensionOrigin(wallet.targetUrl, "funding balance wallet") !== onboardingOrigin) {
998
+ if (evidenceDocument.selectedIdentitySha256 !== onboardingWallet.selectedIdentitySha256 || wallet.selectedIdentitySha256 !== onboardingWallet.selectedIdentitySha256 || wallet.runtimeSessionId !== runtimeSessionId || sourceDev && (wallet.runtimeSessionId !== onboardingWallet.runtimeSessionId || wallet.cdpTargetId !== onboardingWallet.cdpTargetId) || wallet.cdpPort !== onboardingWallet.cdpPort || extensionOrigin(wallet.targetUrl, "funding balance wallet") !== onboardingOrigin) {
976
999
  throw new Error("Funding balance evidence wallet or runtime is not anchored to release onboarding.");
977
1000
  }
978
1001
  const currentRuntime = {
@@ -987,7 +1010,7 @@ async function validateFundingEvidenceChain(documents, identity) {
987
1010
  }
988
1011
  balanceRuntime = currentRuntime;
989
1012
  const visibleState = requireRecord(evidenceDocument.visibleState, "funding balance evidence visibleState");
990
- if (visibleState.perpsAvailableMarginBaseUnits !== actualBaseUnits || visibleBalanceBaseUnits(trace, emittedNodes) !== actualBaseUnits) {
1013
+ if (visibleState.perpsAvailableMarginBaseUnits !== actualBaseUnits || visibleBalanceBaseUnits(trace, emittedNodes, currentRuntime) !== actualBaseUnits) {
991
1014
  throw new Error("Funding balance actual value does not match its visible trace.");
992
1015
  }
993
1016
  if (!exactTerminalPass(recipe, trace, summary, artifactManifest)) {
@@ -999,13 +1022,26 @@ async function validateFundingEvidenceChain(documents, identity) {
999
1022
  return {
1000
1023
  requirements,
1001
1024
  runtime: balanceRuntime,
1002
- releaseTreeSha256: String(artifact.treeSha256)
1025
+ releaseTreeSha256
1003
1026
  };
1004
1027
  }
1005
- function releaseRuntimeValidator(projectRoot) {
1028
+ function releaseRuntimeValidator(projectRoot, proofLane) {
1006
1029
  return async (requireLive, cdpPort) => {
1007
1030
  const { extensionRuntimeReusable } = await import("./commands/launch/extension.js");
1008
1031
  const { releaseArtifactState, runtimeDistCheck } = await import("./adapters/extension/runtime-decision.js");
1032
+ if (proofLane === "source-dev") {
1033
+ const { extensionTreeSha256 } = await import("./adapters/extension/artifact-integrity.js");
1034
+ const configuredPort = Number(process.env.CDP_PORT ?? process.env.RECIPE_CDP_PORT);
1035
+ if (configuredPort !== cdpPort || !await extensionRuntimeReusable(projectRoot, { status: "none" })) {
1036
+ throw new Error("Source-dev funding requires the current harness-owned Extension runtime.");
1037
+ }
1038
+ const treeSha256 = extensionTreeSha256(path.join(
1039
+ projectRoot,
1040
+ recipeRuntimeDir(),
1041
+ process.env.RECIPE_RUNTIME_DIST_DIR || "runtime-dist"
1042
+ ));
1043
+ return { artifactSha256: treeSha256, treeSha256 };
1044
+ }
1009
1045
  const artifact = releaseArtifactState(projectRoot);
1010
1046
  if (artifact.status !== "valid" || runtimeDistCheck(projectRoot, artifact).status !== "fresh" || !artifact.sha256 || !artifact.treeSha256) {
1011
1047
  throw new Error("Runner-owned current release artifact is invalid or stale.");
@@ -1058,11 +1094,17 @@ function extensionOrigin(value, label) {
1058
1094
  throw new Error(`${label} Extension origin is invalid.`);
1059
1095
  }
1060
1096
  }
1061
- function visibleBalanceBaseUnits(trace, emittedNodes) {
1097
+ function visibleBalanceBaseUnits(trace, emittedNodes, runtime) {
1062
1098
  const entries = Array.isArray(trace.entries) ? trace.entries : [];
1063
1099
  const matches = entries.filter((entry) => isRecord(entry) && entry.action === "metamask.perps.read_visible_state" && entry.ok === true && isRecord(entry.output) && entry.output.action === entry.action && entry.output.surface === "home" && entry.output.source === "visible-ui" && isRecord(entry.output.balance) && entry.output.balance.visible === true && isRecord(emittedNodes.get(String(entry.nodeId))) && emittedNodes.get(String(entry.nodeId))?.surface === "home");
1064
1100
  const matchEntry = matches.at(-1);
1065
1101
  if (!matchEntry) throw new Error("Funding balance evidence requires a visible Perps Home balance observation.");
1102
+ for (const entry of matches) {
1103
+ const output = requireRecord(entry.output, "funding balance observation");
1104
+ if (output.runtimeSessionId !== runtime.runtimeSessionId || output.cdpPort !== runtime.cdpPort || extensionOrigin(output.targetUrl, "funding balance observation") !== runtime.extensionOrigin) {
1105
+ throw new Error("Funding balance observation is not from its anchored wallet runtime.");
1106
+ }
1107
+ }
1066
1108
  const label = String(matchEntry.output.balance.label ?? "");
1067
1109
  const match = /(?:^|\s)\$([0-9]+(?:\.[0-9]{1,2})?)(?:\s|$)/u.exec(label);
1068
1110
  if (!match) throw new Error("Funding balance evidence visible amount is invalid.");
@@ -1253,14 +1295,16 @@ function parsePreflightRequirements(value) {
1253
1295
  if (!Array.isArray(value)) throw new Error("Funding preflight requirements must be an array.");
1254
1296
  return value.map((entry, index) => {
1255
1297
  const record = requireRecord(entry, `funding preflight requirements[${index}]`);
1298
+ const evidence = requireRecord(record.evidence, `funding preflight requirements[${index}].evidence`);
1299
+ const wrapper = requireRecord(evidence.wrapper, `funding preflight requirements[${index}].evidence.wrapper`);
1256
1300
  return {
1257
1301
  kind: requireIdentifier(record.kind, `funding preflight requirements[${index}].kind`),
1258
1302
  id: requireIdentifier(record.id, `funding preflight requirements[${index}].id`),
1259
1303
  minBaseUnits: requireInteger(record.minBaseUnits, `funding preflight requirements[${index}].minBaseUnits`),
1260
1304
  actualBaseUnits: requireInteger(record.actualBaseUnits, `funding preflight requirements[${index}].actualBaseUnits`),
1261
1305
  evidenceSha256: requireIdentifier(
1262
- record.evidenceSha256,
1263
- `funding preflight requirements[${index}].evidenceSha256`,
1306
+ wrapper.sha256,
1307
+ `funding preflight requirements[${index}].evidence.wrapper.sha256`,
1264
1308
  true
1265
1309
  )
1266
1310
  };
@@ -1295,7 +1339,8 @@ function validateTrustedFundingRegistry(identity, operations, budgets) {
1295
1339
  for (const [key, aggregate] of requested) {
1296
1340
  const trusted = domain.budgets.get(key);
1297
1341
  const supplied = suppliedBudgets.get(key);
1298
- if (!trusted || !supplied || BigInt(supplied.maxBaseUnits) > BigInt(trusted.maxBaseUnits) || supplied.maxCount > trusted.maxCount || aggregate.maxAmountBaseUnits > BigInt(trusted.maxBaseUnits) || aggregate.count > trusted.maxCount || aggregate.amountBaseUnits > BigInt(trusted.maxBaseUnits) * BigInt(trusted.maxCount)) {
1342
+ const maximum = BigInt(identity.registrySha256 === CURRENT_FUNDING_REGISTRY_SHA256 && identity.client === "extension" && identity.proofLane === "source-dev" && SOURCE_DEV_TESTNET_BUDGETS.has(key) ? "20000000" : trusted?.maxBaseUnits ?? "0");
1343
+ if (!trusted || !supplied || BigInt(supplied.maxBaseUnits) > maximum || supplied.maxCount > trusted.maxCount || aggregate.maxAmountBaseUnits > maximum || aggregate.count > trusted.maxCount || aggregate.amountBaseUnits > maximum * BigInt(trusted.maxCount)) {
1299
1344
  throw new Error(`Funding operation ${aggregate.operation} is not bounded by the runner-trusted funding registry.`);
1300
1345
  }
1301
1346
  }
@@ -63,7 +63,7 @@ async function ensureOverlay(adapter, target, heal, state, json) {
63
63
  function classifyFailure(output) {
64
64
  if (/SCREENSHOT_PROTECTED|screenshot is protected by FLAG_SECURE/iu.test(output)) return "screenshot-protected";
65
65
  if (/CDP_TIMEOUT|Mobile CDP bridge command timed out|NO_TARGET|WS_CLOSED/iu.test(output)) return "infra";
66
- if (/wallet|fixture|keyring|not seeded|\bsrp\b|password|onboard/iu.test(output)) return "wallet";
66
+ if (/fixture|keyring|not seeded|\bsrp\b|password|onboard/iu.test(output)) return "wallet";
67
67
  if (/metro|cdp|chrome|bridge|bundle|packager|port\b|econnrefused|not reachable|watcher|dev client|websocket/iu.test(output)) {
68
68
  return "infra";
69
69
  }
@@ -101,8 +101,8 @@ function checkHealBounds(target, output, state) {
101
101
  return {
102
102
  code: "WALLET_STATE_REQUIRED",
103
103
  exitCode: EXIT.bounded,
104
- message: "recovery would require a seeded wallet \u2014 healing never touches fixtures.",
105
- userAction: "run mm-harness fixtures set",
104
+ message: "wallet prerequisites need attention; healing never changes wallet state.",
105
+ userAction: /(?:^|\n)\s*Next:\s*([^\r\n]+)/u.exec(output)?.[1] ?? "run mm-harness doctor --json",
106
106
  originalError
107
107
  };
108
108
  }
@@ -334,6 +334,10 @@ async function runLiveAdapterScript({ platform, action, node, context, prepared
334
334
  throw new Error(`Live adapter ${script} timed out after ${processTimeoutMs}ms.`);
335
335
  }
336
336
  if (result.exitCode !== 0) {
337
+ const failureOutput = await readFile(outputPath, "utf8").then(JSON.parse).catch(() => null);
338
+ if (Array.isArray(failureOutput?.artifacts)) {
339
+ for (const artifact of failureOutput.artifacts) context.registerArtifact(artifact);
340
+ }
337
341
  throw new Error(`Live adapter ${script} exited ${result.exitCode}: ${result.stderr || result.stdout}`);
338
342
  }
339
343
  let parsed = null;
package/dist/manifest.js CHANGED
@@ -2,7 +2,6 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { OFFICIAL_RECIPE_ACTIONS } from "@farmslot/protocol";
4
4
  import { manifestPath, readJson, importRecipeProtocol } from "./paths.js";
5
- import { actionSourceIntegrity } from "./recipe-security.js";
6
5
  function loadMetaMaskMobileActionManifest() {
7
6
  return asActionManifest(readJson(manifestPath("mobile")));
8
7
  }
@@ -26,7 +25,6 @@ async function resolveActionManifest(adapter, overridePath, librarySources, task
26
25
  );
27
26
  const manifest = loadActionManifest(adapter, manifestPath2);
28
27
  await validateManifest(manifest);
29
- const integrity = await actionSourceIntegrity(manifestPath2, "task", implementationRoot);
30
28
  return {
31
29
  manifest,
32
30
  actionSources: await describeManifestSources(manifest, {
@@ -34,8 +32,7 @@ async function resolveActionManifest(adapter, overridePath, librarySources, task
34
32
  tier: "task",
35
33
  manifestPath: manifestPath2,
36
34
  implementationRoot,
37
- trust: "unknown",
38
- ...integrity
35
+ trust: "unknown"
39
36
  })
40
37
  };
41
38
  }
@@ -67,12 +64,6 @@ async function resolveActionManifest(adapter, overridePath, librarySources, task
67
64
  for (const [name, entry] of Object.entries(canonicalActions)) {
68
65
  if (official.has(name)) mergedActions[name] = entry;
69
66
  }
70
- const canonicalImplementationRoot = path.join(canonical.source.root, "actions");
71
- const canonicalIntegrity = await actionSourceIntegrity(
72
- canonical.file,
73
- "canonical",
74
- canonicalImplementationRoot
75
- );
76
67
  const actionSources = /* @__PURE__ */ new Map();
77
68
  for (const name of Object.keys(mergedActions)) {
78
69
  actionSources.set(name, {
@@ -96,14 +87,12 @@ async function resolveActionManifest(adapter, overridePath, librarySources, task
96
87
  const sourceName = isCanonical ? "metamask" : entry.source.name ?? path.basename(entry.source.root);
97
88
  const tier = isCanonical ? "canonical" : sourceName === "personal" ? "personal" : "team";
98
89
  const implementationRoot = path.join(entry.source.root, "actions");
99
- const integrity = isCanonical ? canonicalIntegrity : await actionSourceIntegrity(entry.file, tier, implementationRoot);
100
90
  const sourceInfo = {
101
91
  name: sourceName,
102
92
  tier,
103
93
  manifestPath: entry.file,
104
94
  implementationRoot,
105
- trust: entry.source.provenance?.trust ?? (isCanonical ? "trusted" : "unknown"),
106
- ...integrity
95
+ trust: entry.source.provenance?.trust ?? (isCanonical ? "trusted" : "unknown")
107
96
  };
108
97
  for (const [name, action] of Object.entries(actionsOf(entry.manifest))) {
109
98
  if (official.has(name)) {
@@ -191,7 +191,8 @@ function validateMetaMaskActionInputs(recipe, adapter, params) {
191
191
  }
192
192
  if (action === "metamask.perps.close_orders") {
193
193
  required("side", /^(?:long|short)$/u, `${action} requires the exact order side`);
194
- if (!/^[1-9][0-9]*(?:\.[0-9]{1,6})?$/u.test(String(node.limit_price ?? ""))) {
194
+ const outputPrice = /^\{\{outputs\.[A-Za-z0-9_.-]+\}\}$/u.test(String(node.limit_price ?? ""));
195
+ if (!outputPrice && (!/^(?:0|[1-9][0-9]*)(?:\.[0-9]{1,6})?$/u.test(String(node.limit_price ?? "")) || !(Number(node.limit_price) > 0))) {
195
196
  findings.push({
196
197
  severity: "error",
197
198
  code: "recipe.invalid_param",
@@ -200,7 +201,7 @@ function validateMetaMaskActionInputs(recipe, adapter, params) {
200
201
  });
201
202
  }
202
203
  }
203
- if (action === "metamask.perps.place_order" && String(node.order_type ?? "market") === "limit" && !/^[1-9][0-9]*(?:\.[0-9]{1,6})?$/u.test(String(node.limit_price ?? ""))) {
204
+ if (action === "metamask.perps.place_order" && String(node.order_type ?? "market") === "limit" && (!/^(?:0|[1-9][0-9]*)(?:\.[0-9]{1,6})?$/u.test(String(node.limit_price ?? "")) || !(Number(node.limit_price) > 0))) {
204
205
  findings.push({
205
206
  severity: "error",
206
207
  code: "recipe.invalid_param",
@@ -288,7 +288,7 @@ Advanced integrations:
288
288
 
289
289
  Example:
290
290
  mm-harness run wallet.smoke --describe
291
- mm-harness run perps.clean-market-testnet market=BTC --plan --adapter mobile
291
+ mm-harness run perps.release-market-search query=Bitcoin --plan --adapter mobile
292
292
  mm-harness run recipe.json market=ETH --adapter extension`
293
293
  },
294
294
  {
@@ -613,6 +613,7 @@ Example:
613
613
  --device <serial> Explicit Android device serial
614
614
  --cdp-port <port> Extension CDP port
615
615
  --chrome-user-data-dir <dir> Isolated Chrome profile path
616
+ --reset-profile Explicitly clear that profile; default preserves wallet state
616
617
  --artifacts-dir <dir> Validation evidence directory
617
618
  --start-watch Build through the checkout watcher
618
619
  --build-lavamoat Build through the production-like path
@@ -825,7 +826,8 @@ for (const command of REAL) {
825
826
  process.exit(await handleUpdate(rawArgv.slice(1)));
826
827
  }
827
828
  if (command.name === "help") {
828
- process.exit(handleHelp(rawArgv.slice(1), { packageRoot, harnessVersion: pkgVersion }));
829
+ process.exitCode = handleHelp(rawArgv.slice(1), { packageRoot, harnessVersion: pkgVersion });
830
+ return;
829
831
  }
830
832
  if (command.name === "tutorial") {
831
833
  process.exit(handleTutorial(rawArgv.slice(1), { harnessVersion: pkgVersion }));
package/dist/runner.js CHANGED
@@ -20,10 +20,11 @@ import {
20
20
  bindMetaMaskMobileLifecycleDeadline,
21
21
  createMetaMaskMobileLifecycleCommandRunner,
22
22
  MOBILE_LIFECYCLE_DEADLINE_FIELD,
23
+ MOBILE_LIFECYCLE_PREPARED,
23
24
  resolveMetaMaskMobileLifecycleTarget
24
25
  } from "./app-lifecycle.js";
25
26
  import { loadMetaMaskExtensionActionManifest, loadMetaMaskMobileActionManifest } from "./manifest.js";
26
- import { metaMaskActionExecutionCapabilities } from "./recipe-security.js";
27
+ import { actionSourceIntegrity, metaMaskActionExecutionCapabilities } from "./recipe-security.js";
27
28
  import {
28
29
  prepareLiveAdapterScript
29
30
  } from "./live-adapter-contract.js";
@@ -210,7 +211,7 @@ function withTrustedMutationContext(adapters, base, externalMutationActions, ada
210
211
  ...entry,
211
212
  async execute(node, context) {
212
213
  const identityProof = entry.action === identityProofAction;
213
- const invalidatesIdentity = identityInvalidatingActions.has(entry.action) || entry.action.startsWith("ui.") && !identityPreservingUiActions.has(entry.action);
214
+ const invalidatesIdentity = identityInvalidatingActions.has(entry.action) || base?.proofLane !== "source-dev" && entry.action.startsWith("ui.") && !identityPreservingUiActions.has(entry.action);
214
215
  if (identityProof || invalidatesIdentity) selectedIdentitySha256 = void 0;
215
216
  const externalMutation = externalMutationActions.has(entry.action);
216
217
  if (externalMutation && !base) {
@@ -272,6 +273,15 @@ async function prepareLiveAdapterImplementations(platform, actions, sources, tru
272
273
  source.digest = executable.sourceDigest;
273
274
  source.resolveDigest = void 0;
274
275
  }));
276
+ for (const source of sources.values()) {
277
+ if (source.tier === "official" || source.digest) continue;
278
+ const integrity = await actionSourceIntegrity(source.manifestPath, source.tier, source.implementationRoot);
279
+ for (const candidate of sources.values()) {
280
+ if (!candidate.digest && candidate.manifestPath === source.manifestPath && candidate.implementationRoot === source.implementationRoot && candidate.tier === source.tier) {
281
+ Object.assign(candidate, integrity);
282
+ }
283
+ }
284
+ }
275
285
  return prepared;
276
286
  }
277
287
  function adapterSecurity(action, sources, trustTaskActions = false) {
@@ -405,7 +415,8 @@ function mobileSourceAwareLifecycleAdapters(adapter, lifecycle, readinessProbe =
405
415
  command: "foreground",
406
416
  settle_ms: 0,
407
417
  timeout_ms: foregroundTimeoutMs,
408
- [MOBILE_LIFECYCLE_DEADLINE_FIELD]: readinessDeadline
418
+ [MOBILE_LIFECYCLE_DEADLINE_FIELD]: readinessDeadline,
419
+ [MOBILE_LIFECYCLE_PREPARED]: true
409
420
  },
410
421
  context
411
422
  );