@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 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
- const linkInfo = await lstat(file);
328
- if (linkInfo.isSymbolicLink() || !linkInfo.isFile()) {
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
- let currentClaimExists = true;
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 info = await lstat(claimPath);
398
- if (!info.isFile() || info.isSymbolicLink()) throw new Error("Funding execution claim is unsafe.");
399
- const existing = await readFile(claimPath, "utf8");
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
- let info;
421
- try {
422
- info = await lstat(claimPath);
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
- const receiptInfo = await lstat(receiptPath);
523
- if (!receiptInfo.isFile() || receiptInfo.isSymbolicLink() || receiptInfo.size <= 0 || receiptInfo.size > MAX_DOCUMENT_BYTES) {
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 !== receiptInfo.size) {
531
- throw new Error("Funding recovery mutation receipt changed during inspection.");
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 entry;
356
+ let handle;
357
357
  try {
358
- entry = await lstat(destination.absolute);
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
- bytes = await readFile(destination.absolute, 'utf8');
369
- } catch {
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 page.evaluate(`new Promise((resolve) => setTimeout(resolve, ${JSON.stringify(pollIntervalMs)}))`);
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 page.evaluate(`new Promise((resolve) => setTimeout(resolve, ${JSON.stringify(pollIntervalMs)}))`);
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)};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deeeed/metamask-harness",
3
- "version": "0.40.0",
3
+ "version": "0.40.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mm-harness": "bin/mm-harness"