#!/usr/bin/env node const assert = require("assert"); const fs = require("fs"); const path = require("path"); const repo = path.resolve(__dirname, ".."); function read(relativePath) { return fs.readFileSync(path.join(repo, relativePath), "utf8"); } function expect(source, name, pattern) { assert.match(source, pattern, `missing node lifecycle evidence: ${name}`); } const nodeMain = read("crates/clusterflux-node/src/daemon.rs"); const nodeIdentity = read("crates/clusterflux-node/src/node_identity.rs"); const cliNode = read("crates/clusterflux-cli/src/node.rs"); const nodeTaskReports = read("crates/clusterflux-node/src/task_reports.rs"); const nodeDebugAgent = read("crates/clusterflux-node/src/debug_agent.rs"); const nodeLib = read("crates/clusterflux-node/src/lib.rs"); const sharedWasmtimeRuntime = `${read("crates/clusterflux-wasm-runtime/src/lib.rs")}\n${read("crates/clusterflux-wasm-runtime/src/task_host_linker.rs")}`; const nodeRuntimeSurface = `${nodeLib}\n${sharedWasmtimeRuntime}`; const nodeLifecycleSurface = `${nodeMain}\n${nodeIdentity}\n${nodeTaskReports}\n${nodeDebugAgent}`; const nodeAssignmentRunner = `${read("crates/clusterflux-node/src/assignment_runner.rs")}\n${read("crates/clusterflux-node/src/assignment_runner/control_watcher.rs")}\n${read("crates/clusterflux-node/src/assignment_runner/process_runner.rs")}\n${read("crates/clusterflux-node/src/assignment_runner/validation.rs")}`; const coordinatorCore = read("crates/clusterflux-coordinator/src/lib.rs"); const coordinatorService = `${read("crates/clusterflux-coordinator/src/service.rs")}\n${read("crates/clusterflux-coordinator/src/service/routing.rs")}`; const coordinatorServiceTests = read("crates/clusterflux-coordinator/src/service/tests.rs"); const coordinatorServiceSurface = `${coordinatorService}\n${coordinatorServiceTests}`; const wasmtimeAssignmentSmoke = read("scripts/wasmtime-assignment-smoke.js"); const wasmtimeSmoke = read("scripts/wasmtime-node-smoke.js"); const debugCore = read("crates/clusterflux-core/src/debug.rs"); assert.strictEqual( (nodeMain.match(/CoordinatorSession::connect/g) || []).length, 1, "node runtime should open one coordinator session in the local process-boundary runtime" ); for (const [name, pattern] of [ ["enrollment exchange over session", /"type": "exchange_node_enrollment_grant"/], ["persisted node identity is reused locally", /"type": "node_identity_reused"/], ["heartbeat over session", /"type": "node_heartbeat"/], ["node-originated requests use signed envelope", /"type": "signed_node"/], ["capability report over session", /"type": "report_node_capabilities"/], ["task assignment polling over session", /"type": "poll_task_assignment"/], ["process start over session", /"type": "start_process"/], ["reconnect over session", /"type": "reconnect_node"/], ["debug command polling over session", /"type": "poll_debug_command"/], ["log event over session", /"type": "report_task_log"/], ["VFS metadata over session", /"type": "report_vfs_metadata"/], ["task control polling over session", /"type": "poll_task_control"/], ["completion over session", /"type": "task_completed"/], ["cancellation uses same session", /poll_task_cancellation\(session, args, &task, node_private_key\)/], ["request count is reported", /session\.requests\(\)/], ]) { expect(nodeLifecycleSurface, name, pattern); } expect(cliNode, "user-authorized node attach over Client session", /"type": "attach_node"/); assert.doesNotMatch( nodeIdentity, /"type": "attach_node"/, "a persisted node must authenticate with its signed identity instead of replaying Client attach" ); for (const [name, pattern] of [ ["worker uses enrollment exchange", /workerReady\.node_status, "ready"/], ["worker receives the task instance selected by the SDK runtime", /nodeRun\.task_assignment_response\.task,[\s\S]*sdkRun\.task_spec\.task_instance/], ["worker verifies task ABI", /task_assignment_response\.task_spec\.dispatch\.abi/], ["worker records task completion", /nodeRun\.coordinator_response\.type, "task_recorded"/], ["worker exposes task result", /events\.events\[0\]\.result, \{ SmallJson: 42 \}/], ["Wasm task observes cooperative cancellation", /task_definition: "cooperative_cancellation_probe"[\s\S]*type: "cancel_process"/], ["cooperative cancellation returns under task control", /cancellationNodeRun\.terminal_state, "completed"/], ["running command abort is exercised", /task_definition: "abort_probe"[\s\S]*type: "abort_process"/], ["running command abort reaches terminal state", /abortedNodeRun\.terminal_state, "cancelled"/], ["running native command receives a real Debug Epoch freeze", /waitForNativeSleep[\s\S]*type: "create_debug_epoch"[\s\S]*fully_frozen/], ["native Linux process is observed stopped and resumed", /assert\.match\(procState\(nativeSleepPid\)[\s\S]*stopped[\s\S]*resume_debug_epoch[\s\S]*fully_resumed[\s\S]*assert\.doesNotMatch\(procState\(nativeSleepPid\)/], ["parent and child Wasm tasks are frozen as one process", /task_definition: "debug_parent_probe"[\s\S]*debugChildInstance = "debug_parent_probe-1:child:1"[\s\S]*all-stop across active parent and child Wasm participants[\s\S]*affected_tasks\.length, 2[\s\S]*acknowledgements\.length, 2[\s\S]*\[debugChildInstance, "debug_parent_probe-1"\]/], ["parent and child Wasm tasks both acknowledge resume", /multiParticipantResume[\s\S]*fully_resumed[\s\S]*acknowledgements\.length, 2/], ["abort releases process slot", /afterAbort\.processes, \[\]/], ]) { expect(wasmtimeAssignmentSmoke, name, pattern); } for (const [name, pattern] of [ ["cooperative cancellation and abort are distinct", /cancel_requested: false,[\s\S]*abort_requested: true/], ["controlled runner polls abort while command runs", /fn abort_requested[\s\S]*poll_task_control/], ["controlled runner creates a process group", /process\.process_group\(0\)/], ["controlled runner freezes the native process group", /libc::kill\(process_group, libc::SIGSTOP\)/], ["controlled runner resumes the native process group", /libc::kill\(process_group, libc::SIGCONT\)/], ["controlled runner kills the process group", /libc::kill\(process_group, libc::SIGKILL\)/], ["Wasm code can poll cooperative cancellation", /task_control_v1/], ["matched Wasm probes remain at a quiescent boundary", /TaskHostOperation::DebugProbe[\s\S]*enter_quiescent_host_boundary[\s\S]*leave_quiescent_host_boundary/], ["debug snapshots use the live Wasm task handle registry", /debug_handle_snapshot[\s\S]*task_handle_\{handle_id\}[\s\S]*state=active/], ["native command status comes from the controlled runner", /set_command_status[\s\S]*frozen native command pid[\s\S]*native command exited with status/], ]) { expect(`${coordinatorServiceSurface}\n${nodeLifecycleSurface}\n${sharedWasmtimeRuntime}\n${nodeAssignmentRunner}`, name, pattern); } for (const [name, pattern] of [ ["coordinator rejects stale process ownership", /fn node_reconnect_rejects_stale_process_epoch_after_restart\(\)/], ["reconnect preserves enrolled node identity", /reconnect_node\(&NodeId::from\("node"\), None\)/], ["stale process epoch is rejected", /CoordinatorError::StaleProcessEpoch/], ]) { expect(coordinatorCore, name, pattern); } for (const [name, pattern] of [ ["coordinator delivers cancellation to connected node", /fn service_delivers_cancellation_to_connected_node_and_records_terminal_state\(\)/], ["node polls task control", /CoordinatorRequest::PollTaskControl/], ["cancelled terminal state is recorded", /TaskTerminalState::Cancelled/], ]) { expect(coordinatorServiceSurface, name, pattern); } for (const [name, pattern] of [ ["native lifecycle test exists", /fn linux_task_lifecycle_supports_cancel_and_all_stop_freeze_resume\(\)/], ["native freeze succeeds when supported", /lifecycle\.freeze_for_debug_epoch\(\)\.unwrap\(\)/], ["native resume succeeds", /lifecycle\.resume_after_debug_epoch\(\)/], ["native cancel reaches lifecycle", /lifecycle\.cancel\(\)/], ["unsupported freeze errors", /BackendError::DebugFreezeUnsupported/], ["wasmtime runtime exposes freeze resume probe", /pub fn freeze_resume_i32_export_probe/], ["wasmtime runtime captures Wasm frame locals", /debug_i32_export_snapshot[\s\S]*local_values/], ["wasmtime runtime creates Wasm debug participant", /kind: DebugParticipantKind::WasmTask/], ["wasmtime debug participant carries local values", /local_values: snapshot\.local_values\.clone\(\)/], ["wasmtime runtime resumes after freeze", /epoch\.continue_all\(\)/], ]) { expect(nodeRuntimeSurface, name, pattern); } for (const [name, pattern] of [ ["wasmtime smoke runs debug freeze resume mode", /--debug-freeze-resume/], ["wasmtime smoke verifies frozen state", /debugReport\.frozen_state, "Frozen"/], ["wasmtime smoke verifies resumed state", /debugReport\.resumed_state, "Running"/], ["wasmtime smoke verifies frame local values", /debugReport\.local_values[\s\S]*wasm_local_0/], ["wasmtime smoke proves node runtime reached wasm task", /node_runtime_reached_wasm_task/], ["wasmtime smoke proves node captured locals", /node_runtime_captured_wasm_locals/], ]) { expect(wasmtimeSmoke, name, pattern); } for (const [name, pattern] of [ ["debug model freezes wasm and command participants", /fn breakpoint_creates_all_stop_debug_epoch_for_wasm_and_command_tasks\(\)/], ["debug model rejects unsupported freeze", /fn debug_epoch_reports_failure_when_no_participant_can_freeze\(\)/], ["debug model resumes frozen participants", /fn continue_resumes_every_frozen_participant\(\)/], ["debug model includes captured locals", /local_values/], ["wasm participants are modeled", /DebugParticipantKind::WasmTask/], ["controlled native command participants are modeled", /DebugParticipantKind::ControlledNativeCommand/], ]) { expect(debugCore, name, pattern); } console.log("Node lifecycle contract smoke passed");