@deeeed/metamask-harness 0.28.0 → 0.29.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 (52) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +41 -0
  3. package/adapters/extension/build-lavamoat.sh +2 -1
  4. package/adapters/extension/ensure-browser.sh +82 -9
  5. package/adapters/extension/inject.mjs +1 -0
  6. package/adapters/extension/launch-browser.cjs +83 -1
  7. package/adapters/extension/lib/chrome-args.cjs +325 -1
  8. package/adapters/extension/lib/playwright-cdp.cjs +34 -0
  9. package/adapters/extension/lib/slot-title.cjs +2 -4
  10. package/adapters/extension/lib/validation-launch-supervisor.cjs +292 -0
  11. package/adapters/extension/lib/validation-process-ownership.cjs +69 -0
  12. package/adapters/extension/reattach.sh +2 -1
  13. package/adapters/extension/sidepanel-toggle.sh +14 -96
  14. package/adapters/extension/wallet-fixture-state.cjs +8 -31
  15. package/adapters/manifest.json +16 -0
  16. package/adapters/shared/private-atomic-write.cjs +47 -0
  17. package/adapters/shared/setup-base.sh +864 -0
  18. package/dist/adapters/extension/runtime.js +367 -24
  19. package/dist/adapters/extension/validation-process-ownership.js +10 -0
  20. package/dist/cli-commands.js +1 -0
  21. package/dist/command-contract.js +12 -0
  22. package/dist/commands/launch/extension.js +130 -19
  23. package/dist/commands/setup-base.js +24 -0
  24. package/dist/mm-harness-cli.js +28 -2
  25. package/library/actions/extension/analytics/consent.mjs +203 -0
  26. package/library/actions/extension/analytics/set_consent.mjs +19 -143
  27. package/library/actions/extension/perps/perps.mjs +2 -16
  28. package/library/actions/extension/perps/state.mjs +20 -0
  29. package/library/actions/extension/wallet/list_accounts.mjs +3 -25
  30. package/library/actions/extension/wallet/read_state.mjs +3 -23
  31. package/library/actions/extension/wallet/select_account.mjs +6 -33
  32. package/library/actions/extension/wallet/setup.mjs +2 -20
  33. package/library/actions/extension/wallet/state.mjs +111 -0
  34. package/library/recipes/runner/action-validation.extension.recipe.json +1 -1
  35. package/library/recipes/runner/action-validation.mobile.recipe.json +1 -1
  36. package/package.json +7 -4
  37. package/scripts/site-contrast.mjs +538 -0
  38. package/site/architecture.html +415 -0
  39. package/site/assets/progress.mjs +272 -0
  40. package/site/assets/style.css +808 -0
  41. package/site/cheatsheet.html +305 -0
  42. package/site/index.html +643 -0
  43. package/site/recipes.html +396 -0
  44. package/site/reviewers.html +374 -0
  45. package/site/tutorials/index.html +180 -0
  46. package/site/tutorials/v1.html +211 -0
  47. package/site/tutorials/v2.html +207 -0
  48. package/site/tutorials/v3.html +214 -0
  49. package/site/tutorials/v4.html +195 -0
  50. package/site/tutorials/v5.html +163 -0
  51. package/site/tutorials/v6.html +165 -0
  52. package/site/tutorials/v7.html +184 -0
