@deeeed/metamask-harness 0.2.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 +161 -0
- package/README.md +140 -0
- package/bin/mm-harness +99 -0
- package/docs/CHEATSHEET.md +61 -0
- package/docs/CLI-SPEC.md +915 -0
- package/docs/MENTAL-MODEL.md +295 -0
- package/docs/architecture.md +367 -0
- package/docs/extension-runtime-commands.md +60 -0
- package/docs/harness-cli.md +43 -0
- package/docs/live-adapter-contract.md +188 -0
- package/docs/package-boundaries.md +47 -0
- package/docs/perps-flow-catalog.md +235 -0
- package/docs/recipe-libraries.md +95 -0
- package/docs/runtime-file-conventions.md +36 -0
- package/library/actions/core/perps/_controller.mjs +727 -0
- package/library/actions/core/perps/assert_orders.mjs +53 -0
- package/library/actions/core/perps/assert_positions.mjs +52 -0
- package/library/actions/core/perps/close_orders.mjs +97 -0
- package/library/actions/core/perps/close_positions.mjs +118 -0
- package/library/actions/core/perps/ensure_orders.mjs +40 -0
- package/library/actions/core/perps/ensure_positions.mjs +37 -0
- package/library/actions/core/perps/place_order.mjs +201 -0
- package/library/actions/core/perps/read_account.mjs +30 -0
- package/library/actions/core/perps/read_orders.mjs +27 -0
- package/library/actions/core/perps/read_positions.mjs +27 -0
- package/library/actions/core/perps/start_state.mjs +92 -0
- package/library/actions/core/perps/teardown_state.mjs +86 -0
- package/library/actions/extension/perps/assert_orders.mjs +11 -0
- package/library/actions/extension/perps/assert_positions.mjs +11 -0
- package/library/actions/extension/perps/close_orders.mjs +8 -0
- package/library/actions/extension/perps/close_positions.mjs +8 -0
- package/library/actions/extension/perps/ensure_orders.mjs +4 -0
- package/library/actions/extension/perps/ensure_positions.mjs +4 -0
- package/library/actions/extension/perps/perps.mjs +730 -0
- package/library/actions/extension/perps/place_order.mjs +7 -0
- package/library/actions/extension/perps/read_orders.mjs +4 -0
- package/library/actions/extension/perps/read_positions.mjs +3 -0
- package/library/actions/extension/platform/cdp.mjs +541 -0
- package/library/actions/extension/ui/navigate.mjs +44 -0
- package/library/actions/extension/wallet/ensure_unlocked.mjs +36 -0
- package/library/actions/extension/wallet/read_state.mjs +27 -0
- package/library/actions/extension/wallet/select_account.mjs +48 -0
- package/library/actions/extension/wallet/setup.mjs +35 -0
- package/library/actions/mobile/app-overlay/app/dev-tools/AgenticService/AgentStepHud.tsx.patch +185 -0
- package/library/actions/mobile/app-overlay/app/dev-tools/AgenticService/AgenticService.ts.patch +1662 -0
- package/library/actions/mobile/bridge-runtime/cdp-bridge.cjs +686 -0
- package/library/actions/mobile/bridge-runtime/lib/cdp-eval.cjs +110 -0
- package/library/actions/mobile/bridge-runtime/lib/config.cjs +39 -0
- package/library/actions/mobile/bridge-runtime/lib/issue-capture.cjs +446 -0
- package/library/actions/mobile/bridge-runtime/lib/target-discovery.cjs +204 -0
- package/library/actions/mobile/bridge-runtime/lib/ws-client.cjs +108 -0
- package/library/actions/mobile/bridge-runtime/setup-wallet.sh +442 -0
- package/library/actions/mobile/perps/assert_orders.mjs +11 -0
- package/library/actions/mobile/perps/assert_positions.mjs +11 -0
- package/library/actions/mobile/perps/close_orders.mjs +8 -0
- package/library/actions/mobile/perps/close_positions.mjs +8 -0
- package/library/actions/mobile/perps/ensure_orders.mjs +4 -0
- package/library/actions/mobile/perps/ensure_positions.mjs +4 -0
- package/library/actions/mobile/perps/perps.mjs +709 -0
- package/library/actions/mobile/perps/place_order.mjs +7 -0
- package/library/actions/mobile/perps/read_orders.mjs +4 -0
- package/library/actions/mobile/perps/read_positions.mjs +3 -0
- package/library/actions/mobile/platform/bridge.mjs +283 -0
- package/library/actions/mobile/ui/navigate.mjs +38 -0
- package/library/actions/mobile/wallet/ensure_unlocked.mjs +107 -0
- package/library/actions/mobile/wallet/home.mjs +35 -0
- package/library/actions/mobile/wallet/read_state.mjs +40 -0
- package/library/actions/mobile/wallet/select_account.mjs +48 -0
- package/library/actions/mobile/wallet/setup.mjs +220 -0
- package/library/flows/perps.flows.json +64 -0
- package/library/library.json +7 -0
- package/library/manifests/core.action-manifest.json +1282 -0
- package/library/manifests/extension.action-manifest.json +1749 -0
- package/library/manifests/mobile.action-manifest.json +1753 -0
- package/library/recipes/action-validation.extension.recipe.json +417 -0
- package/library/recipes/action-validation.mobile.recipe.json +422 -0
- package/library/recipes/order-lifecycle.core.recipe.json +78 -0
- package/library/recipes/perps-lifecycle.recipe.json +194 -0
- package/library/recipes/read-markets.core.recipe.json +38 -0
- package/library/recipes/smoke.extension.recipe.json +31 -0
- package/library/recipes/smoke.mobile.recipe.json +31 -0
- package/library/recipes/trading-lifecycle.core.recipe.json +76 -0
- package/orchestration/compat-overlays/README.md +19 -0
- package/orchestration/compat-overlays/mobile/README.md +13 -0
- package/orchestration/compat-overlays/mobile/rn81-message-event-source.patch +42 -0
- package/orchestration/core/cleanup.sh +37 -0
- package/orchestration/core/inject.sh +154 -0
- package/orchestration/doctor.mjs +72 -0
- package/orchestration/extension/cleanup.mjs +60 -0
- package/orchestration/extension/console-tail.mjs +228 -0
- package/orchestration/extension/ensure-browser.sh +416 -0
- package/orchestration/extension/ensure-ready.ts +185 -0
- package/orchestration/extension/extension-id.ts +107 -0
- package/orchestration/extension/inject.mjs +266 -0
- package/orchestration/extension/launch-browser.cjs +216 -0
- package/orchestration/extension/launch.sh +175 -0
- package/orchestration/extension/live.sh +320 -0
- package/orchestration/extension/pin-remote-flags.cjs +45 -0
- package/orchestration/extension/readiness.mjs +414 -0
- package/orchestration/extension/refresh-build.sh +190 -0
- package/orchestration/extension/runtime-decision.ts +445 -0
- package/orchestration/extension/runtime.ts +407 -0
- package/orchestration/extension/seed-fixture.sh +177 -0
- package/orchestration/extension/sidepanel-toggle.sh +291 -0
- package/orchestration/extension/snapshot-dist.sh +84 -0
- package/orchestration/extension/start-watch.sh +339 -0
- package/orchestration/extension/wallet-fixture-state.cjs +1086 -0
- package/orchestration/lib/activate-repo-node.sh +144 -0
- package/orchestration/lib/cli-color.mjs +84 -0
- package/orchestration/lib/cli-commands.mjs +243 -0
- package/orchestration/lib/cli-home.mjs +354 -0
- package/orchestration/lib/cli-ux.sh +252 -0
- package/orchestration/lib/cli-version.mjs +123 -0
- package/orchestration/lib/ensure-runner-deps.sh +56 -0
- package/orchestration/lib/harness-path.sh +55 -0
- package/orchestration/lib/hash-helpers.sh +44 -0
- package/orchestration/lib/json-field.sh +23 -0
- package/orchestration/lib/log-tui.mjs +304 -0
- package/orchestration/lib/open-debug.mjs +317 -0
- package/orchestration/lib/path-defaults.json +4 -0
- package/orchestration/lib/progress.mjs +107 -0
- package/orchestration/lib/recipe-paths.mjs +26 -0
- package/orchestration/lib/resolve-farmslot-ports.sh +144 -0
- package/orchestration/manifest.json +358 -0
- package/orchestration/mobile/cleanup.sh +192 -0
- package/orchestration/mobile/deps-markers.ts +21 -0
- package/orchestration/mobile/inject.sh +681 -0
- package/orchestration/mobile/launch.sh +137 -0
- package/orchestration/mobile/live.sh +125 -0
- package/orchestration/mobile/runtime-decision.ts +292 -0
- package/orchestration/porcelain/metamask-recipe +99 -0
- package/orchestration/porcelain/mm-recipe +1591 -0
- package/orchestration/porcelain/mme-recipe +1181 -0
- package/package.json +59 -0
- package/runner/extension/verify.sh +511 -0
- package/runner/mobile/verify.sh +501 -0
- package/runner/src/adapters.ts +601 -0
- package/runner/src/cli.ts +1820 -0
- package/runner/src/commands/debug.ts +44 -0
- package/runner/src/commands/fixtures.ts +99 -0
- package/runner/src/commands/launch.ts +397 -0
- package/runner/src/commands/logs.ts +60 -0
- package/runner/src/commands/shared.ts +138 -0
- package/runner/src/completions-cache.ts +86 -0
- package/runner/src/doctor.ts +203 -0
- package/runner/src/harness.ts +516 -0
- package/runner/src/heal-bounds.ts +179 -0
- package/runner/src/index.ts +6 -0
- package/runner/src/live-adapter-contract.ts +274 -0
- package/runner/src/manifest.ts +47 -0
- package/runner/src/mm-harness-cli.ts +488 -0
- package/runner/src/paths.ts +198 -0
- package/runner/src/recording-target.ts +147 -0
- package/runner/src/run-recording.ts +329 -0
- package/runner/src/runner.ts +108 -0
- package/runner/src/types.ts +57 -0
- package/scripts/completions.sh +125 -0
- package/scripts/install-completions.sh +62 -0
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@deeeed/metamask-harness",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"bin": {
|
|
6
|
+
"mm-harness": "bin/mm-harness"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"dev:link-farmslot": "node scripts/link-local-farmslot.mjs",
|
|
10
|
+
"check": "node scripts/check.mjs",
|
|
11
|
+
"self-test": "orchestration/porcelain/metamask-recipe self-test",
|
|
12
|
+
"manifest:mobile": "bin/mm-harness actions --raw --adapter mobile --json",
|
|
13
|
+
"manifest:extension": "bin/mm-harness actions --raw --adapter extension --json",
|
|
14
|
+
"check:syntax": "find . -name '*.mjs' -print0 | xargs -0 -n1 node --check"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@farmslot/protocol": "^0.7.2",
|
|
18
|
+
"@farmslot/recipe-harness": "^0.3.3",
|
|
19
|
+
"commander": "^12.0.0",
|
|
20
|
+
"tsx": "^4.19.0",
|
|
21
|
+
"viem": "^2.52.2"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/node": "^22.0.0",
|
|
25
|
+
"typescript": "^5.6.0"
|
|
26
|
+
},
|
|
27
|
+
"main": "./runner/src/index.ts",
|
|
28
|
+
"types": "./runner/src/index.ts",
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"types": "./runner/src/index.ts",
|
|
32
|
+
"import": "./runner/src/index.ts"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"bin",
|
|
40
|
+
"runner",
|
|
41
|
+
"orchestration",
|
|
42
|
+
"library",
|
|
43
|
+
"scripts/completions.sh",
|
|
44
|
+
"scripts/install-completions.sh",
|
|
45
|
+
"!scripts/README.md",
|
|
46
|
+
"docs",
|
|
47
|
+
"README.md",
|
|
48
|
+
"CHANGELOG.md"
|
|
49
|
+
],
|
|
50
|
+
"packageManager": "yarn@4.5.3",
|
|
51
|
+
"repository": {
|
|
52
|
+
"type": "git",
|
|
53
|
+
"url": "git+https://github.com/MetaMask/experimental-metamask-harness.git"
|
|
54
|
+
},
|
|
55
|
+
"bugs": {
|
|
56
|
+
"url": "https://github.com/MetaMask/experimental-metamask-harness/issues"
|
|
57
|
+
},
|
|
58
|
+
"homepage": "https://github.com/MetaMask/experimental-metamask-harness#readme"
|
|
59
|
+
}
|
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# verify.sh — extension slot verification (readiness/proof layer)
|
|
3
|
+
#
|
|
4
|
+
# Purpose:
|
|
5
|
+
# Recipe-tree verification for ONE extension instance: static dist checks,
|
|
6
|
+
# live readiness over CDP, and the smoke recipe run, aggregated into a
|
|
7
|
+
# verify summary.
|
|
8
|
+
#
|
|
9
|
+
# Inputs (flags / env):
|
|
10
|
+
# --target <metamask-extension> (default $PWD), --cdp-port <port>
|
|
11
|
+
# (numeric; empty = static-only), --out <recipes-dir> (validated under
|
|
12
|
+
# target), --static-only, --artifacts-dir <dir>
|
|
13
|
+
# env RECIPE_HARNESS_ROOT, RECIPE_RUNTIME_DIR
|
|
14
|
+
#
|
|
15
|
+
# Outputs:
|
|
16
|
+
# <artifacts>/summary.json + logs/ (readiness, recipe runs).
|
|
17
|
+
# Exit 0 — verify pass; 1 — checks failed / helpers missing; 2 — bad args
|
|
18
|
+
# or unsafe/missing --out.
|
|
19
|
+
#
|
|
20
|
+
# Never touches: product source files; the dist build (read-only);
|
|
21
|
+
# anything outside the harness dir and artifacts dir.
|
|
22
|
+
set -euo pipefail
|
|
23
|
+
|
|
24
|
+
TARGET="$PWD"
|
|
25
|
+
CDP_PORT=""
|
|
26
|
+
ARTIFACTS=""
|
|
27
|
+
STATIC_ONLY=false
|
|
28
|
+
OUT=""
|
|
29
|
+
require_value() { [ "$#" -ge 2 ] || { echo "Missing value for $1" >&2; exit 2; }; }
|
|
30
|
+
while [ "$#" -gt 0 ]; do
|
|
31
|
+
case "$1" in
|
|
32
|
+
--target) require_value "$@"; TARGET="$2"; shift 2 ;;
|
|
33
|
+
--out) require_value "$@"; OUT="$2"; shift 2 ;;
|
|
34
|
+
--cdp-port) require_value "$@"; CDP_PORT="$2"; shift 2 ;;
|
|
35
|
+
--artifacts-dir) require_value "$@"; ARTIFACTS="$2"; shift 2 ;;
|
|
36
|
+
--static-only) STATIC_ONLY=true; shift ;;
|
|
37
|
+
-h|--help) echo "Usage: verify.sh [--target <metamask-extension>] [--out <recipes-dir>] [--cdp-port <port>] [--static-only]"; exit 0 ;;
|
|
38
|
+
*) echo "Unknown arg: $1" >&2; exit 2 ;;
|
|
39
|
+
esac
|
|
40
|
+
done
|
|
41
|
+
|
|
42
|
+
# Reject a non-numeric --cdp-port before it is interpolated into shell/HTTP/CDP
|
|
43
|
+
# strings. Empty is allowed (static-only verify needs no port).
|
|
44
|
+
if [ -n "$CDP_PORT" ]; then
|
|
45
|
+
case "$CDP_PORT" in
|
|
46
|
+
*[!0-9]*) echo "Invalid --cdp-port (must be numeric): $CDP_PORT" >&2; exit 2 ;;
|
|
47
|
+
esac
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
51
|
+
# shellcheck disable=SC1091
|
|
52
|
+
for _hp in "$SCRIPT_DIR/lib/harness-path.sh" "$SCRIPT_DIR/../../orchestration/lib/harness-path.sh" "$SCRIPT_DIR/../lib/harness-path.sh"; do
|
|
53
|
+
[ -f "$_hp" ] && { . "$_hp"; break; }
|
|
54
|
+
done
|
|
55
|
+
unset _hp
|
|
56
|
+
if ! command -v harness_root >/dev/null 2>&1; then
|
|
57
|
+
echo "extension verify: shared lib orchestration/lib/harness-path.sh not found; reinstall the harness." >&2
|
|
58
|
+
exit 1
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
# Safe task-local --out resolution (kept from the legacy extension path.sh).
|
|
62
|
+
resolve_harness_out() {
|
|
63
|
+
local target="$1"
|
|
64
|
+
local out="$2"
|
|
65
|
+
node - "$target" "$out" <<'NODE'
|
|
66
|
+
const path = require('path');
|
|
67
|
+
|
|
68
|
+
const target = path.resolve(process.argv[2]);
|
|
69
|
+
const out = process.argv[3];
|
|
70
|
+
if (!out) process.exit(1);
|
|
71
|
+
if (out.split(/[\\/]+/).includes('..')) process.exit(1);
|
|
72
|
+
|
|
73
|
+
const resolved = path.resolve(target, out);
|
|
74
|
+
const relative = path.relative(target, resolved);
|
|
75
|
+
if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
process.stdout.write(resolved);
|
|
80
|
+
NODE
|
|
81
|
+
}
|
|
82
|
+
TARGET="$(cd "$TARGET" && pwd)"
|
|
83
|
+
RUNTIME_DIR="$(recipe_runtime_dir)"
|
|
84
|
+
HARNESS_ROOT="$(harness_root)"
|
|
85
|
+
HARNESS_REL="$HARNESS_ROOT/extension"
|
|
86
|
+
HARNESS_DIR="$(harness_dir "$TARGET" extension)"
|
|
87
|
+
RUNNER_BIN="$HARNESS_DIR/runner/bin/metamask-recipe"
|
|
88
|
+
# --out (optional): a task-local recipes dir. Resolve it safely within the target
|
|
89
|
+
# (resolve_harness_out rejects absolute/.. escapes) and prefer its smoke recipe so
|
|
90
|
+
# `live --out <dir>` does not silently fall back to the installed default.
|
|
91
|
+
SMOKE_RECIPE="$HARNESS_DIR/runner/recipes/smoke.extension.recipe.json"
|
|
92
|
+
if [ -n "$OUT" ]; then
|
|
93
|
+
# Fail fast on a task-local --out that does not contain the requested recipe.
|
|
94
|
+
# Silently falling back to the installed default would validate a different
|
|
95
|
+
# (possibly stale) recipe than the caller explicitly asked for.
|
|
96
|
+
OUT_ABS="$(resolve_harness_out "$TARGET" "$OUT" 2>/dev/null || true)"
|
|
97
|
+
if [ -z "$OUT_ABS" ]; then
|
|
98
|
+
echo "extension verify: --out '$OUT' did not resolve to a safe path under the target." >&2
|
|
99
|
+
exit 2
|
|
100
|
+
fi
|
|
101
|
+
if [ ! -f "$OUT_ABS/smoke.extension.recipe.json" ]; then
|
|
102
|
+
echo "extension verify: --out '$OUT' (resolved: $OUT_ABS) has no smoke.extension.recipe.json. Refusing to fall back to the installed default recipe; place the recipe under --out or omit --out." >&2
|
|
103
|
+
exit 2
|
|
104
|
+
fi
|
|
105
|
+
SMOKE_RECIPE="$OUT_ABS/smoke.extension.recipe.json"
|
|
106
|
+
fi
|
|
107
|
+
ARTIFACTS="${ARTIFACTS:-$HARNESS_DIR/verify/$(date -u +%Y%m%dT%H%M%SZ)}"
|
|
108
|
+
mkdir -p "$ARTIFACTS/logs"
|
|
109
|
+
EXTENSION_ID_FILE="$TARGET/$RUNTIME_DIR/extension.id"
|
|
110
|
+
# Shared JSON reader: $SCRIPT_DIR/lib when running from the installed copy,
|
|
111
|
+
# else the runner canonical at orchestration/lib.
|
|
112
|
+
# shellcheck disable=SC1091
|
|
113
|
+
for _lib in "$SCRIPT_DIR/lib/json-field.sh" "$SCRIPT_DIR/../../orchestration/lib/json-field.sh"; do
|
|
114
|
+
[ -f "$_lib" ] && { . "$_lib"; break; }
|
|
115
|
+
done
|
|
116
|
+
unset _lib
|
|
117
|
+
# Fail fast with an actionable message if neither path loaded (e.g. an install that
|
|
118
|
+
# predates the lib co-location), instead of a cryptic later `set -e` abort.
|
|
119
|
+
if ! command -v read_runtime_context_field >/dev/null 2>&1; then
|
|
120
|
+
echo "extension verify: json-field helper missing (orchestration/lib/json-field.sh). Run metamask-recipe extension prepare --target $TARGET" >&2
|
|
121
|
+
exit 1
|
|
122
|
+
fi
|
|
123
|
+
# readiness probe is app control: co-located in installed copies, under
|
|
124
|
+
# orchestration/extension in a repo checkout.
|
|
125
|
+
READINESS_MJS=""
|
|
126
|
+
for _r in "$SCRIPT_DIR/readiness.mjs" "$SCRIPT_DIR/../../orchestration/extension/readiness.mjs"; do
|
|
127
|
+
[ -f "$_r" ] && { READINESS_MJS="$_r"; break; }
|
|
128
|
+
done
|
|
129
|
+
unset _r
|
|
130
|
+
if [ -z "$READINESS_MJS" ]; then
|
|
131
|
+
echo "extension verify: readiness.mjs missing next to $SCRIPT_DIR; reinstall the runner." >&2
|
|
132
|
+
exit 1
|
|
133
|
+
fi
|
|
134
|
+
refresh_extension_id() {
|
|
135
|
+
if [ -f "$EXTENSION_ID_FILE" ]; then
|
|
136
|
+
RECIPE_HARNESS_EXTENSION_ID="$(tr -d '[:space:]' < "$EXTENSION_ID_FILE")"
|
|
137
|
+
export RECIPE_HARNESS_EXTENSION_ID
|
|
138
|
+
else
|
|
139
|
+
unset RECIPE_HARNESS_EXTENSION_ID || true
|
|
140
|
+
fi
|
|
141
|
+
}
|
|
142
|
+
CONTEXT_PATH="${RECIPE_RUNTIME_CONTEXT:-$TARGET/$RUNTIME_DIR/agentic-runtime.json}"
|
|
143
|
+
if [ -f "$CONTEXT_PATH" ]; then
|
|
144
|
+
CONTEXT_EXTENSION_ID="$(read_runtime_context_field "$CONTEXT_PATH" extensionId || true)"
|
|
145
|
+
if [[ "$CONTEXT_EXTENSION_ID" =~ ^[a-p]{32}$ ]]; then
|
|
146
|
+
mkdir -p "$(dirname "$EXTENSION_ID_FILE")"
|
|
147
|
+
printf '%s\n' "$CONTEXT_EXTENSION_ID" > "$EXTENSION_ID_FILE"
|
|
148
|
+
RECIPE_HARNESS_EXTENSION_ID="$CONTEXT_EXTENSION_ID"
|
|
149
|
+
export RECIPE_HARNESS_EXTENSION_ID
|
|
150
|
+
else
|
|
151
|
+
refresh_extension_id
|
|
152
|
+
fi
|
|
153
|
+
unset CONTEXT_EXTENSION_ID
|
|
154
|
+
else
|
|
155
|
+
refresh_extension_id
|
|
156
|
+
fi
|
|
157
|
+
|
|
158
|
+
status="pass"
|
|
159
|
+
checks=()
|
|
160
|
+
|
|
161
|
+
fixture_status_json() {
|
|
162
|
+
TARGET_FOR_FIXTURE="$TARGET" RECIPE_RUNTIME_DIR="$RUNTIME_DIR" node <<'NODE'
|
|
163
|
+
const fs = require('fs');
|
|
164
|
+
const crypto = require('crypto');
|
|
165
|
+
const path = require('path');
|
|
166
|
+
const target = process.env.TARGET_FOR_FIXTURE;
|
|
167
|
+
const runtimeDir = process.env.RECIPE_RUNTIME_DIR;
|
|
168
|
+
if (!runtimeDir) throw new Error('RECIPE_RUNTIME_DIR is required');
|
|
169
|
+
const candidates = [
|
|
170
|
+
runtimeDir + '/wallet-fixture.json',
|
|
171
|
+
'test/e2e/seeder/withFixtures.js',
|
|
172
|
+
'test/e2e/fixtures',
|
|
173
|
+
'fixtures',
|
|
174
|
+
].map((rel) => path.join(target, rel));
|
|
175
|
+
const found = candidates.find((file) => fs.existsSync(file));
|
|
176
|
+
const extensionId = path.join(target, runtimeDir, 'extension.id');
|
|
177
|
+
const profileHints = [];
|
|
178
|
+
if (fs.existsSync(extensionId)) profileHints.push({ path: `${runtimeDir}/extension.id`, type: 'extension-id' });
|
|
179
|
+
if (!found) {
|
|
180
|
+
console.log(JSON.stringify({
|
|
181
|
+
status: 'MISSING_FIXTURES',
|
|
182
|
+
message: 'Fixture status: MISSING_FIXTURES. This run may depend on an inherited browser/profile state. Prefer a prepared debug profile or fixture seed before spending time repairing state manually.',
|
|
183
|
+
profileHints,
|
|
184
|
+
}));
|
|
185
|
+
process.exit(0);
|
|
186
|
+
}
|
|
187
|
+
const stat = fs.statSync(found);
|
|
188
|
+
const isFile = stat.isFile();
|
|
189
|
+
let sha256 = null;
|
|
190
|
+
let validJson = null;
|
|
191
|
+
let hasWalletPassword = false;
|
|
192
|
+
let mobileAccountShape = false;
|
|
193
|
+
const rel = path.relative(target, found);
|
|
194
|
+
const isWalletFixture = rel === runtimeDir + '/wallet-fixture.json';
|
|
195
|
+
if (isFile) {
|
|
196
|
+
const bytes = fs.readFileSync(found);
|
|
197
|
+
sha256 = crypto.createHash('sha256').update(bytes).digest('hex');
|
|
198
|
+
if (found.endsWith('.json')) {
|
|
199
|
+
try {
|
|
200
|
+
const parsed = JSON.parse(bytes.toString('utf8'));
|
|
201
|
+
validJson = true;
|
|
202
|
+
const accounts = Array.isArray(parsed.accounts) ? parsed.accounts : [];
|
|
203
|
+
hasWalletPassword = typeof parsed.password === 'string' && parsed.password.length > 0;
|
|
204
|
+
mobileAccountShape = accounts.some((account) => account?.type === 'mnemonic') &&
|
|
205
|
+
accounts.filter((account) => account?.type === 'privateKey').length >= 2;
|
|
206
|
+
} catch {
|
|
207
|
+
validJson = false;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const status = validJson === false
|
|
212
|
+
? 'STALE_OR_INVALID'
|
|
213
|
+
: isWalletFixture && hasWalletPassword
|
|
214
|
+
? 'READY'
|
|
215
|
+
: 'PROFILE_HINTS';
|
|
216
|
+
console.log(JSON.stringify({
|
|
217
|
+
status,
|
|
218
|
+
path: rel,
|
|
219
|
+
type: isFile ? 'file' : 'directory',
|
|
220
|
+
sha256,
|
|
221
|
+
modifiedAt: stat.mtime.toISOString(),
|
|
222
|
+
profileHints,
|
|
223
|
+
hasWalletPassword,
|
|
224
|
+
mobileAccountShape,
|
|
225
|
+
message: validJson === false
|
|
226
|
+
? `Fixture status: STALE_OR_INVALID (${rel}). Fix before relying on a clean sandbox.`
|
|
227
|
+
: status === 'READY'
|
|
228
|
+
? `Fixture status: READY (${rel}).`
|
|
229
|
+
: `Fixture status: PROFILE_HINTS (${rel}); no Mobile-shaped wallet fixture was found for automatic account parity validation.`,
|
|
230
|
+
}));
|
|
231
|
+
NODE
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
cdp_holder_json() {
|
|
235
|
+
local port="$1"
|
|
236
|
+
PORT_FOR_STATUS="$port" node <<'NODE'
|
|
237
|
+
const cp = require('child_process');
|
|
238
|
+
const http = require('http');
|
|
239
|
+
const port = process.env.PORT_FOR_STATUS;
|
|
240
|
+
function run(cmd) {
|
|
241
|
+
try { return cp.execSync(cmd, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim(); }
|
|
242
|
+
catch { return ''; }
|
|
243
|
+
}
|
|
244
|
+
function getJson(path) {
|
|
245
|
+
return new Promise((resolve) => {
|
|
246
|
+
const req = http.get(`http://127.0.0.1:${port}${path}`, { timeout: 1000 }, (res) => {
|
|
247
|
+
let data = '';
|
|
248
|
+
res.on('data', (chunk) => { data += chunk; });
|
|
249
|
+
res.on('end', () => {
|
|
250
|
+
try { resolve(JSON.parse(data)); } catch { resolve(null); }
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
req.on('timeout', () => { req.destroy(); resolve(null); });
|
|
254
|
+
req.on('error', () => resolve(null));
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
(async () => {
|
|
258
|
+
const pid = run(`lsof -iTCP:${port} -sTCP:LISTEN -t | head -1`);
|
|
259
|
+
// Validate pid is numeric before interpolating it into the `ps -p` shell string.
|
|
260
|
+
const safePid = /^[0-9]+$/.test(pid) ? pid : '';
|
|
261
|
+
const command = safePid ? run(`ps -p ${safePid} -o command=`) : '';
|
|
262
|
+
const version = await getJson('/json/version');
|
|
263
|
+
const targets = await getJson('/json/list');
|
|
264
|
+
const extensionTargets = Array.isArray(targets)
|
|
265
|
+
? targets.filter((target) => String(target.url || '').startsWith('chrome-extension://')).length
|
|
266
|
+
: 0;
|
|
267
|
+
console.log(JSON.stringify({
|
|
268
|
+
port,
|
|
269
|
+
listening: Boolean(pid),
|
|
270
|
+
pid: pid || null,
|
|
271
|
+
command: command || null,
|
|
272
|
+
cdpReachable: Boolean(version),
|
|
273
|
+
browser: version?.Browser || null,
|
|
274
|
+
targetCount: Array.isArray(targets) ? targets.length : null,
|
|
275
|
+
extensionTargets,
|
|
276
|
+
}));
|
|
277
|
+
})();
|
|
278
|
+
NODE
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
check_file() {
|
|
282
|
+
local rel="$1"
|
|
283
|
+
if [ -e "$TARGET/$rel" ]; then
|
|
284
|
+
checks+=("{\"name\":\"$rel\",\"status\":\"pass\"}")
|
|
285
|
+
else
|
|
286
|
+
checks+=("{\"name\":\"$rel\",\"status\":\"fail\"}")
|
|
287
|
+
status="fail"
|
|
288
|
+
fi
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
check_file "$HARNESS_REL/manifest.json"
|
|
292
|
+
check_file "$HARNESS_REL/action-manifest.json"
|
|
293
|
+
check_file "$HARNESS_REL/runner/bin/metamask-recipe"
|
|
294
|
+
check_file "$HARNESS_REL/runner/manifests/extension.action-manifest.json"
|
|
295
|
+
|
|
296
|
+
# dist-freshness + build-health now come from the runner's single source of
|
|
297
|
+
# truth (`runtime-decision`), which subsumes the probes this helper used to
|
|
298
|
+
# hand-roll — so the dist-id/source-dirty + webpack-log logic lives in ONE place
|
|
299
|
+
# (matching the runner readiness algorithm) and the three layers cannot disagree.
|
|
300
|
+
# git/fs only (no --cdp-port) so this stays harness-independent. The derived
|
|
301
|
+
# dist-freshness.json / build-health.json keep the same status vocab the case
|
|
302
|
+
# mappings + summary below already consume, so behavior is unchanged.
|
|
303
|
+
"$RUNNER_BIN" runtime-decision --adapter extension --target "$TARGET" --json \
|
|
304
|
+
> "$ARTIFACTS/logs/runtime-decision.json" 2>/dev/null \
|
|
305
|
+
|| printf '%s' '{}' > "$ARTIFACTS/logs/runtime-decision.json"
|
|
306
|
+
# Surface an empty/{} decision loudly: without it, dist-freshness/build-health
|
|
307
|
+
# silently degrade to WARN and the runtime-proof gate is effectively bypassed.
|
|
308
|
+
if [ ! -s "$ARTIFACTS/logs/runtime-decision.json" ] || [ "$(cat "$ARTIFACTS/logs/runtime-decision.json")" = "{}" ]; then
|
|
309
|
+
echo "recipe-harness verify: runtime-decision returned empty/{} — dist-freshness and build-health cannot be derived from the runner and will degrade to WARN (runtime-proof gate NOT fully validated). Check the runner/build state." >&2
|
|
310
|
+
fi
|
|
311
|
+
node -e '
|
|
312
|
+
const fs = require("fs");
|
|
313
|
+
const dir = process.argv[process.argv.length - 1];
|
|
314
|
+
let r = {};
|
|
315
|
+
try { r = JSON.parse(fs.readFileSync(dir + "/runtime-decision.json", "utf8")); } catch {}
|
|
316
|
+
const c = r.checks || {};
|
|
317
|
+
const dist = c.dist || { status: "unknown" };
|
|
318
|
+
const distMsg = dist.status === "fresh" ? "dist id matches HEAD; no uncommitted source."
|
|
319
|
+
: dist.status === "stale" ? (dist.reason === "uncommitted-source"
|
|
320
|
+
? ((dist.modified ? dist.modified.length : "some") + " uncommitted source file(s); rebuild or commit.")
|
|
321
|
+
: ("dist id " + (dist.distGitId || "?") + " != HEAD " + (dist.head || "?") + "; rebuild."))
|
|
322
|
+
: dist.status === "no-build" ? "no dist/chrome build."
|
|
323
|
+
: "no git id in dist or not a git checkout; cannot prove parity.";
|
|
324
|
+
fs.writeFileSync(dir + "/dist-freshness.json", JSON.stringify({ ...dist, message: distMsg }));
|
|
325
|
+
const bl = c.buildLog || { status: "unknown" };
|
|
326
|
+
const blMsg = bl.status === "ok" ? "webpack compiled."
|
|
327
|
+
: bl.status === "no-watch" ? "no webpack watch log; build-health n/a (e.g. one-shot build)."
|
|
328
|
+
: bl.status === "building" ? "webpack has not reported a successful compile yet."
|
|
329
|
+
: bl.status === "errors" ? (bl.reason === "stale-cache"
|
|
330
|
+
? "webpack build failing on a stale cache (ENOENT on a deduped module). Run `metamask-recipe runtime-launch --adapter extension --target <repo> --cdp-port <port>` to auto-clear the cache and rebuild."
|
|
331
|
+
: "webpack build has errors; fix the source/build before validating.")
|
|
332
|
+
: "build-health unknown.";
|
|
333
|
+
fs.writeFileSync(dir + "/build-health.json", JSON.stringify({ status: bl.status, reason: bl.reason, excerpt: bl.excerpt, message: blMsg }));
|
|
334
|
+
' "$ARTIFACTS/logs" 2>/dev/null \
|
|
335
|
+
|| { printf '%s' '{"status":"unknown","message":"dist-freshness probe error"}' > "$ARTIFACTS/logs/dist-freshness.json"; printf '%s' '{"status":"unknown","message":"build-health probe error"}' > "$ARTIFACTS/logs/build-health.json"; }
|
|
336
|
+
df_read() { node -e 'const fs=require("fs");try{const [file,key]=process.argv.slice(-2);const v=JSON.parse(fs.readFileSync(file,"utf8"));process.stdout.write(String(v[key]??""))}catch{process.stdout.write("")}' "$ARTIFACTS/logs/dist-freshness.json" "$1"; }
|
|
337
|
+
df_status="$(df_read status)"
|
|
338
|
+
echo "dist-freshness: ${df_status:-unknown} — $(df_read message)" >&2
|
|
339
|
+
case "$df_status" in
|
|
340
|
+
fresh) checks+=("{\"name\":\"dist-freshness\",\"status\":\"pass\"}") ;;
|
|
341
|
+
stale)
|
|
342
|
+
# A stale dist only fails a LIVE verify (runtime proof would use the wrong
|
|
343
|
+
# build). --static-only checks install/idempotency shape, not runtime, so
|
|
344
|
+
# there it is a warning — don't regress install-only verification.
|
|
345
|
+
if [ "$STATIC_ONLY" = false ]; then
|
|
346
|
+
checks+=("{\"name\":\"dist-freshness\",\"status\":\"fail\",\"detail\":\"see logs/dist-freshness.json\"}"); status="fail"
|
|
347
|
+
else
|
|
348
|
+
checks+=("{\"name\":\"dist-freshness\",\"status\":\"warn\",\"detail\":\"stale (static-only); see logs/dist-freshness.json\"}")
|
|
349
|
+
fi
|
|
350
|
+
;;
|
|
351
|
+
*) checks+=("{\"name\":\"dist-freshness\",\"status\":\"warn\",\"detail\":\"${df_status:-unknown}; see logs/dist-freshness.json\"}") ;;
|
|
352
|
+
esac
|
|
353
|
+
|
|
354
|
+
# build-health.json was produced by the runtime-decision derivation above (the
|
|
355
|
+
# runner reads the webpack watch log; same status vocab as before).
|
|
356
|
+
bh_read() { node -e 'const fs=require("fs");try{const [file,key]=process.argv.slice(-2);process.stdout.write(String(JSON.parse(fs.readFileSync(file,"utf8"))[key]??""))}catch{process.stdout.write("")}' "$ARTIFACTS/logs/build-health.json" "$1"; }
|
|
357
|
+
bh_status="$(bh_read status)"
|
|
358
|
+
echo "build-health: ${bh_status:-unknown} — $(bh_read message)" >&2
|
|
359
|
+
case "$bh_status" in
|
|
360
|
+
ok|no-watch) checks+=("{\"name\":\"build-health\",\"status\":\"pass\"}") ;;
|
|
361
|
+
errors)
|
|
362
|
+
# A broken build only fails a LIVE verify; in --static-only (install-shape only)
|
|
363
|
+
# it is a loud warning so install/idempotency checks aren't regressed.
|
|
364
|
+
if [ "$STATIC_ONLY" = false ]; then
|
|
365
|
+
checks+=("{\"name\":\"build-health\",\"status\":\"fail\",\"detail\":\"see logs/build-health.json\"}"); status="fail"
|
|
366
|
+
else
|
|
367
|
+
checks+=("{\"name\":\"build-health\",\"status\":\"warn\",\"detail\":\"build errors (static-only); see logs/build-health.json\"}")
|
|
368
|
+
fi
|
|
369
|
+
;;
|
|
370
|
+
building) checks+=("{\"name\":\"build-health\",\"status\":\"warn\",\"detail\":\"still compiling; see logs/build-health.json\"}") ;;
|
|
371
|
+
*) checks+=("{\"name\":\"build-health\",\"status\":\"warn\",\"detail\":\"${bh_status:-unknown}; see logs/build-health.json\"}") ;;
|
|
372
|
+
esac
|
|
373
|
+
|
|
374
|
+
live_mode="static-only"
|
|
375
|
+
if [ "$STATIC_ONLY" = false ]; then
|
|
376
|
+
fixture_status_json > "$ARTIFACTS/logs/fixture-status.json"
|
|
377
|
+
node -e 'const fs=require("fs"); const file=process.argv[process.argv.length - 1]; const v=JSON.parse(fs.readFileSync(file,"utf8")); console.error(v.message || v.status);' "$ARTIFACTS/logs/fixture-status.json"
|
|
378
|
+
checks+=("{\"name\":\"fixture/profile status\",\"status\":\"$(node -e 'const fs=require("fs"); const file=process.argv[process.argv.length - 1]; const v=JSON.parse(fs.readFileSync(file,"utf8")); console.log(v.status === "READY" ? "pass" : "warn");' "$ARTIFACTS/logs/fixture-status.json")\"}")
|
|
379
|
+
|
|
380
|
+
if [ -z "$CDP_PORT" ]; then
|
|
381
|
+
echo "Live extension verify requires --cdp-port. Static checks may pass, but runtime proof is missing." > "$ARTIFACTS/logs/live-missing-cdp.log"
|
|
382
|
+
checks+=("{\"name\":\"live runtime CDP port\",\"status\":\"fail\",\"detail\":\"missing --cdp-port\"}")
|
|
383
|
+
status="fail"
|
|
384
|
+
live_mode="missing-cdp"
|
|
385
|
+
else
|
|
386
|
+
live_mode="live"
|
|
387
|
+
cdp_holder_json "$CDP_PORT" > "$ARTIFACTS/logs/cdp-holder.json"
|
|
388
|
+
if node "$READINESS_MJS" --target "$TARGET" --cdp-port "$CDP_PORT" --json > "$ARTIFACTS/logs/extension-readiness.json" 2>&1; then
|
|
389
|
+
checks+=("{\"name\":\"live extension readiness\",\"status\":\"pass\"}")
|
|
390
|
+
# extension-readiness.mjs may repair $RUNTIME_DIR/extension.id when the
|
|
391
|
+
# supplied Chrome profile loads a fresh extension ID. Reload it before
|
|
392
|
+
# running recipe smoke checks so the recipe bridge targets the live
|
|
393
|
+
# extension instead of a stale marker from a previous browser profile.
|
|
394
|
+
refresh_extension_id
|
|
395
|
+
else
|
|
396
|
+
checks+=("{\"name\":\"live extension readiness\",\"status\":\"fail\",\"detail\":\"see logs/extension-readiness.json\"}")
|
|
397
|
+
status="fail"
|
|
398
|
+
fi
|
|
399
|
+
|
|
400
|
+
if (
|
|
401
|
+
cd "$TARGET"
|
|
402
|
+
"$RUNNER_BIN" manifest --adapter extension --json
|
|
403
|
+
) > "$ARTIFACTS/logs/runner-manifest.json" 2> "$ARTIFACTS/logs/runner-manifest.err"; then
|
|
404
|
+
checks+=("{\"name\":\"runner manifest\",\"status\":\"pass\"}")
|
|
405
|
+
else
|
|
406
|
+
checks+=("{\"name\":\"runner manifest\",\"status\":\"fail\",\"detail\":\"see logs/runner-manifest.err\"}")
|
|
407
|
+
status="fail"
|
|
408
|
+
fi
|
|
409
|
+
|
|
410
|
+
if (
|
|
411
|
+
cd "$TARGET"
|
|
412
|
+
"$RUNNER_BIN" run "$SMOKE_RECIPE" --adapter extension --project-root "$TARGET" --cdp-port "$CDP_PORT" --artifacts-dir "$ARTIFACTS/runner-smoke" --json
|
|
413
|
+
) > "$ARTIFACTS/logs/runner-smoke.log" 2>&1; then
|
|
414
|
+
checks+=("{\"name\":\"runner v1 smoke\",\"status\":\"pass\"}")
|
|
415
|
+
else
|
|
416
|
+
checks+=("{\"name\":\"runner v1 smoke\",\"status\":\"fail\",\"detail\":\"see logs/runner-smoke.log\"}")
|
|
417
|
+
status="fail"
|
|
418
|
+
fi
|
|
419
|
+
fi
|
|
420
|
+
fi
|
|
421
|
+
|
|
422
|
+
if git -C "$TARGET" rev-parse --git-dir >/dev/null 2>&1; then
|
|
423
|
+
git -C "$TARGET" status --short -- . ":(exclude)$HARNESS_ROOT" > "$ARTIFACTS/logs/product-diff-excluding-harness.log" 2>&1 || true
|
|
424
|
+
fi
|
|
425
|
+
|
|
426
|
+
RECIPE_HARNESS_LIVE_MODE="$live_mode" RECIPE_HARNESS_ROOT_EXCLUDE="$HARNESS_ROOT" RECIPE_RUNTIME_DIR="$RUNTIME_DIR" node - "$ARTIFACTS" "$TARGET" "$status" "${checks[@]}" <<'NODE'
|
|
427
|
+
const fs = require('fs');
|
|
428
|
+
const path = require('path');
|
|
429
|
+
const cp = require('child_process');
|
|
430
|
+
const [artifacts, target, status, ...checks] = process.argv.slice(2);
|
|
431
|
+
const parsedChecks = checks.map((entry) => JSON.parse(entry));
|
|
432
|
+
const liveMode = process.env.RECIPE_HARNESS_LIVE_MODE || 'unknown';
|
|
433
|
+
let fixtureStatus = null;
|
|
434
|
+
let cdpHolder = null;
|
|
435
|
+
let readinessReport = null;
|
|
436
|
+
let distFreshness = null;
|
|
437
|
+
try { distFreshness = JSON.parse(fs.readFileSync(path.join(artifacts, 'logs/dist-freshness.json'), 'utf8')); } catch {}
|
|
438
|
+
try { fixtureStatus = JSON.parse(fs.readFileSync(path.join(artifacts, 'logs/fixture-status.json'), 'utf8')); } catch {}
|
|
439
|
+
try { cdpHolder = JSON.parse(fs.readFileSync(path.join(artifacts, 'logs/cdp-holder.json'), 'utf8')); } catch {}
|
|
440
|
+
try { readinessReport = JSON.parse(fs.readFileSync(path.join(artifacts, 'logs/extension-readiness.json'), 'utf8')); } catch {}
|
|
441
|
+
function runGit(args) {
|
|
442
|
+
try {
|
|
443
|
+
return cp.execFileSync('git', ['-C', target, ...args], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim();
|
|
444
|
+
} catch (error) {
|
|
445
|
+
// Git metadata is diagnostic-only; non-git targets still produce a usable verify summary.
|
|
446
|
+
return null;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
const harnessRootExclude = process.env.RECIPE_HARNESS_ROOT_EXCLUDE;
|
|
450
|
+
if (!harnessRootExclude) throw new Error('RECIPE_HARNESS_ROOT_EXCLUDE is required');
|
|
451
|
+
const statusShort = runGit(['status', '--short', '--', '.', `:(exclude)${harnessRootExclude}`]);
|
|
452
|
+
const gitStatus = {
|
|
453
|
+
branch: runGit(['branch', '--show-current']),
|
|
454
|
+
head: runGit(['rev-parse', '--short', 'HEAD']),
|
|
455
|
+
dirtyCount: statusShort ? statusShort.split('\n').filter(Boolean).length : 0,
|
|
456
|
+
dirtyPreview: statusShort ? statusShort.split('\n').filter(Boolean).slice(0, 25) : [],
|
|
457
|
+
};
|
|
458
|
+
const readiness = parsedChecks.find((check) => check.name === 'live extension readiness');
|
|
459
|
+
const extensionIdPath = path.join(target, process.env.RECIPE_RUNTIME_DIR, 'extension.id');
|
|
460
|
+
let markerExtensionId = null;
|
|
461
|
+
try {
|
|
462
|
+
const value = fs.readFileSync(extensionIdPath, 'utf8').trim();
|
|
463
|
+
if (/^[a-p]{32}$/.test(value)) markerExtensionId = value;
|
|
464
|
+
} catch {}
|
|
465
|
+
const cdpTarget = readinessReport?.cdp ? {
|
|
466
|
+
selectedExtensionId: readinessReport.cdp.selectedExtensionId || null,
|
|
467
|
+
markerExtensionId,
|
|
468
|
+
markerMatched: readinessReport.cdp.markerMatched ?? null,
|
|
469
|
+
markerRepaired: readinessReport.cdp.markerRepaired ?? null,
|
|
470
|
+
extensionIds: readinessReport.cdp.extensionIds || [],
|
|
471
|
+
targetCount: readinessReport.cdp.targetCount ?? null,
|
|
472
|
+
browser: readinessReport.cdp.browser || cdpHolder?.browser || null,
|
|
473
|
+
} : null;
|
|
474
|
+
const runtimeOwner = liveMode === 'static-only'
|
|
475
|
+
? 'static-only'
|
|
476
|
+
: liveMode === 'missing-cdp'
|
|
477
|
+
? 'none'
|
|
478
|
+
: cdpHolder?.cdpReachable
|
|
479
|
+
? (readiness?.status === 'pass' ? 'compatible-external-or-harness' : 'incompatible-external-or-stale')
|
|
480
|
+
: 'none';
|
|
481
|
+
fs.writeFileSync(path.join(artifacts, 'summary.json'), `${JSON.stringify({
|
|
482
|
+
adapter: 'extension',
|
|
483
|
+
status,
|
|
484
|
+
liveMode,
|
|
485
|
+
runtimeClassification: {
|
|
486
|
+
runtimeOwner,
|
|
487
|
+
recipeControllable: readiness?.status === 'pass',
|
|
488
|
+
startedByVerify: false,
|
|
489
|
+
},
|
|
490
|
+
cleanupOwnership: {
|
|
491
|
+
mayStop: false,
|
|
492
|
+
reason: 'extension verify inspects the supplied CDP runtime; wrapper/preflight ownership must be recorded by the caller before stopping processes',
|
|
493
|
+
},
|
|
494
|
+
gitStatus,
|
|
495
|
+
distFreshness,
|
|
496
|
+
runtimePolicy: {
|
|
497
|
+
runtimeReusePolicy: 'reuse a running harness-compatible CDP target when possible; wrapper auto-start must use a cached/watch-only prepare path unless the human explicitly permits a rebuild',
|
|
498
|
+
},
|
|
499
|
+
fixtureStatus,
|
|
500
|
+
cdpHolder,
|
|
501
|
+
cdpTarget,
|
|
502
|
+
checks: parsedChecks,
|
|
503
|
+
generatedAt: new Date().toISOString(),
|
|
504
|
+
}, null, 2)}\n`);
|
|
505
|
+
fs.writeFileSync(path.join(artifacts, 'artifact-manifest.json'), `${JSON.stringify({
|
|
506
|
+
artifacts: fs.readdirSync(artifacts).map((name) => ({ path: name })),
|
|
507
|
+
}, null, 2)}\n`);
|
|
508
|
+
NODE
|
|
509
|
+
|
|
510
|
+
echo "Extension harness verify $status: $ARTIFACTS/summary.json"
|
|
511
|
+
[ "$status" = "pass" ]
|