@deeeed/metamask-harness 0.57.0 → 0.59.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 +19 -0
- package/adapters/extension/inject.mjs +0 -1
- package/adapters/extension/live.sh +11 -14
- package/adapters/manifest.json +7 -7
- package/adapters/mobile/inject.sh +4 -7
- package/adapters/shared/resolve-slot-ports-core.mjs +47 -19
- package/dist/adapters/extension/runtime-decision.js +1 -2
- package/dist/adapters/extension/runtime.js +4 -3
- package/dist/adapters/extension/verify.js +608 -0
- package/dist/adapters/mobile/verify.js +549 -0
- package/dist/adapters/resolve-slot-ports.js +2 -0
- package/dist/cli.js +1 -1
- package/dist/commands/run-engine.js +2 -2
- package/dist/execution-provenance.js +16 -28
- package/dist/harness.js +34 -18
- package/package.json +2 -2
- package/adapters/extension/verify.sh +0 -572
- package/adapters/mobile/verify.sh +0 -557
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.59.0 - 2026-09-18
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Extension `verify` is runner-owned TypeScript: `adapters/extension/verify.sh` (572 lines, with node heredocs assembling `summary.json`, the fixture status and the CDP holder probe) is now `src/adapters/extension/verify.ts`, run in-process by `mm-harness verify --adapter extension`. Same flags, env, exit codes, check names and artifacts (`summary.json`, `artifact-manifest.json`, `logs/`, `runner-smoke/`); a live run against the same checkout produced a byte-identical normalised summary. Deliberate differences: `install` no longer copies a `scripts/verify.sh` into the checkout and dispatch no longer prefers one, so an older overlay cannot shadow the current runner's verify; the two shell-lib preflights (`harness-path.sh`, `json-field.sh`) are gone because the typed helpers cannot be missing; usage now names `mm-harness verify --adapter extension`; a `--target` that does not exist is a named exit-1 refusal instead of a bare `cd` failure; and `live.sh` reaches verify through the checkout's runner delegate, refusing early when that delegate is absent. Three more deliberate differences: `--artifacts-dir` is resolved to an absolute path once (the shell resolved a relative value against the cwd for its own files and against the target for the smoke run); `readiness.mjs` runs under the harness's own node instead of `PATH` `node`; and `live.sh`, having just installed the overlay, tells verify to skip the overlay re-ensure (`MM_HARNESS_SKIP_OVERLAY_ENSURE=1`) instead of reinstalling it between launch and the live probe.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Slot identity resolves against this machine's pool first. Every farm node keeps the same checkout paths, so a repo path matched a slot in every `pool/*.json` and the alphabetically first pool won: `doctor --fix` on macwork stamped `metamask-extension-2` as `macpro-mme-2` on CDP 7662 instead of `macwork-mmedev-2` on 6662. Pools whose `machine` or `host` is this host are consulted first; other machines' pools only when none of ours names the repo. A malformed pool file is skipped like the port resolver already did, and `*.bak` files never count.
|
|
14
|
+
- An invalid `RECIPE_RUNTIME_DIR` no longer crashes every command at startup with a stack trace. The extension runtime decision read it while loading; it reads it when a build-log check needs it, so `--help` still works and the command that needs the value reports it.
|
|
15
|
+
|
|
16
|
+
## 0.58.0 - 2026-09-18
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- Pins `@farmslot/agent-runtime` 0.12.0, whose `mark` records step names through the protocol's `checklistStepName`, so SIGNAL.json events and `status --watch` name a step identically.
|
|
21
|
+
- Mobile `verify` is runner-owned TypeScript: `adapters/mobile/verify.sh` (557 lines, with node heredocs assembling `summary.json`) is now `src/adapters/mobile/verify.ts`, run in-process by `mm-harness verify --adapter mobile`. Same flags, env, exit codes, check names and artifacts (`summary.json`, `artifact-manifest.json`, `logs/`, smoke recipes, `runner-live-smoke/`); a live run against the same checkout produced a byte-identical normalised summary. `install` no longer copies a `scripts/verify.sh` into the checkout and dispatch no longer prefers one, so an older overlay cannot shadow the current runner's verify; the mobile overlay's `scripts/` tree is gone and its manifest advertises `runtimeHelpers: {}`, like Core. Extension verify is unchanged.
|
|
22
|
+
- The execution-provenance record is indexed into `artifact-manifest.json` through the same hardened manifest writer every other artifact uses (`indexArtifactManifest`), instead of a private copy that skipped a missing or unreadable manifest without a word. A manifest the run said it wrote but cannot be read is now the error it is.
|
|
23
|
+
|
|
5
24
|
## 0.57.0 - 2026-09-18
|
|
6
25
|
|
|
7
26
|
### Changed
|
|
@@ -114,7 +114,6 @@ copyFile(path.join(runnerDir, 'adapters/extension/sidepanel-toggle.sh'), path.jo
|
|
|
114
114
|
copyFile(path.join(runnerDir, 'adapters/extension/reattach.sh'), path.join(harnessDir, 'scripts/reattach.sh'));
|
|
115
115
|
copyFile(path.join(runnerDir, 'adapters/extension/readiness.mjs'), path.join(harnessDir, 'scripts/readiness.mjs'));
|
|
116
116
|
copyFile(path.join(runnerDir, 'adapters/extension/wallet-fixture-state.cjs'), path.join(harnessDir, 'scripts/wallet-fixture-state.cjs'));
|
|
117
|
-
copyFile(path.join(runnerDir, 'adapters/extension/verify.sh'), path.join(harnessDir, 'scripts/verify.sh'));
|
|
118
117
|
copyFile(path.join(runnerDir, 'adapters/extension/refresh-build.sh'), path.join(harnessDir, 'scripts/refresh-build.sh'));
|
|
119
118
|
copyFile(path.join(runnerDir, 'adapters/extension/ensure-browser.sh'), path.join(harnessDir, 'scripts/reopen-browser.sh'));
|
|
120
119
|
copyFile(path.join(runnerDir, 'adapters/shared/harness-path.sh'), path.join(harnessDir, 'scripts/lib/harness-path.sh'));
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
# Purpose:
|
|
7
7
|
# Builds the per-run prepare command from the named feature scripts
|
|
8
8
|
# (watch, dist snapshot, fixture seed, detached chrome, CDP poll), then
|
|
9
|
-
# runs launch.sh and, unless --launch-only is set, verify
|
|
10
|
-
# timestamped artifact dir.
|
|
9
|
+
# runs launch.sh and, unless --launch-only is set, `mm-harness verify` under
|
|
10
|
+
# a timestamped artifact dir.
|
|
11
11
|
#
|
|
12
12
|
# Inputs (flags / env):
|
|
13
13
|
# --target <metamask-extension> (default $PWD)
|
|
@@ -128,17 +128,6 @@ case "$CDP_PORT" in
|
|
|
128
128
|
*[!0-9]*) echo "Invalid --cdp-port (must be numeric): $CDP_PORT" >&2; exit 2 ;;
|
|
129
129
|
esac
|
|
130
130
|
|
|
131
|
-
# verify.sh is a recipe-tree feature: co-located in installed copies, under
|
|
132
|
-
# adapters/extension in a repo checkout.
|
|
133
|
-
VERIFY_SH=""
|
|
134
|
-
for _v in "$SCRIPT_DIR/verify.sh" "$SCRIPT_DIR/../../adapters/extension/verify.sh"; do
|
|
135
|
-
[ -f "$_v" ] && { VERIFY_SH="$_v"; break; }
|
|
136
|
-
done
|
|
137
|
-
unset _v
|
|
138
|
-
if [ -z "$VERIFY_SH" ]; then
|
|
139
|
-
echo "mm-harness: extension verify.sh not found next to $SCRIPT_DIR; reinstall the runner." >&2
|
|
140
|
-
exit 1
|
|
141
|
-
fi
|
|
142
131
|
RUNTIME_DIR="$(recipe_runtime_dir)"
|
|
143
132
|
RUNTIME_DIST_DIR="${RECIPE_RUNTIME_DIST_DIR:-runtime-dist}"
|
|
144
133
|
case "$RUNTIME_DIST_DIR" in
|
|
@@ -152,6 +141,12 @@ CHROME_PROFILE_NAME="${RECIPE_CHROME_PROFILE_NAME:-chrome-profile}"
|
|
|
152
141
|
# to defer the cache-clear DECISION to `runtime-decision` (single source) and to
|
|
153
142
|
# record the deps/cache baseline after a confirmed-good build.
|
|
154
143
|
RUNNER_BIN="$(harness_dir "$TARGET" extension)/runner/bin/mm-harness"
|
|
144
|
+
# Verify is runner-owned typed code reached through the runner delegate, not a
|
|
145
|
+
# shipped leaf this sequencer could resolve next to itself.
|
|
146
|
+
if $RUN_VERIFY && [ ! -x "$RUNNER_BIN" ]; then
|
|
147
|
+
echo "mm-harness: runner delegate missing at $RUNNER_BIN; run mm-harness install --adapter extension --target $TARGET." >&2
|
|
148
|
+
exit 1
|
|
149
|
+
fi
|
|
155
150
|
OUT="${OUT:-$(harness_root)/extension/runner/recipes}"
|
|
156
151
|
ARTIFACTS="${ARTIFACTS:-$(harness_dir "$TARGET" extension)/live/$(date -u +%Y%m%dT%H%M%SZ)}"
|
|
157
152
|
mkdir -p "$ARTIFACTS/logs"
|
|
@@ -390,7 +385,9 @@ verify_status=0
|
|
|
390
385
|
if [ "$launch_status" -eq 0 ] && $RUN_VERIFY; then
|
|
391
386
|
echo "[recipe-harness] phase 2/2: live verify (readiness + smoke recipe)" >&2
|
|
392
387
|
set +e
|
|
393
|
-
|
|
388
|
+
# The overlay was installed by this sequencer moments ago; verify must not
|
|
389
|
+
# reinstall it between launch and the live probe.
|
|
390
|
+
MM_HARNESS_SKIP_OVERLAY_ENSURE=1 "$RUNNER_BIN" verify --adapter extension --target "$TARGET" -- --out "$OUT" --cdp-port "$CDP_PORT" --artifacts-dir "$ARTIFACTS/verify"
|
|
394
391
|
verify_status=$?
|
|
395
392
|
set -e
|
|
396
393
|
elif [ "$launch_status" -ne 0 ]; then
|
package/adapters/manifest.json
CHANGED
|
@@ -140,9 +140,9 @@
|
|
|
140
140
|
},
|
|
141
141
|
{
|
|
142
142
|
"id": "mobile/verify",
|
|
143
|
-
"entry": "adapters/mobile/verify.
|
|
144
|
-
"kind": "
|
|
145
|
-
"purpose": "Verify the mobile harness/runtime is present and healthy (no app launch).",
|
|
143
|
+
"entry": "src/adapters/mobile/verify.ts",
|
|
144
|
+
"kind": "module",
|
|
145
|
+
"purpose": "Verify the mobile harness/runtime is present and healthy (no app launch). Runner-owned: mm-harness verify runs it in-process.",
|
|
146
146
|
"inputs": "--target --artifacts-dir --static-only --platform ios|android --preflight-mode --no-auto-start",
|
|
147
147
|
"outputs": "verify report + evidence; exit 0/1/2"
|
|
148
148
|
},
|
|
@@ -164,10 +164,10 @@
|
|
|
164
164
|
},
|
|
165
165
|
{
|
|
166
166
|
"id": "extension/verify",
|
|
167
|
-
"entry": "adapters/extension/verify.
|
|
168
|
-
"kind": "
|
|
169
|
-
"purpose": "Verify the extension harness/runtime is present and healthy (no app launch).",
|
|
170
|
-
"inputs": "--target --out --cdp-port --static-only",
|
|
167
|
+
"entry": "src/adapters/extension/verify.ts",
|
|
168
|
+
"kind": "module",
|
|
169
|
+
"purpose": "Verify the extension harness/runtime is present and healthy (no app launch). Runner-owned: mm-harness verify runs it in-process.",
|
|
170
|
+
"inputs": "--target --out --cdp-port --static-only --artifacts-dir",
|
|
171
171
|
"outputs": "verify report + evidence; exit 0/1/2"
|
|
172
172
|
},
|
|
173
173
|
{
|
|
@@ -34,7 +34,7 @@ fi
|
|
|
34
34
|
HARNESS_ROOT="$(harness_root)"
|
|
35
35
|
HARNESS_REL="$HARNESS_ROOT/mobile"
|
|
36
36
|
HARNESS_DIR="$(harness_dir "$TARGET" mobile)"
|
|
37
|
-
for rel in "$HARNESS_REL" "$HARNESS_REL/runner"
|
|
37
|
+
for rel in "$HARNESS_REL" "$HARNESS_REL/runner"; do
|
|
38
38
|
current="$TARGET"
|
|
39
39
|
IFS='/' read -r -a parts <<< "$rel"
|
|
40
40
|
for part in "${parts[@]}"; do
|
|
@@ -45,7 +45,7 @@ for rel in "$HARNESS_REL" "$HARNESS_REL/runner" "$HARNESS_REL/scripts"; do
|
|
|
45
45
|
done
|
|
46
46
|
|
|
47
47
|
rm -rf "$HARNESS_DIR/runner" "$HARNESS_DIR/scripts" "$HARNESS_DIR/bridge-runtime"
|
|
48
|
-
mkdir -p "$HARNESS_DIR/runner/bin" "$HARNESS_DIR/runner/manifests" "$HARNESS_DIR/runner/recipes"
|
|
48
|
+
mkdir -p "$HARNESS_DIR/runner/bin" "$HARNESS_DIR/runner/manifests" "$HARNESS_DIR/runner/recipes"
|
|
49
49
|
|
|
50
50
|
RUNNER_EXEC_Q="$(printf '%q' "$RUNNER_DIR/bin/mm-harness")"
|
|
51
51
|
TARGET_Q="$(printf '%q' "$TARGET")"
|
|
@@ -76,10 +76,7 @@ cp "$RUNNER_DIR/library/manifests/extension.action-manifest.json" "$HARNESS_DIR/
|
|
|
76
76
|
rsync -a --delete "$RUNNER_DIR/library/recipes/" "$HARNESS_DIR/runner/recipes/"
|
|
77
77
|
rm -rf "$HARNESS_DIR/runner/flows"
|
|
78
78
|
rm -f "$HARNESS_DIR/runner/library.json"
|
|
79
|
-
cp "$RUNNER_DIR/adapters/mobile/verify.sh" "$HARNESS_DIR/scripts/verify.sh"
|
|
80
79
|
cp -R "$RUNNER_DIR/adapters/mobile/bridge-runtime" "$HARNESS_DIR/bridge-runtime"
|
|
81
|
-
cp "$RUNNER_DIR/adapters/shared/harness-path.sh" "$HARNESS_DIR/scripts/lib/harness-path.sh"
|
|
82
|
-
cp "$RUNNER_DIR/adapters/shared/path-defaults.json" "$HARNESS_DIR/scripts/lib/path-defaults.json"
|
|
83
80
|
chmod -R u+rwX,go+rX "$HARNESS_DIR"
|
|
84
81
|
|
|
85
82
|
add_git_exclude() {
|
|
@@ -114,8 +111,8 @@ fs.writeFileSync(manifestPath, JSON.stringify({
|
|
|
114
111
|
protocolVersion: 'v1',
|
|
115
112
|
actionManifestPath: `${harnessRel}/action-manifest.json`,
|
|
116
113
|
runnerEntrypoint: `${harnessRel}/runner/bin/mm-harness`,
|
|
117
|
-
runtimeHelpers: {
|
|
118
|
-
harnessInstalledPaths: [`${harnessRel}/runner`, `${harnessRel}/
|
|
114
|
+
runtimeHelpers: {},
|
|
115
|
+
harnessInstalledPaths: [`${harnessRel}/runner`, `${harnessRel}/bridge-runtime`, `${harnessRel}/action-manifest.json`],
|
|
119
116
|
productOwnedPaths: ['app/dev-tools/AgenticService', 'app/core/NavigationService/NavigationService.ts'],
|
|
120
117
|
cleanupCommand,
|
|
121
118
|
productDiffExcludes: [`:(exclude)${harnessRoot}`],
|
|
@@ -73,6 +73,35 @@ function scoreSlot(slot, slotSuffix) {
|
|
|
73
73
|
return [score, cdp, port, slotId, simulator, adbSerial, androidDevice];
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
// Pool files that describe this machine, then the rest. Every farm node keeps
|
|
77
|
+
// the same checkout paths, so a repo path alone matches a slot in every pool;
|
|
78
|
+
// the pool whose `machine`/`host` is this host is the one that owns the slot.
|
|
79
|
+
export function poolFilesForHost(poolDir, hostname = os.hostname()) {
|
|
80
|
+
// macOS may report a capitalised or FQDN hostname; pools carry lowercase names.
|
|
81
|
+
const canonical = (value) => String(value ?? '').toLowerCase().replace(/\.$/u, '');
|
|
82
|
+
const full = canonical(hostname);
|
|
83
|
+
const short = full.split('.')[0];
|
|
84
|
+
const names = (value) => {
|
|
85
|
+
const name = canonical(value);
|
|
86
|
+
return name ? [name, name.split('.')[0]] : [];
|
|
87
|
+
};
|
|
88
|
+
const local = [];
|
|
89
|
+
const other = [];
|
|
90
|
+
for (const name of fs.readdirSync(poolDir).sort()) {
|
|
91
|
+
// Backups (`*.json.bak.<ts>`, `*.bak`) never define a live slot.
|
|
92
|
+
if (!name.endsWith('.json') || name.includes('.bak')) continue;
|
|
93
|
+
let data;
|
|
94
|
+
try {
|
|
95
|
+
data = JSON.parse(fs.readFileSync(path.join(poolDir, name), 'utf8'));
|
|
96
|
+
} catch {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const isLocal = [...names(data.machine), ...names(data.host)].some((name) => name === full || name === short);
|
|
100
|
+
(isLocal ? local : other).push({ name, data });
|
|
101
|
+
}
|
|
102
|
+
return { local, other };
|
|
103
|
+
}
|
|
104
|
+
|
|
76
105
|
export function resolveSlotPortsByRepo(repo) {
|
|
77
106
|
const realRepo = realRepoPath(repo);
|
|
78
107
|
if (!realRepo) return null;
|
|
@@ -91,29 +120,28 @@ export function resolveSlotPortsByRepo(repo) {
|
|
|
91
120
|
for (const root of roots) {
|
|
92
121
|
if (!root || !fs.existsSync(path.join(root, 'pool'))) continue;
|
|
93
122
|
const poolDir = path.join(root, 'pool');
|
|
123
|
+
const pools = poolFilesForHost(poolDir);
|
|
94
124
|
let best = null;
|
|
95
125
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
slotReal
|
|
110
|
-
|
|
111
|
-
|
|
126
|
+
// This host's pools decide first; other machines' pools only when none of
|
|
127
|
+
// ours names the repo (single-node and demo setups).
|
|
128
|
+
for (const group of [pools.local, pools.other]) {
|
|
129
|
+
for (const { data } of group) {
|
|
130
|
+
for (const slot of data.slots ?? []) {
|
|
131
|
+
const slotRepo = slot.repo ?? '';
|
|
132
|
+
if (!slotRepo) continue;
|
|
133
|
+
let slotReal;
|
|
134
|
+
try {
|
|
135
|
+
slotReal = fs.realpathSync(slotRepo);
|
|
136
|
+
} catch {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (slotReal !== realRepo) continue;
|
|
140
|
+
const scored = scoreSlot(slot, slotSuffix);
|
|
141
|
+
if (!best || scored[0] > best[0]) best = scored;
|
|
112
142
|
}
|
|
113
|
-
if (slotReal !== realRepo) continue;
|
|
114
|
-
const scored = scoreSlot(slot, slotSuffix);
|
|
115
|
-
if (!best || scored[0] > best[0]) best = scored;
|
|
116
143
|
}
|
|
144
|
+
if (best) break;
|
|
117
145
|
}
|
|
118
146
|
|
|
119
147
|
if (best) {
|
|
@@ -11,7 +11,6 @@ import { recipeHarnessPath, recipeRuntimeDir, recipeWatchLogCandidates } from ".
|
|
|
11
11
|
const WEBPACK_DIRECT_INPUTS = ["package.json", "yarn.lock", ".yarnrc.yml", ".tool-versions"];
|
|
12
12
|
const WEBPACK_RECURSIVE_INPUTS = ["development/webpack"];
|
|
13
13
|
const WEBPACK_CACHE_DIR = "node_modules/.cache/webpack";
|
|
14
|
-
const WATCH_LOG_CANDIDATES = recipeWatchLogCandidates();
|
|
15
14
|
function git(target, args) {
|
|
16
15
|
try {
|
|
17
16
|
return execFileSync("git", ["-C", target, ...args], {
|
|
@@ -95,7 +94,7 @@ function webpackCacheCheck(target) {
|
|
|
95
94
|
return { cachePresent: true, status, hasBaseline: true };
|
|
96
95
|
}
|
|
97
96
|
function buildLogCheck(target, watchLog) {
|
|
98
|
-
const candidates = (watchLog ? [path.isAbsolute(watchLog) ? watchLog : path.join(target, watchLog)] :
|
|
97
|
+
const candidates = (watchLog ? [path.isAbsolute(watchLog) ? watchLog : path.join(target, watchLog)] : recipeWatchLogCandidates().map((rel) => path.join(target, rel))).filter((file) => fs.existsSync(file));
|
|
99
98
|
if (!candidates.length) return { status: "no-watch" };
|
|
100
99
|
const newestFirst = [...candidates].sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs);
|
|
101
100
|
const lines = fs.readFileSync(newestFirst[0], "utf8").split("\n");
|
|
@@ -3,6 +3,7 @@ import fs from "node:fs";
|
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { importRecipeHarnessRuntimeBrowserExtension, importRecipeHarnessRuntimeCdp, recipeHarnessPath, recipeRuntimeDir, resolveLocalProtocolRoot, runnerDir } from "../../paths.js";
|
|
6
|
+
import { poolFilesForHost } from "../resolve-slot-ports.js";
|
|
6
7
|
import { profileProcessPids } from "./validation-process-ownership.js";
|
|
7
8
|
const require2 = createRequire(import.meta.url);
|
|
8
9
|
const { detachedLaunchUnprovenPath, hasDetachedLaunchUnproven, shellQuote } = require2(
|
|
@@ -543,9 +544,9 @@ function resolveExtensionSlot(projectRoot, requestedSlot) {
|
|
|
543
544
|
}
|
|
544
545
|
const poolsDir = path.join(protocolRoot, "pool");
|
|
545
546
|
if (!fs.existsSync(poolsDir)) return null;
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
const pool =
|
|
547
|
+
const pools = poolFilesForHost(poolsDir);
|
|
548
|
+
for (const { data } of [...pools.local, ...pools.other]) {
|
|
549
|
+
const pool = data;
|
|
549
550
|
for (const rawSlot of pool.slots ?? []) {
|
|
550
551
|
if (!rawSlot || typeof rawSlot !== "object") continue;
|
|
551
552
|
const slot = rawSlot;
|