test(evm): verify real worker cancellation and cleanup recovery (#2404)

Verify unchanged real EVMole worker replies through compiled CLI and stdio MCP, including late SDK cancellation, permission-induced retained cleanup failure/retry, preserved malformed-input diagnostics and a healthy real SQLite WASM peer. Add launcher receipts to prove blocked retries do not start another EVM worker.

Preserve production contracts/defaults and explicit frozen-runtime/platform/measurement boundaries. Final head cf558a0661eb29bda932242abc3687b1860428e7 passes all 36 CI checks (run38313631216); manual final review and related real Factory rerun pass. Raw artifacts and receipts remain outside Git.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
This commit is contained in:
Ysopwnable 2026-10-13 06:05:20 +08:00 committed by GitHub
parent 8fc2043eb8
commit ae8051302b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 667 additions and 0 deletions

View File

@ -23,6 +23,10 @@ Follow-up: [task-scoped preset lifecycle verification](preset-lifecycle.md) chec
real Ghidra profile/snapshot boundaries, startup cancellation and retained cleanup
retry while preserving defaults and distinguishing verification from performance.
Follow-up: [EVM descriptor lifecycle acceptance](evm-lifecycle.md) verifies actual
worker replies, late SDK cancellation, permission-induced cleanup failures,
healthy WASM peers and old CLI parity with explicit runtime and platform scope.
The 12 detailed checkpoint receipts were archived byte-for-byte before removal
from the final PR tree. The archive `review-receipts-00d51e0fc-20261012` is in the
private experiment lab outside the checkout; its `archive-index.json` records

View File

@ -0,0 +1,159 @@
# EVM descriptor lifecycle acceptance
Continues [#2016](https://github.com/morluto/rea/issues/2016) after
[#2349](https://github.com/morluto/rea/pull/2349) merged. The
[public verifier](verify-evm-lifecycle.mjs) exercises actual compiled CLI and
stdio MCP workflows with unchanged EVMole 0.9.3/WASM. This increment adds
verification only; production contracts, factories, algorithms and defaults
remain unchanged. It does not introduce a plugin SDK or database cache.
## Runtime and public artifacts
The verified runtime is explicitly frozen from
`67f817e3653efad35f7044e68fbd6f163b86864b`, built with Node 24.18.0 and the
matching locked dependencies. Its `dist/` and `bridge/` file-table SHA-256 is
`cf03167870ac9def929cfaab536d6bd791089031660d1b35bff20672ac07cbbf`.
The table is checked again after each workflow; source and target bytes must
remain unchanged before a success receipt is published.
Inputs are the digest-pinned [corpus](corpus.json): published historical WETH
and official Uniswap v3-core 1.0.1 Factory/Pool compiler runtime templates.
They are not independently authenticated live deployments. Their deployment,
hardfork, immutable-reference and inferred-interface limitations remain as
recorded in [the descriptor checkpoint](evm-descriptor.md). The healthy peer
is the shipped SQLite WASM 3.53.4-build1 module, inspected by real WABT 1.0.42.
No contract, native target, chain/RPC query or transaction is executed.
## Actual cancellation and retained cleanup
The verifier supplies a private test launcher through the existing explicit
`REA_EVM_PRLIMIT_COMMAND` setting. It passes the original arguments to real
`/usr/bin/prlimit`, runs the unchanged Node/EVMole worker, then briefly gates
launcher completion after that worker exits and writes its complete reply.
This controls a real lifecycle boundary without replacing the analysis engine.
It adds process overhead and is not a performance comparison.
Before cancellation or permission changes, production inspection primitives
verify the live launcher's run token, parent/group relationship and native
start identity. The complete worker reply must match the original selected
bytecode and producer facts. Filesystem changes apply only to the verified,
non-root user's temporary runtime directory.
Three scenarios are verified:
- **Late cancellation:** the real worker has completed, while the public SDK
request remains pending. SDK cancellation reports `REQUEST_TIMEOUT` locally;
token sweeps and directory removal independently confirm provider cleanup.
A subsequent complete request returns identical analysis facts.
- **Successful analysis followed by cleanup failure:** removing write permission
produces actual `EACCES`. MCP reports `cleanup_incomplete` and retains the
original resource path. A retry before permission repair fails on that same
resource. Real SQLite WASM analysis still succeeds. Restoring permission lets
the provider retire the old resource and perform a fresh complete EVM query.
- **Format failure followed by cleanup failure:** a deliberately malformed
odd-nibble carrier reaches the unchanged worker. The cleanup error preserves
its original `invalid_input` category and exact producer diagnostic rather
than overwriting the failure. The same repair/retry and healthy-peer checks
succeed.
The existing CLI `inspect-evm-interface PATH hex --json` produces the same
complete normalized facts, parameters and subject as MCP. Fresh Evidence IDs
are not equated. Final verifier lineage is checked after client/transport close;
an empty descendant list is a point-in-time observation, not proof that the
workflow never launched children. Cleanup callbacks always run and preserve
verification and cleanup failures together.
This proves cancellation after actual analysis, not interruption inside
EVMole's synchronous WASM computation. Server-shutdown cleanup retry still has
the separately labeled injected-owner SDK tests from the descriptor checkpoint.
These Linux x64 observations do not establish Windows/macOS provider behavior.
Earlier real CLI/MCP malformed/unavailable/capacity checks remain linked in the
descriptor checkpoint; they are not silently rerun or attributed to this script.
## Measurements and compatibility scope
Initial three-carrier verifier source SHA-256 is
`2fec069b53324ef82070d323b26dd613b6da0caa7f18100adbeb39b045cf98dc`.
All three carriers pass all three scenarios and CLI parity with this same source.
| Carrier | Seconds | Sampled peak family bytes | Last-observed CPU seconds |
| ------- | ------: | ------------------------: | ------------------------: |
| WETH | 25.595 | 1,116,884,992 | 23.926 |
| Factory | 23.979 | 1,132,994,560 | 23.419 |
| Pool | 24.589 | 1,141,415,936 | 24.043 |
Runs are serial under the existing one-CPU quota, 4 GiB hard memory, unchanged
3 GiB early-stop, no swap and 192-task ceiling. Resource receipts, complete
producer replies, derived malformed input, launcher and runtime copies stay in
the private lab outside Git. Final cgroup counters disappear at scope teardown
and remain unknown; sampled zero events do not prove final no-OOM/no-limit
results. This is compatibility and recovery evidence, not a model-token,
whole-task latency or CPU saving claim.
Latest main `81cca2e0e3ebd98c21f41d7ad63d6a00e4cbc3f9` could not build during
setup: its shared partial-error handler passes three arguments to an interface
requiring four. Independently completed main CI at
`fdd8f826023c568937d237c2f97d855de283a9b8` reports the same TypeScript failure
([run 38309578208](https://github.com/morluto/rea/actions/runs/38309578208)).
The unrelated regression is recorded in the maintainer-requested private bug
handoff; this change does not repair it. The maintainer independently corrected
the producing caller in main `8fc2043eb8bcedbf34dce064f977e455438635d6`.
The frozen-runtime observations above remain pinned to their original revision;
synchronizing the branch does not relabel them as latest-main verification.
After rebasing, a fresh build and all 10 focused EVM MCP boundary tests pass.
The same unchanged source verifier also passes all three real Factory scenarios,
CLI parity, healthy SQLite WASM checks and final cleanup on the newly built
runtime `b3eadf147221a2fa997ac26667a183ca5bccb1f4` (parent is corrected main
`8fc2043eb8bcedbf34dce064f977e455438635d6`). Its `dist/`/`bridge/` file-table
SHA-256 is `7a4cec0fa4f7e929356b50a6b13472f80bd6f526221e427125370cca3ef98d00`.
This related rerun takes 24.285 seconds, samples 1,146,167,296 peak family bytes
and records 23.662 last-observed CPU seconds. The older failed build and earlier
cohort receipts are preserved; wider domain/model matrices are not repeated.
Static composition continues to bind one named canonical contract and one
typed factory. Existing SDK checks reject duplicate registration before use
and reject malformed inputs before invoking the provider. Producer contract
violations are detected when the producer returns a result; they cannot be
validated in advance without that result. A dynamic third-party module ABI
preflight is not implemented or claimed. The corresponding broader acceptance
item remains open rather than creating the deferred generic plugin registry.
## Final launcher review
A missing gate marker while gating is disabled cannot establish absence of a
new launch. The fixture now appends a launch record before starting the actual
worker; both permission-failure retries must leave that log byte-identical.
This independently verifies that retained cleanup failure stops a new EVM
launch, while preserving the original resource and healthy WASM peer.
Final verifier source SHA-256 is
`fc9a50e2e54e4c8eede4b8a19283e11ddaa1faadea52eebc390b55b5a1902668`.
All three related real Factory scenarios, CLI parity and final cleanup pass
again on the corrected-main runtime: 23.579 seconds, 1,163,685,888 peak sampled
family bytes and 23.110 last-observed CPU seconds. The earlier three-carrier
receipts remain pinned to their earlier source; wider artifacts/models are
not repeated for this review fix.
## Reproduction
Build and freeze the explicit source revision as documented in the measurement
README. Supply an object of explicit string-valued provider settings, including
the caller-installed WABT paths. As a non-root Linux x64 user, run under the
existing resource wrapper:
```bash
python3 experiments/analysis-efficiency/resource-run.py -- \
node experiments/analysis-efficiency/verify-evm-lifecycle.mjs \
--runtime-root /absolute/frozen-runtime \
--revision 67f817e3653efad35f7044e68fbd6f163b86864b \
--path /absolute/UniswapV3Factory.runtime.hex \
--peer-path /absolute/sqlite3.wasm \
--providers /absolute/explicit-providers.json \
--output /absolute/new-private-receipt-directory
```
The verifier checks the carrier and SQLite module against the committed corpus,
creates an owner-only receipt directory without overwriting existing receipts,
and acquires no dependencies. Preserve failed attempts separately. A shorter
reply or a successful cleanup verifier does not establish an agent-cost gain.

View File

@ -0,0 +1,504 @@
import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { execFile } from "node:child_process";
import { createReadStream } from "node:fs";
import {
access,
chmod,
lstat,
mkdir,
readFile,
readdir,
rm,
writeFile,
} from "node:fs/promises";
import { isAbsolute, join } from "node:path";
import { setTimeout as delay } from "node:timers/promises";
import { pathToFileURL } from "node:url";
import { parseArgs, promisify } from "node:util";
import { z } from "zod";
import { Client } from "@modelcontextprotocol/client";
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
const { values } = parseArgs({
options: {
"runtime-root": { type: "string" },
revision: { type: "string" },
path: { type: "string" },
"peer-path": { type: "string" },
providers: { type: "string" },
output: { type: "string" },
},
});
for (const key of ["runtime-root", "path", "peer-path", "providers", "output"])
assert.ok(isAbsolute(values[key] ?? ""), "Supply --" + key + " ABS");
assert.match(values.revision ?? "", /^[a-f0-9]{40}$/u);
assert.equal(process.platform, "linux");
assert.equal(process.arch, "x64");
assert.notEqual(
process.getuid(),
0,
"Permission faults require a non-root user",
);
const runtime = values["runtime-root"],
lab = values.output;
const providers = z
.record(z.string(), z.string())
.parse(JSON.parse(await readFile(values.providers, "utf8")));
const sha = (bytes) => createHash("sha256").update(bytes).digest("hex");
const fileSha = async (path) => {
const hash = createHash("sha256");
for await (const part of createReadStream(path)) hash.update(part);
return hash.digest("hex");
};
const sourceBefore = await fileSha(new URL(import.meta.url));
const targetBefore = await fileSha(values.path);
const peerBefore = await fileSha(values["peer-path"]);
const corpus = JSON.parse(
await readFile(new URL("./corpus.json", import.meta.url)),
);
assert.ok(corpus.artifacts.some((x) => x.sha256 === targetBefore));
assert.equal(
corpus.artifacts.find((x) => x.id === "sqlite.wasm").sha256,
peerBefore,
);
const load = (path) => import(pathToFileURL(join(runtime, "dist", path)));
const { toolContract } = await load("contracts/toolContracts.js");
const { analysisErrorProjectionSchema } = await load(
"contracts/errorSchemas.js",
);
const { parseEvidence } = await load("domain/evidence.js");
const { evmoleInterfaceReplySchema } = await load(
"evm/EvmoleInterfaceAdapter.js",
);
const {
observeOwnedProcessLineage,
observeProcessStartIdentity,
systemProcessOwnershipHost: host,
} = await load("process/ProcessOwnershipObservation.js");
const { verifyNoTokenOwnedProcesses } = await load(
"process/ProcessOwnership.js",
);
const { createVerifierRun, completeVerifierRun } = await import(
pathToFileURL(join(runtime, "scripts/lib/verifier-run.mjs"))
);
const run = createVerifierRun();
await mkdir(lab, { mode: 0o700 });
const runtimeFiles = [];
const fingerprint = async (directory) => {
for (const entry of await readdir(directory, { withFileTypes: true })) {
const path = join(directory, entry.name);
if (entry.isDirectory()) await fingerprint(path);
else if (entry.isFile())
runtimeFiles.push({
path: path.slice(runtime.length + 1),
sha256: await fileSha(path),
});
else throw new Error("Runtime fingerprint must not follow symlinks");
}
};
await fingerprint(join(runtime, "dist"));
await fingerprint(join(runtime, "bridge"));
runtimeFiles.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
await writeFile(join(lab, "runtime-files.json"), JSON.stringify(runtimeFiles), {
flag: "wx",
mode: 0o600,
});
const work = join(lab, "temporary");
await mkdir(work, { mode: 0o700 });
const wrapper = join(lab, "gated-prlimit.mjs");
// Only gate command completion AFTER unchanged EVMole has produced its reply.
// This does not measure interruption inside its synchronous WASM computation.
await writeFile(
wrapper,
`#!${process.execPath}
import { spawn } from "node:child_process";
import { access, appendFile, readFile, rename, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { setTimeout as delay } from "node:timers/promises";
const gate = process.env.REA_VERIFY_EVM_GATE;
await appendFile(join(gate, "launches.log"), process.cwd() + "\\n", { mode: 0o600 });
const child = spawn("/usr/bin/prlimit", process.argv.slice(2), { stdio: "inherit" });
const code = await new Promise((resolve, reject) => {
child.once("error", reject);
child.once("exit", (code, signal) => signal ? reject(new Error(signal)) : resolve(code));
});
if (code !== 0) process.exit(code ?? 1);
if (await readFile(join(gate, "mode"), "utf8") === "hold") {
await writeFile(join(gate, "ready.pending.json"), JSON.stringify({
pid: process.pid, parent_pid: process.ppid, run_id: process.env.REA_PROCESS_RUN_ID,
root: process.cwd(), worker_exit: code,
}), { flag: "wx", mode: 0o600 });
await rename(join(gate, "ready.pending.json"), join(gate, "ready.json"));
const end = Date.now() + 15000;
for (;;) {
try { await access(join(gate, "release")); break; }
catch (error) { if (error.code !== "ENOENT") throw error; }
if (Date.now() >= end) throw new Error("Verification gate exceeded 15 seconds");
await delay(25);
}
}
`,
{ flag: "wx", mode: 0o700 },
);
const markerSchema = z.strictObject({
pid: z.number().int().positive(),
parent_pid: z.number().int().positive(),
run_id: z.string().regex(/^rea-evm-interface-[a-f0-9-]+$/u),
root: z.string(),
worker_exit: z.literal(0),
});
const roots = async () =>
(await readdir(work)).filter((x) => x.startsWith("rea-evm-interface-"));
const gateMode = async (mode) => {
await rm(join(lab, "ready.json"), { force: true });
await rm(join(lab, "ready.pending.json"), { force: true });
await rm(join(lab, "release"), { force: true });
await writeFile(join(lab, "mode"), mode, { mode: 0o600 });
};
await gateMode("off");
const client = new Client({ name: "rea-real-evm-lifecycle", version: "1" });
const environment = {
...process.env,
...providers,
TMPDIR: work,
REA_EVM_PRLIMIT_COMMAND: wrapper,
REA_VERIFY_EVM_GATE: lab,
};
const transport = new StdioClientTransport({
command: process.execPath,
args: [join(runtime, "scripts/rea.mjs"), "mcp"],
env: environment,
stderr: "pipe",
});
const rows = [];
let fault;
const textValue = (reply) =>
JSON.parse(reply.content.find((x) => x.type === "text").text);
const errorValue = (reply) => {
assert.equal(reply.isError, true);
return analysisErrorProjectionSchema.parse(textValue(reply).error);
};
const evidenceValue = (reply, name) => {
assert.notEqual(reply.isError, true, JSON.stringify(reply));
return parseEvidence(
toolContract(name).outputSchema.parse(reply.structuredContent),
);
};
const request = (path = values.path, signal) =>
client.callTool(
{
name: "inspect_evm_interface",
arguments: { path, encoding: "hex" },
},
{ timeout: 45000, ...(signal === undefined ? {} : { signal }) },
);
const waitFor = async (probe, message, milliseconds = 10000) => {
const end = Date.now() + milliseconds;
while (Date.now() < end) {
const value = await probe();
if (value) return value;
await delay(50);
}
throw new Error(message);
};
const observeGate = async (expectedSuccess) => {
const marker = await waitFor(async () => {
try {
return markerSchema.parse(
JSON.parse(await readFile(join(lab, "ready.json"))),
);
} catch (error) {
if (error.code === "ENOENT") return;
throw error;
}
}, "No completed real worker at the pending command gate");
assert.equal(marker.root.startsWith(work + "/rea-evm-interface-"), true);
const rootStat = await lstat(marker.root);
assert.equal(rootStat.isDirectory(), true);
assert.equal(rootStat.uid, process.getuid());
const processRow = (await host.listProcesses()).find(
(x) => x.pid === marker.pid,
);
assert.ok(processRow, "Gated owned launcher already exited");
const ownership = {
runId: marker.run_id,
leaderPid: marker.pid,
processGroupId: processRow.processGroupId,
expectedParentPid: marker.parent_pid,
};
const lineage = await observeOwnedProcessLineage(ownership);
assert.equal(lineage.status, "verified", JSON.stringify(lineage));
const identity = await observeProcessStartIdentity(
marker.pid,
undefined,
marker.run_id,
);
assert.equal(identity?.state, "readable");
const rawBytes = await readFile(join(marker.root, "reply.json"));
const raw = JSON.parse(rawBytes);
assert.equal(raw.ok, expectedSuccess);
if (raw.ok) {
assert.equal(raw.version, "0.9.3");
evmoleInterfaceReplySchema.parse(raw.raw);
} else assert.equal(raw.reason, "format");
return {
marker,
ownership,
lineage,
identity,
root_mode: rootStat.mode & 0o777,
worker_reply_sha256: sha(rawBytes),
worker_reply: raw,
};
};
const confirmCleanup = async (launch, baseline, absentRoot) =>
waitFor(async () => {
const result = await verifyNoTokenOwnedProcesses(
launch.marker.run_id,
undefined,
baseline,
{
leaderPid: launch.ownership.leaderPid,
processGroupId: launch.ownership.processGroupId,
},
);
if (!result.cleaned) return;
if (absentRoot && (await roots()).length !== 0) return;
return result;
}, "Owned EVM cleanup not verified");
const peer = async () =>
evidenceValue(
await client.callTool(
{
name: "inspect_wasm_artifact",
arguments: {
path: values["peer-path"],
glue_paths: [],
candidate_paths: [],
detail: "summary",
},
},
{ timeout: 45000 },
),
"inspect_wasm_artifact",
);
const restore = async () => {
if (fault === undefined) return;
await chmod(fault.path, fault.mode);
fault = undefined;
};
const verifyWithCleanup = async (verify, cleanups) => {
const failures = [];
let value;
try {
value = await verify();
} catch (error) {
failures.push(error);
}
for (const cleanup of cleanups) {
try {
await cleanup();
} catch (error) {
failures.push(error);
}
}
if (failures.length > 0)
throw new AggregateError(failures, "Verification or cleanup failed");
return value;
};
const verifyLateCancellation = async (original) => {
const baseline = await host.captureBaseline();
await gateMode("hold");
const controller = new AbortController();
let settled = false;
const pending = request(values.path, controller.signal).then(
(reply) => {
settled = true;
return { reply };
},
(error) => {
settled = true;
return {
error: { name: error.name, code: error.code, message: error.message },
};
},
);
let launch;
try {
launch = await observeGate(true);
assert.deepEqual(launch.worker_reply.raw, original.raw_result);
assert.deepEqual(launch.worker_reply.bytecode, {
sha256: original.normalized_result.bytecode.sha256,
bytes: original.normalized_result.bytecode.bytes,
hex: original.normalized_result.bytecode.hex,
});
assert.equal(settled, false, "Request completed before SDK cancellation");
} finally {
controller.abort(
new Error("Late real EVM worker cancellation verification"),
);
await pending;
}
const canceled = await pending;
assert.equal(canceled.error?.code, "REQUEST_TIMEOUT");
const cleanup = await confirmCleanup(launch, baseline, true);
await assert.rejects(access(launch.marker.root), { code: "ENOENT" });
await gateMode("off");
const recovered = evidenceValue(await request(), "inspect_evm_interface");
assert.deepEqual(recovered.normalized_result, original.normalized_result);
return {
scenario: "late-cancellation",
launch,
canceled,
cleanup,
recovery_evidence_id: recovered.evidence_id,
};
};
await verifyWithCleanup(async () => {
await client.connect(transport);
const catalog = await client.listTools();
assert.equal(
catalog.tools.filter((x) => x.name === "inspect_evm_interface").length,
1,
);
const original = evidenceValue(await request(), "inspect_evm_interface");
assert.equal(original.subject.digest.sha256, targetBefore);
assert.equal(original.provider.version, "evmole@0.9.3");
const cli = await promisify(execFile)(
process.execPath,
[
join(runtime, "scripts/rea.mjs"),
"inspect-evm-interface",
values.path,
"hex",
"--json",
],
{ env: environment, timeout: 45000, maxBuffer: 32 * 1024 * 1024 },
);
const cliEvidence = parseEvidence(JSON.parse(cli.stdout));
assert.deepEqual(cliEvidence.normalized_result, original.normalized_result);
assert.deepEqual(cliEvidence.parameters, original.parameters);
assert.deepEqual(cliEvidence.subject, original.subject);
await writeFile(join(lab, "cli.json"), cli.stdout, {
flag: "wx",
mode: 0o600,
});
await writeFile(join(lab, "original.json"), JSON.stringify(original), {
flag: "wx",
mode: 0o600,
});
rows.push(await verifyLateCancellation(original));
const malformed = join(lab, "odd-nibble.hex");
await writeFile(malformed, "0x0\n", { flag: "wx", mode: 0o600 });
for (const scenario of ["success", "format-failure"]) {
const baseline = await host.captureBaseline();
await gateMode("hold");
const pending = request(scenario === "success" ? values.path : malformed);
let launch;
await verifyWithCleanup(async () => {
launch = await observeGate(scenario === "success");
fault = { path: launch.marker.root, mode: launch.root_mode };
await chmod(fault.path, 0o500);
await writeFile(join(lab, "release"), "release", {
flag: "wx",
mode: 0o600,
});
const failed = errorValue(await pending);
assert.equal(failed.code, "cleanup_incomplete");
assert.deepEqual(failed.details.resources, [launch.marker.root]);
assert.match(failed.details.diagnostics.reason, /EACCES/u);
const previous = failed.details.diagnostics.previous_error;
if (scenario === "success") assert.equal(previous, null);
else assert.equal(previous.category, "invalid_input");
const cleanup = await confirmCleanup(launch, baseline, false);
assert.deepEqual(await roots(), [
launch.marker.root.slice(work.length + 1),
]);
await gateMode("off");
const launchLogBefore = await readFile(join(lab, "launches.log"));
const retryBeforeRepair = errorValue(await request());
assert.equal(retryBeforeRepair.code, "cleanup_incomplete");
assert.deepEqual(retryBeforeRepair.details.resources, [
launch.marker.root,
]);
assert.deepEqual(
await readFile(join(lab, "launches.log")),
launchLogBefore,
);
const peerEvidence = await peer();
assert.equal(peerEvidence.subject.digest.sha256, peerBefore);
await restore();
const recovered = evidenceValue(await request(), "inspect_evm_interface");
assert.deepEqual(recovered.normalized_result, original.normalized_result);
await assert.rejects(access(launch.marker.root), { code: "ENOENT" });
assert.deepEqual(await roots(), []);
rows.push({
scenario: "cleanup-retry-after-" + scenario,
launch,
failed,
cleanup,
retry_before_permission_repair: retryBeforeRepair,
launch_log_sha256_before_retry: sha(launchLogBefore),
peer_evidence_id: peerEvidence.evidence_id,
recovery_evidence_id: recovered.evidence_id,
});
}, [
restore,
() => writeFile(join(lab, "release"), "release", { mode: 0o600 }),
() => pending,
]);
}
}, [
restore,
() => writeFile(join(lab, "release"), "release", { mode: 0o600 }),
() => client.close(),
() => transport.close(),
]);
assert.deepEqual(await roots(), []);
const finalRun = await completeVerifierRun(run);
assert.equal(
finalRun.process_lineage.status,
"verified",
JSON.stringify(finalRun),
);
assert.deepEqual(finalRun.process_lineage.descendants, []);
assert.equal(await fileSha(values.path), targetBefore);
assert.equal(await fileSha(values["peer-path"]), peerBefore);
assert.equal(await fileSha(new URL(import.meta.url)), sourceBefore);
const runtimeTable = JSON.stringify(runtimeFiles);
runtimeFiles.length = 0;
await fingerprint(join(runtime, "dist"));
await fingerprint(join(runtime, "bridge"));
runtimeFiles.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
assert.equal(
JSON.stringify(runtimeFiles),
runtimeTable,
"Runtime changed during verification",
);
await writeFile(
join(lab, "summary.json"),
JSON.stringify({
revision: values.revision,
verifier_run: finalRun,
verifier_sha256: sourceBefore,
runtime_file_table_sha256: sha(JSON.stringify(runtimeFiles)),
cli_default_parity: true,
fixture_sha256: await fileSha(wrapper),
artifact: { path: values.path, sha256: targetBefore },
peer: { path: values["peer-path"], sha256: peerBefore },
scope:
"Linux x64; actual EVMole reply before fixture-held command completion; no mid-WASM interruption claim",
rows,
source_bytes_unchanged: true,
target_bytes_unchanged: true,
peer_bytes_unchanged: true,
owned_runtime_roots_removed: true,
runtime_files_unchanged: true,
}),
{ flag: "wx", mode: 0o600 },
);
console.log(
JSON.stringify({ pass: true, scenarios: rows.map((x) => x.scenario) }),
);