@deeeed/metamask-harness 0.47.4 → 0.48.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.
- package/CHANGELOG.md +6 -0
- package/README.md +3 -2
- package/adapters/extension/lib/macos-focus.cjs +2 -2
- package/adapters/extension/live.sh +6 -8
- package/adapters/mobile/bridge-runtime/lib/bridge-errors.cjs +1 -0
- package/adapters/shared/ensure-runner-deps.sh +6 -0
- package/dist/adapters/extension/runtime-decision.js +18 -1
- package/dist/adapters/extension/runtime.js +5 -5
- package/dist/adapters/extension/surface.js +1 -0
- package/dist/app-lifecycle.js +3 -1
- package/dist/command-contract.js +2 -0
- package/dist/commands/call.js +1 -8
- package/dist/commands/device-target.js +12 -3
- package/dist/commands/doctor.js +7 -5
- package/dist/commands/fixtures.js +1 -1
- package/dist/commands/launch/index.js +10 -0
- package/dist/commands/manifest.js +3 -1
- package/dist/commands/mobile-device-view.js +13 -3
- package/dist/commands/parse-args.js +2 -0
- package/dist/commands/run-engine.js +29 -4
- package/dist/commands/run.js +1 -1
- package/dist/commands/runtime-launch.js +10 -1
- package/dist/commands/status.js +8 -4
- package/dist/devices.js +31 -9
- package/dist/execution-provenance.js +8 -7
- package/dist/funding-execution-context.js +43 -13
- package/dist/heal-bounds.js +3 -3
- package/dist/live-adapter-contract.js +4 -0
- package/dist/manifest.js +2 -13
- package/dist/mm-harness-cli.js +2 -1
- package/dist/runner.js +14 -3
- package/docs/RECIPES.md +133 -22
- package/docs/RELEASE-QA-CAPABILITY-MAP.md +3 -2
- package/library/actions/core/perps/_controller.mjs +10 -0
- package/library/actions/core/perps/close_orders.mjs +13 -17
- package/library/actions/extension/assets/home-token-identity.mjs +30 -0
- package/library/actions/extension/assets/import_custom_token.mjs +61 -79
- package/library/actions/extension/assets/open_details.mjs +18 -7
- package/library/actions/extension/assets/read_details.mjs +61 -29
- package/library/actions/extension/assets/read_visible_state.mjs +4 -1
- package/library/actions/extension/assets/select_network_scope.mjs +16 -9
- package/library/actions/extension/assets/set_token_visibility.mjs +84 -17
- package/library/actions/extension/assets/verify_sorting.mjs +62 -14
- package/library/actions/extension/networks/add_custom.mjs +19 -17
- package/library/actions/extension/networks/custom_network.mjs +8 -3
- package/library/actions/extension/networks/read_visible_state.mjs +1 -0
- package/library/actions/extension/performance/_navigation-memory.mjs +35 -6
- package/library/actions/extension/performance/compare_idle_navigation_memory.mjs +3 -2
- package/library/actions/extension/performance/measure_detached_dom.mjs +3 -2
- package/library/actions/extension/performance/measure_navigation_memory.mjs +3 -2
- package/library/actions/extension/perps/assert_visible_consistency.mjs +9 -4
- package/library/actions/extension/perps/close_visible_position.mjs +3 -3
- package/library/actions/extension/perps/edit_margin.mjs +1 -1
- package/library/actions/extension/perps/open_balance_action.mjs +16 -7
- package/library/actions/extension/perps/perps.mjs +150 -45
- package/library/actions/extension/perps/read_funds_confirmation.mjs +8 -0
- package/library/actions/extension/perps/read_snapshot.mjs +2 -1
- package/library/actions/extension/perps/read_visible_state.mjs +10 -6
- package/library/actions/extension/perps/search_markets.mjs +7 -2
- package/library/actions/extension/perps/select_activity_filter.mjs +1 -1
- package/library/actions/extension/perps/select_market_filter.mjs +74 -45
- package/library/actions/extension/perps/set_market_favorite.mjs +6 -3
- package/library/actions/extension/perps/state.mjs +1 -0
- package/library/actions/extension/perps/update_position_tpsl.mjs +1 -1
- package/library/actions/extension/perps/visible-mutation-identity.mjs +4 -2
- package/library/actions/extension/platform/cdp.mjs +23 -142
- package/library/actions/extension/swap_bridge/quote-state.mjs +54 -0
- package/library/actions/extension/swap_bridge/read_visible_state.mjs +4 -1
- package/library/actions/extension/swap_bridge/select_assets.mjs +11 -1
- package/library/actions/extension/swap_bridge/set_amount.mjs +10 -8
- package/library/actions/extension/swap_bridge/set_max_amount.mjs +17 -18
- package/library/actions/extension/swap_bridge/set_slippage.mjs +0 -9
- package/library/actions/extension/ui/navigate.mjs +58 -14
- package/library/actions/extension/wallet/lock.mjs +2 -1
- package/library/actions/mobile/assets/import_custom_token.mjs +1 -0
- package/library/actions/mobile/assets/read_visible_state.mjs +3 -1
- package/library/actions/mobile/assets/set_token_visibility.mjs +13 -9
- package/library/actions/mobile/networks/network-management.mjs +22 -15
- package/library/actions/mobile/networks/remove_custom.mjs +6 -2
- package/library/actions/mobile/perps/perps.mjs +3 -0
- package/library/actions/mobile/perps/read-visible-state-loop.mjs +1 -0
- package/library/actions/mobile/perps/set_market_favorite.mjs +15 -19
- package/library/actions/mobile/platform/native-session.mjs +26 -17
- package/library/actions/mobile/platform/observe-ui.mjs +2 -2
- package/library/actions/mobile/ui/navigate.mjs +1 -1
- package/library/actions/shared/perps/visible-state.mjs +14 -3
- package/library/manifests/core.action-manifest.json +14 -2
- package/library/manifests/extension.action-manifest.json +49 -16
- package/library/manifests/mobile.action-manifest.json +8 -7
- package/library/recipes/core/perps/smoke.recipe.json +23 -0
- package/library/recipes/core/perps/snapshot.recipe.json +26 -1
- package/library/recipes/extension/assets/release-custom-network-token-persistence.recipe.json +25 -8
- package/library/recipes/extension/performance/navigation-memory-suite.recipe.json +219 -0
- package/library/recipes/extension/performance/navigation-memory.recipe.json +61 -156
- package/library/recipes/extension/wallet/smoke.recipe.json +17 -8
- package/library/recipes/mobile/app/lifecycle.android-smoke.recipe.json +23 -4
- package/library/recipes/mobile/networks/release-custom-network-persistence.recipe.json +31 -6
- package/library/recipes/mobile/networks/release-multichain-visibility.recipe.json +5 -4
- package/library/recipes/mobile/wallet/smoke.recipe.json +17 -8
- package/library/recipes/{assets → shared/assets}/release-token-address-import.recipe.json +7 -0
- package/library/recipes/{assets → shared/assets}/release-token-details.recipe.json +2 -2
- package/library/recipes/shared/assets/release-token-hide-readd.recipe.json +91 -0
- package/library/recipes/{assets → shared/assets}/release-token-persistence.recipe.json +3 -0
- package/library/recipes/{assets → shared/assets}/release-token-search-import.recipe.json +4 -1
- package/library/recipes/{assets → shared/assets}/release-token-sorting.recipe.json +13 -2
- package/library/recipes/{swap-bridge → shared/swap-bridge}/release-quote.recipe.json +29 -3
- package/package.json +3 -3
- package/site/assets/help-recipes.json +63 -23
- package/library/recipes/assets/release-token-hide-readd.recipe.json +0 -57
- package/library/recipes/core/perps/clean-market-testnet.recipe.json +0 -44
- package/library/recipes/core/perps/order-lifecycle.recipe.json +0 -82
- package/library/recipes/core/perps/read-markets.recipe.json +0 -36
- package/library/recipes/core/perps/trading-lifecycle.recipe.json +0 -80
- package/library/recipes/extension/perps/release-activity-filters.recipe.json +0 -51
- package/library/recipes/extension/perps/release-funds-flows.recipe.json +0 -76
- package/library/recipes/extension/perps/release-live-limit-order.recipe.json +0 -109
- package/library/recipes/extension/perps/release-market-filters.recipe.json +0 -97
- package/library/recipes/extension/perps/release-order-entry.recipe.json +0 -158
- package/library/recipes/extension/perps/release-order-validation.recipe.json +0 -259
- package/library/recipes/extension/perps/release-visible-consistency.recipe.json +0 -47
- package/library/recipes/extension/perps/source-dev-snapshot-consistency.recipe.json +0 -76
- package/library/recipes/mobile/perps/chase-assert-running.recipe.json +0 -110
- package/library/recipes/mobile/perps/chase-place.recipe.json +0 -145
- package/library/recipes/mobile/perps/chase-terminate.recipe.json +0 -98
- package/library/recipes/mobile/perps/performance.recipe.json +0 -722
- package/library/recipes/mobile/perps/pro-order-setup.recipe.json +0 -111
- package/library/recipes/mobile/perps/pro-order-start-state.recipe.json +0 -53
- package/library/recipes/mobile/perps/scale-assert-orders.recipe.json +0 -126
- package/library/recipes/mobile/perps/scale-place.recipe.json +0 -186
- package/library/recipes/mobile/perps/twap-assert-active.recipe.json +0 -97
- package/library/recipes/mobile/perps/twap-place.recipe.json +0 -146
- package/library/recipes/mobile/runner/action-validation.recipe.json +0 -336
- package/library/recipes/perps/clean-market-testnet.recipe.json +0 -49
- package/library/recipes/perps/lifecycle.recipe.json +0 -144
- package/library/recipes/perps/release-live-market-order.recipe.json +0 -139
- package/library/recipes/perps/release-watchlist.recipe.json +0 -111
- package/library/recipes/swap-bridge/release-max-quote.recipe.json +0 -72
- /package/library/recipes/{assets → shared/assets}/release-token-list.recipe.json +0 -0
- /package/library/recipes/{perps → shared/perps}/release-market-details.recipe.json +0 -0
- /package/library/recipes/{perps → shared/perps}/release-market-search.recipe.json +0 -0
- /package/library/recipes/{swap-bridge → shared/swap-bridge}/release-custom-slippage.recipe.json +0 -0
- /package/library/recipes/{swap-bridge → shared/swap-bridge}/release-live-max-swap.recipe.json +0 -0
- /package/library/recipes/{swap-bridge → shared/swap-bridge}/release-live-swap.recipe.json +0 -0
- /package/library/recipes/{swap-bridge → shared/swap-bridge}/release-surface.recipe.json +0 -0
- /package/library/recipes/{wallet → shared/wallet}/import.recipe.json +0 -0
- /package/library/recipes/{wallet → shared/wallet}/reset-import.recipe.json +0 -0
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
|
|
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
|
|
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:
|
|
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(
|
|
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
|
-
"
|
|
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;
|
|
@@ -99,7 +100,7 @@ async function loadTrustedMutationBase(options) {
|
|
|
99
100
|
fundingBalanceProof
|
|
100
101
|
);
|
|
101
102
|
const identity = await validateFundingDocuments(documents);
|
|
102
|
-
const currentReleaseRuntime = options.testIo?.currentReleaseRuntime ?? releaseRuntimeValidator(path.resolve(options.projectRoot));
|
|
103
|
+
const currentReleaseRuntime = options.testIo?.currentReleaseRuntime ?? releaseRuntimeValidator(path.resolve(options.projectRoot), identity.proofLane);
|
|
103
104
|
const currentRelease = await currentReleaseRuntime(false, identity.cdpPort);
|
|
104
105
|
if (currentRelease.artifactSha256 !== identity.subjectDigest || currentRelease.treeSha256 !== identity.releaseTreeSha256) {
|
|
105
106
|
throw new Error("Runner-owned current release artifact does not match the funding release proof.");
|
|
@@ -162,6 +163,7 @@ async function authorizeTrustedMutationPlan(base, plan, testIo = {}) {
|
|
|
162
163
|
}
|
|
163
164
|
}
|
|
164
165
|
if (actual.size === 0) throw new Error("Funding documents require a resolved external-mutation plan.");
|
|
166
|
+
await mkdir(source.artifactsDir, { recursive: true, mode: 448 });
|
|
165
167
|
const artifactsIdentitySha256 = await directoryIdentity(source.artifactsDir);
|
|
166
168
|
const operationCounts = [...expected.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([action, count]) => ({ action, count }));
|
|
167
169
|
const runId = sha256(canonical({
|
|
@@ -719,14 +721,27 @@ async function validateFundingEvidenceChain(documents, identity) {
|
|
|
719
721
|
if (policy.required !== true || policy.registrySha256 !== identity.registrySha256 || policy.profileId !== identity.profileId || policy.domain !== identity.domain) {
|
|
720
722
|
throw new Error("Funding release proof does not bind the runner-trusted funding policy.");
|
|
721
723
|
}
|
|
722
|
-
const
|
|
723
|
-
|
|
724
|
-
|
|
724
|
+
const sourceDev = identity.proofLane === "source-dev";
|
|
725
|
+
let releaseTreeSha256;
|
|
726
|
+
if (sourceDev) {
|
|
727
|
+
const source = requireRecord(releaseProof.source, "funding source-dev proof source");
|
|
728
|
+
const workingTree = requireRecord(source.workingTree, "funding source-dev working tree");
|
|
729
|
+
const build = requireRecord(source.build, "funding source-dev build");
|
|
730
|
+
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) {
|
|
731
|
+
throw new Error("Funding source-dev proof requires bound build provenance and compatibility-only evidence.");
|
|
732
|
+
}
|
|
733
|
+
releaseTreeSha256 = String(build.outputSha256);
|
|
734
|
+
} else {
|
|
735
|
+
const artifact = requireRecord(releaseProof.artifact, "funding release proof artifact");
|
|
736
|
+
if (artifact.sha256 !== identity.subjectDigest || !SHA256.test(String(artifact.treeSha256 ?? ""))) {
|
|
737
|
+
throw new Error("Funding release proof artifact identity is invalid.");
|
|
738
|
+
}
|
|
739
|
+
releaseTreeSha256 = String(artifact.treeSha256);
|
|
725
740
|
}
|
|
726
741
|
const runtime = requireRecord(releaseProof.runtime, "funding release proof runtime");
|
|
727
742
|
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
|
|
729
|
-
throw new Error("Funding
|
|
743
|
+
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)) {
|
|
744
|
+
throw new Error("Funding proof does not match its permitted Extension runtime lane.");
|
|
730
745
|
}
|
|
731
746
|
const onboarding = requireRecord(releaseProof.walletOnboarding, "funding release proof walletOnboarding");
|
|
732
747
|
const onboardingRuntime = requireRecord(onboarding.runtime, "funding release proof wallet runtime");
|
|
@@ -837,8 +852,8 @@ async function validateFundingEvidenceChain(documents, identity) {
|
|
|
837
852
|
const onboardingWallet = successfulWalletOutput(
|
|
838
853
|
onboardingTrace,
|
|
839
854
|
"funding release onboarding evidence",
|
|
840
|
-
"ui-import",
|
|
841
|
-
"extension-visible-onboarding-import"
|
|
855
|
+
sourceDev ? "ui-visible-session" : "ui-import",
|
|
856
|
+
sourceDev ? "extension-visible-wallet-session" : "extension-visible-onboarding-import"
|
|
842
857
|
);
|
|
843
858
|
const onboardingOrigin = extensionOrigin(onboardingWallet.targetUrl, "funding release onboarding wallet");
|
|
844
859
|
if (onboardingWallet.runtimeSessionId !== onboardingRuntime.runtimeSessionId || onboardingWallet.cdpPort !== onboardingRuntime.cdpPort || onboardingWallet.cdpTargetId !== onboardingRuntime.cdpTargetId || extensionOrigin(onboardingRuntime.targetUrl, "funding release onboarding runtime") !== onboardingOrigin) {
|
|
@@ -972,7 +987,7 @@ async function validateFundingEvidenceChain(documents, identity) {
|
|
|
972
987
|
evidenceDocument.runtimeSessionId,
|
|
973
988
|
"funding balance evidence runtimeSessionId"
|
|
974
989
|
);
|
|
975
|
-
if (evidenceDocument.selectedIdentitySha256 !== onboardingWallet.selectedIdentitySha256 || wallet.selectedIdentitySha256 !== onboardingWallet.selectedIdentitySha256 || wallet.runtimeSessionId !== runtimeSessionId || wallet.cdpPort !== onboardingWallet.cdpPort || extensionOrigin(wallet.targetUrl, "funding balance wallet") !== onboardingOrigin) {
|
|
990
|
+
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
991
|
throw new Error("Funding balance evidence wallet or runtime is not anchored to release onboarding.");
|
|
977
992
|
}
|
|
978
993
|
const currentRuntime = {
|
|
@@ -999,13 +1014,26 @@ async function validateFundingEvidenceChain(documents, identity) {
|
|
|
999
1014
|
return {
|
|
1000
1015
|
requirements,
|
|
1001
1016
|
runtime: balanceRuntime,
|
|
1002
|
-
releaseTreeSha256
|
|
1017
|
+
releaseTreeSha256
|
|
1003
1018
|
};
|
|
1004
1019
|
}
|
|
1005
|
-
function releaseRuntimeValidator(projectRoot) {
|
|
1020
|
+
function releaseRuntimeValidator(projectRoot, proofLane) {
|
|
1006
1021
|
return async (requireLive, cdpPort) => {
|
|
1007
1022
|
const { extensionRuntimeReusable } = await import("./commands/launch/extension.js");
|
|
1008
1023
|
const { releaseArtifactState, runtimeDistCheck } = await import("./adapters/extension/runtime-decision.js");
|
|
1024
|
+
if (proofLane === "source-dev") {
|
|
1025
|
+
const { extensionTreeSha256 } = await import("./adapters/extension/artifact-integrity.js");
|
|
1026
|
+
const configuredPort = Number(process.env.CDP_PORT ?? process.env.RECIPE_CDP_PORT);
|
|
1027
|
+
if (configuredPort !== cdpPort || !await extensionRuntimeReusable(projectRoot, { status: "none" })) {
|
|
1028
|
+
throw new Error("Source-dev funding requires the current harness-owned Extension runtime.");
|
|
1029
|
+
}
|
|
1030
|
+
const treeSha256 = extensionTreeSha256(path.join(
|
|
1031
|
+
projectRoot,
|
|
1032
|
+
recipeRuntimeDir(),
|
|
1033
|
+
process.env.RECIPE_RUNTIME_DIST_DIR || "runtime-dist"
|
|
1034
|
+
));
|
|
1035
|
+
return { artifactSha256: treeSha256, treeSha256 };
|
|
1036
|
+
}
|
|
1009
1037
|
const artifact = releaseArtifactState(projectRoot);
|
|
1010
1038
|
if (artifact.status !== "valid" || runtimeDistCheck(projectRoot, artifact).status !== "fresh" || !artifact.sha256 || !artifact.treeSha256) {
|
|
1011
1039
|
throw new Error("Runner-owned current release artifact is invalid or stale.");
|
|
@@ -1253,14 +1281,16 @@ function parsePreflightRequirements(value) {
|
|
|
1253
1281
|
if (!Array.isArray(value)) throw new Error("Funding preflight requirements must be an array.");
|
|
1254
1282
|
return value.map((entry, index) => {
|
|
1255
1283
|
const record = requireRecord(entry, `funding preflight requirements[${index}]`);
|
|
1284
|
+
const evidence = requireRecord(record.evidence, `funding preflight requirements[${index}].evidence`);
|
|
1285
|
+
const wrapper = requireRecord(evidence.wrapper, `funding preflight requirements[${index}].evidence.wrapper`);
|
|
1256
1286
|
return {
|
|
1257
1287
|
kind: requireIdentifier(record.kind, `funding preflight requirements[${index}].kind`),
|
|
1258
1288
|
id: requireIdentifier(record.id, `funding preflight requirements[${index}].id`),
|
|
1259
1289
|
minBaseUnits: requireInteger(record.minBaseUnits, `funding preflight requirements[${index}].minBaseUnits`),
|
|
1260
1290
|
actualBaseUnits: requireInteger(record.actualBaseUnits, `funding preflight requirements[${index}].actualBaseUnits`),
|
|
1261
1291
|
evidenceSha256: requireIdentifier(
|
|
1262
|
-
|
|
1263
|
-
`funding preflight requirements[${index}].
|
|
1292
|
+
wrapper.sha256,
|
|
1293
|
+
`funding preflight requirements[${index}].evidence.wrapper.sha256`,
|
|
1264
1294
|
true
|
|
1265
1295
|
)
|
|
1266
1296
|
};
|
package/dist/heal-bounds.js
CHANGED
|
@@ -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 (/
|
|
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: "
|
|
105
|
-
userAction: "run mm-harness
|
|
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)) {
|
package/dist/mm-harness-cli.js
CHANGED
|
@@ -288,7 +288,7 @@ Advanced integrations:
|
|
|
288
288
|
|
|
289
289
|
Example:
|
|
290
290
|
mm-harness run wallet.smoke --describe
|
|
291
|
-
mm-harness run perps.
|
|
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
|
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
|
);
|