@deeeed/metamask-harness 0.81.0 → 0.82.1
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.
- package/CHANGELOG.md +23 -0
- package/adapters/manifest.json +51 -51
- package/adapters/{terminal → web-dapp}/cleanup.mjs +9 -9
- package/adapters/{terminal → web-dapp}/inject.mjs +14 -14
- package/adapters/{terminal → web-dapp}/launch.mjs +23 -24
- package/adapters/web-dapp/lib/blocked-hosts.mjs +24 -0
- package/adapters/{terminal → web-dapp}/lib/cdp-client.mjs +1 -1
- package/adapters/{terminal → web-dapp}/lib/display.mjs +1 -1
- package/adapters/{terminal → web-dapp}/lib/hud.mjs +1 -1
- package/adapters/web-dapp/lib/policy.mjs +79 -0
- package/adapters/{terminal → web-dapp}/lib/processes.mjs +8 -8
- package/adapters/{terminal → web-dapp}/lib/readiness.mjs +15 -25
- package/adapters/{terminal → web-dapp}/stop.mjs +5 -4
- package/adapters/{terminal → web-dapp}/verify.mjs +11 -15
- package/adapters/{terminal → web-dapp}/wallet-host.mjs +10 -11
- package/dist/adapters/extension/run.js +5 -28
- package/dist/adapters/plugin-surface.js +98 -0
- package/dist/adapters/surface.js +1 -1
- package/dist/adapters/{terminal → web-dapp}/actions.js +7 -7
- package/dist/adapters/web-dapp/console-capture.js +63 -0
- package/dist/adapters/web-dapp/doctor-checks.js +33 -0
- package/dist/adapters/{terminal → web-dapp}/launch.js +21 -22
- package/dist/adapters/web-dapp/overlay.js +21 -0
- package/dist/adapters/web-dapp/readiness.js +10 -0
- package/dist/adapters/{terminal → web-dapp}/run.js +15 -15
- package/dist/adapters/{terminal → web-dapp}/stop.js +5 -4
- package/dist/adapters/web-dapp/surface.js +149 -0
- package/dist/cli.js +3 -0
- package/dist/command-contract.js +29 -7
- package/dist/commands/check.js +5 -4
- package/dist/commands/doctor.js +19 -2
- package/dist/commands/farmslot-ready.js +1 -0
- package/dist/commands/help.js +1 -1
- package/dist/commands/launch/device.js +5 -5
- package/dist/commands/self-test.js +9 -5
- package/dist/engine.js +6 -16
- package/dist/manifest.js +48 -24
- package/dist/mm-harness-cli.js +17 -0
- package/dist/performance-observation.js +7 -13
- package/dist/runner.js +26 -7
- package/docs/cli-contract.md +5 -3
- package/library/actions/shared/console-collector.mjs +1 -1
- package/library/actions/{terminal → web-dapp}/app/assert_no_console_errors.mjs +4 -4
- package/library/actions/{terminal → web-dapp}/app/launch.mjs +7 -7
- package/library/actions/{terminal → web-dapp}/platform/hud.mjs +5 -5
- package/library/actions/{terminal → web-dapp}/platform/page.mjs +11 -11
- package/library/actions/{terminal → web-dapp}/platform/runtime.mjs +16 -12
- package/library/actions/web-dapp/ui/navigate.mjs +24 -0
- package/library/actions/web-dapp/wallet/_signature-log.mjs +32 -0
- package/library/actions/{terminal → web-dapp}/wallet/assert_signatures.mjs +4 -4
- package/library/actions/{terminal → web-dapp}/wallet/list_accounts.mjs +2 -2
- package/library/actions/{terminal → web-dapp}/wallet/read_signatures.mjs +6 -6
- package/library/manifests/{terminal.action-manifest.json → web-dapp.action-manifest.json} +18 -702
- package/package.json +5 -5
- package/adapters/terminal/lib/mainnet-guard.mjs +0 -186
- package/adapters/terminal/policy.mjs +0 -35
- package/dist/adapters/terminal/console-capture.js +0 -59
- package/dist/adapters/terminal/doctor-checks.js +0 -24
- package/dist/adapters/terminal/overlay.js +0 -25
- package/dist/adapters/terminal/readiness.js +0 -11
- package/dist/adapters/terminal/surface.js +0 -135
- package/library/actions/terminal/perps/_venue.mjs +0 -242
- package/library/actions/terminal/perps/assert_orders.mjs +0 -54
- package/library/actions/terminal/perps/assert_positions.mjs +0 -40
- package/library/actions/terminal/perps/read_orders.mjs +0 -26
- package/library/actions/terminal/perps/read_positions.mjs +0 -25
- package/library/actions/terminal/perps/read_snapshot.mjs +0 -40
- package/library/actions/terminal/perps/revoke_agent.mjs +0 -58
- package/library/actions/terminal/perps/teardown_state.mjs +0 -104
- package/library/actions/terminal/ui/navigate.mjs +0 -31
- package/library/actions/terminal/wallet/_signature-log.mjs +0 -56
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.82.1 - 2026-10-07
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Extension `check diff [--fix]` no longer fails when every changed file is one the repo's oxfmt config ignores (for example `package.json`, `yarn.lock` and regenerated LavaMoat policy JSON): oxfmt runs with `--no-error-on-unmatched-pattern`, so it checks nothing instead of exiting 2 with "Expected at least one target file". A real formatting problem in a file oxfmt does check still fails the gate.
|
|
10
|
+
|
|
11
|
+
## 0.82.0 - 2026-10-07
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- Adapters a recipe library declares in `recipe-library.json` `adapters` (on `--library`, the `RECIPE_LIBRARY_PATH` you set or else the personal library, or `config set libraries.<name>`; never a task-local library, a sibling checkout or `<target>/.skills-cache`, which serve recipe discovery only) work as `--adapter <id>`: the command loads the plugin only when it selects it (the last `--adapter`, else the last `--platform`), composes it on the adapter it `extends` (a built-in or another plugin; a plugin that extends a built-in also offers the built-in's official actions, library actions and observers), runs its doctor checks after the shared ones (a check that throws is a required failure, `adapter-plugin:<id>:doctor`) and runs its in-code actions. `call <action> --adapter <id> --help` shows a plugin action's fields. `doctor --json` names each loaded plugin with its library and digest (`adapter-plugin:<id>`), and an approved plan (`--approve-plan`) binds that digest: every run on a plugin adapter carries it in `METAMASK_RECIPE_ADAPTER_PLUGIN`, so editing the plugin's module or a file beside it fails the old approval with `RECIPE_APPROVAL_MISMATCH`, whatever actions the recipe runs. A plugin's own manifest may declare official actions and observers for that plugin, but a plugin that extends it inherits only its custom actions: inherited official actions and observers come from the bundled library alone, and a parent manifest outside it that declares one fails with `ACTION_MANIFEST_INVALID` (a JSON envelope under `--json`), like any other action manifest the plugin can't use. A malformed `RECIPE_LIBRARY_PATH` never blocks a built-in adapter. A plugin may import from disk only its module's directory and the library's `actions/` (`RECIPE_SOURCE_INVALID` otherwise); a bare package it imports resolves from mm-harness's own install. Plugins need Node.js 22.15 or later. A refused plugin fails with `ADAPTER_SDK_UNSUPPORTED`, `ADAPTER_PLUGIN_INVALID`, `ADAPTER_PLUGIN_LOAD_FAILED`, `ADAPTER_ID_CONFLICT`, `ADAPTER_EXTENDS_UNKNOWN` or `RECIPE_SOURCE_INVALID`, exit 2. A plugin that extends nothing (or only other plugins) gets no Runway provisioning and no wallet fixture operations; one that extends a built-in keeps the built-in's (the terminal plugin keeps web-dapp's `fixtures init|sync|set`).
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- **BREAKING:** the built-in `terminal` adapter becomes the generic `web-dapp` built-in, and the Web Terminal becomes the `terminal` plugin of the `experimental-metamask-recipe-terminal` library (`recipe-library.json` `adapters.terminal`, `extends: web-dapp`). `--adapter terminal` needs a revision of that library that declares `adapters.terminal` (8b409b6 or later, on its `feat/console-allowlist` branch; the library's `main` does not declare it yet) on `RECIPE_LIBRARY_PATH`, as on the Terminal farm slots. A host without it (an older checkout, or none, as on macpro and mini today) refuses every `--adapter terminal` command: `--adapter must be mobile, extension, core, or web-dapp; received 'terminal'`. Update the library before this harness; the hooks, runtime dir (`temp/recipe/runtime/terminal`), doctor check ids, evidence names and action ids are unchanged.
|
|
20
|
+
- The plugin holds everything Hyperliquid: the venue policy (blocked and served hosts, link hosts, the launch probe, the testnet typed-data refusal, the start page and product routes, the injected wallet's start chain, the checkout dependencies and detection, the dev-server testnet variable), the signature-log rules, and the 7 `metamask.perps.*` venue actions with their manifest. They leave this package: `adapters/terminal/lib/mainnet-guard.mjs`, `adapters/terminal/policy.mjs`, `library/actions/terminal/perps/`.
|
|
21
|
+
- `web-dapp` keeps the generic parts (`adapters/web-dapp/`, `library/actions/web-dapp/`, `library/manifests/web-dapp.action-manifest.json`): launch, stop, verify, inject, cleanup, the wallet host, console capture, `ui.*`, `app.hud`, `metamask.app.*` and `metamask.wallet.*`. It reads the venue policy from the module in `RECIPE_WEB_DAPP_POLICY`, which the harness sets when it adopts an adapter that extends `web-dapp`; the leaf scripts no longer take `--policy-module`.
|
|
22
|
+
- `web-dapp` selected on its own has no venue: launch, verify and runtime status refuse, and doctor fails its required `web-dapp-venue-policy` check. The policy module is checked when it loads (every member web-dapp reads, and `policyVersion: 1`).
|
|
23
|
+
- Without `--adapter`, a Terminal checkout is no longer auto-detected, and `actions --matrix` has no `terminal` column: the loader imports a plugin only when a command selects it (the ADR trust boundary), so the plugin's detection runs once `--adapter terminal` selects it. Every farm hook passes `--adapter terminal`.
|
|
24
|
+
- `check diff` takes any registered or library-declared adapter for `--adapter`/`--platform` (it listed the built-ins).
|
|
25
|
+
- Depend on `@farmslot/recipe-cli` 0.6.0 and `@farmslot/adapter-sdk` 0.5.0 (were 0.5.0 and 0.4.0), which carry recipe-library adapter plugins, adapter-aware trusted-mutation hooks and one environment scope for a run's ports (recipe-cli 0.6.0 also writes the task's acceptance ledger from a recipe's proof targets and warns at `run --plan` on command nodes whose exit code cannot prove the claim), and on `@farmslot/recipe-runner` 0.25.0 and `@farmslot/adapter-rn` 0.17.1 (were 0.24.0 and 0.17.0). `@farmslot/adapter-web` 0.3.1, `@farmslot/agent-runtime` 0.17.0 and `@farmslot/protocol` 0.34.0 are unchanged, and the install still holds one `@farmslot/recipe-runner`.
|
|
26
|
+
- The Extension proof-runtime check no longer pins `--cdp-port`/`--watcher-port` itself: `run` and `call` run it, the observers and the execution in one environment scope, so every platform's runtime check and observers see the run's ports, its dev-server port names (`METRO_PORT`) and its platform environment. The performance observers read their ports from that environment (`PerformanceObserver.backend(target, env)`), and `self-test` opens the scope around its smoke runs.
|
|
27
|
+
|
|
5
28
|
## 0.81.0 - 2026-10-07
|
|
6
29
|
|
|
7
30
|
### Added
|
package/adapters/manifest.json
CHANGED
|
@@ -584,115 +584,115 @@
|
|
|
584
584
|
"outputs": "numbered checkouts, saved preferences, and run summary"
|
|
585
585
|
},
|
|
586
586
|
{
|
|
587
|
-
"id": "
|
|
588
|
-
"entry": "adapters/
|
|
587
|
+
"id": "web-dapp/launch",
|
|
588
|
+
"entry": "adapters/web-dapp/launch.mjs",
|
|
589
589
|
"kind": "node",
|
|
590
|
-
"purpose": "Start or reuse the
|
|
591
|
-
"inputs": "--target --cdp-port --app-port
|
|
592
|
-
"outputs": "<runtime>/
|
|
590
|
+
"purpose": "Start or reuse the web-dapp slot browser (Chrome for Testing unless TERMINAL_CHROME_BIN; MetaMask extension with the fixture imported, or the injected strict wallet) and its wallet host. On macOS the browser starts through LaunchServices with no startup window; in testnet mode the hosts the venue policy blocks are unresolvable for the whole browser.",
|
|
591
|
+
"inputs": "--target --cdp-port --app-port [--signer extension|injected] [--account] [--fresh-profile] [--headless|--headful] [--slow-mo] [--network mainnet --mainnet-confirmation REAL_FUNDS] [--json]; env TERMINAL_CHROME_BIN, TERMINAL_EXTENSION_DIR, TERMINAL_EXTENSION_CHECKOUT, RECIPE_WALLET_FIXTURE, TERMINAL_HEADLESS, TERMINAL_SLOW_MO, TERMINAL_WINDOW, TERMINAL_SCREEN",
|
|
592
|
+
"outputs": "<runtime>/<adapter id>/browser.json + pid/log files; JSON on stdout; exit 0/1/2"
|
|
593
593
|
},
|
|
594
594
|
{
|
|
595
|
-
"id": "
|
|
596
|
-
"entry": "adapters/
|
|
595
|
+
"id": "web-dapp/wallet-host",
|
|
596
|
+
"entry": "adapters/web-dapp/wallet-host.mjs",
|
|
597
597
|
"kind": "node",
|
|
598
|
-
"purpose": "Long-running CDP observer: opens the app tab blank, hooks it once per target, navigates it to the app and verifies the log binding before ready; logs every wallet request from the app top frame and every MetaMask confirmation shown; answers the injected strict wallet in injected mode (other frames and blank popups get 4100). The page script, the frame judgement and the strict wallet are @farmslot/adapter-web/dapp's; the
|
|
599
|
-
"inputs": "--cdp-port --runtime-dir --signer --app-origin
|
|
600
|
-
"outputs": "<runtime>/
|
|
598
|
+
"purpose": "Long-running CDP observer: opens the app tab blank, hooks it once per target, navigates it to the app and verifies the log binding before ready; logs every wallet request from the app top frame and every MetaMask confirmation shown; answers the injected strict wallet in injected mode (other frames and blank popups get 4100). The page script, the frame judgement and the strict wallet are @farmslot/adapter-web/dapp's; the venue policy (RECIPE_WEB_DAPP_POLICY) gives the testnet refusal, hosts, probe and start page.",
|
|
599
|
+
"inputs": "--cdp-port --runtime-dir --signer --app-origin [--extension-id] [--target --account --start-chain] [--browser-app <.app>]",
|
|
600
|
+
"outputs": "<runtime>/<adapter id>/wallet-requests.jsonl, wallet-host.ready"
|
|
601
601
|
},
|
|
602
602
|
{
|
|
603
|
-
"id": "
|
|
604
|
-
"entry": "adapters/
|
|
603
|
+
"id": "web-dapp/stop",
|
|
604
|
+
"entry": "adapters/web-dapp/stop.mjs",
|
|
605
605
|
"kind": "node",
|
|
606
|
-
"purpose": "Stop the
|
|
606
|
+
"purpose": "Stop the web-dapp slot browser, wallet host and console collector and prove the CDP port is free.",
|
|
607
607
|
"inputs": "--target [--cdp-port]",
|
|
608
608
|
"outputs": "JSON on stdout; exit 0/1"
|
|
609
609
|
},
|
|
610
610
|
{
|
|
611
|
-
"id": "
|
|
612
|
-
"entry": "adapters/
|
|
611
|
+
"id": "web-dapp/inject",
|
|
612
|
+
"entry": "adapters/web-dapp/inject.mjs",
|
|
613
613
|
"kind": "node",
|
|
614
|
-
"purpose": "
|
|
614
|
+
"purpose": "web-dapp install: overlay marker, runtime dir and wallet fixture (0600); refuses a checkout the venue policy does not match; no product writes.",
|
|
615
615
|
"inputs": "--target [--fixture] [--force]; env RECIPE_WALLET_FIXTURE",
|
|
616
|
-
"outputs": "<harness>/
|
|
616
|
+
"outputs": "<harness>/<adapter id>/install.json, <runtime>/wallet-fixture.json; exit 0/1"
|
|
617
617
|
},
|
|
618
618
|
{
|
|
619
|
-
"id": "
|
|
620
|
-
"entry": "adapters/
|
|
619
|
+
"id": "web-dapp/cleanup",
|
|
620
|
+
"entry": "adapters/web-dapp/cleanup.mjs",
|
|
621
621
|
"kind": "node",
|
|
622
|
-
"purpose": "
|
|
622
|
+
"purpose": "web-dapp cleanup: stop browser/host, remove the slot's runtime state, overlay marker and installed fixture; profiles kept unless --reset-profile.",
|
|
623
623
|
"inputs": "--target [--cdp-port] [--reset-profile]",
|
|
624
624
|
"outputs": "JSON on stdout; exit 0/1"
|
|
625
625
|
},
|
|
626
626
|
{
|
|
627
|
-
"id": "
|
|
628
|
-
"entry": "adapters/
|
|
627
|
+
"id": "web-dapp/verify",
|
|
628
|
+
"entry": "adapters/web-dapp/verify.mjs",
|
|
629
629
|
"kind": "node",
|
|
630
|
-
"purpose": "Read-only
|
|
631
|
-
"inputs": "--
|
|
630
|
+
"purpose": "Read-only web-dapp slot readiness: checkout, deps, dev server, forced testnet, fixture, browser start probe, extension copy, CDP port.",
|
|
631
|
+
"inputs": "--target [--cdp-port] [--watcher-port] [--signer] [--account] [--json]",
|
|
632
632
|
"outputs": "readiness report; exit 0 ready / 1 not ready"
|
|
633
633
|
},
|
|
634
634
|
{
|
|
635
|
-
"id": "
|
|
636
|
-
"entry": "adapters/
|
|
635
|
+
"id": "web-dapp/lib-cdp-client",
|
|
636
|
+
"entry": "adapters/web-dapp/lib/cdp-client.mjs",
|
|
637
637
|
"kind": "lib",
|
|
638
638
|
"purpose": "Browser-level CDP client with flattened target sessions for the wallet host.",
|
|
639
639
|
"inputs": "CdpClient.connect(url)",
|
|
640
640
|
"outputs": "send/on"
|
|
641
641
|
},
|
|
642
642
|
{
|
|
643
|
-
"id": "
|
|
644
|
-
"entry": "adapters/
|
|
643
|
+
"id": "web-dapp/lib-processes",
|
|
644
|
+
"entry": "adapters/web-dapp/lib/processes.mjs",
|
|
645
645
|
"kind": "lib",
|
|
646
646
|
"purpose": "Pid-file process stop with owner-marker checks (--mm-harness-owner=<kind>-<id>, exact token; live foreign pids kept and reported, dead ones dropped), process-group termination, CDP port release check.",
|
|
647
|
-
"inputs": "
|
|
647
|
+
"inputs": "stopWebDappBrowser(target, { cdpPort }), stopConsoleCollector(target), ownerMarker(kind, runtimeDir), ownsProcess(kind, pid, runtimeDir)",
|
|
648
648
|
"outputs": "stopped processes"
|
|
649
649
|
},
|
|
650
650
|
{
|
|
651
|
-
"id": "
|
|
652
|
-
"entry": "adapters/
|
|
651
|
+
"id": "web-dapp/lib-readiness",
|
|
652
|
+
"entry": "adapters/web-dapp/lib/readiness.mjs",
|
|
653
653
|
"kind": "lib",
|
|
654
|
-
"purpose": "
|
|
655
|
-
"inputs": "
|
|
654
|
+
"purpose": "web-dapp slot readiness checks shared by verify, doctor and the surface.",
|
|
655
|
+
"inputs": "webDappReadiness({ policy, target, appPort, cdpPort, signer }), assertLaunchNetwork(target, { network, mainnetConfirmation }), devServerTestnetDiagnostic(target, { appPort, variable }) (advisory)",
|
|
656
656
|
"outputs": "checks[]"
|
|
657
657
|
},
|
|
658
658
|
{
|
|
659
|
-
"id": "
|
|
660
|
-
"entry": "adapters/
|
|
659
|
+
"id": "web-dapp/lib-display",
|
|
660
|
+
"entry": "adapters/web-dapp/lib/display.mjs",
|
|
661
661
|
"kind": "lib",
|
|
662
662
|
"purpose": "Headless/headful and slow-mo resolution, per-slot window placement and the profile zoom that keeps the desktop layout.",
|
|
663
663
|
"inputs": "resolveHeadless, resolveSlowMo, windowPlacement, writeProfileZoom",
|
|
664
664
|
"outputs": "browser display args"
|
|
665
665
|
},
|
|
666
666
|
{
|
|
667
|
-
"id": "
|
|
668
|
-
"entry": "adapters/
|
|
667
|
+
"id": "web-dapp/lib-hud",
|
|
668
|
+
"entry": "adapters/web-dapp/lib/hud.mjs",
|
|
669
669
|
"kind": "lib",
|
|
670
670
|
"purpose": "Recipe HUD state (<runtime>/hud.json) and the page expression that draws it on the app origin only.",
|
|
671
671
|
"inputs": "nextHudState, hudRenderExpression",
|
|
672
672
|
"outputs": "HUD state, expression source"
|
|
673
673
|
},
|
|
674
674
|
{
|
|
675
|
-
"id": "
|
|
676
|
-
"entry": "adapters/
|
|
675
|
+
"id": "web-dapp/lib-blocked-hosts",
|
|
676
|
+
"entry": "adapters/web-dapp/lib/blocked-hosts.mjs",
|
|
677
677
|
"kind": "lib",
|
|
678
|
-
"purpose": "Testnet enforcement: the
|
|
679
|
-
"inputs": "
|
|
680
|
-
"outputs": "
|
|
678
|
+
"purpose": "Testnet enforcement mechanics: the host-resolver rule that makes the policy's blocked hosts unresolvable, and the canonical host check the wallet host's request block uses.",
|
|
679
|
+
"inputs": "hostResolverRules(hosts), hostOf(url), isBlockedUrl(url, hosts)",
|
|
680
|
+
"outputs": "launch switch, booleans"
|
|
681
681
|
},
|
|
682
682
|
{
|
|
683
|
-
"id": "
|
|
684
|
-
"entry": "adapters/
|
|
683
|
+
"id": "web-dapp/lib-policy",
|
|
684
|
+
"entry": "adapters/web-dapp/lib/policy.mjs",
|
|
685
685
|
"kind": "lib",
|
|
686
|
-
"purpose": "The
|
|
687
|
-
"inputs": "
|
|
688
|
-
"outputs": "
|
|
686
|
+
"purpose": "The venue policy of the adapter that extends web-dapp (e.g. the Terminal plugin's), loaded from the module RECIPE_WEB_DAPP_POLICY names; the host sets it when it binds that adapter.",
|
|
687
|
+
"inputs": "env RECIPE_WEB_DAPP_POLICY",
|
|
688
|
+
"outputs": "webDappPolicy(), webDappAdapterId()"
|
|
689
689
|
},
|
|
690
690
|
{
|
|
691
|
-
"id": "
|
|
692
|
-
"entry": "src/adapters/
|
|
691
|
+
"id": "web-dapp/surface",
|
|
692
|
+
"entry": "src/adapters/web-dapp/surface.ts",
|
|
693
693
|
"kind": "module",
|
|
694
|
-
"purpose": "
|
|
695
|
-
"inputs": "
|
|
694
|
+
"purpose": "The web-dapp adapter surface (runtime status, browser stop, logs, hints) and bindWebDappPolicy, which binds the venue policy of the adapter that extends it.",
|
|
695
|
+
"inputs": "webDappSurface",
|
|
696
696
|
"outputs": "AdapterSurface"
|
|
697
697
|
}
|
|
698
698
|
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// cleanup.mjs — `mm-harness cleanup --adapter
|
|
3
|
-
// and wallet host, then remove the
|
|
2
|
+
// cleanup.mjs — `mm-harness cleanup --adapter <web-dapp adapter>`: stop the slot
|
|
3
|
+
// browser and wallet host, then remove the slot's runtime state and the installed
|
|
4
4
|
// wallet fixture. Browser profiles are kept unless --reset-profile.
|
|
5
5
|
//
|
|
6
6
|
// Inputs: --target <checkout> [--cdp-port <port>] [--reset-profile]
|
|
@@ -13,16 +13,16 @@ import { fileURLToPath } from 'node:url';
|
|
|
13
13
|
|
|
14
14
|
import { recipeHarnessPath } from '@farmslot/recipe-cli/harness';
|
|
15
15
|
import { walletFixturePath } from '../../library/actions/harness-exports.mjs';
|
|
16
|
-
import {
|
|
17
|
-
import { stopConsoleCollector,
|
|
16
|
+
import { webDappPolicy, webDappRuntimeDir } from '../../library/actions/web-dapp/platform/runtime.mjs';
|
|
17
|
+
import { stopConsoleCollector, stopWebDappBrowser } from './lib/processes.mjs';
|
|
18
18
|
|
|
19
19
|
const usage = 'Usage: cleanup.mjs --target <checkout> [--cdp-port <port>] [--reset-profile]';
|
|
20
20
|
|
|
21
|
-
export async function
|
|
21
|
+
export async function cleanupWebDappRuntime({ target, cdpPort, resetProfile = false }) {
|
|
22
22
|
const root = path.resolve(target);
|
|
23
|
-
const stop = await
|
|
23
|
+
const stop = await stopWebDappBrowser(root, { cdpPort });
|
|
24
24
|
await stopConsoleCollector(root);
|
|
25
|
-
const runtime =
|
|
25
|
+
const runtime = webDappRuntimeDir(root);
|
|
26
26
|
const removed = [];
|
|
27
27
|
if (existsSync(runtime)) {
|
|
28
28
|
for (const entry of readdirSync(runtime)) {
|
|
@@ -31,7 +31,7 @@ export async function cleanupTerminalRuntime({ target, cdpPort, resetProfile = f
|
|
|
31
31
|
removed.push(entry);
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
-
const overlay = recipeHarnessPath(root,
|
|
34
|
+
const overlay = recipeHarnessPath(root, webDappPolicy().adapterId);
|
|
35
35
|
if (existsSync(overlay)) {
|
|
36
36
|
rmSync(overlay, { recursive: true, force: true });
|
|
37
37
|
removed.push(path.relative(root, overlay));
|
|
@@ -56,7 +56,7 @@ if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.me
|
|
|
56
56
|
};
|
|
57
57
|
const rawPort = value('--cdp-port') ?? process.env.RECIPE_CDP_PORT ?? process.env.CDP_PORT;
|
|
58
58
|
try {
|
|
59
|
-
const result = await
|
|
59
|
+
const result = await cleanupWebDappRuntime({
|
|
60
60
|
target: value('--target') ?? process.cwd(),
|
|
61
61
|
cdpPort: rawPort ? Number(rawPort) : undefined,
|
|
62
62
|
resetProfile: argv.includes('--reset-profile'),
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// inject.mjs — `mm-harness install --adapter
|
|
3
|
-
// directory and install the wallet fixture the
|
|
2
|
+
// inject.mjs — `mm-harness install --adapter <web-dapp adapter>`: prepare the slot
|
|
3
|
+
// runtime directory and install the wallet fixture the web-dapp actions read.
|
|
4
4
|
//
|
|
5
|
-
// Inputs: --target <
|
|
5
|
+
// Inputs: --target <checkout> [--fixture <wallet-fixture.json>] [--force]
|
|
6
6
|
// env RECIPE_WALLET_FIXTURE (fixture source when --fixture is absent)
|
|
7
|
-
// Outputs: <target>/<harness root>/
|
|
8
|
-
// <target>/<runtime>/
|
|
7
|
+
// Outputs: <target>/<harness root>/<adapter id>/install.json (overlay marker),
|
|
8
|
+
// <target>/<runtime>/<adapter id>/ (0700), <runtime>/wallet-fixture.json (0600);
|
|
9
9
|
// JSON summary on stdout (account names only). Exit 0 installed; 1 failure; 2 usage.
|
|
10
10
|
// Never touches: product source files; never prints key material.
|
|
11
11
|
|
|
@@ -15,16 +15,16 @@ import { fileURLToPath } from 'node:url';
|
|
|
15
15
|
|
|
16
16
|
import { recipeHarnessPath, recipeRuntimeDir } from '@farmslot/recipe-cli/harness';
|
|
17
17
|
import { walletFixturePath } from '../../library/actions/harness-exports.mjs';
|
|
18
|
-
import {
|
|
19
|
-
import { isTerminalCheckout } from './lib/readiness.mjs';
|
|
18
|
+
import { webDappPolicy, webDappRuntimeDir } from '../../library/actions/web-dapp/platform/runtime.mjs';
|
|
20
19
|
|
|
21
|
-
const usage = 'Usage: inject.mjs --target <
|
|
20
|
+
const usage = 'Usage: inject.mjs --target <checkout> [--fixture <wallet-fixture.json>] [--force]';
|
|
22
21
|
|
|
23
|
-
export function
|
|
22
|
+
export function installWebDappRuntime({ target, fixture, force = false }) {
|
|
24
23
|
const root = path.resolve(target);
|
|
25
|
-
|
|
24
|
+
const { adapterId, checkout } = webDappPolicy();
|
|
25
|
+
if (!checkout.matches(root)) throw new Error(`${root} is not a ${checkout.name} checkout (needs ${checkout.needs}).`);
|
|
26
26
|
mkdirSync(path.join(root, recipeRuntimeDir()), { recursive: true, mode: 0o700 });
|
|
27
|
-
mkdirSync(
|
|
27
|
+
mkdirSync(webDappRuntimeDir(root), { recursive: true, mode: 0o700 });
|
|
28
28
|
const destination = walletFixturePath(root);
|
|
29
29
|
let fixtureAction = 'kept';
|
|
30
30
|
if (fixture && (force || !existsSync(destination))) {
|
|
@@ -39,9 +39,9 @@ export function installTerminalRuntime({ target, fixture, force = false }) {
|
|
|
39
39
|
const accounts = existsSync(destination)
|
|
40
40
|
? JSON.parse(readFileSync(destination, 'utf8')).accounts.map((account) => account?.name).filter(Boolean)
|
|
41
41
|
: [];
|
|
42
|
-
const marker = { schemaVersion: 1, adapter:
|
|
42
|
+
const marker = { schemaVersion: 1, adapter: adapterId, installedAt: new Date().toISOString(), fixture: fixtureAction };
|
|
43
43
|
// The overlay directory is the install marker run/call auto-ensure checks.
|
|
44
|
-
const overlay = recipeHarnessPath(root,
|
|
44
|
+
const overlay = recipeHarnessPath(root, adapterId);
|
|
45
45
|
mkdirSync(overlay, { recursive: true, mode: 0o700 });
|
|
46
46
|
writeFileSync(path.join(overlay, 'install.json'), `${JSON.stringify(marker, null, 2)}\n`, { mode: 0o600 });
|
|
47
47
|
return { status: fixtureAction === 'missing' ? 'fail' : 'pass', target: root, fixture: fixtureAction, fixturePath: destination, accounts };
|
|
@@ -58,7 +58,7 @@ if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.me
|
|
|
58
58
|
return index >= 0 ? argv[index + 1] : undefined;
|
|
59
59
|
};
|
|
60
60
|
try {
|
|
61
|
-
const result =
|
|
61
|
+
const result = installWebDappRuntime({
|
|
62
62
|
target: value('--target') ?? process.cwd(),
|
|
63
63
|
fixture: value('--fixture') ?? process.env.RECIPE_WALLET_FIXTURE,
|
|
64
64
|
force: argv.includes('--force'),
|
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// launch.mjs — start (or reuse) the
|
|
2
|
+
// launch.mjs — start (or reuse) the web-dapp slot browser and its wallet host.
|
|
3
3
|
//
|
|
4
4
|
// Purpose:
|
|
5
5
|
// One Chromium-family browser per slot, bound to 127.0.0.1:<cdp-port>, with a
|
|
6
|
-
// profile under <runtime
|
|
6
|
+
// profile under <runtime>/<adapter id>/. signer=extension loads the slot's copy of
|
|
7
7
|
// the MetaMask extension and imports the fixture account through the Extension
|
|
8
8
|
// adapter's fixture flow (wallet-fixture-state.cjs generate → prefill-profile →
|
|
9
9
|
// seed-cdp). signer=injected loads no extension; the wallet host provides the
|
|
10
10
|
// strict test wallet. Either way wallet-host.mjs then records wallet requests.
|
|
11
11
|
//
|
|
12
|
-
// Inputs (flags): --target <
|
|
12
|
+
// Inputs (flags): --target <checkout> --cdp-port <port> --app-port <port>
|
|
13
13
|
// [--signer extension|injected] [--account <fixture name>] [--fresh-profile]
|
|
14
14
|
// [--headless | --headful] [--slow-mo <ms>] [--start-chain <id>] [--json]
|
|
15
|
-
// [--network mainnet --mainnet-confirmation REAL_FUNDS] (
|
|
16
|
-
//
|
|
15
|
+
// [--network mainnet --mainnet-confirmation REAL_FUNDS] (testnet otherwise,
|
|
16
|
+
// enforced in the browser from the venue policy)
|
|
17
17
|
// env: TERMINAL_CHROME_BIN, TERMINAL_EXTENSION_DIR,
|
|
18
18
|
// TERMINAL_EXTENSION_CHECKOUT, TERMINAL_EXTENSION_NODE, RECIPE_WALLET_FIXTURE,
|
|
19
19
|
// TERMINAL_HEADLESS, TERMINAL_SLOW_MO, TERMINAL_WINDOW, TERMINAL_SCREEN
|
|
20
20
|
// The browser is headful by default, in a window placed per slot (see
|
|
21
21
|
// lib/display.mjs); --headless or TERMINAL_HEADLESS=1 hides it.
|
|
22
|
-
// Outputs: <runtime>/
|
|
22
|
+
// Outputs: <runtime>/<adapter id>/browser.json (+ pid files, logs); JSON summary on stdout.
|
|
23
23
|
// Exit 0 ready; 1 launch/seed failure; 2 usage.
|
|
24
24
|
// Never prints: mnemonics, private keys, the wallet password.
|
|
25
25
|
|
|
@@ -29,20 +29,21 @@ import { createRequire } from 'node:module';
|
|
|
29
29
|
import { appendFileSync, chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
30
30
|
import os from 'node:os';
|
|
31
31
|
import path from 'node:path';
|
|
32
|
-
import { fileURLToPath
|
|
32
|
+
import { fileURLToPath } from 'node:url';
|
|
33
33
|
|
|
34
34
|
import {
|
|
35
35
|
fixtureEntry,
|
|
36
36
|
loadWalletFixture,
|
|
37
37
|
resolveSigner,
|
|
38
|
-
terminalRuntimeDir,
|
|
39
38
|
viemAccountFromEntry,
|
|
40
|
-
|
|
39
|
+
webDappPolicy,
|
|
40
|
+
webDappRuntimeDir,
|
|
41
|
+
} from '../../library/actions/web-dapp/platform/runtime.mjs';
|
|
41
42
|
import { CdpClient, browserWebSocketUrl } from './lib/cdp-client.mjs';
|
|
42
43
|
import { clearHudState } from './lib/hud.mjs';
|
|
43
44
|
import { browserDisplayArgs, resolveHeadless, resolveSlowMo, windowPlacement, writeProfileZoom } from './lib/display.mjs';
|
|
44
|
-
import { cdpListenerPids, ownedBrowserPids, ownerMarker, ownsProcess, pidAlive,
|
|
45
|
-
import { hostResolverRules } from './lib/
|
|
45
|
+
import { cdpListenerPids, ownedBrowserPids, ownerMarker, ownsProcess, pidAlive, stopWebDappBrowser, terminate } from './lib/processes.mjs';
|
|
46
|
+
import { hostResolverRules } from './lib/blocked-hosts.mjs';
|
|
46
47
|
import { assertLaunchNetwork, devServerTestnetDiagnostic } from './lib/readiness.mjs';
|
|
47
48
|
|
|
48
49
|
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -51,7 +52,7 @@ const HARNESS_ROOT = path.resolve(SCRIPT_DIR, '../..');
|
|
|
51
52
|
const FIXTURE_STATE_SCRIPT = path.join(HARNESS_ROOT, 'adapters/extension/wallet-fixture-state.cjs');
|
|
52
53
|
|
|
53
54
|
function usage() {
|
|
54
|
-
return 'Usage: launch.mjs --target <checkout> --cdp-port <port> --app-port <port>
|
|
55
|
+
return 'Usage: launch.mjs --target <checkout> --cdp-port <port> --app-port <port> [--signer extension|injected] [--account <name>] [--fresh-profile] [--headless | --headful] [--slow-mo <ms>] [--network testnet|mainnet --mainnet-confirmation REAL_FUNDS] [--start-chain <id>] [--json]';
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
export function parseLaunchArgs(argv) {
|
|
@@ -67,7 +68,7 @@ export function parseLaunchArgs(argv) {
|
|
|
67
68
|
args[flag.slice(2)] = argv[index + 1];
|
|
68
69
|
index += 1;
|
|
69
70
|
}
|
|
70
|
-
for (const key of ['target', 'cdp-port', 'app-port'
|
|
71
|
+
for (const key of ['target', 'cdp-port', 'app-port']) {
|
|
71
72
|
if (!args[key]) throw Object.assign(new Error(`Missing --${key}\n${usage()}`), { exitCode: 2 });
|
|
72
73
|
}
|
|
73
74
|
args.signer = resolveSigner(args.signer);
|
|
@@ -76,7 +77,7 @@ export function parseLaunchArgs(argv) {
|
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
function log(message) {
|
|
79
|
-
process.stderr.write(`[
|
|
80
|
+
process.stderr.write(`[${webDappPolicy().adapterId}/launch] ${message}\n`);
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
function browserStarts(bin) {
|
|
@@ -113,7 +114,7 @@ export async function resolveBrowser(env = process.env, candidates = chromeForTe
|
|
|
113
114
|
if (!existsSync(explicit)) throw new Error(`TERMINAL_CHROME_BIN does not exist: ${explicit}`);
|
|
114
115
|
return { bin: explicit, version: browserStarts(explicit), source: 'TERMINAL_CHROME_BIN' };
|
|
115
116
|
}
|
|
116
|
-
if (env.RECIPE_HARNESS_CHROME_BIN) log(`ignoring RECIPE_HARNESS_CHROME_BIN (${env.RECIPE_HARNESS_CHROME_BIN}) for the
|
|
117
|
+
if (env.RECIPE_HARNESS_CHROME_BIN) log(`ignoring RECIPE_HARNESS_CHROME_BIN (${env.RECIPE_HARNESS_CHROME_BIN}) for the ${webDappPolicy().adapterId}; set TERMINAL_CHROME_BIN to override Chrome for Testing`);
|
|
117
118
|
const bin = candidates.find((candidate) => existsSync(candidate));
|
|
118
119
|
if (bin) return { bin, version: browserStarts(bin), source: 'chrome-for-testing' };
|
|
119
120
|
throw new Error(
|
|
@@ -344,9 +345,9 @@ function writePrivateFile(file, text) {
|
|
|
344
345
|
// which waits for a cold dev-server compile.
|
|
345
346
|
export const LAUNCH_TIMEOUTS = Object.freeze({ browserStartMs: 90000, hostReadyMs: 180000 });
|
|
346
347
|
|
|
347
|
-
//
|
|
348
|
-
export async function
|
|
349
|
-
|
|
348
|
+
// The venue policy comes from RECIPE_WEB_DAPP_POLICY (webDappPolicy), as for every web-dapp leaf.
|
|
349
|
+
export async function launchWebDappBrowser(args, env = process.env, { timeouts = LAUNCH_TIMEOUTS } = {}) {
|
|
350
|
+
const policy = webDappPolicy(env);
|
|
350
351
|
const target = path.resolve(args.target);
|
|
351
352
|
const cdpPort = Number(args['cdp-port']);
|
|
352
353
|
const appPort = Number(args['app-port']);
|
|
@@ -364,7 +365,7 @@ export async function launchTerminalBrowser(args, env = process.env, { timeouts
|
|
|
364
365
|
if (devServer && !(devServer.known && devServer.forced)) {
|
|
365
366
|
log(`WARN dev server testnet check: ${devServer.detail} (advisory; the browser enforces testnet itself)`);
|
|
366
367
|
}
|
|
367
|
-
const runtime =
|
|
368
|
+
const runtime = webDappRuntimeDir(target);
|
|
368
369
|
mkdirSync(runtime, { recursive: true, mode: 0o700 });
|
|
369
370
|
chmodSync(runtime, 0o700);
|
|
370
371
|
const stateFile = path.join(runtime, 'browser.json');
|
|
@@ -394,7 +395,7 @@ export async function launchTerminalBrowser(args, env = process.env, { timeouts
|
|
|
394
395
|
if (previous.slowMoMs !== slowMoMs) writePrivateFile(stateFile, `${JSON.stringify(state, null, 2)}\n`);
|
|
395
396
|
return { ...state, reused: true };
|
|
396
397
|
}
|
|
397
|
-
await
|
|
398
|
+
await stopWebDappBrowser(target, { cdpPort, log });
|
|
398
399
|
|
|
399
400
|
const browser = await resolveBrowser(env);
|
|
400
401
|
const profile = path.join(runtime, `profile-${signer}-${args.account.replace(/[^A-Za-z0-9_-]/gu, '_')}`);
|
|
@@ -545,7 +546,6 @@ export async function launchTerminalBrowser(args, env = process.env, { timeouts
|
|
|
545
546
|
'--account', args.account,
|
|
546
547
|
'--start-chain', String(args['start-chain'] ?? policy.startChain),
|
|
547
548
|
'--network', network,
|
|
548
|
-
'--policy-module', policy.module,
|
|
549
549
|
ownerMarker('wallet-host', runtime),
|
|
550
550
|
];
|
|
551
551
|
if (network === 'testnet') hostArgs.push('--mainnet-hosts', mainnetHosts.join(','), '--testnet-hosts', venues.served.join(','));
|
|
@@ -598,7 +598,7 @@ export async function launchTerminalBrowser(args, env = process.env, { timeouts
|
|
|
598
598
|
} catch (error) {
|
|
599
599
|
if (started) {
|
|
600
600
|
log(`launch failed, stopping what it started: ${String(error?.message ?? error).split('\n')[0]}`);
|
|
601
|
-
await
|
|
601
|
+
await stopWebDappBrowser(target, { cdpPort, log }).catch((stopError) => log(`rollback incomplete: ${stopError.message}`));
|
|
602
602
|
for (const pid of ownedBrowserPids(runtime)) await terminate(pid, 3000);
|
|
603
603
|
}
|
|
604
604
|
throw error;
|
|
@@ -621,8 +621,7 @@ if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.me
|
|
|
621
621
|
process.exit(0);
|
|
622
622
|
}
|
|
623
623
|
try {
|
|
624
|
-
const
|
|
625
|
-
const state = await launchTerminalBrowser(args, process.env, { policy });
|
|
624
|
+
const state = await launchWebDappBrowser(args);
|
|
626
625
|
process.stdout.write(`${JSON.stringify({ status: 'pass', ...state }, null, args.json ? 0 : 2)}\n`);
|
|
627
626
|
} catch (error) {
|
|
628
627
|
const message = String(error?.stderr ?? '').trim() || error.message;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Testnet enforcement mechanics for the web-dapp slot browser: the hosts a
|
|
2
|
+
// venue policy blocks become unresolvable for the whole browser (pages,
|
|
3
|
+
// workers, WebSockets), and the wallet host fails and logs each request it
|
|
4
|
+
// sees to one of them. Which hosts is the venue policy's (venueHosts).
|
|
5
|
+
|
|
6
|
+
// A URL's host as the network sees it: lowercase, no trailing dot. null when
|
|
7
|
+
// it does not parse.
|
|
8
|
+
export function hostOf(url) {
|
|
9
|
+
try {
|
|
10
|
+
return new URL(String(url)).hostname.toLowerCase().replace(/\.$/u, '');
|
|
11
|
+
} catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function isBlockedUrl(url, hosts) {
|
|
17
|
+
const host = hostOf(url);
|
|
18
|
+
return host !== null && hosts.includes(host);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function hostResolverRules(hosts) {
|
|
22
|
+
// The host and its fully qualified form (trailing dot) alike.
|
|
23
|
+
return `--host-resolver-rules=${hosts.flatMap((host) => [`MAP ${host} ~NOTFOUND`, `MAP ${host}. ~NOTFOUND`]).join(', ')}`;
|
|
24
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Minimal browser-level Chrome DevTools Protocol client with flattened target
|
|
2
|
-
// sessions, for the long-running
|
|
2
|
+
// sessions, for the long-running web-dapp wallet host. Page-level UI actions use
|
|
3
3
|
// @farmslot/recipe-runner/runtime/cdp instead.
|
|
4
4
|
|
|
5
5
|
import WebSocket from 'ws';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// How the slot browser is shown: headful (default) or headless, where its
|
|
2
|
-
// window sits on screen, the page zoom that keeps the
|
|
2
|
+
// window sits on screen, the page zoom that keeps the app's desktop layout
|
|
3
3
|
// inside that window, and the optional slow-mo between UI actions.
|
|
4
4
|
|
|
5
5
|
import { execFileSync } from 'node:child_process';
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// The venue policy of the adapter that extends web-dapp (e.g. the Terminal
|
|
2
|
+
// plugin's plugins/terminal/policy.mjs): what web-dapp's launch, wallet host,
|
|
3
|
+
// readiness and actions know about the app's venue. mm-harness sets its module
|
|
4
|
+
// path in RECIPE_WEB_DAPP_POLICY when it binds such an adapter, so every
|
|
5
|
+
// web-dapp leaf process and live action it starts reads the same policy.
|
|
6
|
+
|
|
7
|
+
import { createRequire } from 'node:module';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
|
|
10
|
+
export const POLICY_ENV = 'RECIPE_WEB_DAPP_POLICY';
|
|
11
|
+
// The policy contract this web-dapp reads; a policy declares the version it was written for.
|
|
12
|
+
export const POLICY_VERSION = 1;
|
|
13
|
+
let loaded;
|
|
14
|
+
|
|
15
|
+
// Every member web-dapp reads, checked once when the policy loads: a missing or mistyped
|
|
16
|
+
// member fails here, naming the module, instead of deep in a launch or an assertion (a
|
|
17
|
+
// missing signatureLog would silently drop the venue's L1 and mainnet rules).
|
|
18
|
+
const MEMBERS = {
|
|
19
|
+
adapterId: 'string',
|
|
20
|
+
checkout: 'object',
|
|
21
|
+
venueHosts: 'function',
|
|
22
|
+
linkHosts: 'array',
|
|
23
|
+
probe: 'object',
|
|
24
|
+
startChain: 'number',
|
|
25
|
+
refuseTypedData: 'object',
|
|
26
|
+
signatureLog: 'object',
|
|
27
|
+
startPath: 'string',
|
|
28
|
+
pagePath: 'function',
|
|
29
|
+
dependencies: 'array',
|
|
30
|
+
testnetVariable: 'string',
|
|
31
|
+
module: 'string',
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function checkPolicy(policy, file) {
|
|
35
|
+
const problems = [];
|
|
36
|
+
if (!policy || typeof policy !== 'object') problems.push('exports no policy object');
|
|
37
|
+
else {
|
|
38
|
+
if (policy.policyVersion !== POLICY_VERSION) problems.push(`policyVersion is ${policy.policyVersion ?? 'missing'}, this web-dapp reads ${POLICY_VERSION}`);
|
|
39
|
+
for (const [name, type] of Object.entries(MEMBERS)) {
|
|
40
|
+
const value = policy[name];
|
|
41
|
+
const ok = type === 'array' ? Array.isArray(value) : type === 'object' ? value !== null && typeof value === 'object' : typeof value === type;
|
|
42
|
+
if (!ok) problems.push(`${name} must be ${type === 'array' || type === 'object' ? `an ${type}` : `a ${type}`}`);
|
|
43
|
+
}
|
|
44
|
+
const { checkout, refuseTypedData, signatureLog } = policy;
|
|
45
|
+
if (checkout && typeof checkout.matches !== 'function') problems.push('checkout.matches must be a function');
|
|
46
|
+
for (const name of ['name', 'label', 'needs']) if (checkout && typeof checkout[name] !== 'string') problems.push(`checkout.${name} must be a string`);
|
|
47
|
+
if (refuseTypedData && typeof refuseTypedData.reason !== 'function') problems.push('refuseTypedData.reason must be a function');
|
|
48
|
+
for (const name of ['kind', 'message']) if (refuseTypedData && typeof refuseTypedData[name] !== 'string') problems.push(`refuseTypedData.${name} must be a string`);
|
|
49
|
+
// The venue's request-log rules: both maps are required (empty only on purpose), so a
|
|
50
|
+
// policy cannot drop its L1 or mainnet rules by omission.
|
|
51
|
+
for (const name of ['typedDataClasses', 'forbiddenEntries']) {
|
|
52
|
+
if (signatureLog && (signatureLog[name] === null || typeof signatureLog[name] !== 'object')) problems.push(`signatureLog.${name} must be an object`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (problems.length) {
|
|
56
|
+
throw new Error(`web-dapp venue policy ${file}: ${problems.join('; ')}.\nNext: update the library that declares the adapter to a revision written for this mm-harness.`);
|
|
57
|
+
}
|
|
58
|
+
return policy;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function webDappPolicy(env = process.env) {
|
|
62
|
+
const file = env[POLICY_ENV];
|
|
63
|
+
if (!file) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
'web-dapp needs a venue policy: select an adapter that extends it.\n' +
|
|
66
|
+
'Next: put the library that declares it on RECIPE_LIBRARY_PATH and pass its id, e.g. --adapter terminal.',
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
if (loaded?.file !== file) {
|
|
70
|
+
// The policy module uses node built-ins only and has no top-level await, so it loads synchronously.
|
|
71
|
+
loaded = { file, policy: checkPolicy(createRequire(import.meta.url)(path.resolve(file)).policy, file) };
|
|
72
|
+
}
|
|
73
|
+
return loaded.policy;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// The bound adapter's id, for names and paths; web-dapp's own when none is bound.
|
|
77
|
+
export function webDappAdapterId(env = process.env) {
|
|
78
|
+
return env[POLICY_ENV] ? webDappPolicy(env).adapterId : 'web-dapp';
|
|
79
|
+
}
|