@@ -0,0 +1,396 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>What is a recipe?</title>
7
+ <meta name="description" content="The anatomy of a recipe: a JSON graph of typed actions, human-authored intent, first-class assertions, and a verdict derived from the trace.">
8
+ <link rel="stylesheet" href="assets/style.css">
9
+ </head>
10
+ <body data-progress-page="recipes">
11
+ <a class="skip" href="#anatomy">Skip to the anatomy</a>
12
+
13
+ <header class="topbar">
14
+ <div class="wrap topbar-inner">
15
+ <a class="brand" href="index.html">
16
+ <span class="brand-mark" aria-hidden="true"></span>
17
+ <span class="brand-name">mm-harness</span>
18
+ </a>
19
+ <nav class="nav" aria-label="Main">
20
+ <a href="index.html">Start Here</a>
21
+ <a href="recipes.html" aria-current="page">Recipes</a>
22
+ <a href="cheatsheet.html">Cheatsheet</a>
23
+ <a href="architecture.html">Architecture</a>
24
+ <a href="tutorials/index.html">Tutorials</a>
25
+ <a href="reviewers.html">For Reviewers</a>
26
+ </nav>
27
+ </div>
28
+ </header>
29
+
30
+ <main>
31
+ <section class="wrap hero" style="padding-bottom:1rem">
32
+ <span class="eyebrow">The central idea</span>
33
+ <h1>What is a recipe?</h1>
34
+ <p class="lede">
35
+ A recipe turns "I checked, it works" into something a reviewer can verify without trusting you —
36
+ and something that objects later if the behaviour breaks.
37
+ </p>
38
+ </section>
39
+
40
+ <section class="wrap">
41
+ <div class="doctrine">
42
+ <p>
43
+ <span class="k">A recipe proves a task with actions.</span> A JSON graph whose nodes are typed
44
+ actions drawn only from what this checkout discovered, each carrying a human-written intent,
45
+ executed against a real build, ending in a verdict computed from what happened rather than
46
+ asserted in prose.
47
+ </p>
48
+ </div>
49
+
50
+ <p>Every clause there does a job against a specific failure mode. Here they are, one at a time.</p>
51
+ </section>
52
+
53
+ <section class="wrap">
54
+ <h2 id="anatomy">The anatomy of a real one</h2>
55
+ <p>
56
+ <code>perps.lifecycle</code> ships with the harness. Abridged here — the full graph has eighteen
57
+ nodes — but nothing is invented; read it all with
58
+ <code>mm-harness run perps.lifecycle --describe</code>.
59
+ </p>
60
+
61
+ <div class="annot">
62
+ <div class="out">{
63
+ "title": "MetaMask Perps lifecycle smoke",
64
+ "description": "Proves a deterministic Perps prestate,
65
+ opens a small ETH testnet position, verifies it,
66
+ closes it, and tears down to a clean state.",
67
+ "workflow": {
68
+ "entry": <span class="hint">"status"</span>,
69
+ "nodes": {
70
+ "status": {
71
+ "action": <span class="hint">"app.status"</span>,
72
+ "intent": "Read runner compatibility status",
73
+ "next": "fixture"
74
+ },
75
+ <span class="dim">… wallet fixture, unlock, account select …</span>
76
+
77
+ "start-state": {
78
+ "action": "metamask.perps.start_state",
79
+ "market": "ETH",
80
+ "positions": { "state": "none" },
81
+ "intent": "Prepare clean ETH testnet Perps state",
82
+ "next": "assert-clean-position"
83
+ },
84
+ <span class="hint">"assert-clean-position"</span>: {
85
+ "action": "metamask.perps.assert_positions",
86
+ "market": "ETH",
87
+ "state": "none",
88
+ "intent": "Confirm ETH position state is clean",
89
+ "next": "place-order"
90
+ },
91
+ "place-order": {
92
+ "action": "metamask.perps.place_order",
93
+ "market": "ETH", "side": "long",
94
+ "amount": "11", "leverage": 3,
95
+ "intent": "Open a small ETH 3x long position",
96
+ "next": "assert-open"
97
+ },
98
+ <span class="hint">"assert-open"</span>: {
99
+ "action": "metamask.perps.assert_positions",
100
+ "market": "ETH",
101
+ <span class="ok">"state": "open"</span>,
102
+ "intent": "Confirm the ETH position opened",
103
+ "next": "read-orders"
104
+ },
105
+ <span class="dim">… close orders, close position, assert closed …</span>
106
+
107
+ "teardown-state": {
108
+ "action": "metamask.perps.teardown_state",
109
+ "intent": "Return Perps to globally clean home state",
110
+ "next": "done"
111
+ },
112
+ "done": {
113
+ "action": <span class="hint">"end"</span>,
114
+ "status": "pass"
115
+ }
116
+ }
117
+ }
118
+ }</div>
119
+
120
+ <div class="annot-notes">
121
+ <div class="annot-note">
122
+ <b>entry + next</b>
123
+ <p>It is a graph, not a script. Execution starts at <code>entry</code> and follows each node's <code>next</code>. Branching exists too — a <code>switch</code> node picks an edge — which is why the trace records the path actually taken rather than assuming the file's reading order.</p>
124
+ </div>
125
+ <div class="annot-note">
126
+ <b>action</b>
127
+ <p>Always a name from the discovered manifest. <code>app.status</code>, <code>metamask.perps.place_order</code>, and <code>end</code> are all real entries you can look up with <code>mm-harness actions --action &lt;name&gt;</code>. You cannot write an action that does not exist and have it run.</p>
128
+ </div>
129
+ <div class="annot-note">
130
+ <b>intent</b>
131
+ <p>Written by a human, before the run. It is not a comment — it is carried into the trace, so the evidence reads as an argument rather than a log.</p>
132
+ </div>
133
+ <div class="annot-note">
134
+ <b>assert-clean-position</b>
135
+ <p>The pattern worth stealing: assert the <em>starting</em> state before acting. Without it, a position that was already open would make the next step look like a success it did not cause.</p>
136
+ </div>
137
+ <div class="annot-note">
138
+ <b>assert-open</b>
139
+ <p>And assert the effect afterwards, independently. <code>place_order</code> reporting success is the action's own word for it; <code>assert_positions</code> goes and looks. These are deliberately two different nodes.</p>
140
+ </div>
141
+ <div class="annot-note">
142
+ <b>end</b>
143
+ <p>The terminal node. Reaching it is what a passing run means — and it is only reachable by passing through every assertion on the path.</p>
144
+ </div>
145
+ </div>
146
+ </div>
147
+ </section>
148
+
149
+ <section class="wrap">
150
+ <h2 id="vocabulary">Actions come only from the discovered manifest</h2>
151
+ <p>
152
+ The available actions are fixed and discoverable, and a recipe may use nothing else. Validation
153
+ happens against that manifest <em>before</em> anything executes:
154
+ </p>
155
+
156
+ <div class="cmd"><pre><code><span class="p">$ </span>mm-harness run my-recipe.json --plan</code></pre></div>
157
+
158
+ <div class="out"><span class="ok">✓</span> [static] validate.schema: recipe document schema + action existence/platform vs the adapter manifest</div>
159
+
160
+ <p>
161
+ A misspelled or imagined action fails validation with exit code 5, and nothing touches the app.
162
+ That is the cheap check — run it before the expensive one.
163
+ </p>
164
+
165
+ <div class="note blue">
166
+ <span class="note-title">The constraint is the point</span>
167
+ <p>
168
+ A tool free to invent action names is free to invent plausible ones, and a recipe full of
169
+ plausible steps that never ran is worse than no recipe. Bounding the vocabulary to what exists is
170
+ what makes a green run mean something — and what makes this safe to hand to an agent.
171
+ </p>
172
+ </div>
173
+ </section>
174
+
175
+ <section class="wrap">
176
+ <h2 id="intent">Intent turns a trace into a narrative</h2>
177
+ <p>
178
+ The runner copies each node's <code>intent</code> into the trace beside its result. Read them in
179
+ order and you have the argument the recipe makes:
180
+ </p>
181
+
182
+ <div class="out"><span class="ok">PASS</span> status — Read runner compatibility status
183
+ <span class="ok">PASS</span> fixture — Verify wallet fixture availability
184
+ <span class="ok">PASS</span> assert-clean-position — Confirm ETH position state is clean
185
+ <span class="ok">PASS</span> place-order — Open a small ETH 3x long position
186
+ <span class="ok">PASS</span> assert-open — Confirm the ETH position opened</div>
187
+
188
+ <p>
189
+ A reviewable claim in five lines, generated from the recipe rather than written about it
190
+ afterwards. It gives review a precise question: if the intents do not add up to what the pull
191
+ request claims, the bundle does not support it — however green.
192
+ </p>
193
+ </section>
194
+
195
+ <section class="wrap">
196
+ <h2 id="assertions">Assertions are first-class — and counting matters</h2>
197
+ <p>
198
+ An assertion is a node like any other: it occupies a position in the graph, carries its own intent,
199
+ and failing it stops the path to <code>end</code>.
200
+ </p>
201
+
202
+ <div class="table-scroll" data-copy-cells>
203
+ <table>
204
+ <thead><tr><th>Assertion</th><th>Proves</th></tr></thead>
205
+ <tbody>
206
+ <tr><td><code>assert_file</code></td><td>A project file exists, and optionally contains given text.</td></tr>
207
+ <tr><td><code>assert_json</code></td><td>A JSON value satisfies a selector and operator.</td></tr>
208
+ <tr><td><code>assert_output</code></td><td>Captured stdout or stderr from an earlier command node matches.</td></tr>
209
+ <tr><td><code>assert_exit_code</code></td><td>An earlier command node exited as expected.</td></tr>
210
+ <tr><td><code>metamask.perps.assert_positions</code></td><td>Live positions are present or absent for an explicit selection.</td></tr>
211
+ <tr><td><code>metamask.analytics.assert_events</code></td><td>Event names, property values, <strong>and counts</strong>.</td></tr>
212
+ </tbody>
213
+ </table>
214
+ </div>
215
+
216
+ <div class="doctrine">
217
+ <p>
218
+ <span class="k">Counted assertions earn their keep.</span> The analytics assertion takes an
219
+ <code>exact</code> count, and its own description says why: an exact count catches a double emit
220
+ that a presence check cannot. "Did the event fire?" answers yes to a bug that fires it twice.
221
+ </p>
222
+ </div>
223
+
224
+ <p>
225
+ <code>assert_positions</code> takes <code>expect_count</code>; <code>assert_orders</code> can also
226
+ assert a matching order's trigger price, execution mode, and reduce-only flag. Reach for the most
227
+ specific assertion the surface supports — a vague one passes for the wrong reasons.
228
+ </p>
229
+ </section>
230
+
231
+ <section class="wrap">
232
+ <h2 id="composition">Recipes call recipes</h2>
233
+ <p>
234
+ <code>call</code> is itself an action: it invokes a named recipe by reference and runs its steps
235
+ inline, so shared setup is written once instead of copied into every proof. The manifest's own
236
+ example:
237
+ </p>
238
+
239
+ <div class="out">{
240
+ "action": <span class="hint">"call"</span>,
241
+ "ref": "mydev.open_perps_setup",
242
+ "intent": "Run a personal Perps setup recipe",
243
+ "next": "done"
244
+ }</div>
245
+
246
+ <p>
247
+ Running inline means the called recipe's nodes appear in the trace like any others: composition is
248
+ an author's convenience, not a hole in the evidence. <code>--describe</code> reports both the
249
+ actions and the recipes pulled in:
250
+ </p>
251
+
252
+ <div class="cmd"><pre><code><span class="p">$ </span>mm-harness run &lt;recipe&gt; --describe</code></pre></div>
253
+
254
+ <div class="out"> actions (3)
255
+ app.status
256
+ assert_file
257
+ end
258
+ called recipes: none</div>
259
+ </section>
260
+
261
+ <section class="wrap">
262
+ <h2 id="verdict">The verdict is derived, never declared</h2>
263
+ <p>
264
+ Status is computed from the trace, not asserted by the author or written by an agent afterwards.
265
+ Every node has an <code>ok</code>; the run passes only if the path reached <code>end</code> with all
266
+ of them true:
267
+ </p>
268
+
269
+ <div class="out">{
270
+ "status": <span class="ok">"pass"</span>,
271
+ "total": 6,
272
+ "passed": 6,
273
+ "failed": 0
274
+ }</div>
275
+
276
+ <p>
277
+ Two outcomes only: <strong>pass</strong> or <strong>fail</strong>. No "inconclusive" hiding place at
278
+ this level — either the graph reached its terminal node with every assertion satisfied, or it did
279
+ not.
280
+ </p>
281
+
282
+ <div class="note">
283
+ <span class="note-title">Where "cannot be proven here" lives</span>
284
+ <p>
285
+ One level up. When a review workflow maps acceptance criteria onto proofs, a criterion the surface
286
+ cannot demonstrate is reported as needing a human rather than quietly passed. That third answer
287
+ belongs to the review layer; a recipe either proved the thing or did not.
288
+ </p>
289
+ </div>
290
+
291
+ <p>
292
+ So every number you cite comes from <code>trace.json</code>; prose around a bundle is commentary.
293
+ <a href="reviewers.html">The reviewer's guide</a> walks a real one.
294
+ </p>
295
+ </section>
296
+
297
+ <section class="wrap">
298
+ <h2 id="falsifiability">The test of a good recipe</h2>
299
+
300
+ <div class="doctrine">
301
+ <p>
302
+ <span class="k">A good recipe fails if the change it proves is reverted.</span> That is the whole
303
+ quality bar.
304
+ </p>
305
+ </div>
306
+
307
+ <p>
308
+ A sharper question than "does it pass?", because passing is easy and uninformative. A recipe that
309
+ navigates to a screen, screenshots it, and ends will pass forever — including after someone deletes
310
+ the feature. It proves the app still starts.
311
+ </p>
312
+
313
+ <p>
314
+ So the check is adversarial: imagine the change gone and ask which node goes red. If the answer is
315
+ "none", the recipe is decoration. Common ways this goes wrong:
316
+ </p>
317
+
318
+ <div class="table-scroll">
319
+ <table>
320
+ <thead><tr><th>Shape</th><th>Why it survives a revert</th><th>Fix</th></tr></thead>
321
+ <tbody>
322
+ <tr>
323
+ <td>Screenshot with no assertion</td>
324
+ <td>An image is evidence for a human, but nothing in the graph inspects it.</td>
325
+ <td>Add an assertion on the state the screenshot illustrates.</td>
326
+ </tr>
327
+ <tr>
328
+ <td>Presence check where a count belongs</td>
329
+ <td>Still true when the bug fires the event twice, or leaves two positions open.</td>
330
+ <td>Use <code>exact</code> / <code>expect_count</code>.</td>
331
+ </tr>
332
+ <tr>
333
+ <td>No starting-state assertion</td>
334
+ <td>Passes off pre-existing state as the effect of the action under test.</td>
335
+ <td>Assert clean before acting, as <code>perps.lifecycle</code> does.</td>
336
+ </tr>
337
+ <tr>
338
+ <td>Asserting the action's own report</td>
339
+ <td>Confirms the call returned, not that the world changed.</td>
340
+ <td>Read the state back with a separate node.</td>
341
+ </tr>
342
+ </tbody>
343
+ </table>
344
+ </div>
345
+
346
+ <p>
347
+ Cheapest way to run this test rather than reason about it: revert your change locally, run the
348
+ recipe, watch it fail, restore. A recipe you have seen fail for the right reason is one you can
349
+ trust when it passes.
350
+ </p>
351
+ </section>
352
+
353
+ <section class="wrap">
354
+ <hr class="sep">
355
+ <h2 id="author">Author one yourself</h2>
356
+ <p>
357
+ You do not have to write the JSON by hand, and mostly should not. Point the recipe-authoring skill
358
+ at an acceptance criterion and it discovers the actions, composes the graph, validates with
359
+ <code>--plan</code>, and runs it — following the rules on this page because the harness enforces
360
+ them. <a href="tutorials/v4.html">V4 walks that flow.</a>
361
+ </p>
362
+ <p>Expect one failure-and-fix loop on the way; that is the error messages doing their job.</p>
363
+
364
+ <div class="grid grid-2">
365
+ <div class="card">
366
+ <h3>Before you author</h3>
367
+ <p>Learn the vocabulary for your checkout — you are choosing from this list, not inventing.</p>
368
+ <div class="cmd"><pre><code><span class="p">$ </span>mm-harness actions --categories
369
+ <span class="p">$ </span>mm-harness actions --action assert_orders</code></pre></div>
370
+ </div>
371
+ <div class="card">
372
+ <h3>Read one that works</h3>
373
+ <p>The bundled recipes are worked examples. Start from the closest one to your problem.</p>
374
+ <div class="cmd"><pre><code><span class="p">$ </span>mm-harness run --list
375
+ <span class="p">$ </span>mm-harness run perps.lifecycle --describe</code></pre></div>
376
+ </div>
377
+ </div>
378
+
379
+ <div class="btn-row">
380
+ <a class="btn btn-primary" href="index.html#steps">Run your first recipe →</a>
381
+ <a class="btn btn-ghost" href="reviewers.html">Read the evidence it produces</a>
382
+ <a class="btn btn-ghost" href="architecture.html">Where skills fit</a>
383
+ </div>
384
+ </section>
385
+ </main>
386
+
387
+ <footer class="footer">
388
+ <div class="wrap">
389
+ <p>Internal getting-started guide for the MetaMask agentic coding workflow. Not official MetaMask product documentation.</p>
390
+ <p>Verified against mm-harness 0.26+. The annotated recipe is a bundled library recipe, abridged.</p>
391
+ </div>
392
+ </footer>
393
+
394
+ <script type="module" src="assets/progress.mjs"></script>
395
+ </body>
396
+ </html>