@deeeed/metamask-harness 0.51.7 → 0.51.9
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 +14 -0
- package/adapters/mobile/start-metro.sh +21 -0
- package/adapters/mobile/stop-metro.sh +4 -2
- package/dist/adapters/mobile/prepare.js +5 -0
- package/dist/adapters/mobile/runtime-decision.js +62 -0
- package/docs/RECIPES.md +23 -5
- package/library/manifests/core.action-manifest.json +4 -4
- package/library/manifests/extension.action-manifest.json +3 -3
- package/library/manifests/mobile.action-manifest.json +4 -4
- package/package.json +1 -1
- package/site/assets/help-recipes.json +5 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
## 0.51.9 - 2026-09-16
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Recipe guidance states how Perps testnet account state is handled: on an account or market the task owns, the proof converges the positions and orders it needs (team library `perps.ensure-market-state` / `perps.clean-market-testnet`, or Mobile/Core `ensure_positions` / `ensure_orders` with `mode=all` for strays) instead of refusing on a pre-existing position; Extension `ensure_*` stay assertion-only. `help`, `docs/RECIPES.md`, and the Perps mutation action descriptions say so. Mainnet stays read-only.
|
|
10
|
+
|
|
11
|
+
## 0.51.8 - 2026-09-15
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- Mobile launch replaces a Metro process that Metro itself reported unusable (delta-graph corruption after a commit failed mid-way, or its "Detected a change in babel.config.js. Restart the server" advisory) by stopping it before `start-metro`, keeping the transform cache. Previously every "restart Metro" verdict and the `metro.restarted` recovery reused the live listener, so a Metro that outlived a bundler-config change failed every bundle until someone killed it by hand. Plain bundle errors still never restart Metro, and `--clear` stays explicit. `start-metro` now records the content hashes of the bundler config files so an advisory raised by an mtime-only touch is ignored; a Metro started before this release has no record, so its first advisory restarts it once.
|
|
16
|
+
|
|
3
17
|
## 0.51.7 - 2026-09-14
|
|
4
18
|
|
|
5
19
|
### Added
|
|
@@ -257,6 +257,27 @@ printf '(Full log: %s — mm-harness logs | mm-harness logs --full)\n' "$LOG_FIL
|
|
|
257
257
|
ROTATION_REASON="metro-start"
|
|
258
258
|
[ "$CLEAR" = true ] && ROTATION_REASON="clear-cache-restart"
|
|
259
259
|
node "$METRO_LOG_GENERATION" "$LOG_DIR" "$PORT" "$ROTATION_REASON" >/dev/null || exit 1
|
|
260
|
+
# Record what this process loads from the files Expo's FileNotifier watches, so
|
|
261
|
+
# the readiness decision can tell a real config change from an mtime-only touch
|
|
262
|
+
# when Metro later prints its "Detected a change in <file>" advisory.
|
|
263
|
+
record_metro_config_hashes() {
|
|
264
|
+
local file hash hashes=""
|
|
265
|
+
for file in babel.config.js metro.config.js metro.transform.js app.json app.config.js; do
|
|
266
|
+
[ -f "$TARGET/$file" ] || continue
|
|
267
|
+
if command -v shasum >/dev/null 2>&1; then
|
|
268
|
+
hash="$(shasum -a 256 "$TARGET/$file" | cut -d' ' -f1)"
|
|
269
|
+
else
|
|
270
|
+
hash="$(sha256sum "$TARGET/$file" | cut -d' ' -f1)"
|
|
271
|
+
fi
|
|
272
|
+
# An entry without a hash could never match and would force a restart on
|
|
273
|
+
# every advisory; leave the file out so the decision fails closed to
|
|
274
|
+
# "changed" for that file only, visibly.
|
|
275
|
+
[ -n "$hash" ] || continue
|
|
276
|
+
hashes+="$hash $file"$'\n'
|
|
277
|
+
done
|
|
278
|
+
printf '%s' "$hashes" > "$LOG_DIR/metro-config.sha256"
|
|
279
|
+
}
|
|
280
|
+
record_metro_config_hashes
|
|
260
281
|
|
|
261
282
|
write_metro_build_env || {
|
|
262
283
|
printf 'start-metro: could not stage the scoped Metro environment\n' >&2
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
set -euo pipefail
|
|
7
7
|
|
|
8
8
|
TARGET="."
|
|
9
|
-
|
|
9
|
+
# Same fallback order as start-metro so stop and start always agree on the port.
|
|
10
|
+
PORT="${WATCHER_PORT:-${METRO_PORT:-}}"
|
|
10
11
|
while [ $# -gt 0 ]; do
|
|
11
12
|
case "$1" in
|
|
12
13
|
--target) TARGET="$2"; shift 2 ;;
|
|
@@ -14,7 +15,7 @@ while [ $# -gt 0 ]; do
|
|
|
14
15
|
-h|--help)
|
|
15
16
|
printf 'Usage: stop-metro.sh [--target <dir>] [--port <port>]\n'
|
|
16
17
|
printf ' --target MetaMask Mobile checkout directory\n'
|
|
17
|
-
printf ' --port Metro port (default: WATCHER_PORT env, else the slot context, else 8081)\n'
|
|
18
|
+
printf ' --port Metro port (default: WATCHER_PORT env, else METRO_PORT, else the slot context, else 8081)\n'
|
|
18
19
|
exit 0
|
|
19
20
|
;;
|
|
20
21
|
*) printf 'stop-metro: unknown arg: %s\n' "$1" >&2; exit 2 ;;
|
|
@@ -63,6 +64,7 @@ set +e
|
|
|
63
64
|
rm -f "$PID_FILE"
|
|
64
65
|
rm -f "$LOG_DIR/metro-launch.json"
|
|
65
66
|
rm -f "$LOG_DIR/metro-build-env.sh"
|
|
67
|
+
rm -f "$LOG_DIR/metro-config.sha256"
|
|
66
68
|
|
|
67
69
|
# start-metro runs a per-checkout console-forwarder holding the device debugger
|
|
68
70
|
# slot; Metro going down must take it too, or the orphan keeps polling and later
|
|
@@ -386,6 +386,11 @@ async function dispatchAction(action, target, platform, json, preflightMode = "f
|
|
|
386
386
|
const leaf = path.join(runnerDir, "adapters/mobile/yarn-setup.sh");
|
|
387
387
|
return spawnScriptStreaming(leaf, ["--target", cwd], target, POD_PROBE_ENV);
|
|
388
388
|
}
|
|
389
|
+
case "stop-metro": {
|
|
390
|
+
const leaf = path.join(runnerDir, "adapters/mobile/stop-metro.sh");
|
|
391
|
+
const port = watcherPort ?? Number(process.env.WATCHER_PORT || process.env.METRO_PORT || 8081);
|
|
392
|
+
return spawnScriptStreaming(leaf, ["--target", cwd, "--port", String(port)], target);
|
|
393
|
+
}
|
|
389
394
|
case "start-metro": {
|
|
390
395
|
const leaf = path.join(runnerDir, "adapters/mobile/start-metro.sh");
|
|
391
396
|
const extra = action.argv ?? [];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
2
3
|
import fs from "node:fs";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import {
|
|
@@ -16,6 +17,12 @@ import { mobileProductMarkers } from "./deps-markers.js";
|
|
|
16
17
|
import { mobileMetroEnvCheck } from "./metro-env.js";
|
|
17
18
|
const BUNDLE_ERR = /Bundling failed|Unable to resolve /u;
|
|
18
19
|
const BUNDLE_OK = /Bundled \d+ms|iOS Bundled|Android Bundled|Finished bundling/u;
|
|
20
|
+
const METRO_GRAPH_ROLLBACK = /attempted to roll back a graph commit but there were still changes/u;
|
|
21
|
+
const METRO_GRAPH_COMMIT_THROW = /Got unexpected undefined/u;
|
|
22
|
+
const METRO_GRAPH_FRAME = /DeltaBundler\/Graph\.js/u;
|
|
23
|
+
const METRO_RESTART_ADVISORY = /Detected a change in ([\w.-]+)\. Restart the server/u;
|
|
24
|
+
const METRO_REASON_MAX = 200;
|
|
25
|
+
const METRO_CONFIG_HASHES_FILE = "metro-config.sha256";
|
|
19
26
|
const NATIVE_MODULE_STALE = /\[runtime not ready\].*HybridObject "([^"]+)" - It has not yet been registered in the Nitro Modules HybridObjectRegistry/u;
|
|
20
27
|
function resolveMetroLog(target, metroLog) {
|
|
21
28
|
const abs = metroLog ? path.isAbsolute(metroLog) ? metroLog : path.join(target, metroLog) : recipeRuntimePath(target, "metro.log");
|
|
@@ -87,6 +94,46 @@ function appRunningOnDevice(platform) {
|
|
|
87
94
|
return false;
|
|
88
95
|
}
|
|
89
96
|
}
|
|
97
|
+
function metroConfigUnchangedSinceStart(target, relative) {
|
|
98
|
+
let recorded;
|
|
99
|
+
try {
|
|
100
|
+
recorded = fs.readFileSync(recipeRuntimePath(target, METRO_CONFIG_HASHES_FILE), "utf8");
|
|
101
|
+
} catch {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
const entry = recorded.split("\n").find((line) => line.endsWith(` ${relative}`));
|
|
105
|
+
if (!entry) return false;
|
|
106
|
+
try {
|
|
107
|
+
const current = createHash("sha256").update(fs.readFileSync(path.join(target, relative))).digest("hex");
|
|
108
|
+
return entry.startsWith(current);
|
|
109
|
+
} catch {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function metroProcessUnusable(target, logText) {
|
|
114
|
+
const lines = logText.split("\n");
|
|
115
|
+
let lastOk = -1;
|
|
116
|
+
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
|
117
|
+
if (BUNDLE_OK.test(lines[i])) {
|
|
118
|
+
lastOk = i;
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
for (let i = lines.length - 1; i > lastOk; i -= 1) {
|
|
123
|
+
if (METRO_GRAPH_ROLLBACK.test(lines[i])) return lines[i].trim().slice(0, METRO_REASON_MAX);
|
|
124
|
+
if (METRO_GRAPH_COMMIT_THROW.test(lines[i]) && lines.slice(i + 1, i + 4).some((frame) => METRO_GRAPH_FRAME.test(frame))) {
|
|
125
|
+
return lines[i].trim().slice(0, METRO_REASON_MAX);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const advised = /* @__PURE__ */ new Set();
|
|
129
|
+
for (const line of lines) {
|
|
130
|
+
const advisory = METRO_RESTART_ADVISORY.exec(line);
|
|
131
|
+
if (!advisory || advised.has(advisory[1])) continue;
|
|
132
|
+
advised.add(advisory[1]);
|
|
133
|
+
if (!metroConfigUnchangedSinceStart(target, advisory[1])) return advisory[0];
|
|
134
|
+
}
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
90
137
|
const launchActions = (target, clearMetro = false) => {
|
|
91
138
|
const actions = [];
|
|
92
139
|
actions.push({
|
|
@@ -218,6 +265,21 @@ async function computeMobileReadiness(resolved, options, fast) {
|
|
|
218
265
|
]
|
|
219
266
|
};
|
|
220
267
|
}
|
|
268
|
+
const unusable = metro.status !== "down" && metroLog.reason !== "unresolved-module" ? metroProcessUnusable(resolved, metroLogText) : null;
|
|
269
|
+
if (unusable) {
|
|
270
|
+
return {
|
|
271
|
+
schemaVersion: 1,
|
|
272
|
+
adapter: "mobile",
|
|
273
|
+
target: resolved,
|
|
274
|
+
decision: "launch",
|
|
275
|
+
reasonCode: "metro-restart-required",
|
|
276
|
+
reasons: [
|
|
277
|
+
`Metro reported its process unusable ("${unusable}"); restarting Metro and keeping its transform cache. Pass --clear-metro only if the same failure returns in the new process.`
|
|
278
|
+
],
|
|
279
|
+
checks,
|
|
280
|
+
actions: [{ id: "stop-metro", cwd: resolved }, ...launchActions(resolved)]
|
|
281
|
+
};
|
|
282
|
+
}
|
|
221
283
|
if (metroLog.status === "errors") {
|
|
222
284
|
const depsSatisfied = deps.status === "current";
|
|
223
285
|
if (depsSatisfied && (metroLog.reason === "stale-bundle-error" || metroLog.reason === "bundle-error")) {
|
package/docs/RECIPES.md
CHANGED
|
@@ -192,10 +192,26 @@ transitions must use an explicitly funded leaf action.
|
|
|
192
192
|
The Perps team library owns `perps.clean-market-testnet` for Mobile and Core,
|
|
193
193
|
not Extension. Register that library before discovery or execution.
|
|
194
194
|
They require explicit `account`, `market` and `exclusive_testnet_market=true`.
|
|
195
|
-
That confirmation does not reserve the market
|
|
196
|
-
|
|
195
|
+
That confirmation does not reserve the market. Plan first and execute with
|
|
196
|
+
actual task ownership; on an account or market the task owns, converge the
|
|
197
|
+
state the proof needs (see Testnet account state) instead of refusing.
|
|
197
198
|
Planning or calling a recipe never weakens those gates.
|
|
198
199
|
|
|
200
|
+
### Testnet account state
|
|
201
|
+
|
|
202
|
+
On a testnet account or market the task owns for its run (the slot's fixture
|
|
203
|
+
account, or one reserved with `exclusive_testnet_market=true`), converging is an
|
|
204
|
+
explicit, ownership-scoped setup choice, not a violation. Use the team library's
|
|
205
|
+
`perps.ensure-market-state` or `perps.clean-market-testnet`, or on Mobile and
|
|
206
|
+
Core `metamask.perps.ensure_positions` / `metamask.perps.ensure_orders`
|
|
207
|
+
(`mode=all` for strays), record what was closed in the trace, then prove. A
|
|
208
|
+
pre-existing position or order on an account the task owns is never a reason to
|
|
209
|
+
block, skip, or refuse; a fixture that demands an empty account must converge
|
|
210
|
+
rather than refuse. Extension `ensure_*` and `teardown_state` only assert, so
|
|
211
|
+
converge there through an explicitly funded leaf action. Never broaden cleanup
|
|
212
|
+
onto an account or market the task does not own; obtain an owned one instead
|
|
213
|
+
of blocking. Mainnet is read-only.
|
|
214
|
+
|
|
199
215
|
### Mobile advanced-order setup
|
|
200
216
|
|
|
201
217
|
These recipes belong to the Perps team library. Register it once:
|
|
@@ -389,9 +405,11 @@ The Perps team library's `perps.trading-lifecycle` combines the Core long market
|
|
|
389
405
|
and limit examples. The harness retains read-only `perps.smoke` and
|
|
390
406
|
`perps.snapshot` examples; trading policy belongs to the team library.
|
|
391
407
|
It requires an explicit fixture account, market, and confirmation of exclusive
|
|
392
|
-
testnet ownership.
|
|
393
|
-
it
|
|
394
|
-
|
|
408
|
+
testnet ownership. Its initial assertions refuse non-flat state rather than
|
|
409
|
+
clearing it, so a task proof converges its owned account first (see Testnet
|
|
410
|
+
account state). Failure-path cleanup is guarded by completed
|
|
411
|
+
initial assertions and remains market-scoped, so it cannot safely share that
|
|
412
|
+
account/market with another task.
|
|
395
413
|
The limit branch uses the original 30%-below-mid offset; it must prove the order
|
|
396
414
|
rests rather than assume it cannot fill. These trading paths still require live
|
|
397
415
|
validation with a reserved funded testnet account.
|
|
@@ -1157,7 +1157,7 @@
|
|
|
1157
1157
|
]
|
|
1158
1158
|
},
|
|
1159
1159
|
"metamask.perps.close_positions": {
|
|
1160
|
-
"description": "core Close selected live Perps positions on HyperLiquid testnet by driving the headless perps controller closePosition() through the full signing/provider path. Testnet only.",
|
|
1160
|
+
"description": "core Close selected live Perps positions on HyperLiquid testnet by driving the headless perps controller closePosition() through the full signing/provider path. Testnet only. Pre-existing testnet state on an account the task owns is converged, not refused; mainnet is never mutated.",
|
|
1161
1161
|
"schema": {
|
|
1162
1162
|
"type": "object",
|
|
1163
1163
|
"properties": {
|
|
@@ -1427,7 +1427,7 @@
|
|
|
1427
1427
|
"execution_capabilities": []
|
|
1428
1428
|
},
|
|
1429
1429
|
"metamask.perps.ensure_positions": {
|
|
1430
|
-
"description": "core Higher-level wrapper that reads selected positions, places or closes via the headless controller as needed, then asserts the final position state. Testnet only.",
|
|
1430
|
+
"description": "core Higher-level wrapper that reads selected positions, places or closes via the headless controller as needed, then asserts the final position state. Testnet only. Pre-existing testnet state on an account the task owns is converged, not refused; mainnet is never mutated.",
|
|
1431
1431
|
"schema": {
|
|
1432
1432
|
"type": "object",
|
|
1433
1433
|
"properties": {
|
|
@@ -1580,7 +1580,7 @@
|
|
|
1580
1580
|
]
|
|
1581
1581
|
},
|
|
1582
1582
|
"metamask.perps.close_orders": {
|
|
1583
|
-
"description": "Core Perps cancellation selects current orders by market/side or mode=all, then dispatches and verifies those exact order IDs. Missing or duplicate selected IDs fail before dispatch. Requires authorized signing and task-owned selection; defaults to testnet.",
|
|
1583
|
+
"description": "Core Perps cancellation selects current orders by market/side or mode=all, then dispatches and verifies those exact order IDs. Missing or duplicate selected IDs fail before dispatch. Requires authorized signing and task-owned selection; defaults to testnet. Pre-existing testnet state on an account the task owns is converged, not refused; mainnet is never mutated.",
|
|
1584
1584
|
"schema": {
|
|
1585
1585
|
"type": "object",
|
|
1586
1586
|
"properties": {
|
|
@@ -1914,7 +1914,7 @@
|
|
|
1914
1914
|
"execution_capabilities": []
|
|
1915
1915
|
},
|
|
1916
1916
|
"metamask.perps.ensure_orders": {
|
|
1917
|
-
"description": "core Default to testnet for Perps mutations; mainnet is read-only unless explicitly requested.Higher-level wrapper that reads selected orders, cancels when needed, then asserts final order state.",
|
|
1917
|
+
"description": "core Default to testnet for Perps mutations; mainnet is read-only unless explicitly requested.Higher-level wrapper that reads selected orders, cancels when needed, then asserts final order state. Pre-existing testnet state on an account the task owns is converged, not refused; mainnet is never mutated.",
|
|
1918
1918
|
"schema": {
|
|
1919
1919
|
"type": "object",
|
|
1920
1920
|
"properties": {
|
|
@@ -4018,7 +4018,7 @@
|
|
|
4018
4018
|
"execution_capabilities": []
|
|
4019
4019
|
},
|
|
4020
4020
|
"metamask.perps.close_orders": {
|
|
4021
|
-
"description": "extension Default to testnet for Perps mutations; mainnet is read-only unless explicitly requested. Cancel selected live Perps open orders. Use selector/mode params to cancel matching symbols or all orders.",
|
|
4021
|
+
"description": "extension Default to testnet for Perps mutations; mainnet is read-only unless explicitly requested. Cancel selected live Perps open orders. Use selector/mode params to cancel matching symbols or all orders. Extension ensure_* only assert existing state; converge an owned testnet account through an explicitly funded leaf action. Mainnet is never mutated.",
|
|
4022
4022
|
"schema": {
|
|
4023
4023
|
"type": "object",
|
|
4024
4024
|
"properties": {
|
|
@@ -4595,7 +4595,7 @@
|
|
|
4595
4595
|
"execution_capabilities": []
|
|
4596
4596
|
},
|
|
4597
4597
|
"metamask.perps.ensure_positions": {
|
|
4598
|
-
"description": "Read and assert the selected Extension Perps position state without creating or closing a position. Use an explicitly funded leaf action for any transition.",
|
|
4598
|
+
"description": "Read and assert the selected Extension Perps position state without creating or closing a position. Use an explicitly funded leaf action for any transition. Extension ensure_* only assert existing state; converge an owned testnet account through an explicitly funded leaf action. Mainnet is never mutated.",
|
|
4599
4599
|
"schema": {
|
|
4600
4600
|
"type": "object",
|
|
4601
4601
|
"properties": {
|
|
@@ -4724,7 +4724,7 @@
|
|
|
4724
4724
|
]
|
|
4725
4725
|
},
|
|
4726
4726
|
"metamask.perps.ensure_orders": {
|
|
4727
|
-
"description": "Read and assert the selected Extension Perps order state without creating or canceling an order. Use an explicitly funded leaf action for any transition.",
|
|
4727
|
+
"description": "Read and assert the selected Extension Perps order state without creating or canceling an order. Use an explicitly funded leaf action for any transition. Extension ensure_* only assert existing state; converge an owned testnet account through an explicitly funded leaf action. Mainnet is never mutated.",
|
|
4728
4728
|
"schema": {
|
|
4729
4729
|
"type": "object",
|
|
4730
4730
|
"properties": {
|
|
@@ -2395,7 +2395,7 @@
|
|
|
2395
2395
|
"execution_capabilities": []
|
|
2396
2396
|
},
|
|
2397
2397
|
"metamask.perps.close_positions": {
|
|
2398
|
-
"description": "mobile Default to testnet for Perps mutations; mainnet is read-only unless explicitly requested. Close selected live Perps positions. Use selector/mode params to close matching symbols or all positions.",
|
|
2398
|
+
"description": "mobile Default to testnet for Perps mutations; mainnet is read-only unless explicitly requested. Close selected live Perps positions. Use selector/mode params to close matching symbols or all positions. Pre-existing testnet state on an account the task owns is converged, not refused; mainnet is never mutated.",
|
|
2399
2399
|
"schema": {
|
|
2400
2400
|
"type": "object",
|
|
2401
2401
|
"properties": {
|
|
@@ -2486,7 +2486,7 @@
|
|
|
2486
2486
|
"execution_capabilities": ["app-mutation", "external-mutation"]
|
|
2487
2487
|
},
|
|
2488
2488
|
"metamask.perps.close_orders": {
|
|
2489
|
-
"description": "mobile Default to testnet for Perps mutations; mainnet is read-only unless explicitly requested. Cancel selected live Perps open orders. Use selector/mode params to cancel matching symbols or all orders.",
|
|
2489
|
+
"description": "mobile Default to testnet for Perps mutations; mainnet is read-only unless explicitly requested. Cancel selected live Perps open orders. Use selector/mode params to cancel matching symbols or all orders. Pre-existing testnet state on an account the task owns is converged, not refused; mainnet is never mutated.",
|
|
2490
2490
|
"schema": {
|
|
2491
2491
|
"type": "object",
|
|
2492
2492
|
"properties": {
|
|
@@ -2886,7 +2886,7 @@
|
|
|
2886
2886
|
"execution_capabilities": []
|
|
2887
2887
|
},
|
|
2888
2888
|
"metamask.perps.ensure_positions": {
|
|
2889
|
-
"description": "mobile Default to testnet for Perps mutations; mainnet is read-only unless explicitly requested. Higher-level wrapper that reads selected positions, closes or places when needed, then asserts final position state.",
|
|
2889
|
+
"description": "mobile Default to testnet for Perps mutations; mainnet is read-only unless explicitly requested. Higher-level wrapper that reads selected positions, closes or places when needed, then asserts final position state. Pre-existing testnet state on an account the task owns is converged, not refused; mainnet is never mutated.",
|
|
2890
2890
|
"schema": {
|
|
2891
2891
|
"type": "object",
|
|
2892
2892
|
"properties": {
|
|
@@ -3002,7 +3002,7 @@
|
|
|
3002
3002
|
"execution_capabilities": ["app-mutation", "external-mutation"]
|
|
3003
3003
|
},
|
|
3004
3004
|
"metamask.perps.ensure_orders": {
|
|
3005
|
-
"description": "mobile Converge selected testnet orders to open or absent, then independently assert the final state. Creating an open state uses a resting limit order.",
|
|
3005
|
+
"description": "mobile Converge selected testnet orders to open or absent, then independently assert the final state. Creating an open state uses a resting limit order. Pre-existing testnet state on an account the task owns is converged, not refused; mainnet is never mutated.",
|
|
3006
3006
|
"schema": {
|
|
3007
3007
|
"type": "object",
|
|
3008
3008
|
"properties": {
|
package/package.json
CHANGED
|
@@ -191,12 +191,15 @@
|
|
|
191
191
|
"Performance: define a measured window, exclude setup, and compare equivalent builds, devices, and lifecycle conditions; action duration alone is not app latency.",
|
|
192
192
|
{
|
|
193
193
|
"text": "For Extension timing, inspect app.performance_capture/app.network_capture and the installed recipe-performance capture-extension-window.mjs helper before writing a collector. If LavaMoat blocks an observation API, capture its reference before initialization via Page.addScriptToEvaluateOnNewDocument during explicit setup, not inside the measured window. Keep domain predicates in the team recipe; see docs/RECIPES.md#extension-timing-observation.",
|
|
194
|
-
"adapters": [
|
|
194
|
+
"adapters": [
|
|
195
|
+
"extension"
|
|
196
|
+
]
|
|
195
197
|
},
|
|
196
198
|
"Retain samples and declared limits when a measurement fails. For memory growth, compare an equal-duration idle control before attributing it to navigation; do not raise the budget to make the run pass.",
|
|
197
199
|
"Analytics: verify the collection destination and existing consent before capture. Changing participation or marketing consent requires operator approval and restoration of the prior settings; a recipe's hardcoded opt-in is not permission. Missing collection prerequisites are not missing product events.",
|
|
198
200
|
"Flake risk: wait on observable state instead of sleeping, keep device and runtime identity explicit, and never overwrite a prior run's artifacts.",
|
|
199
|
-
"Mutation: require an independent state assertion and identity-bound receipt. Bind activity to that operation's accepted ID; an old row or nonzero count is insufficient. Opening and closing orders have different IDs. Schedule authorized cleanup in teardown, verify its result, and report any residue. Testnet cleanup
|
|
201
|
+
"Mutation: require an independent state assertion and identity-bound receipt. Bind activity to that operation's accepted ID; an old row or nonzero count is insufficient. Opening and closing orders have different IDs. Schedule authorized cleanup in teardown, verify its result, and report any residue. Testnet cleanup closes what the task opened and never silently undoes another task's live proof on a reserved account or market.",
|
|
202
|
+
"Testnet account state: on a testnet account or market the task owns for its run (the slot's fixture account, or one reserved with exclusive_testnet_market=true), converging is an explicit, ownership-scoped setup choice, not a violation. Use the team library's perps.ensure-market-state or perps.clean-market-testnet, or on Mobile and Core metamask.perps.ensure_positions / metamask.perps.ensure_orders (mode=all for strays), record what was closed in the trace, then prove. A pre-existing position or order on an account the task owns is never a reason to block, skip, or refuse, and a fixture that demands an empty account must converge rather than refuse. Extension ensure_* and teardown_state only assert; converge there through an explicitly funded leaf action. Never broaden cleanup onto an account or market the task does not own; obtain an owned one instead of blocking. Mainnet is read-only.",
|
|
200
203
|
{
|
|
201
204
|
"text": "An accepted order can fill before the UI updates. Preserve its receipt even when the expected resting-order assertion fails; one empty read does not prove cleanup. Check the read API's cache and freshness options before adding polling. Reconcile returned margin against the pre-placement balance. A successful close does not turn the original failed proof into a pass.",
|
|
202
205
|
"adapters": [
|