import assert from "node:assert/strict"; import { randomUUID } from "node:crypto"; import { setTimeout as delay } from "node:timers/promises"; import { SubstrateClient } from "../src/client.js"; const endpoint = process.env.SUBSTRATE_ENDPOINT; if (!endpoint) throw new Error("Set SUBSTRATE_ENDPOINT to an existing private ate-env-api. This proof creates one disposable environment, suspends it, and deletes only that environment."); const id = `pi-proof-${randomUUID().slice(0, 8)}`; const client = new SubstrateClient({ endpoint, atespace: process.env.SUBSTRATE_ATESPACE ?? "ate-env", token: process.env.SUBSTRATE_TOKEN, timeoutMs: 120_000, maxBytes: 1_048_576 }); let createAttempted = false; let finished = false; try { createAttempted = true; await client.create(id); // Only a read-only readiness probe may retry. Never replay shell execution or writes. const deadline = Date.now() + 180_000; while (true) { try { await client.read(id, "/etc/hostname", { timeoutMs: 5000 }); break; } catch (error) { if (Date.now() >= deadline) throw error; await delay(1000); } } const token = randomUUID(); await client.write(id, "/tmp/substrate-proof.txt", Buffer.from(token)); const counter = await client.start(id, { command: ["sh", "-c", "n=0; trap 'n=$((n+1)); echo COUNT=$n' USR1; echo PID=$$; while :; do sleep 1; done"] }); let pid = ""; const poll = async (pattern: RegExp) => { const until = Date.now() + 10_000; while (Date.now() < until) { const output = await client.outputs(id, counter); const match = pattern.exec(output.stdout); if (match) return match; await delay(100); } throw new Error(`Remote counter did not produce ${pattern}. Environment retained as ${id}.`); }; pid = (await poll(/PID=(\d+)/))[1]; const signal = async () => { const command = await client.start(id, { command: ["sh", "-c", `kill -USR1 ${pid}`] }); await client.outputs(id, command, { follow: true }); assert.equal((await client.process(id, command)).exitCode, 0); }; await signal(); await poll(/COUNT=1\b/); await client.suspend(id); const suspendDeadline = Date.now() + 60_000; while ((await client.get(id)).status !== "ENVIRONMENT_STATUS_SUSPENDED") { if (Date.now() >= suspendDeadline) throw new Error(`Environment ${id} did not reach suspended state.`); await delay(500); } assert.equal((await client.read(id, "/tmp/substrate-proof.txt")).toString(), token); await signal(); await poll(/COUNT=2\b/); assert.equal((await client.process(id, counter)).status, "PROCESS_STATUS_RUNNING"); await client.kill(id, counter); await client.delete(id); finished = true; console.log(JSON.stringify({ scenario: "full-cluster-suspend-resume", environment: id, upstreamProtocol: "ab40c7bfb2049af1a7aade9e7bf9c6cac925b5ca", filesystemRestored: true, sameProcessMemoryRestored: true, environmentDeleted: true }, null, 2)); } finally { if (createAttempted && !finished) console.error(`Proof incomplete. Inspect environment ${id}; creation or later operation may have succeeded. Delete it explicitly after recovery.`); client.close(); }