@deeeed/metamask-harness 0.91.6 → 0.92.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 +27 -0
- package/README.md +3 -4
- package/bin/mm-harness +3 -35
- package/dist/adapters/mobile/runtime-decision.js +3 -18
- package/dist/adapters/terminal-backend/provider.js +28 -36
- package/dist/command-contract.js +2 -2
- package/dist/commands/launch/device.js +1 -2
- package/dist/mm-harness-cli.js +2 -2
- package/dist/pr-body/render.js +8 -2
- package/docs/cli-contract.md +1 -1
- package/library/recipes/extension/perps/smoke-observation.recipe.json +63 -0
- package/library/recipes/extension/perps/smoke.recipe.json +17 -43
- package/package.json +9 -9
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.92.0 - 2026-10-14
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Use the coordinated public recipe-engine release for immutable artifact closeout and engine-owned attempt publication manifests.
|
|
10
|
+
- Keep browser provenance checks on the engine finalization contract, with native browser lifetime conformance in the live release tier.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Extension Perps smoke binds document and native captures to its passing observations for live conformance. Existing `perps-home` callers remain supported, and `smoke` names the shared QA proof.
|
|
15
|
+
- Retain invocation-owned backend logs through engine closeout before the canonical artifact manifest is written.
|
|
16
|
+
- Finish successful Mobile dependency recovery with explicit install-baseline recording so a retry accepts the installed content.
|
|
17
|
+
|
|
18
|
+
### Removed
|
|
19
|
+
|
|
20
|
+
- `MM_HARNESS_BIN` entrypoint delegation. Invoke a development checkout directly; Farmslot selects the provider package root. Public commands, arguments and executable identity are preserved.
|
|
21
|
+
|
|
22
|
+
## 0.91.7 - 2026-10-13
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- Use the coordinated Farmslot recipe closure for fresh task attempt directories, selected-package checks and declared output provenance. Reusing a retained `--artifacts-dir` refuses and preserves its evidence.
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- Render PR validation logs from the selected retained attempt, including when an older legacy report remains.
|
|
31
|
+
|
|
5
32
|
## 0.91.6 - 2026-10-13
|
|
6
33
|
|
|
7
34
|
### Fixed
|
package/README.md
CHANGED
|
@@ -447,14 +447,13 @@ failure.
|
|
|
447
447
|
- [QA](docs/QA.md) — clean-machine and human release checks.
|
|
448
448
|
- [Contributing](docs/CONTRIBUTING.md) — ownership, layout, and change gates.
|
|
449
449
|
|
|
450
|
-
For development,
|
|
450
|
+
For development, invoke the source checkout directly:
|
|
451
451
|
|
|
452
452
|
```bash
|
|
453
|
-
|
|
454
|
-
mm-harness --version
|
|
453
|
+
/path/to/metamask-harness/bin/mm-harness --version
|
|
455
454
|
```
|
|
456
455
|
|
|
457
|
-
|
|
456
|
+
Farmslot selects that same package through `recipe.provider.root.env`, configured as `MM_HARNESS_PACKAGE_ROOT`. Binary environment overrides do not redirect the public entrypoint.
|
|
458
457
|
|
|
459
458
|
|
|
460
459
|
### Workflow entry points and completion
|
package/bin/mm-harness
CHANGED
|
@@ -29,43 +29,11 @@ done
|
|
|
29
29
|
SCRIPT_DIR="$(cd "$(dirname "$SOURCE")" && pwd -P)"
|
|
30
30
|
RUNNER_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
31
31
|
|
|
32
|
-
# Dev/prod switch (the single override point). MM_HARNESS_BIN, when set, is the
|
|
33
|
-
# authoritative path to a dev checkout's bin/mm-harness with active pre-release
|
|
34
|
-
# changes; unset resolves the installed/global mm-harness (prod). Handing the
|
|
35
|
-
# invocation over here, before any dependency work, lets the dev checkout fully
|
|
36
|
-
# own the run. The self-path guard prevents an exec loop when MM_HARNESS_BIN
|
|
37
|
-
# already points at this script — including via a symlink (both sides are resolved
|
|
38
|
-
# to their real paths before comparing).
|
|
39
|
-
if [ -n "${MM_HARNESS_BIN:-}" ]; then
|
|
40
|
-
self_real="$SCRIPT_DIR/${SOURCE##*/}"
|
|
41
|
-
# Resolve MM_HARNESS_BIN through any symlink chain so a symlink-to-self is caught.
|
|
42
|
-
mm_bin_src="$MM_HARNESS_BIN"
|
|
43
|
-
while [ -L "$mm_bin_src" ]; do
|
|
44
|
-
mm_bin_dir="$(cd "$(dirname "$mm_bin_src")" && pwd -P)"
|
|
45
|
-
mm_bin_target="$(readlink "$mm_bin_src")"
|
|
46
|
-
if [[ "$mm_bin_target" == /* ]]; then
|
|
47
|
-
mm_bin_src="$mm_bin_target"
|
|
48
|
-
else
|
|
49
|
-
mm_bin_src="$mm_bin_dir/$mm_bin_target"
|
|
50
|
-
fi
|
|
51
|
-
done
|
|
52
|
-
bin_real_dir="$(cd "$(dirname "$mm_bin_src")" 2>/dev/null && pwd -P || true)"
|
|
53
|
-
bin_real="${bin_real_dir:+$bin_real_dir/${mm_bin_src##*/}}"
|
|
54
|
-
if [ "$bin_real" != "$self_real" ]; then
|
|
55
|
-
if [ ! -x "$MM_HARNESS_BIN" ]; then
|
|
56
|
-
echo "mm-harness: MM_HARNESS_BIN is not an executable: $MM_HARNESS_BIN" >&2
|
|
57
|
-
echo " Next: point MM_HARNESS_BIN at a checkout's bin/mm-harness, or unset it to use the global install" >&2
|
|
58
|
-
exit 1
|
|
59
|
-
fi
|
|
60
|
-
exec "$MM_HARNESS_BIN" "$@"
|
|
61
|
-
fi
|
|
62
|
-
fi
|
|
63
|
-
|
|
64
32
|
# Internal executable identity (not part of the public env surface): the CLI
|
|
65
33
|
# embeds this in emitted recovery commands so they run verbatim even when
|
|
66
|
-
# mm-harness is not on PATH (task-local installs). Set
|
|
67
|
-
#
|
|
68
|
-
#
|
|
34
|
+
# mm-harness is not on PATH (task-local installs). Set at the
|
|
35
|
+
# invocation entry so the executable always overwrites an inherited/stale
|
|
36
|
+
# value with its own resolved self path.
|
|
69
37
|
export MM_HARNESS_EXECUTABLE="$SCRIPT_DIR/${SOURCE##*/}"
|
|
70
38
|
export MM_HARNESS_INVOKED_AS="$INVOKED_AS"
|
|
71
39
|
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
supersededErrorCapture
|
|
12
12
|
} from "@farmslot/recipe-runner/runtime/log-analysis";
|
|
13
13
|
import { probeMetroPackager } from "@farmslot/recipe-runner/runtime/metro-probe";
|
|
14
|
-
import {
|
|
14
|
+
import { recipeRuntimePath, shellQuote } from "@farmslot/recipe-cli/harness";
|
|
15
15
|
import { resolveMobileToolPath } from "@farmslot/adapter-rn";
|
|
16
16
|
import { mobileProductMarkers } from "./deps-markers.js";
|
|
17
17
|
import { mobileMetroEnvCheck } from "./metro-env.js";
|
|
@@ -164,7 +164,7 @@ async function decideMobileReadiness(target, options = {}) {
|
|
|
164
164
|
...report.reasons,
|
|
165
165
|
"Quick launch does not install dependencies."
|
|
166
166
|
],
|
|
167
|
-
userAction: `cd ${shellQuote(resolved)} && ${depsInstallCommand(resolved)}`,
|
|
167
|
+
userAction: `cd ${shellQuote(resolved)} && ${depsInstallCommand(resolved)} && mm-harness runtime-decision --adapter mobile --target ${shellQuote(resolved)} --record-baseline`,
|
|
168
168
|
actions: []
|
|
169
169
|
};
|
|
170
170
|
}
|
|
@@ -185,24 +185,10 @@ async function decideMobileReadiness(target, options = {}) {
|
|
|
185
185
|
)
|
|
186
186
|
};
|
|
187
187
|
}
|
|
188
|
-
function writeDepsNote() {
|
|
189
|
-
const stream = process.stderr;
|
|
190
|
-
const body = "No install baseline is recorded. Required package checks passed; timestamps alone do not establish dependency drift. Reusing the installed dependencies.";
|
|
191
|
-
stream.write(
|
|
192
|
-
`
|
|
193
|
-
${color("warn", "mm-harness note (deps):", { stream })} ${color("dim", body, { stream })}
|
|
194
|
-
|
|
195
|
-
`
|
|
196
|
-
);
|
|
197
|
-
}
|
|
198
188
|
async function computeMobileReadiness(resolved, options, fast) {
|
|
199
|
-
|
|
189
|
+
let deps = depsCheck(resolved, {
|
|
200
190
|
productMarkers: mobileProductMarkers(options.platform)
|
|
201
191
|
});
|
|
202
|
-
let deps = rawDeps;
|
|
203
|
-
if (rawDeps.status === "stale" && !rawDeps.hasBaseline) {
|
|
204
|
-
deps = { installed: rawDeps.installed, status: "current", hasBaseline: false };
|
|
205
|
-
}
|
|
206
192
|
if (deps.status === "current") {
|
|
207
193
|
const absent = missingRequiredDeps(resolved);
|
|
208
194
|
if (absent.length > 0) {
|
|
@@ -320,7 +306,6 @@ async function computeMobileReadiness(resolved, options, fast) {
|
|
|
320
306
|
actions: installActions(resolved)
|
|
321
307
|
};
|
|
322
308
|
}
|
|
323
|
-
if (rawDeps.status === "stale" && !rawDeps.hasBaseline) writeDepsNote();
|
|
324
309
|
if (metro.status === "down") {
|
|
325
310
|
return {
|
|
326
311
|
schemaVersion: 1,
|
|
@@ -40,18 +40,6 @@ async function createBackendProvider(context) {
|
|
|
40
40
|
};
|
|
41
41
|
const serviceLog = path.join(target, ".agent/terminal-backend/service.log");
|
|
42
42
|
const logOffset = fs.existsSync(serviceLog) ? fs.statSync(serviceLog).size : 0;
|
|
43
|
-
const output = path.resolve(target, String(context.options?.artifactsDir ?? context.project.artifactDir));
|
|
44
|
-
const manifest = path.join(output, "artifact-manifest.json");
|
|
45
|
-
const manifestIdentity = () => {
|
|
46
|
-
try {
|
|
47
|
-
const s = fs.statSync(manifest, { bigint: true });
|
|
48
|
-
return [s.dev, s.ino, s.size, s.mtimeNs, s.ctimeNs].join(":");
|
|
49
|
-
} catch (error) {
|
|
50
|
-
if (error.code === "ENOENT") return void 0;
|
|
51
|
-
throw error;
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
const previousManifest = manifestIdentity();
|
|
55
43
|
if (owner) {
|
|
56
44
|
setEnv("BACKEND_OWNER_ID", owner);
|
|
57
45
|
setEnv("BACKEND_LIFECYCLE_SCRIPT", lifecycle);
|
|
@@ -59,6 +47,19 @@ async function createBackendProvider(context) {
|
|
|
59
47
|
const cleanup = async () => {
|
|
60
48
|
if (owner) await main([...args("stop"), owner]);
|
|
61
49
|
};
|
|
50
|
+
let finalized = false;
|
|
51
|
+
const finalize = async () => {
|
|
52
|
+
if (finalized) return;
|
|
53
|
+
finalized = true;
|
|
54
|
+
try {
|
|
55
|
+
await cleanup();
|
|
56
|
+
} finally {
|
|
57
|
+
for (const [key, value] of previous) {
|
|
58
|
+
if (value === void 0) delete process.env[key];
|
|
59
|
+
else process.env[key] = value;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
62
63
|
const logSource = () => ({ label: "NestJS and monitor", path: serviceLog });
|
|
63
64
|
const hint = "Prepare the selected slot with the full profile.";
|
|
64
65
|
const runtime = defineAdapter({
|
|
@@ -83,6 +84,20 @@ async function createBackendProvider(context) {
|
|
|
83
84
|
readiness: { statusRuntime: true, captureProviders: [], readyIndicator: () => "OK" },
|
|
84
85
|
logSources: () => [logSource()],
|
|
85
86
|
appLogSource: logSource,
|
|
87
|
+
...owner ? { observation: { performance: {
|
|
88
|
+
async start({ artifactsDir }) {
|
|
89
|
+
return {
|
|
90
|
+
onActionEvent() {
|
|
91
|
+
},
|
|
92
|
+
async finalize(observerManifestPath) {
|
|
93
|
+
await finalize();
|
|
94
|
+
if (!observerManifestPath || !fs.existsSync(serviceLog)) return;
|
|
95
|
+
await writeContainedArtifact(artifactsDir, "logs/backend.log", fs.readFileSync(serviceLog).subarray(logOffset), "Backend logs");
|
|
96
|
+
await indexArtifactManifest(observerManifestPath, [{ category: "log", label: "Backend and monitor logs", path: "logs/backend.log", type: "text" }]);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
} } } : {},
|
|
86
101
|
hints: { launch: hint, relaunch: hint, runtimeProbeRecovery: () => "Inspect the slot service.log and run farmslot doctor --conformance." },
|
|
87
102
|
devServer: {
|
|
88
103
|
label: "NestJS",
|
|
@@ -119,32 +134,9 @@ async function createBackendProvider(context) {
|
|
|
119
134
|
}
|
|
120
135
|
});
|
|
121
136
|
if (executing) runtime.resolveSlotPorts();
|
|
122
|
-
let finalized = false;
|
|
123
137
|
return {
|
|
124
138
|
runtime,
|
|
125
|
-
...owner ? {
|
|
126
|
-
cancel: cleanup,
|
|
127
|
-
async finalize() {
|
|
128
|
-
if (finalized) return;
|
|
129
|
-
finalized = true;
|
|
130
|
-
try {
|
|
131
|
-
try {
|
|
132
|
-
await cleanup();
|
|
133
|
-
} finally {
|
|
134
|
-
const currentManifest = manifestIdentity();
|
|
135
|
-
if (fs.existsSync(serviceLog) && currentManifest !== void 0 && currentManifest !== previousManifest) {
|
|
136
|
-
await writeContainedArtifact(output, "logs/backend.log", fs.readFileSync(serviceLog).subarray(logOffset), "Backend logs");
|
|
137
|
-
await indexArtifactManifest(manifest, [{ category: "log", label: "Backend and monitor logs", path: "logs/backend.log", type: "text" }]);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
} finally {
|
|
141
|
-
for (const [key, value] of previous) {
|
|
142
|
-
if (value === void 0) delete process.env[key];
|
|
143
|
-
else process.env[key] = value;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
} : {}
|
|
139
|
+
...owner ? { cancel: cleanup, finalize } : {}
|
|
148
140
|
};
|
|
149
141
|
}
|
|
150
142
|
export {
|
package/dist/command-contract.js
CHANGED
|
@@ -779,7 +779,7 @@ Common overrides:
|
|
|
779
779
|
--adapter <{adapters}> Target adapter (auto-detected inside a checkout)
|
|
780
780
|
--platform <ios|android> Mobile platform preference; use --adapter for the product adapter
|
|
781
781
|
--target <path> Checkout path (default: cwd)
|
|
782
|
-
--artifacts-dir <dir>
|
|
782
|
+
--artifacts-dir <dir> Fresh evidence output (default: active task/artifacts/recipe-runs/<attempt>, otherwise checkout-local temp/recipe/runs/<run>); retained packages cannot be reused
|
|
783
783
|
--funding-request <absolute-file> Original consented mutation request for external-mutation actions
|
|
784
784
|
--funding-preflight <absolute-file> Eligible funding preflight bound to the request bytes
|
|
785
785
|
--funding-release-proof <absolute-file> Exact release proof independently bound to funding eligibility
|
|
@@ -1343,7 +1343,7 @@ Example:
|
|
|
1343
1343
|
where available) and prints the copy's executable. A task records that path,
|
|
1344
1344
|
so \`npm i -g\` upgrading the global install never changes the harness a running
|
|
1345
1345
|
task runs. Idempotent: the same install returns the same pin. A source checkout
|
|
1346
|
-
|
|
1346
|
+
invoked directly or an existing pin is printed as is.
|
|
1347
1347
|
|
|
1348
1348
|
--json Machine-readable { version, source, executable, pinned, created }
|
|
1349
1349
|
|
|
@@ -260,9 +260,8 @@ function launchPass(launch, profile, state, remoteFlags) {
|
|
|
260
260
|
);
|
|
261
261
|
} else {
|
|
262
262
|
const tierNote = tier === "quick" ? "quick relaunch, no native build" : tier;
|
|
263
|
-
const devNote = process.env.MM_HARNESS_BIN ? ` ${color("dim", "[dev: MM_HARNESS_BIN]")}` : "";
|
|
264
263
|
console.error(
|
|
265
|
-
`${color("ok", "\u2713")} launch ${adapter}${platformTarget ? ` ${platformTarget}` : ""} \u2014 ${color("ok", profile.deviceLabel(launch))} \xB7 ${tierNote} \xB7 app + bridge ready
|
|
264
|
+
`${color("ok", "\u2713")} launch ${adapter}${platformTarget ? ` ${platformTarget}` : ""} \u2014 ${color("ok", profile.deviceLabel(launch))} \xB7 ${tierNote} \xB7 app + bridge ready`
|
|
266
265
|
);
|
|
267
266
|
}
|
|
268
267
|
return EXIT.ok;
|
package/dist/mm-harness-cli.js
CHANGED
|
@@ -265,8 +265,8 @@ await createHarnessCli({
|
|
|
265
265
|
"SETUP \u2014 tab-completion (zsh + bash), sourced from the bundled scripts:",
|
|
266
266
|
" mm-harness completions install # print the lines to add to ~/.zshrc / ~/.bashrc",
|
|
267
267
|
"",
|
|
268
|
-
"
|
|
269
|
-
"
|
|
268
|
+
"DEVELOPMENT \u2014 invoke the source checkout directly; Farmslot selects its configured provider package root:",
|
|
269
|
+
" /path/to/checkout/bin/mm-harness --version",
|
|
270
270
|
"",
|
|
271
271
|
"See README.md for the workflow and docs/RECIPES.md for proof authoring."
|
|
272
272
|
],
|
package/dist/pr-body/render.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
2
3
|
import path from "node:path";
|
|
4
|
+
const { readRecipeAttemptSelection } = createRequire(import.meta.url)(
|
|
5
|
+
"@farmslot/agent-runtime/scripts/retained-recipe-attempt.cjs"
|
|
6
|
+
);
|
|
3
7
|
const PROSE_FILE = "pr-description.md";
|
|
4
8
|
const BODY_FILE = "pr-body.md";
|
|
5
9
|
const RECIPE_HEADING = "## **Validation Recipe**";
|
|
@@ -77,7 +81,9 @@ function renderPrBody(input) {
|
|
|
77
81
|
throw new Error(`Missing ${path.relative(input.taskDir, prosePath)}: write the PR description there first.`);
|
|
78
82
|
}
|
|
79
83
|
const recipePath = path.join(artifacts, "recipe.json");
|
|
80
|
-
const
|
|
84
|
+
const selected = readRecipeAttemptSelection(input.taskDir);
|
|
85
|
+
const reportRoot = selected?.relativeArtifactRoot ?? "recipe-run";
|
|
86
|
+
const reportPath = path.join(artifacts, reportRoot, "report.md");
|
|
81
87
|
const workflowPath = path.join(artifacts, "workflow.mmd");
|
|
82
88
|
const machine = [];
|
|
83
89
|
let recipe = null;
|
|
@@ -91,7 +97,7 @@ function renderPrBody(input) {
|
|
|
91
97
|
const hasReport = fs.existsSync(reportPath);
|
|
92
98
|
machine.push([
|
|
93
99
|
LOGS_HEADING,
|
|
94
|
-
hasReport ? logsSection(reportPath, input.command) :
|
|
100
|
+
hasReport ? logsSection(reportPath, input.command) : `Not applicable: no recipe run report under \`artifacts/${reportRoot}/\`.`
|
|
95
101
|
]);
|
|
96
102
|
const hasWorkflow = fs.existsSync(workflowPath);
|
|
97
103
|
if (hasWorkflow) {
|
package/docs/cli-contract.md
CHANGED
|
@@ -10,7 +10,7 @@ Callers scanned (2026-10-02):
|
|
|
10
10
|
|
|
11
11
|
## Resolution and environment
|
|
12
12
|
|
|
13
|
-
- Farms
|
|
13
|
+
- Farms select the provider package through `recipe.provider.root.env`, configured as `MM_HARNESS_PACKAGE_ROOT`, and invoke its public `mm-harness` command. The entrypoint stays in the package invoked; inherited binary overrides cannot redirect it.
|
|
14
14
|
- Static review runs `{{support}}/bin/mm-harness` with entry `dist/mm-harness-cli.js`, so the package layout (`bin/`, `dist/`) is part of the contract.
|
|
15
15
|
- Hook environment: `RECIPE_SLOT_ID`, `RECIPE_RUNTIME_DIR` (default `temp/recipe/runtime`), `RECIPE_WALLET_FIXTURE`, `RECIPE_LIBRARY_PATH`, `MM_HARNESS_DOMAIN`, `IOS_SIMULATOR`/`ADB_SERIAL` (mobile slots), `RECIPE_HARNESS_ROOT=temp/recipe/harness` (Extension).
|
|
16
16
|
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://farmslot.io/schemas/recipe-v1.schema.json",
|
|
3
|
+
"title": "MetaMask Extension Perps smoke observation",
|
|
4
|
+
"description": "Observe the populated Perps home and retain its document and native viewport. The caller establishes the network and opens Perps before this read-only proof.",
|
|
5
|
+
"workflow": {
|
|
6
|
+
"entry": "read-home",
|
|
7
|
+
"nodes": {
|
|
8
|
+
"read-home": {
|
|
9
|
+
"action": "metamask.perps.read_visible_state",
|
|
10
|
+
"surface": "home",
|
|
11
|
+
"minimum_market_count": 1,
|
|
12
|
+
"require_features": [
|
|
13
|
+
"balance",
|
|
14
|
+
"market-list"
|
|
15
|
+
],
|
|
16
|
+
"intent": "Observe a populated Perps home after setup",
|
|
17
|
+
"next": "assert-home",
|
|
18
|
+
"forbid_test_ids": [
|
|
19
|
+
"perps-tutorial-skip-button"
|
|
20
|
+
],
|
|
21
|
+
"max_items": 200
|
|
22
|
+
},
|
|
23
|
+
"assert-home": {
|
|
24
|
+
"action": "assert_output",
|
|
25
|
+
"source": "read-home",
|
|
26
|
+
"assert": {
|
|
27
|
+
"path": "$.surface",
|
|
28
|
+
"operator": "eq",
|
|
29
|
+
"value": "home"
|
|
30
|
+
},
|
|
31
|
+
"intent": "The observed surface is Perps home",
|
|
32
|
+
"next": "assert-markets"
|
|
33
|
+
},
|
|
34
|
+
"assert-markets": {
|
|
35
|
+
"action": "assert_output",
|
|
36
|
+
"source": "read-home",
|
|
37
|
+
"assert": {
|
|
38
|
+
"path": "$.marketCount",
|
|
39
|
+
"operator": "gte",
|
|
40
|
+
"value": 1
|
|
41
|
+
},
|
|
42
|
+
"intent": "Perps home contains market data rather than only a loading shell",
|
|
43
|
+
"next": "capture-document"
|
|
44
|
+
},
|
|
45
|
+
"capture-document": {
|
|
46
|
+
"action": "ui.capture_surface",
|
|
47
|
+
"path": "evidence/perps-smoke-document.png",
|
|
48
|
+
"next": "capture-window",
|
|
49
|
+
"intent": "Keep the observed Perps home document for visual review"
|
|
50
|
+
},
|
|
51
|
+
"capture-window": {
|
|
52
|
+
"action": "ui.screenshot",
|
|
53
|
+
"path": "screenshots/perps-smoke.png",
|
|
54
|
+
"next": "done",
|
|
55
|
+
"intent": "Keep the native Perps viewport after its market checks"
|
|
56
|
+
},
|
|
57
|
+
"done": {
|
|
58
|
+
"action": "end",
|
|
59
|
+
"status": "pass"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://farmslot.io/schemas/recipe-v1.schema.json",
|
|
3
3
|
"title": "MetaMask Extension Perps smoke",
|
|
4
|
-
"description": "Sets and verifies the configured Perps network, default testnet, before proof.
|
|
4
|
+
"description": "Sets and verifies the configured Perps network, default testnet, before proof. Verifies CDP, unlocks if needed, opens Perps, asserts visible home and market data, retains document and native captures, and reads positions/orders. Requires approved wallet setup and completed onboarding gates; a visible tutorial is setup, not passing smoke.",
|
|
5
5
|
"paramsSchema": {
|
|
6
6
|
"type": "object",
|
|
7
7
|
"additionalProperties": false,
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"page": false,
|
|
27
27
|
"positions": false,
|
|
28
28
|
"orders": false,
|
|
29
|
-
"next": "
|
|
29
|
+
"next": "observe-perps",
|
|
30
30
|
"intent": "Verify the configured Perps network before proof; default testnet. Keep accounts and orders."
|
|
31
31
|
},
|
|
32
32
|
"status": {
|
|
@@ -67,55 +67,29 @@
|
|
|
67
67
|
"action": "end",
|
|
68
68
|
"status": "pass"
|
|
69
69
|
},
|
|
70
|
-
"
|
|
71
|
-
"action": "
|
|
72
|
-
"
|
|
73
|
-
"minimum_market_count": 1,
|
|
74
|
-
"require_features": [
|
|
75
|
-
"balance",
|
|
76
|
-
"market-list"
|
|
77
|
-
],
|
|
78
|
-
"intent": "Observe a populated Perps home after setup",
|
|
79
|
-
"next": "assert-home",
|
|
80
|
-
"forbid_test_ids": [
|
|
81
|
-
"perps-tutorial-skip-button"
|
|
82
|
-
],
|
|
83
|
-
"max_items": 200
|
|
84
|
-
},
|
|
85
|
-
"assert-home": {
|
|
86
|
-
"action": "assert_output",
|
|
87
|
-
"source": "read-home",
|
|
88
|
-
"assert": {
|
|
89
|
-
"path": "$.surface",
|
|
90
|
-
"operator": "eq",
|
|
91
|
-
"value": "home"
|
|
92
|
-
},
|
|
70
|
+
"observe-perps": {
|
|
71
|
+
"action": "call",
|
|
72
|
+
"ref": "perps.smoke-observation",
|
|
93
73
|
"proves": [
|
|
94
|
-
"perps-home"
|
|
74
|
+
"perps-home",
|
|
75
|
+
"smoke"
|
|
95
76
|
],
|
|
96
|
-
"
|
|
97
|
-
"
|
|
98
|
-
},
|
|
99
|
-
"assert-markets": {
|
|
100
|
-
"action": "assert_output",
|
|
101
|
-
"source": "read-home",
|
|
102
|
-
"assert": {
|
|
103
|
-
"path": "$.marketCount",
|
|
104
|
-
"operator": "gte",
|
|
105
|
-
"value": 1
|
|
106
|
-
},
|
|
107
|
-
"proves": [
|
|
108
|
-
"perps-home"
|
|
109
|
-
],
|
|
110
|
-
"intent": "Perps home contains market data rather than only a loading shell",
|
|
111
|
-
"next": "read-positions"
|
|
77
|
+
"next": "read-positions",
|
|
78
|
+
"intent": "Check the populated Perps home and retain its observed viewport"
|
|
112
79
|
}
|
|
113
80
|
}
|
|
114
81
|
},
|
|
115
82
|
"proofTargets": [
|
|
116
83
|
{
|
|
117
84
|
"id": "perps-home",
|
|
118
|
-
"claim": "The unlocked Perps home has visible market data and no observed tutorial gate."
|
|
85
|
+
"claim": "The unlocked Perps home has visible market data and no observed tutorial gate.",
|
|
86
|
+
"proofMode": "state"
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"id": "smoke",
|
|
90
|
+
"kind": "qa-check",
|
|
91
|
+
"claim": "The unlocked Perps home has visible market data and no observed tutorial gate.",
|
|
92
|
+
"proofMode": "state"
|
|
119
93
|
}
|
|
120
94
|
]
|
|
121
95
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deeeed/metamask-harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.92.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"mm-harness": "bin/mm-harness"
|
|
@@ -21,15 +21,15 @@
|
|
|
21
21
|
"check:syntax": "find . -name '*.mjs' -print0 | xargs -0 -n1 node --check"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@farmslot/adapter-node": "0.1.
|
|
25
|
-
"@farmslot/adapter-rn": "0.
|
|
26
|
-
"@farmslot/adapter-sdk": "0.
|
|
27
|
-
"@farmslot/adapter-web": "0.
|
|
28
|
-
"@farmslot/agent-runtime": "0.
|
|
24
|
+
"@farmslot/adapter-node": "0.1.17",
|
|
25
|
+
"@farmslot/adapter-rn": "0.22.1",
|
|
26
|
+
"@farmslot/adapter-sdk": "0.12.0",
|
|
27
|
+
"@farmslot/adapter-web": "0.8.2",
|
|
28
|
+
"@farmslot/agent-runtime": "0.20.1",
|
|
29
29
|
"@farmslot/handoff": "^0.3.1",
|
|
30
|
-
"@farmslot/protocol": "0.
|
|
31
|
-
"@farmslot/recipe-cli": "0.
|
|
32
|
-
"@farmslot/recipe-runner": "0.
|
|
30
|
+
"@farmslot/protocol": "0.43.0",
|
|
31
|
+
"@farmslot/recipe-cli": "0.16.1",
|
|
32
|
+
"@farmslot/recipe-runner": "0.34.0",
|
|
33
33
|
"agent-device": "0.19.3",
|
|
34
34
|
"commander": "^12.0.0",
|
|
35
35
|
"esbuild": "0.28.1",
|