@deeeed/metamask-harness 0.91.1 → 0.91.3
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 +13 -0
- package/adapters/terminal-backend/action-manifest.json +6 -0
- package/dist/adapters/terminal-backend/provider.js +152 -0
- package/dist/provider.js +24 -1
- package/docs/TERMINAL-BACKEND-PROVIDER.md +21 -0
- package/library/recipes/mobile/perps/smoke-observation.recipe.json +78 -0
- package/library/recipes/mobile/perps/smoke.recipe.json +52 -11
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.91.3 - 2026-10-13
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Give Mobile `perps.smoke` executable `smoke` and `cleanup` proof targets for live conformance. Assert live controller reads, bind a HUD screenshot to the smoke, and observe wallet home in teardown, including after a failed smoke.
|
|
10
|
+
|
|
11
|
+
## 0.91.2 - 2026-10-13
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- Support the registered terminal-backend pack through the public provider, with pack-owned startup, invocation-owned teardown and indexed attempt logs. Backend recipes inherit the shared engine.
|
|
16
|
+
- Forward existing shared QA suite, recipe digest, owned-process and portability helpers through the public provider so dependency-free packs can use their installed tooling.
|
|
17
|
+
|
|
5
18
|
## 0.91.1 - 2026-10-12
|
|
6
19
|
|
|
7
20
|
### Fixed
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
|
+
import { defineAdapter } from "@farmslot/adapter-sdk";
|
|
7
|
+
import { indexArtifactManifest, isRecipeExecution, usageError, writeContainedArtifact } from "@farmslot/recipe-cli/harness";
|
|
8
|
+
import { validatePackPortability } from "@farmslot/protocol/node/pack-portability";
|
|
9
|
+
async function createBackendProvider(context) {
|
|
10
|
+
if (!context.project?.root || !path.isAbsolute(context.project.root)) {
|
|
11
|
+
throw usageError("terminal-backend requires a registered project root.");
|
|
12
|
+
}
|
|
13
|
+
const root = fs.realpathSync(context.project.root);
|
|
14
|
+
const lifecycle = fs.realpathSync(path.join(root, "scripts/backend.mjs"));
|
|
15
|
+
if (!lifecycle.startsWith(root + path.sep)) throw usageError("Backend lifecycle must belong to the registered pack.");
|
|
16
|
+
const { configuration, health, main } = await import(pathToFileURL(lifecycle).href);
|
|
17
|
+
const target = context.target.value;
|
|
18
|
+
const ports = context.slot && "ports" in context.slot ? context.slot.ports : {};
|
|
19
|
+
const cfg = () => {
|
|
20
|
+
const state = path.join(target, ".agent/terminal-backend/service.json");
|
|
21
|
+
const record = fs.existsSync(state) ? JSON.parse(fs.readFileSync(state, "utf8")) : {};
|
|
22
|
+
return configuration(
|
|
23
|
+
target,
|
|
24
|
+
context.options?.watcherPort ?? ports.port ?? ports.devServerPort ?? record.api,
|
|
25
|
+
ports.redis_port ?? record.redis,
|
|
26
|
+
context.slot?.value ?? record.slot
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
const args = (action) => {
|
|
30
|
+
const c = cfg();
|
|
31
|
+
return [action, c.checkout, String(c.api), String(c.redis), c.slot];
|
|
32
|
+
};
|
|
33
|
+
const executing = isRecipeExecution(context.command, context.options ?? {});
|
|
34
|
+
if (executing) cfg();
|
|
35
|
+
const owner = executing ? randomBytes(16).toString("hex") : void 0;
|
|
36
|
+
const previous = /* @__PURE__ */ new Map();
|
|
37
|
+
const setEnv = (key, value) => {
|
|
38
|
+
if (!previous.has(key)) previous.set(key, process.env[key]);
|
|
39
|
+
process.env[key] = value;
|
|
40
|
+
};
|
|
41
|
+
const serviceLog = path.join(target, ".agent/terminal-backend/service.log");
|
|
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
|
+
if (owner) {
|
|
56
|
+
setEnv("BACKEND_OWNER_ID", owner);
|
|
57
|
+
setEnv("BACKEND_LIFECYCLE_SCRIPT", lifecycle);
|
|
58
|
+
}
|
|
59
|
+
const cleanup = async () => {
|
|
60
|
+
if (owner) await main([...args("stop"), owner]);
|
|
61
|
+
};
|
|
62
|
+
const logSource = () => ({ label: "NestJS and monitor", path: serviceLog });
|
|
63
|
+
const hint = "Prepare the selected slot with the full profile.";
|
|
64
|
+
const runtime = defineAdapter({
|
|
65
|
+
id: "terminal-backend",
|
|
66
|
+
sdkVersion: 1,
|
|
67
|
+
targets: ["cli"],
|
|
68
|
+
headless: true,
|
|
69
|
+
resolveSlotPorts() {
|
|
70
|
+
const c = cfg();
|
|
71
|
+
setEnv("BACKEND_API_PORT", String(c.api));
|
|
72
|
+
setEnv("BACKEND_REDIS_PORT", String(c.redis));
|
|
73
|
+
setEnv("BACKEND_SLOT_ID", c.slot);
|
|
74
|
+
},
|
|
75
|
+
async runtimeStatus() {
|
|
76
|
+
try {
|
|
77
|
+
await health(cfg());
|
|
78
|
+
return { decision: "ready", reasons: ["Redis and a fresh complete v3 snapshot are ready."] };
|
|
79
|
+
} catch (error) {
|
|
80
|
+
return { decision: "blocked", reasons: [error.message], nextAction: hint };
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
readiness: { statusRuntime: true, captureProviders: [], readyIndicator: () => "OK" },
|
|
84
|
+
logSources: () => [logSource()],
|
|
85
|
+
appLogSource: logSource,
|
|
86
|
+
hints: { launch: hint, relaunch: hint, runtimeProbeRecovery: () => "Inspect the slot service.log and run farmslot doctor --conformance." },
|
|
87
|
+
devServer: {
|
|
88
|
+
label: "NestJS",
|
|
89
|
+
describe: () => "Slot backend",
|
|
90
|
+
stop() {
|
|
91
|
+
const result = spawnSync(process.execPath, [lifecycle, ...args("stop"), owner ?? ""], { encoding: "utf8", timeout: 3e4 });
|
|
92
|
+
if (result.error || result.status !== 0) throw result.error ?? new Error(result.stderr);
|
|
93
|
+
return { kind: "headless", message: "Backend stopped; Redis volume retained.", userAction: hint };
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
harness: {
|
|
97
|
+
// Installation and destructive cleanup belong to the pack's lifecycle hooks.
|
|
98
|
+
install: { entry: "", fallback: "" },
|
|
99
|
+
cleanup: { entry: "", fallback: "" },
|
|
100
|
+
verify: () => ({ inProcess: async () => async (_args, io) => {
|
|
101
|
+
const result = await validatePackPortability(root);
|
|
102
|
+
if (result.length) throw new Error(result.join("\n"));
|
|
103
|
+
io.out("Portable headless API provider; capture and HUD are unsupported.");
|
|
104
|
+
return 0;
|
|
105
|
+
} })
|
|
106
|
+
},
|
|
107
|
+
runtimeContext: { forbiddenFields: ["cdpPort", "metroPort", "simulator", "adbSerial", "extensionId"] },
|
|
108
|
+
launch: async () => {
|
|
109
|
+
await main(args("start"));
|
|
110
|
+
return 0;
|
|
111
|
+
},
|
|
112
|
+
actions: {
|
|
113
|
+
manifestPath: () => {
|
|
114
|
+
const product = path.join(target, "tests/recipes/action-manifest.json");
|
|
115
|
+
return fs.existsSync(product) ? product : fileURLToPath(new URL("../../../adapters/terminal-backend/action-manifest.json", import.meta.url));
|
|
116
|
+
},
|
|
117
|
+
semantic: [],
|
|
118
|
+
cdpTarget: { transport: "none", probePath: "" }
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
if (executing) runtime.resolveSlotPorts();
|
|
122
|
+
let finalized = false;
|
|
123
|
+
return {
|
|
124
|
+
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
|
+
} : {}
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
export {
|
|
151
|
+
createBackendProvider
|
|
152
|
+
};
|
package/dist/provider.js
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
optionStrings,
|
|
10
10
|
usageError
|
|
11
11
|
} from "@farmslot/recipe-cli/harness";
|
|
12
|
+
import { createAdapterRegistry } from "@farmslot/adapter-sdk";
|
|
12
13
|
import { MAINNET_RUN_OWNER_ENV } from "../library/actions/shared/perps/mainnet-order-ledger.mjs";
|
|
13
14
|
import { adoptPluginAdapter, afterAdapterLoad, configuredLibrarySources, operatorLibraryEnv } from "./adapters/plugin-surface.js";
|
|
14
15
|
import { abortRunMainnetOrders, cancelRunMainnetOrders } from "./adapters/perps-mainnet-cleanup.js";
|
|
@@ -20,14 +21,26 @@ import { providerHost } from "./host.js";
|
|
|
20
21
|
import { configureProviderReadiness, doctorOptions } from "./provider-doctor.js";
|
|
21
22
|
import { statusOptions, stopOptions } from "./provider-lifecycle.js";
|
|
22
23
|
import { createLogsOptions } from "./provider-logs.js";
|
|
24
|
+
import { createBackendProvider } from "./adapters/terminal-backend/provider.js";
|
|
23
25
|
import { providerHost as providerHost2 } from "./host.js";
|
|
24
26
|
import { PUBLIC_COMMANDS } from "./command-contract.js";
|
|
27
|
+
import { digestRecipeDocument, RECIPE_SUITE_SCOPE_SCHEMA_URL, validateRecipeActionManifestDocument } from "@farmslot/protocol";
|
|
28
|
+
import { validatePackPortability, packMachineNames } from "@farmslot/protocol/node/pack-portability";
|
|
29
|
+
import { freezeRecipeSuiteScope, finalizeRecipeSuite, digestRecipeLibrary } from "@farmslot/recipe-runner";
|
|
30
|
+
import { runOwnedRecipeProcess } from "@farmslot/recipe-runner/adapters/core";
|
|
31
|
+
import { withRecipeSignals } from "@farmslot/recipe-cli/harness";
|
|
25
32
|
async function createProvider(context) {
|
|
26
33
|
configureHarnessHost(context.host ?? providerHost);
|
|
27
34
|
configureHarnessAdapters(adapterRegistry);
|
|
28
35
|
const selection = context.adapter?.value;
|
|
29
36
|
if (!selection) throw usageError("The MetaMask provider requires a resolved adapter.");
|
|
30
37
|
const adapter = adapterForPlatform(selection) ?? selection;
|
|
38
|
+
if (adapter === "terminal-backend") {
|
|
39
|
+
const registry = createAdapterRegistry();
|
|
40
|
+
for (const id of adapterRegistry.list()) registry.register(adapterRegistry.get(id));
|
|
41
|
+
configureHarnessAdapters(registry);
|
|
42
|
+
return createBackendProvider(context);
|
|
43
|
+
}
|
|
31
44
|
const options = {
|
|
32
45
|
...context.options,
|
|
33
46
|
...!context.options?.platform && (selection === "ios" || selection === "android") ? { platform: selection } : {}
|
|
@@ -73,7 +86,17 @@ async function createProvider(context) {
|
|
|
73
86
|
};
|
|
74
87
|
}
|
|
75
88
|
export {
|
|
89
|
+
RECIPE_SUITE_SCOPE_SCHEMA_URL,
|
|
76
90
|
createProvider,
|
|
91
|
+
digestRecipeDocument,
|
|
92
|
+
digestRecipeLibrary,
|
|
93
|
+
finalizeRecipeSuite,
|
|
94
|
+
freezeRecipeSuiteScope,
|
|
95
|
+
packMachineNames,
|
|
77
96
|
PUBLIC_COMMANDS as providerCommands,
|
|
78
|
-
providerHost2 as providerHost
|
|
97
|
+
providerHost2 as providerHost,
|
|
98
|
+
runOwnedRecipeProcess,
|
|
99
|
+
validatePackPortability,
|
|
100
|
+
validateRecipeActionManifestDocument,
|
|
101
|
+
withRecipeSignals
|
|
79
102
|
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Terminal backend provider
|
|
2
|
+
|
|
3
|
+
Registered backend packs select `@deeeed/metamask-harness/provider` and adapter `terminal-backend`. The pool supplies the installed package through `MM_HARNESS_PACKAGE_ROOT`. The shared project loader supplies `project.root`, the directory containing the registered pack's `project.json`.
|
|
4
|
+
|
|
5
|
+
The provider imports that pack's `scripts/backend.mjs` and uses its existing `configuration`, `health` and `main` functions. The pack owns service startup, health checks, Redis ports, startup recovery and volumes. Slot resources must supply the API port and `redis_port`; there is no default Redis port. Installation and destructive cleanup run through pack hooks, not harness overlay installation.
|
|
6
|
+
|
|
7
|
+
Backend recipes inherit the shared engine. Their action manifest lives at the selected checkout's `tests/recipes/action-manifest.json`; a packaged `end` manifest supports an empty checkout during planning. Recipe assets remain with the backend code. No backend recipes or generic runner implementations are bundled here.
|
|
8
|
+
|
|
9
|
+
Executing `run` and `call` invocations assign a unique `BACKEND_OWNER_ID` and expose `BACKEND_LIFECYCLE_SCRIPT` and slot ports to action children. Cancellation and finalization stop only that owner through the pack lifecycle. Finalization indexes the current attempt's service log when this invocation creates or updates the shared artifact manifest and restores caller environment values. Early startup failures leave previous evidence packages untouched; service diagnostics remain in the slot's runtime log. Attached services keep their existing ownership. Previews assign no owner and start no services. Capture and HUD are unsupported for this headless runtime.
|
|
10
|
+
|
|
11
|
+
The public provider forwards these existing shared APIs for dependency-free packs. Their contracts and versions remain owned by the listed packages:
|
|
12
|
+
|
|
13
|
+
| Package | Forwarded exports |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| `@farmslot/protocol` | `digestRecipeDocument`, `RECIPE_SUITE_SCOPE_SCHEMA_URL`, `validateRecipeActionManifestDocument` |
|
|
16
|
+
| `@farmslot/protocol/node/pack-portability` | `validatePackPortability`, `packMachineNames` |
|
|
17
|
+
| `@farmslot/recipe-runner` | `freezeRecipeSuiteScope`, `finalizeRecipeSuite`, `digestRecipeLibrary` |
|
|
18
|
+
| `@farmslot/recipe-runner/adapters/core` | `runOwnedRecipeProcess` |
|
|
19
|
+
| `@farmslot/recipe-cli/harness` | `withRecipeSignals` |
|
|
20
|
+
|
|
21
|
+
The backend pack's mandatory QA gate consumes the suite, digest, process and signal APIs. Its CI and prerequisite hooks consume portability. Packs import the public provider export and do not resolve private files or transitive package paths.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://farmslot.io/schemas/recipe-v1.schema.json",
|
|
3
|
+
"title": "MetaMask Mobile Perps smoke observation",
|
|
4
|
+
"description": "Observe live Perps positions and orders and capture the current screen. Caller opens Perps before this read-only proof.",
|
|
5
|
+
"workflow": {
|
|
6
|
+
"entry": "read-positions",
|
|
7
|
+
"nodes": {
|
|
8
|
+
"read-positions": {
|
|
9
|
+
"action": "metamask.perps.read_positions",
|
|
10
|
+
"next": "assert-positions",
|
|
11
|
+
"intent": "Read live Perps positions without changing account state"
|
|
12
|
+
},
|
|
13
|
+
"assert-positions": {
|
|
14
|
+
"action": "assert_output",
|
|
15
|
+
"source": "read-positions",
|
|
16
|
+
"assert": {
|
|
17
|
+
"all": [
|
|
18
|
+
{
|
|
19
|
+
"path": "$.source",
|
|
20
|
+
"operator": "eq",
|
|
21
|
+
"value": "mobile-perps-controller"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"path": "$.count",
|
|
25
|
+
"operator": "gte",
|
|
26
|
+
"value": 0
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"path": "$.positions",
|
|
30
|
+
"operator": "not_null"
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
"next": "read-orders",
|
|
35
|
+
"intent": "Verify the live controller returned Perps positions"
|
|
36
|
+
},
|
|
37
|
+
"read-orders": {
|
|
38
|
+
"action": "metamask.perps.read_orders",
|
|
39
|
+
"next": "assert-orders",
|
|
40
|
+
"intent": "Read live Perps orders without changing account state"
|
|
41
|
+
},
|
|
42
|
+
"assert-orders": {
|
|
43
|
+
"action": "assert_output",
|
|
44
|
+
"source": "read-orders",
|
|
45
|
+
"assert": {
|
|
46
|
+
"all": [
|
|
47
|
+
{
|
|
48
|
+
"path": "$.source",
|
|
49
|
+
"operator": "eq",
|
|
50
|
+
"value": "mobile-perps-controller-open-orders"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"path": "$.count",
|
|
54
|
+
"operator": "gte",
|
|
55
|
+
"value": 0
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"path": "$.orders",
|
|
59
|
+
"operator": "not_null"
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
},
|
|
63
|
+
"next": "capture",
|
|
64
|
+
"intent": "Verify the live controller returned Perps orders"
|
|
65
|
+
},
|
|
66
|
+
"capture": {
|
|
67
|
+
"action": "ui.screenshot",
|
|
68
|
+
"path": "screenshots/perps-smoke.png",
|
|
69
|
+
"next": "done",
|
|
70
|
+
"intent": "Keep a view of the Perps screen after verifying live account reads"
|
|
71
|
+
},
|
|
72
|
+
"done": {
|
|
73
|
+
"action": "end",
|
|
74
|
+
"status": "pass"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://farmslot.io/schemas/recipe-v1.schema.json",
|
|
3
3
|
"title": "MetaMask Mobile Perps smoke",
|
|
4
|
-
"description": "Non-mutating Perps domain smoke for MetaMask Mobile:
|
|
4
|
+
"description": "Non-mutating Perps domain smoke for debug-enabled MetaMask Mobile: unlock if needed, open Perps, assert live positions and orders, and retain a HUD screenshot. Teardown observes wallet home. Does not close positions or stop the app.",
|
|
5
5
|
"workflow": {
|
|
6
6
|
"entry": "status",
|
|
7
7
|
"nodes": {
|
|
@@ -27,23 +27,64 @@
|
|
|
27
27
|
"open-perps": {
|
|
28
28
|
"action": "ui.navigate",
|
|
29
29
|
"page": "perps",
|
|
30
|
-
"next": "
|
|
30
|
+
"next": "observe-perps",
|
|
31
31
|
"intent": "Show the Perps market list"
|
|
32
32
|
},
|
|
33
|
-
"
|
|
34
|
-
"action": "
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
"action": "metamask.perps.read_orders",
|
|
33
|
+
"observe-perps": {
|
|
34
|
+
"action": "call",
|
|
35
|
+
"ref": "perps.smoke-observation",
|
|
36
|
+
"proves": [
|
|
37
|
+
"smoke"
|
|
38
|
+
],
|
|
40
39
|
"next": "done",
|
|
41
|
-
"intent": "
|
|
40
|
+
"intent": "Verify live Perps reads and capture the screen with its intent HUD"
|
|
42
41
|
},
|
|
43
42
|
"done": {
|
|
44
43
|
"action": "end",
|
|
45
44
|
"status": "pass"
|
|
45
|
+
},
|
|
46
|
+
"cleanup-home": {
|
|
47
|
+
"action": "ui.navigate",
|
|
48
|
+
"page": "home",
|
|
49
|
+
"next": "cleanup-state",
|
|
50
|
+
"intent": "Return wallet home after Perps smoke"
|
|
51
|
+
},
|
|
52
|
+
"cleanup-state": {
|
|
53
|
+
"action": "metamask.wallet.read_state",
|
|
54
|
+
"next": "cleanup-assert",
|
|
55
|
+
"intent": "Observe the wallet route after returning home"
|
|
56
|
+
},
|
|
57
|
+
"cleanup-assert": {
|
|
58
|
+
"action": "assert_output",
|
|
59
|
+
"source": "cleanup-state",
|
|
60
|
+
"assert": {
|
|
61
|
+
"path": "$.route.name",
|
|
62
|
+
"operator": "eq",
|
|
63
|
+
"value": "WalletView"
|
|
64
|
+
},
|
|
65
|
+
"proves": [
|
|
66
|
+
"cleanup"
|
|
67
|
+
],
|
|
68
|
+
"next": "cleanup-done",
|
|
69
|
+
"intent": "Verify wallet home was reached during cleanup"
|
|
70
|
+
},
|
|
71
|
+
"cleanup-done": {
|
|
72
|
+
"action": "end",
|
|
73
|
+
"status": "pass"
|
|
46
74
|
}
|
|
75
|
+
},
|
|
76
|
+
"teardown": "cleanup-home"
|
|
77
|
+
},
|
|
78
|
+
"proofTargets": [
|
|
79
|
+
{
|
|
80
|
+
"id": "smoke",
|
|
81
|
+
"claim": "The live Mobile Perps controller returns positions and open orders.",
|
|
82
|
+
"proofMode": "state"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"id": "cleanup",
|
|
86
|
+
"claim": "The wallet reports WalletView after returning home from Perps.",
|
|
87
|
+
"proofMode": "state"
|
|
47
88
|
}
|
|
48
|
-
|
|
89
|
+
]
|
|
49
90
|
}
|