#!/usr/bin/env node const assert = require("assert"); const cp = require("child_process"); const crypto = require("crypto"); const fs = require("fs"); const https = require("https"); const os = require("os"); const path = require("path"); const { DapClient } = require("./dap-client"); const { agentIdentity, signedAgentWorkflowRequest } = require("./agent-signing"); const { coordinatorWireRequest } = require("./coordinator-wire"); const { nodeIdentity, nodeIdentityFromPrivateKey, signedNodeHeartbeat, signedNodeRequest, } = require("./node-signing"); const { configurePodmanTestEnvironment } = require("./podman-test-env"); const repo = path.resolve(__dirname, ".."); configurePodmanTestEnvironment(repo); if ( !process.env.DISASMER_PODMAN_NIX_SHELL && cp.spawnSync("podman", ["--version"], { stdio: "ignore" }).status !== 0 && cp.spawnSync("nix", ["--version"], { stdio: "ignore" }).status === 0 ) { cp.execFileSync( "nix", ["shell", "nixpkgs#podman", "--command", "node", __filename], { cwd: repo, env: { ...process.env, DISASMER_PODMAN_NIX_SHELL: "1" }, stdio: "inherit", } ); process.exit(0); } const releaseRoot = path.resolve( process.env.DISASMER_PUBLIC_RELEASE_DIR || path.join(repo, "target/public-release-dryrun") ); const acceptanceRoot = path.join(repo, "target/acceptance"); const manifestPath = path.join(releaseRoot, "public-release-manifest.json"); const reportPath = path.join(acceptanceRoot, "cli-happy-path-live.json"); const serviceEndpoint = "https://disasmer.michelpaulissen.com"; const serviceAddr = process.env.DISASMER_PUBLIC_RELEASE_DRYRUN_SERVICE_ADDR || "disasmer.michelpaulissen.com:443"; const browserOpenCommand = process.env.DISASMER_PUBLIC_RELEASE_DRYRUN_BROWSER_OPEN_COMMAND; const enabled = process.env.DISASMER_CLI_HAPPY_PATH_LIVE === "1"; const strictFullRelease = process.env.DISASMER_STRICT_FULL_RELEASE === "1"; const strictVpsRestart = process.env.DISASMER_STRICT_VPS_RESTART === "1"; const strictVpsHost = process.env.DISASMER_STRICT_VPS_HOST; const strictVpsIdentity = process.env.DISASMER_STRICT_VPS_IDENTITY; const strictServiceUnit = process.env.DISASMER_STRICT_SERVICE_UNIT || "disasmer-public-release-dryrun.service"; const strictSoakSeconds = Number( process.env.DISASMER_STRICT_SOAK_SECONDS || "300" ); const commands = []; function requireEnabled() { if (!enabled) { throw new Error( "DISASMER_CLI_HAPPY_PATH_LIVE=1 is required because this runs the live CLI happy path" ); } if (!browserOpenCommand) { throw new Error( "DISASMER_PUBLIC_RELEASE_DRYRUN_BROWSER_OPEN_COMMAND is required to complete the server-owned hosted browser login" ); } if ( strictFullRelease && !process.env.DISASMER_SECOND_TENANT_SESSION_FILE && !process.env.DISASMER_SECOND_TENANT_EVIDENCE_FILE ) { throw new Error( "DISASMER_STRICT_FULL_RELEASE=1 requires DISASMER_SECOND_TENANT_SESSION_FILE or DISASMER_SECOND_TENANT_EVIDENCE_FILE for the isolation proof" ); } if (strictFullRelease && !process.env.DISASMER_EXPIRED_USER_SESSION_FILE) { throw new Error( "DISASMER_STRICT_FULL_RELEASE=1 requires DISASMER_EXPIRED_USER_SESSION_FILE" ); } if (strictFullRelease && !strictVpsRestart) { throw new Error( "DISASMER_STRICT_FULL_RELEASE=1 requires DISASMER_STRICT_VPS_RESTART=1" ); } if (strictVpsRestart && (!strictVpsHost || !strictVpsIdentity)) { throw new Error( "strict VPS restart requires DISASMER_STRICT_VPS_HOST and DISASMER_STRICT_VPS_IDENTITY" ); } if ( !Number.isInteger(strictSoakSeconds) || strictSoakSeconds < 120 || strictSoakSeconds > 1800 ) { throw new Error("DISASMER_STRICT_SOAK_SECONDS must be an integer from 120 to 1800"); } } function readJson(file) { return JSON.parse(fs.readFileSync(file, "utf8")); } function ensureDir(dir) { fs.mkdirSync(dir, { recursive: true }); } function sha256(bytes) { return `sha256:${crypto.createHash("sha256").update(bytes).digest("hex")}`; } function nonInteractiveGitEnv(extra = {}) { return { ...process.env, GIT_TERMINAL_PROMPT: "0", GIT_ASKPASS: process.env.GIT_ASKPASS || "/bin/false", SSH_ASKPASS: process.env.SSH_ASKPASS || "/bin/false", GIT_SSH_COMMAND: process.env.GIT_SSH_COMMAND || "ssh -o BatchMode=yes -o NumberOfPasswordPrompts=0", ...extra, }; } function commandEnv(command, env) { if (command !== "git") return env; return nonInteractiveGitEnv(env); } function recordCommand(command, args) { const redacted = []; let hideNext = false; for (const argument of args) { if (hideNext) { redacted.push(""); hideNext = false; } else { redacted.push(argument); hideNext = ["--enrollment-grant", "--admin-token"].includes(argument); } } commands.push([command, ...redacted].join(" ")); } function run(command, args, options = {}) { recordCommand(command, args); return cp.execFileSync(command, args, { cwd: repo, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 15 * 60 * 1000, ...options, env: commandEnv(command, options.env), }); } function parseJsonOutput(output) { const trimmed = output.trim(); if (!trimmed) throw new Error("expected JSON output, got empty stdout"); try { return JSON.parse(trimmed); } catch (_) { // Commands may print progress before their final JSON report. } const lines = trimmed.split(/\r?\n/).filter(Boolean); for (let index = lines.length - 1; index >= 0; index -= 1) { try { return JSON.parse(lines.slice(index).join("\n")); } catch (_) { // Keep scanning for the trailing JSON value. } } throw new Error(`could not parse JSON output:\n${trimmed}`); } function runJson(command, args, options = {}) { return parseJsonOutput(run(command, args, options)); } function authenticatedRequest(sessionSecret, request) { return { type: "authenticated", session_secret: sessionSecret, request, }; } function sendHostedControl(payload) { const body = Buffer.from( JSON.stringify(coordinatorWireRequest(payload, "strict-live")) ); const url = new URL("/api/v1/control", serviceEndpoint); return new Promise((resolve, reject) => { const request = https.request( url, { method: "POST", headers: { "content-type": "application/json", "content-length": body.length, }, timeout: 30_000, }, (response) => { const chunks = []; response.on("data", (chunk) => chunks.push(chunk)); response.on("end", () => { const responseBody = Buffer.concat(chunks).toString("utf8"); if (response.statusCode < 200 || response.statusCode >= 300) { reject( new Error( `hosted control HTTP ${response.statusCode}: ${responseBody}` ) ); return; } try { resolve(JSON.parse(responseBody)); } catch (error) { reject( new Error(`hosted control returned invalid JSON: ${error.message}`) ); } }); } ); request.on("timeout", () => request.destroy(new Error("hosted control request timed out")) ); request.on("error", reject); request.end(body); }); } function assertDenied(response, pattern, label) { assert.strictEqual(response.type, "error", `${label}: ${JSON.stringify(response)}`); assert.match(response.message, pattern, `${label}: ${JSON.stringify(response)}`); return { denied: true, category: response.error?.category || null, message: response.message, }; } function readNodeCredential(projectDir, node) { const directory = path.join(projectDir, ".disasmer", "nodes"); for (const file of fs.readdirSync(directory)) { if (!file.endsWith(".json")) continue; const absolute = path.join(directory, file); const credential = readJson(absolute); if (credential.node === node) return { absolute, credential }; } throw new Error(`persisted credential for ${node} was not found`); } function directoryStats(root) { if (!fs.existsSync(root)) return { files: 0, bytes: 0 }; let files = 0; let bytes = 0; const visit = (directory) => { for (const entry of fs.readdirSync(directory, { withFileTypes: true })) { const absolute = path.join(directory, entry.name); if (entry.isDirectory()) visit(absolute); else if (entry.isFile()) { files += 1; bytes += fs.statSync(absolute).size; } } }; visit(root); return { files, bytes }; } function sshArgs(...remoteArgs) { assert(strictVpsHost && strictVpsIdentity); return [ "-i", path.resolve(strictVpsIdentity.replace(/^~(?=\/)/, os.homedir())), "-o", "BatchMode=yes", strictVpsHost, ...remoteArgs, ]; } function strictInfrastructureSample(projectDir) { const memory = Object.fromEntries( run( "ssh", sshArgs( "systemctl", "show", strictServiceUnit, "--property=MemoryCurrent", "--property=MemoryPeak" ) ) .trim() .split(/\r?\n/) .map((line) => line.split("=")) ); const serviceDisk = Number( run( "ssh", sshArgs("du", "-sb", "/var/lib/disasmer-public-release-dryrun") ) .trim() .split(/\s+/)[0] ); const database = run( "ssh", sshArgs( "runuser", "-u", "postgres", "--", "psql", "-d", "disasmer", "-AtF," ), { input: "SELECT pg_database_size('disasmer'), (SELECT count(*) FROM disasmer_tenants), (SELECT count(*) FROM disasmer_projects), (SELECT count(*) FROM disasmer_node_identities), (SELECT count(*) FROM disasmer_cli_sessions);\n", stdio: ["pipe", "pipe", "pipe"], } ) .trim() .split(",") .map(Number); assert(Number.isFinite(Number(memory.MemoryCurrent))); assert(Number.isFinite(Number(memory.MemoryPeak))); assert.strictEqual(database.length, 5); return { sampled_at_epoch_ms: Date.now(), service_memory_current_bytes: Number(memory.MemoryCurrent), service_memory_peak_bytes: Number(memory.MemoryPeak), service_state_disk_bytes: serviceDisk, postgres_database_bytes: database[0], durable_rows: { tenants: database[1], projects: database[2], node_identities: database[3], cli_sessions: database[4], }, local_node_state: directoryStats(path.join(projectDir, ".disasmer")), }; } function executable(root, name) { return path.join(root, "bin", process.platform === "win32" ? `${name}.exe` : name); } function binaryArchive(manifest) { const platform = `${os.platform()}-${os.arch()}`; const asset = manifest.assets.find( (candidate) => candidate.name.startsWith("disasmer-public-binaries-") && candidate.name.includes(platform) ); assert(asset, `missing released binary archive for ${platform}`); assert(fs.existsSync(asset.file), `missing released binary archive ${asset.file}`); return asset.file; } function publicRepoUrl(manifest) { const url = process.env.DISASMER_PUBLIC_REPO_URL || process.env.DISASMER_PUBLIC_REPO_REMOTE || manifest.public_repo_url || manifest.public_repo_remote; assert(url, "public repository URL is required"); assert(url.includes("git.michelpaulissen.com")); return url; } function stagePublicCheckout(manifest, checkout) { const candidateTree = process.env.DISASMER_LIVE_PUBLIC_TREE; if (candidateTree) { const source = path.resolve(candidateTree); assert.strictEqual( source, path.resolve(manifest.public_tree), "explicit live candidate tree must be the tree recorded by the release manifest" ); fs.cpSync(source, checkout, { recursive: true }); return { kind: "local_filtered_release_candidate", path: source, published: false, }; } run("git", ["clone", "--depth", "1", publicRepoUrl(manifest), checkout]); return { kind: "published_forgejo_checkout", url: publicRepoUrl(manifest), published: true, }; } function delay(milliseconds) { return new Promise((resolve) => setTimeout(resolve, milliseconds)); } async function waitForCli(label, action, predicate, timeoutMs = 10 * 60 * 1000) { const deadline = Date.now() + timeoutMs; let lastValue; let lastError; while (Date.now() < deadline) { try { lastValue = action(); if (predicate(lastValue)) return lastValue; lastError = undefined; } catch (error) { lastError = error; } await delay(250); } throw new Error( `${label} timed out; last value=${JSON.stringify(lastValue)}${ lastError ? `; last error=${lastError.message}` : "" }` ); } function spawnJsonLines(command, args, options) { recordCommand(command, args); const child = cp.spawn(command, args, { ...options, stdio: ["ignore", "pipe", "pipe"], }); let stdout = ""; let stderr = ""; const values = []; const waiters = []; function deliver(value) { values.push(value); for (let index = waiters.length - 1; index >= 0; index -= 1) { const waiter = waiters[index]; if (waiter.predicate(value)) { waiters.splice(index, 1); clearTimeout(waiter.timer); waiter.resolve(value); } } } child.stdout.on("data", (chunk) => { stdout += chunk.toString(); while (stdout.includes("\n")) { const newline = stdout.indexOf("\n"); const line = stdout.slice(0, newline).trim(); stdout = stdout.slice(newline + 1); if (!line) continue; try { deliver(JSON.parse(line)); } catch (_) { // Non-JSON progress stays out of the evidence report. } } }); child.stderr.on("data", (chunk) => { stderr += chunk.toString(); }); function waitFor(predicate, label, timeoutMs = 120000) { const existing = values.find(predicate); if (existing) return Promise.resolve(existing); return new Promise((resolve, reject) => { const waiter = { predicate, resolve, reject, timer: undefined }; waiter.timer = setTimeout(() => { const index = waiters.indexOf(waiter); if (index >= 0) waiters.splice(index, 1); reject(new Error(`${label} timed out\n${stderr}`)); }, timeoutMs); waiters.push(waiter); }); } child.once("exit", (code) => { for (const waiter of waiters.splice(0)) { clearTimeout(waiter.timer); waiter.reject(new Error(`${waiter.label || "child"} exited with ${code}\n${stderr}`)); } }); return { child, values, waitFor, stderr: () => stderr }; } async function stopChild(child) { if (!child || child.exitCode !== null) return; child.kill("SIGTERM"); const exited = new Promise((resolve) => child.once("exit", resolve)); const forced = delay(5000).then(() => { if (child.exitCode === null) child.kill("SIGKILL"); }); await Promise.race([exited, forced]); if (child.exitCode === null) await exited; } function rawTaskEvents(report) { return report?.events?.response?.events || []; } function completedFlagship(events) { const completedDefinitions = new Set( events .filter((event) => event.terminal_state === "completed") .map((event) => event.task_definition) ); return ( completedDefinitions.has("prepare_source") && completedDefinitions.has("compile_linux") && completedDefinitions.has("package_release") && events.some( (event) => event.executor === "coordinator_main" && event.terminal_state === "completed" ) ); } function contentAddressedArtifactId(event, logicalName) { const digest = event?.artifact_digest; if (typeof digest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(digest)) { return null; } const artifactId = `${logicalName}-${digest.slice("sha256:".length)}`; return event.artifact_path?.endsWith(`/vfs/artifacts/${artifactId}`) ? artifactId : null; } async function prepareLiveNodeCredentialSecurity({ disasmer, projectDir, scope, tenant, project, suffix, }) { const node = `security-node-${suffix}`; const grant = runJson(disasmer, ["node", "enroll", ...scope], { cwd: projectDir, }); const attach = runJson( disasmer, [ "node", "attach", "--coordinator", serviceEndpoint, "--tenant", tenant, "--project-id", project, "--node", node, "--enrollment-grant", grant.enrollment_grant.grant, "--json", ], { cwd: projectDir } ); assert.strictEqual(attach.boundary.used_enrollment_exchange, true); const stored = readNodeCredential(projectDir, node); const identity = nodeIdentityFromPrivateKey(stored.credential.private_key); assert.strictEqual(identity.publicKey, stored.credential.public_key); const heartbeat = { type: "node_heartbeat", node, node_signature: signedNodeHeartbeat(node, identity, { nonce: `strict-valid-${suffix}`, }), }; const valid = await sendHostedControl(heartbeat); assert.strictEqual(valid.type, "node_heartbeat"); const replay = assertDenied( await sendHostedControl(heartbeat), /nonce.*already.*used|replay/i, "replayed node credential" ); const expired = assertDenied( await sendHostedControl({ type: "node_heartbeat", node, node_signature: signedNodeHeartbeat(node, identity, { nonce: `strict-expired-${suffix}`, issuedAtEpochSeconds: Math.floor(Date.now() / 1000) - 3600, }), }), /expired|clock skew/i, "expired node credential" ); const forgedIdentity = nodeIdentity("strict-forged-node", node); const forged = assertDenied( await sendHostedControl({ type: "node_heartbeat", node, node_signature: signedNodeHeartbeat(node, forgedIdentity, { nonce: `strict-forged-${suffix}`, }), }), /signature|public key/i, "forged node credential" ); const originalCapabilityBody = { type: "report_node_capabilities", tenant, project, node, capabilities: { os: "Linux", arch: process.arch === "x64" ? "x86_64" : process.arch, capabilities: [], environment_backends: [], source_providers: [], }, cached_environment_digests: [], dependency_cache_digests: [], source_snapshots: [], artifact_locations: [], direct_connectivity: false, online: true, }; const modifiedEnvelope = signedNodeRequest( node, identity, "report_node_capabilities", originalCapabilityBody, { nonce: `strict-modified-${suffix}` } ); modifiedEnvelope.request.online = false; const bodyModified = assertDenied( await sendHostedControl(modifiedEnvelope), /signature/i, "body-modified node credential" ); return { node, identity, credential_path: stored.absolute, credential_digest: sha256(fs.readFileSync(stored.absolute)), capability_body: originalCapabilityBody, evidence: { valid_request: valid.type, replay, expired, forged, body_modified: bodyModified, }, }; } async function runLiveAgentCredentialSecurity({ disasmer, projectDir, scope, sessionSecret, tenant, project, suffix, }) { const agent = `security-agent-${suffix}`; const identity = agentIdentity("strict-live-agent", agent); const added = runJson( disasmer, ["key", "add", ...scope, "--agent", agent, "--public-key", identity.publicKey], { cwd: projectDir } ); assert.strictEqual(added.command, "key add"); const processId = `vp-agent-security-${suffix}`; const unsigned = { type: "start_process", tenant, project, actor_agent: agent, process: processId, restart: false, }; const validRequest = signedAgentWorkflowRequest(identity, unsigned, { nonce: `strict-agent-valid-${suffix}`, }); const valid = await sendHostedControl(validRequest); assert.strictEqual(valid.type, "process_started", JSON.stringify(valid)); const replay = assertDenied( await sendHostedControl(validRequest), /nonce.*already.*used|replay/i, "replayed agent credential" ); const expired = assertDenied( await sendHostedControl( signedAgentWorkflowRequest( identity, { ...unsigned, process: `${processId}-expired` }, { nonce: `strict-agent-expired-${suffix}`, issuedAtEpochSeconds: Math.floor(Date.now() / 1000) - 3600, } ) ), /expired|clock skew/i, "expired agent credential" ); const modifiedRequest = signedAgentWorkflowRequest( identity, { ...unsigned, process: `${processId}-modified` }, { nonce: `strict-agent-modified-${suffix}` } ); modifiedRequest.restart = true; const bodyModified = assertDenied( await sendHostedControl(modifiedRequest), /signature/i, "body-modified agent credential" ); const forgedIdentity = agentIdentity("strict-live-forged-agent", agent); const forged = assertDenied( await sendHostedControl( signedAgentWorkflowRequest( forgedIdentity, { ...unsigned, process: `${processId}-forged` }, { nonce: `strict-agent-forged-${suffix}`, publicKeyFingerprint: identity.publicKeyFingerprint, } ) ), /signature|public key/i, "forged agent credential" ); const aborted = await sendHostedControl( authenticatedRequest(sessionSecret, { type: "abort_process", process: processId, }) ); assert.strictEqual(aborted.type, "process_aborted", JSON.stringify(aborted)); const revoked = runJson( disasmer, ["key", "revoke", ...scope, "--agent", agent, "--yes"], { cwd: projectDir } ); assert.strictEqual(revoked.command, "key revoke"); const revokedUse = assertDenied( await sendHostedControl( signedAgentWorkflowRequest( identity, { ...unsigned, process: `${processId}-revoked` }, { nonce: `strict-agent-revoked-${suffix}` } ) ), /revoked/i, "revoked agent credential" ); return { agent, valid_request: valid.type, replay, expired, forged, body_modified: bodyModified, revoked: revokedUse, }; } async function runSecondTenantIsolation({ firstTenant, firstProject, firstProcess, firstNode, firstArtifact, manifest, }) { const file = process.env.DISASMER_SECOND_TENANT_SESSION_FILE; const evidenceFile = process.env.DISASMER_SECOND_TENANT_EVIDENCE_FILE; if (!file && evidenceFile) { const evidenceBytes = fs.readFileSync(path.resolve(evidenceFile)); const previous = JSON.parse(evidenceBytes); const isolation = previous.tenant_isolation; assert.strictEqual(isolation?.executed, true); assert.notStrictEqual(isolation.second_tenant, firstTenant); assert.strictEqual(isolation.project?.denied, true); assert.strictEqual(isolation.process_hidden, true); assert.strictEqual(isolation.node_hidden, true); assert.strictEqual(isolation.tasks_and_logs?.denied, true); assert.strictEqual(isolation.debug?.denied, true); assert.strictEqual(isolation.debug?.audited, true); assert.strictEqual(isolation.debug?.charged_debug_read_bytes, 0); assert.strictEqual(isolation.artifact_and_download?.denied, true); assert.strictEqual(isolation.process_control?.denied, true); const previousDeployment = previous.release_binding?.deployment; const currentDeployment = deploymentProvenance(); assert.strictEqual( previousDeployment?.hosted_service_sha256, currentDeployment.hosted_service_sha256, "reused isolation evidence must target the exact deployed hosted binary" ); assert.deepStrictEqual( previous.release_binding?.binary_digests, manifest.binary_digests, "reused isolation evidence must target the exact public binaries" ); return { ...isolation, reused_from_immutable_evidence: { report_sha256: sha256(evidenceBytes), source_commit: previous.source_commit, hosted_service_sha256: currentDeployment.hosted_service_sha256, public_binary_digests: manifest.binary_digests, }, }; } if (!file) return { executed: false, reason: "second tenant session not supplied" }; const second = readJson(path.resolve(file)); assert.strictEqual(second.coordinator, serviceEndpoint); assert(second.session_secret, "second tenant session omitted its session secret"); assert.notStrictEqual( second.tenant, firstTenant, "isolation proof requires a genuinely distinct hosted tenant" ); const call = (request) => sendHostedControl(authenticatedRequest(second.session_secret, request)); const project = assertDenied( await call({ type: "select_project", project: firstProject }), /outside|not visible|tenant|project/i, "cross-tenant project selection" ); const processes = await call({ type: "list_processes" }); assert.strictEqual( processes.type, "process_statuses", JSON.stringify(processes) ); assert(!JSON.stringify(processes).includes(firstProcess)); const nodes = await call({ type: "list_node_descriptors" }); assert.strictEqual(nodes.type, "node_descriptors", JSON.stringify(nodes)); assert(!JSON.stringify(nodes).includes(firstNode)); const tasksAndLogs = assertDenied( await call({ type: "list_task_events", process: firstProcess }), /outside|scope|tenant|project|not active|requires an active|unknown/i, "cross-tenant task and log listing" ); const debugResponse = await call({ type: "debug_attach", process: firstProcess, }); assert.strictEqual(debugResponse.type, "debug_attach", JSON.stringify(debugResponse)); assert.strictEqual(debugResponse.authorization?.allowed, false); assert.strictEqual(debugResponse.audit_event?.allowed, false); assert.match( debugResponse.authorization?.reason || "", /outside|scope|permission|tenant|project|not active|unknown/i ); assert.strictEqual(debugResponse.charged_debug_read_bytes, 0); const debug = { denied: true, reason: debugResponse.authorization.reason, audited: true, charged_debug_read_bytes: debugResponse.charged_debug_read_bytes, }; const artifact = assertDenied( await call({ type: "create_artifact_download_link", artifact: firstArtifact, max_bytes: 1024, ttl_seconds: 60, }), /outside|scope|tenant|project|not found|unavailable/i, "cross-tenant artifact download" ); const control = assertDenied( await call({ type: "abort_process", process: firstProcess }), /outside|scope|tenant|project|not active|requires an active|unknown/i, "cross-tenant process control" ); return { executed: true, second_tenant: second.tenant, project, process_hidden: true, node_hidden: true, tasks_and_logs: tasksAndLogs, debug, artifact_and_download: artifact, process_control: control, }; } async function runLiveSoak({ disasmer, projectDir, scope, securityNode, }) { if (!strictVpsRestart) { return { executed: false, reason: "strict VPS measurement access not supplied" }; } const runReport = runJson( disasmer, ["run", "build", "--project", ".", "--json"], { cwd: projectDir } ); assert.strictEqual(runReport.status, "main_launched"); const processId = runReport.process; const parked = await waitForCli( "soak coordinator main to park without a capable node", () => runJson( disasmer, ["process", "status", ...scope, "--process", processId], { cwd: projectDir } ), (status) => status.live_process?.main_state === "running" && status.live_process?.main_wait_state === "waiting_for_node", 120000 ); const startedAt = Date.now(); const deadline = startedAt + strictSoakSeconds * 1000; const samples = []; let sampleIndex = 0; while (true) { const capabilities = await sendHostedControl( signedNodeRequest( securityNode.node, securityNode.identity, "report_node_capabilities", securityNode.capability_body, { nonce: `strict-soak-capabilities-${sampleIndex}-${Date.now()}` } ) ); assert.strictEqual( capabilities.type, "node_capabilities_recorded", JSON.stringify(capabilities) ); const poll = await sendHostedControl( signedNodeRequest( securityNode.node, securityNode.identity, "poll_task_assignment", { type: "poll_task_assignment", tenant: securityNode.capability_body.tenant, project: securityNode.capability_body.project, node: securityNode.node, }, { nonce: `strict-soak-poll-${sampleIndex}-${Date.now()}` } ) ); assert.strictEqual(poll.type, "task_assignment", JSON.stringify(poll)); assert.strictEqual(poll.assignment, null); const status = runJson( disasmer, ["process", "status", ...scope, "--process", processId], { cwd: projectDir } ); assert.strictEqual(status.live_process.main_wait_state, "waiting_for_node"); samples.push(strictInfrastructureSample(projectDir)); sampleIndex += 1; if (Date.now() >= deadline) break; await delay(Math.min(30_000, deadline - Date.now())); } const currentMemory = samples.map( (sample) => sample.service_memory_current_bytes ); const serviceDisk = samples.map((sample) => sample.service_state_disk_bytes); const databaseDisk = samples.map((sample) => sample.postgres_database_bytes); const localDisk = samples.map((sample) => sample.local_node_state.bytes); const spread = (values) => Math.max(...values) - Math.min(...values); assert(spread(currentMemory) <= 128 * 1024 * 1024); assert(spread(serviceDisk) <= 16 * 1024 * 1024); assert(spread(databaseDisk) <= 32 * 1024 * 1024); assert(spread(localDisk) <= 16 * 1024 * 1024); assert.deepStrictEqual( samples.at(-1).durable_rows, samples[0].durable_rows, "durable object counts grew during the parked-process soak" ); const aborted = runJson( disasmer, ["process", "abort", ...scope, "--process", processId, "--yes"], { cwd: projectDir } ); assert.strictEqual(aborted.abort_request.process_slot_released, true); return { executed: true, process: processId, duration_seconds: Math.floor((Date.now() - startedAt) / 1000), sample_interval_seconds: 30, sample_count: samples.length, parked_main: { main_state: parked.live_process.main_state, main_wait_state: parked.live_process.main_wait_state, }, signed_node_poll_cycles: samples.length, memory_spread_bytes: spread(currentMemory), service_disk_spread_bytes: spread(serviceDisk), postgres_disk_spread_bytes: spread(databaseDisk), local_node_state_spread_bytes: spread(localDisk), samples, }; } async function revokeSecurityNode({ disasmer, projectDir, scope, securityNode, }) { const revoked = runJson( disasmer, ["node", "revoke", ...scope, "--node", securityNode.node, "--yes"], { cwd: projectDir } ); assert.strictEqual(revoked.command, "node revoke"); const denied = assertDenied( await sendHostedControl({ type: "node_heartbeat", node: securityNode.node, node_signature: signedNodeHeartbeat( securityNode.node, securityNode.identity, { nonce: `strict-node-revoked-${Date.now()}` } ), }), /revoked|unknown node|not recognized|not enrolled/i, "revoked node credential" ); return { node: securityNode.node, denied }; } async function runLiveQuotaPreallocation({ sessionSecret, suffix }) { let denial; let deniedProcess; let acceptedStarts = 0; for (let index = 0; index < 96; index += 1) { const processId = `vp-quota-${suffix}-${index}`; const started = await sendHostedControl( authenticatedRequest(sessionSecret, { type: "start_process", process: processId, restart: false, }) ); if (started.type === "error") { assert.match(started.message, /quota|spawn|limit|community tier/i); denial = started; deniedProcess = processId; break; } assert.strictEqual(started.type, "process_started", JSON.stringify(started)); acceptedStarts += 1; const aborted = await sendHostedControl( authenticatedRequest(sessionSecret, { type: "abort_process", process: processId, }) ); assert.strictEqual(aborted.type, "process_aborted", JSON.stringify(aborted)); } assert(denial, "live community-tier spawn quota was not reached in 96 attempts"); const processes = await sendHostedControl( authenticatedRequest(sessionSecret, { type: "list_processes" }) ); assert.strictEqual( processes.type, "process_statuses", JSON.stringify(processes) ); assert( !JSON.stringify(processes).includes(deniedProcess), "quota-denied process was allocated before denial" ); return { accepted_starts_before_denial: acceptedStarts, denied_process: deniedProcess, denial: { type: denial.type, category: denial.error?.category || null, message: denial.message, }, denied_process_allocated: false, }; } function deploymentProvenance() { if (!strictVpsRestart) return null; const systemGeneration = run( "ssh", sshArgs("readlink", "-f", "/run/current-system") ).trim(); const binarySha256 = run( "ssh", sshArgs( "sha256sum", "/opt/disasmer-public-release-dryrun/bin/disasmer-hosted-service" ) ) .trim() .split(/\s+/)[0]; const fragment = run( "ssh", sshArgs("systemctl", "show", strictServiceUnit, "--property=FragmentPath", "--value") ).trim(); const serviceUnitSha256 = run( "ssh", sshArgs("sha256sum", fragment) ) .trim() .split(/\s+/)[0]; return { system_generation: systemGeneration, hosted_service_sha256: `sha256:${binarySha256}`, service_unit: fragment, service_unit_sha256: `sha256:${serviceUnitSha256}`, }; } function configurationProvenance() { const configRepo = path.resolve( process.env.DISASMER_DEPLOYMENT_CONFIG_REPO || path.join(repo, "..", "michelpaulissen.com") ); return { repository: configRepo, revision: run("git", ["rev-parse", "HEAD"], { cwd: configRepo }).trim(), clean: run("git", ["status", "--short"], { cwd: configRepo }).trim() === "", }; } async function restartHostedServiceAndResume({ disasmer, disasmerNode, projectDir, scope, workerArgs, workerNode, credentialPath, credentialDigest, }) { if (!strictVpsRestart) { return { executed: false, reason: "strict VPS restart access not supplied", worker: null, }; } const before = deploymentProvenance(); run("ssh", sshArgs("systemctl", "restart", strictServiceUnit)); const deadline = Date.now() + 120000; let ping; let lastError; while (Date.now() < deadline) { try { ping = await sendHostedControl({ type: "ping" }); if (ping.type === "pong") break; } catch (error) { lastError = error; } await delay(500); } assert.strictEqual( ping?.type, "pong", `hosted service did not recover after restart: ${lastError?.message || "no pong"}` ); const afterFirstRestart = deploymentProvenance(); assert.strictEqual(afterFirstRestart.system_generation, before.system_generation); assert.strictEqual( afterFirstRestart.hosted_service_sha256, before.hosted_service_sha256 ); const projects = runJson(disasmer, ["project", "list", ...scope], { cwd: projectDir, }); assert(projects.project_count >= 1, "CLI session/project did not survive restart"); const ephemeralRun = runJson( disasmer, ["run", "build", "--project", ".", "--json"], { cwd: projectDir } ); assert.strictEqual(ephemeralRun.status, "main_launched"); const ephemeralProcess = ephemeralRun.process; await waitForCli( "pre-restart ephemeral main to park", () => runJson( disasmer, ["process", "status", ...scope, "--process", ephemeralProcess], { cwd: projectDir } ), (status) => status.live_process?.main_wait_state === "waiting_for_node", 120000 ); run("ssh", sshArgs("systemctl", "restart", strictServiceUnit)); const secondDeadline = Date.now() + 120000; ping = undefined; lastError = undefined; while (Date.now() < secondDeadline) { try { ping = await sendHostedControl({ type: "ping" }); if (ping.type === "pong") break; } catch (error) { lastError = error; } await delay(500); } assert.strictEqual( ping?.type, "pong", `hosted service did not recover after ephemeral-state restart: ${ lastError?.message || "no pong" }` ); const after = deploymentProvenance(); assert.strictEqual(after.system_generation, before.system_generation); assert.strictEqual(after.hosted_service_sha256, before.hosted_service_sha256); const oldStatus = runJson( disasmer, ["process", "status", ...scope, "--process", ephemeralProcess], { cwd: projectDir } ); assert.strictEqual(oldStatus.state, "not_active"); assert.strictEqual(sha256(fs.readFileSync(credentialPath)), credentialDigest); const worker = spawnJsonLines(disasmerNode, workerArgs, { cwd: projectDir, env: { ...process.env }, }); const ready = await worker.waitFor( (value) => value.node_status === "ready", "persisted worker ready after hosted service restart" ); assert.strictEqual(ready.node, workerNode); assert.strictEqual(sha256(fs.readFileSync(credentialPath)), credentialDigest); const restartedRun = runJson( disasmer, ["run", "restart", "--project", ".", "--json"], { cwd: projectDir } ); assert.strictEqual(restartedRun.status, "main_launched"); const restartedProcess = restartedRun.process; const completed = await waitForCli( "new process completion after hosted service restart", () => runJson( disasmer, ["task", "list", ...scope, "--process", restartedProcess], { cwd: projectDir } ), (tasks) => rawTaskEvents(tasks).some( (event) => event.task_definition === "task_add_one" && event.terminal_state === "completed" && JSON.stringify(event.result) === JSON.stringify({ SmallJson: 42 }) ), 5 * 60 * 1000 ); const completedEvent = rawTaskEvents(completed).find( (event) => event.task_definition === "task_add_one" ); const aborted = runJson( disasmer, ["process", "abort", ...scope, "--process", restartedProcess, "--yes"], { cwd: projectDir } ); assert.strictEqual(aborted.abort_request.process_slot_released, true); return { executed: true, account_project_session_preserved: true, node_identity_preserved: true, old_process_terminated_honestly: true, terminated_ephemeral_process: ephemeralProcess, new_process: restartedProcess, new_process_result: completedEvent.result, before, after_first_restart: afterFirstRestart, after, worker, }; } async function finishUserCredentialSecurity({ disasmer, projectDir, scope, sessionSecret, }) { const forged = assertDenied( await sendHostedControl( authenticatedRequest( `forged-session-${crypto.randomBytes(24).toString("hex")}`, { type: "list_projects" } ) ), /not recognized|invalid|authentication|session/i, "forged user session" ); let expired = null; const expiredFile = process.env.DISASMER_EXPIRED_USER_SESSION_FILE; if (expiredFile) { const oldSession = readJson(path.resolve(expiredFile)); assert(oldSession.session_secret, "expired session evidence omitted its secret"); expired = assertDenied( await sendHostedControl( authenticatedRequest(oldSession.session_secret, { type: "list_projects" }) ), /expired/i, "expired user session" ); } else if (strictFullRelease) { throw new Error( "strict full release requires DISASMER_EXPIRED_USER_SESSION_FILE for a genuinely expired hosted session" ); } const logout = runJson(disasmer, ["logout", ...scope, "--yes"], { cwd: projectDir, }); assert.strictEqual(logout.server_session_revocation.revoked, true); const revoked = assertDenied( await sendHostedControl( authenticatedRequest(sessionSecret, { type: "list_projects" }) ), /revoked|not recognized/i, "revoked user session" ); return { forged, expired, revoked }; } async function dapVariables(client, variablesReference) { const request = client.send("variables", { variablesReference }); return (await client.response(request, "variables")).body.variables; } async function runLiveDapEditRestart({ disasmerDap, disasmer, projectDir, scope, worker, }) { const sourcePath = fs.realpathSync(path.join(projectDir, "src/build.rs")); const originalSource = fs.readFileSync(sourcePath, "utf8"); const sourceLines = originalSource.split(/\r?\n/); const lineFor = (needle) => { const line = sourceLines.findIndex((candidate) => candidate.includes(needle)) + 1; assert(line > 0, `flagship source omitted ${needle}`); return line; }; const mainLine = lineFor("pub async fn restart_main()"); const taskLine = lineFor("fn task_add_one("); const client = new DapClient({ cwd: projectDir, command: disasmerDap, args: [], env: { ...process.env }, }); let sourceRestored = false; try { const initialize = client.send("initialize", { adapterID: "disasmer", linesStartAt1: true, columnsStartAt1: true, }); await client.response(initialize, "initialize"); const launch = client.send("launch", { entry: "restart", project: projectDir, runtimeBackend: "live-services", coordinatorEndpoint: serviceEndpoint, }); await client.response(launch, "launch"); await client.waitFor( (message) => message.type === "event" && message.event === "initialized" ); const setBreakpoints = client.send("setBreakpoints", { source: { path: sourcePath }, breakpoints: [{ line: mainLine }, { line: taskLine }], }); const breakpointResponse = await client.response( setBreakpoints, "setBreakpoints" ); assert.deepStrictEqual( breakpointResponse.body.breakpoints.map((breakpoint) => breakpoint.verified), [true, true] ); const configurationDone = client.send("configurationDone"); await client.response(configurationDone, "configurationDone"); const mainStop = await client.waitFor( (message) => message.type === "event" && message.event === "stopped" && message.body.reason === "breakpoint" ); assert.strictEqual(mainStop.body.allThreadsStopped, true); const mainContinue = client.send("continue", { threadId: mainStop.body.threadId }); await client.response(mainContinue, "continue"); const taskStop = await client.waitFor( (message) => message.seq > mainStop.seq && message.type === "event" && message.event === "stopped" && message.body.reason === "breakpoint" ); assert.strictEqual(taskStop.body.allThreadsStopped, true); assert.notStrictEqual(taskStop.body.threadId, mainStop.body.threadId); const stackTrace = client.send("stackTrace", { threadId: taskStop.body.threadId, startFrame: 0, levels: 1, }); const taskFrames = (await client.response(stackTrace, "stackTrace")).body .stackFrames; assert.strictEqual(taskFrames[0].line, taskLine); const scopesRequest = client.send("scopes", { frameId: taskFrames[0].id }); const scopes = (await client.response(scopesRequest, "scopes")).body.scopes; const argsScope = scopes.find( (candidate) => candidate.name === "Task Args and Handles" ); const runtimeScope = scopes.find( (candidate) => candidate.name === "Disasmer Runtime" ); assert(argsScope && runtimeScope); const taskArgs = await dapVariables(client, argsScope.variablesReference); assert( taskArgs.some( (variable) => variable.name === "arg_0" && String(variable.value).includes("41") ) ); const runtime = await dapVariables(client, runtimeScope.variablesReference); assert( runtime.some( (variable) => variable.name === "runtime_backend" && variable.value === "LiveServices" ) ); assert( runtime.some( (variable) => variable.name === "state" && variable.value === "Frozen" ) ); const dapProcess = runtime.find( (variable) => variable.name === "virtual_process_id" )?.value; assert.match(dapProcess || "", /^vp-[0-9a-f]{12}$/); const taskContinue = client.send("continue", { threadId: taskStop.body.threadId }); await client.response(taskContinue, "continue"); await client.waitFor( (message) => message.seq > taskStop.seq && message.type === "event" && message.event === "terminated", 5 * 60 * 1000 ); const beforeEdit = runJson( disasmer, ["task", "list", ...scope, "--process", dapProcess], { cwd: projectDir } ); const originalTaskEvent = rawTaskEvents(beforeEdit).find( (event) => event.task_definition === "task_add_one" && event.terminal_state === "completed" ); assert(originalTaskEvent, "DAP restart entry omitted its original child event"); assert.deepStrictEqual(originalTaskEvent.result, { SmallJson: 42 }); const editedSource = originalSource.replace( " input + 1\n", " input + 2 // strict hosted compatible restart probe\n" ); assert.notStrictEqual(editedSource, originalSource); fs.writeFileSync(sourcePath, editedSource); const restartFrame = client.send("restartFrame", { frameId: taskFrames[0].id, }); const restartResponse = await client.response(restartFrame, "restartFrame"); const restartedStop = await client.waitFor( (message) => message.seq > restartResponse.seq && message.type === "event" && message.event === "stopped" && message.body.reason === "breakpoint" ); assert.strictEqual(restartedStop.body.allThreadsStopped, true); const restartedContinue = client.send("continue", { threadId: restartedStop.body.threadId, }); await client.response(restartedContinue, "continue"); const restartedNodeReport = await worker.waitFor( (value) => value.virtual_thread !== originalTaskEvent.task && value.task_assignment_response?.task_spec?.task_definition === "task_add_one", "edited task to execute on the live restarted node", 5 * 60 * 1000 ); assert.strictEqual( restartedNodeReport.node_status, "completed", `edited task restart failed on the live node: ${JSON.stringify(restartedNodeReport)}` ); assert.match(restartedNodeReport.stdout_tail, /"SmallJson":43/); const restartedTask = restartedNodeReport.virtual_thread; const afterEdit = await waitForCli( "edited live task event", () => runJson(disasmer, ["task", "list", ...scope, "--process", dapProcess], { cwd: projectDir, }), (tasks) => rawTaskEvents(tasks).some( (event) => event.task === restartedTask && event.terminal_state === "completed" && JSON.stringify(event.result) === JSON.stringify({ SmallJson: 43 }) ), 5 * 60 * 1000 ); const restartedEvent = rawTaskEvents(afterEdit).find( (event) => event.task === restartedTask ); fs.writeFileSync(sourcePath, originalSource); sourceRestored = true; return { process: dapProcess, main_breakpoint_line: mainLine, task_breakpoint_line: taskLine, main_all_threads_stopped: mainStop.body.allThreadsStopped, task_all_threads_stopped: taskStop.body.allThreadsStopped, original_task_instance: originalTaskEvent.task, original_result: originalTaskEvent.result, restarted_task_instance: restartedTask, restarted_result: restartedEvent.result, compatible_source_edit: "task_add_one: input + 1 -> input + 2", original_arguments_preserved: true, clean_vfs_boundary_used: true, }; } finally { if (!sourceRestored) fs.writeFileSync(sourcePath, originalSource); await client.close().catch(() => { if (client.child.exitCode === null) client.child.kill("SIGKILL"); }); } } async function main() { requireEnabled(); const manifest = readJson(manifestPath); assert.strictEqual(manifest.kind, "disasmer-public-release-dryrun"); assert.strictEqual(manifest.default_hosted_coordinator_endpoint, serviceEndpoint); assert.strictEqual(serviceAddr, "disasmer.michelpaulissen.com:443"); const workRoot = fs.mkdtempSync(path.join(os.tmpdir(), "disasmer-cli-happy-")); const installDir = path.join(workRoot, "install"); const checkout = path.join(workRoot, "public-repo"); const downloadPath = path.join(workRoot, "release.tar"); const extractedArtifact = path.join(workRoot, "artifact"); ensureDir(installDir); run("tar", ["-xzf", binaryArchive(manifest), "-C", installDir]); const publicSource = stagePublicCheckout(manifest, checkout); const disasmer = executable(installDir, "disasmer"); const disasmerNode = executable(installDir, "disasmer-node"); const disasmerDap = executable(installDir, "disasmer-debug-dap"); const projectDir = path.join(checkout, "examples/launch-build-demo"); const suffix = String(Date.now()); const requestedProject = `project-${suffix}`; const workerNode = `worker-${suffix}`; const login = runJson( disasmer, ["login", "--browser", "--json", "--project-id", requestedProject], { cwd: projectDir, env: { ...process.env, DISASMER_BROWSER_OPEN_COMMAND: browserOpenCommand }, } ); assert.strictEqual(login.plan.coordinator, serviceEndpoint); assert.strictEqual(login.boundary.scoped_cli_session_received, true); assert.strictEqual(login.boundary.provider_tokens_exposed_to_cli, false); assert.strictEqual(login.boundary.provider_tokens_sent_to_nodes, false); const loginSession = login.coordinator_response.session; assert(loginSession, "hosted browser login omitted its scoped session"); const sessionSecret = loginSession.cli_session_secret || loginSession.session_secret; assert(sessionSecret, "hosted browser login omitted the CLI session credential"); const tenant = loginSession.tenant; const project = loginSession.project; const user = loginSession.user; for (const [name, value] of Object.entries({ tenant, project, user })) { assert.strictEqual(typeof value, "string", `hosted session omitted ${name}`); assert.notStrictEqual(value, "", `hosted session returned an empty ${name}`); } const receivedRequestedProject = project === requestedProject || project.endsWith(`-${requestedProject}`); assert( receivedRequestedProject, "login did not return the requested server-owned default project" ); const scope = [ "--coordinator", serviceEndpoint, "--tenant", tenant, "--project-id", project, "--user", user, "--json", ]; const projectInit = runJson( disasmer, [ "project", "init", ...scope, "--new-project", project, "--name", "CLI Happy Path", "--yes", ], { cwd: projectDir } ); assert.strictEqual(projectInit.command, "project init"); assert.strictEqual(projectInit.project_config_written, true); assert.strictEqual(projectInit.coordinator_response.type, "project_created"); const projectList = runJson(disasmer, ["project", "list", ...scope], { cwd: projectDir, }); assert(projectList.project_count >= 1); const projectSelect = runJson( disasmer, ["project", "select", ...scope, project], { cwd: projectDir } ); assert.strictEqual(projectSelect.command, "project select"); const inspection = runJson(disasmer, ["inspect", "--project", ".", "--json"], { cwd: projectDir, }); assert( inspection.metadata.environments.some((environment) => environment.name === "linux") ); assert(Array.isArray(inspection.source_provider_statuses)); assert(inspection.source_provider_manifest); const runReport = runJson( disasmer, ["run", "build", "--project", ".", "--json"], { cwd: projectDir } ); assert.strictEqual(runReport.command, "run"); assert.strictEqual(runReport.status, "main_launched"); assert.strictEqual(runReport.task_launch.type, "main_launched"); const processId = runReport.process; const parkedStatus = await waitForCli( "hosted coordinator main to park before node enrollment", () => runJson( disasmer, ["process", "status", ...scope, "--process", processId], { cwd: projectDir } ), (status) => status.live_process?.main_state === "running" && status.live_process?.main_wait_state === "waiting_for_node" && status.live_process?.connected_nodes?.length === 0, 120000 ); const grant = runJson(disasmer, ["node", "enroll", ...scope], { cwd: projectDir, }); assert.strictEqual(grant.command, "node enroll"); const attach = runJson( disasmer, [ "node", "attach", "--coordinator", serviceEndpoint, "--tenant", tenant, "--project-id", project, "--node", workerNode, "--enrollment-grant", grant.enrollment_grant.grant, "--json", ], { cwd: projectDir } ); assert.strictEqual(attach.command, "node attach"); assert.strictEqual(attach.boundary.used_enrollment_exchange, true); const credentialDir = path.join(projectDir, ".disasmer", "nodes"); const credentialFiles = fs .readdirSync(credentialDir) .filter((file) => file.endsWith(".json")); assert.strictEqual(credentialFiles.length, 1, "expected one persisted node credential"); const credentialPath = path.join(credentialDir, credentialFiles[0]); const credentialBefore = fs.readFileSync(credentialPath); const credentialDigest = sha256(credentialBefore); if (process.platform !== "win32") { assert.strictEqual(fs.statSync(credentialPath).mode & 0o777, 0o600); } const workerArgs = [ "--coordinator", serviceEndpoint, "--tenant", tenant, "--project-id", project, "--node", workerNode, "--worker", "--project-root", projectDir, "--assignment-poll-ms", "500", "--emit-ready", ]; const spawnWorker = () => spawnJsonLines(disasmerNode, workerArgs, { cwd: projectDir, env: { ...process.env }, }); let worker = spawnWorker(); try { const firstReady = await worker.waitFor( (value) => value.node_status === "ready", "first persisted worker ready" ); assert.strictEqual(firstReady.node, workerNode); const completedTasks = await waitForCli( "real hosted flagship completion", () => runJson(disasmer, ["task", "list", ...scope, "--process", processId], { cwd: projectDir, }), (tasks) => completedFlagship(rawTaskEvents(tasks)) ); const firstEvents = rawTaskEvents(completedTasks); assert( firstEvents .filter((event) => event.executor === "node") .every((event) => event.node === workerNode) ); assert( firstEvents.some( (event) => event.task_definition === "package_release" && event.terminal_state === "completed" && contentAddressedArtifactId(event, "release.tar") ) ); await stopChild(worker.child); worker = spawnWorker(); const secondReady = await worker.waitFor( (value) => value.node_status === "ready", "restarted persisted worker ready" ); assert.strictEqual(secondReady.node, workerNode); assert.strictEqual(sha256(fs.readFileSync(credentialPath)), credentialDigest); const nodeStatus = runJson( disasmer, ["node", "status", ...scope, "--node", workerNode], { cwd: projectDir } ); assert(JSON.stringify(nodeStatus.response).includes(workerNode)); const sourceEvent = firstEvents.find( (event) => event.task_definition === "prepare_source" && event.terminal_state === "completed" ); assert(sourceEvent, "flagship omitted its completed source task"); const restartTask = runJson( disasmer, [ "task", "restart", ...scope, sourceEvent.task, "--process", processId, "--yes", ], { cwd: projectDir } ); assert.strictEqual(restartTask.restart_request.accepted, true); const restartedTask = restartTask.response.restarted_task_instance; assert(restartedTask && restartedTask !== sourceEvent.task); const restartedCompletion = await worker.waitFor( (value) => value.node_status === "completed" && value.virtual_thread === restartedTask, "restarted node to complete work under the persisted identity", 5 * 60 * 1000 ); assert.strictEqual(restartedCompletion.terminal_state, "completed"); assert.strictEqual(sha256(fs.readFileSync(credentialPath)), credentialDigest); const processStatus = runJson( disasmer, ["process", "status", ...scope, "--process", processId], { cwd: projectDir } ); assert.strictEqual(processStatus.command, "process status"); assert(processStatus.current_task_count >= 5); const logs = runJson( disasmer, ["logs", ...scope, "--process", processId], { cwd: projectDir } ); assert(logs.log_entries.length >= 4); const artifacts = runJson( disasmer, ["artifact", "list", ...scope, "--process", processId], { cwd: projectDir } ); const releaseArtifact = artifacts.artifacts.find((artifact) => { const digest = artifact.digest; return ( typeof digest === "string" && /^sha256:[0-9a-f]{64}$/.test(digest) && artifact.artifact === `release.tar-${digest.slice("sha256:".length)}` ); }); assert(releaseArtifact, "hosted flagship did not publish release.tar"); assert.strictEqual(releaseArtifact.state, "metadata_flushed"); const download = runJson( disasmer, [ "artifact", "download", ...scope, releaseArtifact.artifact, "--to", downloadPath, ], { cwd: projectDir } ); assert.strictEqual(download.download_session.link_issued, true); assert.strictEqual(download.local_download.local_bytes_written_by_cli, true); assert.strictEqual(download.local_download.verified_digest, releaseArtifact.digest); assert.strictEqual(sha256(fs.readFileSync(downloadPath)), releaseArtifact.digest); ensureDir(extractedArtifact); const archiveEntries = run("tar", ["-tf", downloadPath]); assert.match(archiveEntries, /(?:^|\n)hello-disasmer(?:\n|$)/); run("tar", ["-xf", downloadPath, "-C", extractedArtifact]); const builtExecutable = path.join(extractedArtifact, "hello-disasmer"); assert(fs.existsSync(builtExecutable)); const builtOutput = run(builtExecutable, []).trim(); assert.strictEqual(builtOutput, "hello from a real Disasmer build"); const abortFlagship = runJson( disasmer, ["process", "abort", ...scope, "--process", processId, "--yes"], { cwd: projectDir } ); assert.strictEqual(abortFlagship.abort_request.accepted, true); assert.strictEqual(abortFlagship.abort_request.process_slot_released, true); await waitForCli( "flagship process slot release before live DAP launch", () => runJson( disasmer, ["process", "status", ...scope, "--process", processId], { cwd: projectDir } ), (status) => status.state === "not_active", 120000 ); const dapEditRestart = await runLiveDapEditRestart({ disasmerDap, disasmer, projectDir, scope, worker, }); const restart = runJson( disasmer, ["process", "restart", ...scope, "--process", dapEditRestart.process, "--yes"], { cwd: projectDir } ); assert.strictEqual(restart.restart_request.accepted, true); const cancel = runJson( disasmer, ["process", "cancel", ...scope, "--process", dapEditRestart.process, "--yes"], { cwd: projectDir } ); assert.strictEqual(cancel.cancel_request.accepted, true); const tenantIsolation = await runSecondTenantIsolation({ firstTenant: tenant, firstProject: project, firstProcess: dapEditRestart.process, firstNode: workerNode, firstArtifact: releaseArtifact.artifact, manifest, }); const abortDap = runJson( disasmer, ["process", "abort", ...scope, "--process", dapEditRestart.process, "--yes"], { cwd: projectDir } ); assert.strictEqual(abortDap.abort_request.process_slot_released, true); const securityNode = await prepareLiveNodeCredentialSecurity({ disasmer, projectDir, scope, tenant, project, suffix, }); const agentCredentialSecurity = await runLiveAgentCredentialSecurity({ disasmer, projectDir, scope, sessionSecret, tenant, project, suffix, }); await stopChild(worker.child); const liveSoak = await runLiveSoak({ disasmer, projectDir, scope, securityNode, }); const revokedNodeCredential = await revokeSecurityNode({ disasmer, projectDir, scope, securityNode, }); const quotaPreallocation = await runLiveQuotaPreallocation({ sessionSecret, suffix, }); const serviceRestart = await restartHostedServiceAndResume({ disasmer, disasmerNode, projectDir, scope, workerArgs, workerNode, credentialPath, credentialDigest, }); if (serviceRestart.worker) worker = serviceRestart.worker; const userCredentialSecurity = await finishUserCredentialSecurity({ disasmer, projectDir, scope, sessionSecret, }); const configProvenance = configurationProvenance(); const fullReleasePassed = strictFullRelease && tenantIsolation.executed && liveSoak.executed && serviceRestart.executed && Boolean(userCredentialSecurity.expired) && manifest.source_tree_clean === true && configProvenance.clean; const report = { kind: "disasmer-cli-happy-path-live", release_name: manifest.release_name, source_commit: manifest.source_commit, public_tree_identity: manifest.public_tree_identity, service_addr: serviceAddr, default_hosted_coordinator_endpoint: serviceEndpoint, public_repository_url: publicSource.published ? publicSource.url : null, public_source: publicSource, tenant, project, worker_node: workerNode, process: processId, signed_in: true, received_default_project: receivedRequestedProject, process_started_before_node: true, parked_main_observed: { main_state: parkedStatus.live_process.main_state, main_wait_state: parkedStatus.live_process.main_wait_state, connected_nodes: parkedStatus.live_process.connected_nodes, }, node_enrollment_grants_used: 2, persisted_node_credential_mode: process.platform === "win32" ? null : fs.statSync(credentialPath).mode & 0o777, persisted_node_credential_digest: credentialDigest, node_restarted_without_grant: true, restarted_node_completed_task: restartedTask, flagship_task_definitions: [ "prepare_source", "compile_linux", "package_release", ], rootless_podman_flagship_completed: true, downstream_release_artifact: releaseArtifact, artifact_download: { bytes_written: download.local_download.bytes_written, verified_digest: download.local_download.verified_digest, executable_output: builtOutput, }, flagship_process_aborted_for_dap: true, live_dap_edit_restart: dapEditRestart, process_restart_requested: true, process_cancel_requested: true, dap_process_aborted: true, tenant_isolation: tenantIsolation, credential_security: { user: userCredentialSecurity, node: { node: securityNode.node, credential_digest: securityNode.credential_digest, ...securityNode.evidence, revoked: revokedNodeCredential.denied, }, agent: agentCredentialSecurity, }, quota_preallocation: quotaPreallocation, live_soak: liveSoak, hosted_service_restart: { ...serviceRestart, worker: undefined, }, release_binding: { manifest: manifestPath, source_commit: manifest.source_commit, source_tree_clean: manifest.source_tree_clean, public_tree_identity: manifest.public_tree_identity, binary_digests: manifest.binary_digests, deployment: serviceRestart.after || deploymentProvenance(), configuration: configProvenance, }, commands, acceptance_result: fullReleasePassed ? "passed" : "partial", }; ensureDir(path.dirname(reportPath)); fs.writeFileSync(reportPath, `${JSON.stringify(report, null, 2)}\n`); console.log(`CLI happy-path live smoke passed: ${reportPath}`); } finally { await stopChild(worker?.child); } } main().catch((error) => { console.error(error.stack || error.message); process.exit(1); });