@deeeed/metamask-harness 0.40.0 → 0.40.1
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 +6 -0
- package/dist/funding-execution-context.js +35 -28
- package/library/actions/extension/perps/mutation-receipt.mjs +12 -8
- package/library/actions/extension/perps/select_activity_filter.mjs +1 -1
- package/library/actions/extension/perps/select_market_filter.mjs +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.40.1 - 2026-08-16
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Read durable funding claims and mutation receipts through one non-symlink file descriptor, and keep filter polling delays out of page-evaluated code.
|
|
10
|
+
|
|
5
11
|
## 0.40.0 - 2026-08-16
|
|
6
12
|
|
|
7
13
|
- Remove Extension's public `metamask.perps.close_positions` and `metamask.perps.start_state` surfaces and its mutating `runner.action-validation` recipe; retain only state assertions it can prove without implicit readiness or unreceipted convergence.
|
|
@@ -324,11 +324,12 @@ async function readFundingDocuments(requestPath, preflightPath, releaseProofPath
|
|
|
324
324
|
};
|
|
325
325
|
}
|
|
326
326
|
async function readRegularFile(file, option) {
|
|
327
|
-
|
|
328
|
-
|
|
327
|
+
let handle;
|
|
328
|
+
try {
|
|
329
|
+
handle = await open(file, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW);
|
|
330
|
+
} catch {
|
|
329
331
|
throw new Error(`${option} must name a regular non-symlink file.`);
|
|
330
332
|
}
|
|
331
|
-
const handle = await open(file, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW);
|
|
332
333
|
try {
|
|
333
334
|
const info = await handle.stat();
|
|
334
335
|
if (!info.isFile() || info.size <= 0 || info.size > MAX_DOCUMENT_BYTES) {
|
|
@@ -339,6 +340,24 @@ async function readRegularFile(file, option) {
|
|
|
339
340
|
await handle.close();
|
|
340
341
|
}
|
|
341
342
|
}
|
|
343
|
+
async function readOptionalRegularFile(file, label) {
|
|
344
|
+
let handle;
|
|
345
|
+
try {
|
|
346
|
+
handle = await open(file, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW);
|
|
347
|
+
} catch (error) {
|
|
348
|
+
if (error.code === "ENOENT") return void 0;
|
|
349
|
+
throw new Error(`${label} is unsafe.`);
|
|
350
|
+
}
|
|
351
|
+
try {
|
|
352
|
+
const info = await handle.stat();
|
|
353
|
+
if (!info.isFile() || info.size <= 0 || info.size > MAX_DOCUMENT_BYTES) {
|
|
354
|
+
throw new Error(`${label} is unsafe.`);
|
|
355
|
+
}
|
|
356
|
+
return await handle.readFile();
|
|
357
|
+
} finally {
|
|
358
|
+
await handle.close();
|
|
359
|
+
}
|
|
360
|
+
}
|
|
342
361
|
async function directoryIdentity(directory) {
|
|
343
362
|
const resolved = path.resolve(directory);
|
|
344
363
|
const info = await lstat(resolved);
|
|
@@ -365,14 +384,7 @@ async function publishExecutionClaim(base, claim, artifactsDir, plan, directory)
|
|
|
365
384
|
const stateRoot = path.dirname(directory);
|
|
366
385
|
const claimPath = path.join(directory, `${identityKey}.json`);
|
|
367
386
|
const bytes = canonical(claim);
|
|
368
|
-
|
|
369
|
-
try {
|
|
370
|
-
await lstat(claimPath);
|
|
371
|
-
} catch (error) {
|
|
372
|
-
if (error.code !== "ENOENT") throw error;
|
|
373
|
-
currentClaimExists = false;
|
|
374
|
-
}
|
|
375
|
-
if (!currentClaimExists) {
|
|
387
|
+
if (!await readOptionalRegularFile(claimPath, "Funding execution claim")) {
|
|
376
388
|
const legacyRecovery = await recoverLegacyExecutionClaim(
|
|
377
389
|
path.join(directory, `${releaseIdentityKey}.json`),
|
|
378
390
|
bytes,
|
|
@@ -394,9 +406,9 @@ async function publishExecutionClaim(base, claim, artifactsDir, plan, directory)
|
|
|
394
406
|
await syncDirectory(stateRoot);
|
|
395
407
|
} catch (error) {
|
|
396
408
|
if (error.code !== "EEXIST") throw error;
|
|
397
|
-
const
|
|
398
|
-
if (!
|
|
399
|
-
const existing =
|
|
409
|
+
const existingBytes = await readOptionalRegularFile(claimPath, "Funding execution claim");
|
|
410
|
+
if (!existingBytes) throw new Error("Funding execution claim disappeared during inspection.");
|
|
411
|
+
const existing = existingBytes.toString("utf8");
|
|
400
412
|
if (existing !== bytes) {
|
|
401
413
|
const comparison = compareExecutionClaims(existing, bytes);
|
|
402
414
|
if (comparison.differences.length === 2 && comparison.differences.includes("runId") && comparison.differences.includes("executionPlanSha256")) {
|
|
@@ -417,15 +429,9 @@ async function publishExecutionClaim(base, claim, artifactsDir, plan, directory)
|
|
|
417
429
|
return void 0;
|
|
418
430
|
}
|
|
419
431
|
async function recoverLegacyExecutionClaim(claimPath, currentBytes, artifactsDir, base, plan) {
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
} catch (error) {
|
|
424
|
-
if (error.code === "ENOENT") return void 0;
|
|
425
|
-
throw error;
|
|
426
|
-
}
|
|
427
|
-
if (!info.isFile() || info.isSymbolicLink()) throw new Error("Funding execution claim is unsafe.");
|
|
428
|
-
const existingBytes = await readFile(claimPath, "utf8");
|
|
432
|
+
const existing = await readOptionalRegularFile(claimPath, "Legacy funding execution claim");
|
|
433
|
+
if (!existing) return void 0;
|
|
434
|
+
const existingBytes = existing.toString("utf8");
|
|
429
435
|
const comparison = compareExecutionClaims(existingBytes, currentBytes);
|
|
430
436
|
const sameFundingDocuments = comparison.existing.fundingRequestSha256 === comparison.current.fundingRequestSha256 && comparison.existing.fundingPreflightSha256 === comparison.current.fundingPreflightSha256 && comparison.existing.fundingReleaseProofSha256 === comparison.current.fundingReleaseProofSha256 && comparison.existing.fundingBalanceProofSha256 === comparison.current.fundingBalanceProofSha256;
|
|
431
437
|
if (!sameFundingDocuments) return void 0;
|
|
@@ -519,16 +525,17 @@ async function requireSafeRecoveryReceiptChain(artifactsDir, claim, base, plan)
|
|
|
519
525
|
let runtimeIdentity;
|
|
520
526
|
for (const entry of entries) {
|
|
521
527
|
const receiptPath = path.join(receiptDirectory, entry.name);
|
|
522
|
-
|
|
523
|
-
|
|
528
|
+
let handle;
|
|
529
|
+
try {
|
|
530
|
+
handle = await open(receiptPath, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW);
|
|
531
|
+
} catch {
|
|
524
532
|
throw new Error("Funding recovery mutation receipt is unsafe.");
|
|
525
533
|
}
|
|
526
|
-
const handle = await open(receiptPath, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW);
|
|
527
534
|
let bytes;
|
|
528
535
|
try {
|
|
529
536
|
const openedInfo = await handle.stat();
|
|
530
|
-
if (!openedInfo.isFile() || openedInfo.size
|
|
531
|
-
throw new Error("Funding recovery mutation receipt
|
|
537
|
+
if (!openedInfo.isFile() || openedInfo.size <= 0 || openedInfo.size > MAX_DOCUMENT_BYTES) {
|
|
538
|
+
throw new Error("Funding recovery mutation receipt is unsafe.");
|
|
532
539
|
}
|
|
533
540
|
bytes = await handle.readFile();
|
|
534
541
|
} finally {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
+
import { constants as fsConstants } from 'node:fs';
|
|
2
3
|
import {
|
|
3
4
|
chmod,
|
|
4
5
|
link,
|
|
@@ -6,7 +7,6 @@ import {
|
|
|
6
7
|
mkdir,
|
|
7
8
|
mkdtemp,
|
|
8
9
|
open,
|
|
9
|
-
readFile,
|
|
10
10
|
readdir,
|
|
11
11
|
realpath,
|
|
12
12
|
rename,
|
|
@@ -353,21 +353,25 @@ function unreadableReceiptError(detail) {
|
|
|
353
353
|
}
|
|
354
354
|
|
|
355
355
|
async function readPublished(destination) {
|
|
356
|
-
let
|
|
356
|
+
let handle;
|
|
357
357
|
try {
|
|
358
|
-
|
|
358
|
+
handle = await open(destination.absolute, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW);
|
|
359
359
|
} catch (error) {
|
|
360
360
|
if (error?.code === 'ENOENT') return null;
|
|
361
361
|
throw unreadableReceiptError('the persisted receipt could not be inspected');
|
|
362
362
|
}
|
|
363
|
-
if (!entry.isFile() || entry.isSymbolicLink()) {
|
|
364
|
-
throw unreadableReceiptError('the persisted receipt is unsafe');
|
|
365
|
-
}
|
|
366
363
|
let bytes;
|
|
367
364
|
try {
|
|
368
|
-
|
|
369
|
-
|
|
365
|
+
const entry = await handle.stat();
|
|
366
|
+
if (!entry.isFile()) {
|
|
367
|
+
throw unreadableReceiptError('the persisted receipt is unsafe');
|
|
368
|
+
}
|
|
369
|
+
bytes = await handle.readFile({ encoding: 'utf8' });
|
|
370
|
+
} catch (error) {
|
|
371
|
+
if (error instanceof Error && error.message.includes('the persisted receipt is unsafe')) throw error;
|
|
370
372
|
throw unreadableReceiptError('the persisted receipt could not be read');
|
|
373
|
+
} finally {
|
|
374
|
+
await handle.close();
|
|
371
375
|
}
|
|
372
376
|
let record;
|
|
373
377
|
try {
|
|
@@ -85,7 +85,7 @@ export async function selectActivityFilter(page, input, options = {}) {
|
|
|
85
85
|
`label=${JSON.stringify(observed.selected)}, visible=${observed.selectedVisible === true}.`,
|
|
86
86
|
);
|
|
87
87
|
}
|
|
88
|
-
await
|
|
88
|
+
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
89
89
|
}
|
|
90
90
|
return {
|
|
91
91
|
action: input.action,
|
|
@@ -74,7 +74,7 @@ export async function selectMarketFilter(page, input, pollOptions = {}) {
|
|
|
74
74
|
`(visible=${selectedState.visible === true}).`,
|
|
75
75
|
);
|
|
76
76
|
}
|
|
77
|
-
await
|
|
77
|
+
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
78
78
|
}
|
|
79
79
|
const raw = await page.evaluate(`(() => {
|
|
80
80
|
const maximum = ${JSON.stringify(options.maxItems)};
|