@evoclock/pi-agentic-driver 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,328 @@
1
+ // SPDX-FileCopyrightText: 2026 Julen Gamboa <j.a.r.gamboa@gmail.com>
2
+ // SPDX-License-Identifier: AGPL-3.0-or-later
3
+
4
+ import { createHash, randomBytes } from "node:crypto";
5
+ import { readFileSync } from "node:fs";
6
+ import { dirname, join } from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+ import { spawnSync } from "node:child_process";
9
+ import { isNativeTuiContext } from "./native_tui_context.js";
10
+
11
+ export const LINUX_MICROVM_CUTOVER_TOOL = "agentic_linux_microvm_cutover";
12
+ export const LINUX_MICROVM_CUTOVER_SCHEMA = "agentic-driver.linux-microvm-cutover.v1";
13
+ const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
14
+ const REMOTE_FIXTURE = join(SCRIPT_DIR, "linux_microvm_remote_fixture.sh");
15
+ const REGISTRATIONS = new WeakSet();
16
+ const PLANNED_ISOLATION_MODES = new Set(["planned", "planned-interactive", "planned-autonomous"]);
17
+ const HASH = /^[0-9a-f]{64}$/;
18
+ const MAX_DETAIL = 512;
19
+ let inFlight = false;
20
+
21
+ function boundedText(value, fallback = "unknown failure") {
22
+ const text = String(value ?? fallback)
23
+ .replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "")
24
+ .replace(/[\u0000-\u001f\u007f]+/g, " ")
25
+ .replace(/\b(api[_-]?key|token|password|secret)\s*[:=]\s*[^\s,;]+/gi, "$1=[REDACTED]")
26
+ .replace(/\s+/g, " ")
27
+ .trim();
28
+ return (text || fallback).slice(0, MAX_DETAIL);
29
+ }
30
+
31
+ function reason(phase, code, detail) {
32
+ return { phase, code, detail: boundedText(detail) };
33
+ }
34
+ function denied(status, failure) {
35
+ const value = typeof failure === "object" && failure !== null
36
+ ? reason(failure.phase || "policy", failure.code || "denied", failure.detail)
37
+ : reason("policy", "denied", failure);
38
+ return { schema: LINUX_MICROVM_CUTOVER_SCHEMA, ok: false, status, reason: value,
39
+ authorityCreated: false, runtimeActivated: false, persisted: false };
40
+ }
41
+ function phaseError(phase, code, detail) {
42
+ const error = new Error(boundedText(detail));
43
+ error.phase = phase;
44
+ error.reasonCode = code;
45
+ return error;
46
+ }
47
+ function reasonFromError(error, fallbackPhase, fallbackCode) {
48
+ return reason(error?.phase || fallbackPhase, error?.reasonCode || fallbackCode,
49
+ error?.message || error);
50
+ }
51
+ function hash(value) { return createHash("sha256").update(value).digest("hex"); }
52
+ function run(executable, args, options = {}) {
53
+ const result = spawnSync(executable, args, { encoding: "utf8", input: options.input,
54
+ timeout: options.timeout ?? 30000 });
55
+ return { code: result.status, stdout: result.stdout?.trim() ?? "", stderr: result.stderr?.trim() ?? "", error: result.error?.message };
56
+ }
57
+ function shellQuote(value) { return `'${value.replaceAll("'", "'\\''")}'`; }
58
+ function fixtureDomainForId(fixtureId) {
59
+ if (!/^microvm-[0-9a-f]{24}$/.test(fixtureId)) {
60
+ throw phaseError("preflight", "fixture-identity-invalid", "internal fixture identity is invalid");
61
+ }
62
+ return `agentic-driver-${fixtureId}`;
63
+ }
64
+ function parseFacts(stdout, fixtureId) {
65
+ const values = {};
66
+ for (const line of String(stdout || "").split("\n")) {
67
+ if (!line) continue;
68
+ const separator = line.indexOf("=");
69
+ if (separator <= 0) throw phaseError("preflight", "facts-invalid", "capability probe returned a malformed fact");
70
+ values[line.slice(0, separator)] = line.slice(separator + 1);
71
+ }
72
+ const facts = {
73
+ host: values.host,
74
+ arch: values.arch,
75
+ kernel: values.kernel,
76
+ libvirt: values.libvirt,
77
+ qemu: values.qemu,
78
+ fixtureDomain: values.fixture_domain_name,
79
+ fixtureDomainState: values.fixture_domain_state,
80
+ };
81
+ const domain = fixtureDomainForId(fixtureId);
82
+ if (facts.host !== "ubuntu-backend" || facts.arch !== "x86_64" || facts.libvirt !== "qemu:///system"
83
+ || facts.fixtureDomain !== domain || facts.fixtureDomainState !== "absent"
84
+ || typeof facts.kernel !== "string" || !facts.kernel
85
+ || typeof facts.qemu !== "string" || !facts.qemu) {
86
+ throw phaseError("preflight", "facts-unexpected", "fixed host facts or the exact fixture-domain absence check failed");
87
+ }
88
+ return facts;
89
+ }
90
+ function sshProbe(execute = run, fixtureId) {
91
+ const domain = fixtureDomainForId(fixtureId);
92
+ const quotedDomain = shellQuote(domain);
93
+ const command = [
94
+ "set -eu", "test \"$(uname -m)\" = x86_64", "test -r /dev/kvm -a -w /dev/kvm",
95
+ "test -x /usr/bin/qemu-system-x86_64", "test -x /usr/bin/busybox",
96
+ "test -x /usr/bin/cpio", "test -x /usr/bin/gzip", "test -x /usr/bin/setfacl",
97
+ "test -x /usr/bin/getfacl", "test -n \"$(virsh uri)\"",
98
+ "printf 'host=%s\\n' \"$(hostname)\"", "printf 'arch=%s\\n' \"$(uname -m)\"",
99
+ "printf 'kernel=%s\\n' \"$(uname -r)\"", "printf 'libvirt=%s\\n' \"$(virsh uri)\"",
100
+ "printf 'qemu=%s\\n' \"$(qemu-system-x86_64 --version | head -1)\"",
101
+ `printf 'fixture_domain_name=%s\\n' ${quotedDomain}`,
102
+ `if virsh dominfo ${quotedDomain} >/dev/null 2>&1; then printf 'fixture_domain_state=present\\n'; else names=$(virsh list --all --name); if printf '%s\\n' \"$names\" | grep -F -x -- ${quotedDomain} >/dev/null; then printf 'fixture_domain_state=present\\n'; else match_status=$?; if [ \"$match_status\" -eq 1 ]; then printf 'fixture_domain_state=absent\\n'; else exit 1; fi; fi; fi`,
103
+ ].join("; ");
104
+ const result = execute("ssh", ["linux-backend", command], { timeout: 30000 });
105
+ if (result.code !== 0) {
106
+ throw phaseError("preflight", "probe-failed", result.stderr || result.error || "fixed linux-backend capability probe failed");
107
+ }
108
+ return parseFacts(result.stdout, fixtureId);
109
+ }
110
+ function exactKeys(value, keys, label) {
111
+ if (!value || typeof value !== "object" || Array.isArray(value)
112
+ || JSON.stringify(Object.keys(value).sort()) !== JSON.stringify([...keys].sort())) {
113
+ throw phaseError("evidence", "receipt-invalid", `${label} fields are not closed`);
114
+ }
115
+ }
116
+ function requireHash(value, label) {
117
+ if (typeof value !== "string" || !HASH.test(value)) {
118
+ throw phaseError("evidence", "receipt-invalid", `${label} is not a SHA-256 digest`);
119
+ }
120
+ }
121
+ function requireBoolean(value, label) {
122
+ if (typeof value !== "boolean") throw phaseError("evidence", "receipt-invalid", `${label} is not boolean evidence`);
123
+ }
124
+ function validateFacts(facts, fixtureId) {
125
+ const domain = fixtureDomainForId(fixtureId);
126
+ if (!facts || typeof facts !== "object" || Array.isArray(facts)) {
127
+ throw phaseError("preflight", "facts-invalid", "trusted host facts are not an object");
128
+ }
129
+ if (facts.host !== "ubuntu-backend" || facts.arch !== "x86_64" || facts.libvirt !== "qemu:///system"
130
+ || facts.fixtureDomain !== domain || facts.fixtureDomainState !== "absent"
131
+ || typeof facts.kernel !== "string" || !facts.kernel || typeof facts.qemu !== "string" || !facts.qemu) {
132
+ throw phaseError("preflight", "facts-unexpected", "fixed host facts or the exact fixture-domain absence check failed");
133
+ }
134
+ return facts;
135
+ }
136
+ function parseReceipt(stdout) {
137
+ const candidates = [];
138
+ const unexpected = [];
139
+ for (const line of String(stdout || "").split(/\r?\n/)) {
140
+ const trimmed = line.trim();
141
+ if (!trimmed) continue;
142
+ const payload = trimmed.startsWith("AGENTIC_MICROVM_RECEIPT:")
143
+ ? trimmed.slice("AGENTIC_MICROVM_RECEIPT:".length).trim() : trimmed;
144
+ if (!payload.startsWith("{")) {
145
+ unexpected.push(trimmed);
146
+ continue;
147
+ }
148
+ try {
149
+ const value = JSON.parse(payload);
150
+ if (value?.schema === LINUX_MICROVM_CUTOVER_SCHEMA) candidates.push(value);
151
+ else unexpected.push(trimmed);
152
+ } catch {
153
+ unexpected.push(trimmed);
154
+ }
155
+ }
156
+ if (candidates.length === 0) {
157
+ const detail = unexpected.length ? `structured microVM receipt was absent; output: ${unexpected.join(" ")}`
158
+ : "structured microVM receipt was absent";
159
+ throw phaseError("evidence", "receipt-missing", detail);
160
+ }
161
+ if (candidates.length !== 1) throw phaseError("evidence", "receipt-ambiguous", "multiple structured microVM receipts were returned");
162
+ if (unexpected.length) throw phaseError("evidence", "receipt-extra-output", `unbound fixture output: ${unexpected.join(" ")}`);
163
+ return candidates[0];
164
+ }
165
+ export function validateLinuxMicroVMReceipt(receipt, facts, fixtureId, scriptHash) {
166
+ exactKeys(receipt, ["schema", "ok", "status", "authorityCreated", "runtimeActivated", "persisted",
167
+ "identity", "marker", "scriptHash", "initramfsSha256", "teardown", "context"], "receipt");
168
+ if (receipt.schema !== LINUX_MICROVM_CUTOVER_SCHEMA || receipt.ok !== true || receipt.status !== "VERIFIED"
169
+ || receipt.authorityCreated !== false || receipt.runtimeActivated !== false || receipt.persisted !== false) {
170
+ throw phaseError("evidence", "receipt-invalid", "receipt status or non-authorizing flags are unexpected");
171
+ }
172
+ const domain = fixtureDomainForId(fixtureId);
173
+ exactKeys(receipt.identity, ["remoteHost", "fixtureId", "domain"], "receipt identity");
174
+ if (receipt.identity.remoteHost !== facts.host || receipt.identity.fixtureId !== fixtureId || receipt.identity.domain !== domain) {
175
+ throw phaseError("evidence", "identity-mismatch", "receipt identity does not match the reviewed fixture");
176
+ }
177
+ exactKeys(receipt.marker, ["value", "sha256"], "receipt marker");
178
+ const marker = `AGENTIC_MICROVM_PROBE:${fixtureId}`;
179
+ if (receipt.marker.value !== marker) throw phaseError("evidence", "marker-mismatch", "receipt marker does not match the fixture identity");
180
+ requireHash(receipt.marker.sha256, "marker hash");
181
+ if (receipt.marker.sha256 !== hash(marker)) throw phaseError("evidence", "marker-mismatch", "receipt marker hash does not match the marker");
182
+ requireHash(receipt.scriptHash, "script hash");
183
+ if (receipt.scriptHash !== scriptHash) throw phaseError("evidence", "payload-mismatch", "receipt script hash does not match the reviewed payload");
184
+ requireHash(receipt.initramfsSha256, "initramfs hash");
185
+
186
+ exactKeys(receipt.teardown, ["domain", "acl"], "receipt teardown");
187
+ exactKeys(receipt.teardown.domain, ["name", "transient", "destroyOnExit", "destroyRequested", "absent", "checked", "check"], "domain teardown proof");
188
+ if (receipt.teardown.domain.name !== domain || receipt.teardown.domain.transient !== true
189
+ || receipt.teardown.domain.destroyOnExit !== true || receipt.teardown.domain.absent !== true
190
+ || receipt.teardown.domain.checked !== true || receipt.teardown.domain.check !== "virsh dominfo/list") {
191
+ throw phaseError("teardown", "domain-proof-invalid", "exact fixture-domain absence was not checked");
192
+ }
193
+ requireBoolean(receipt.teardown.domain.destroyRequested, "domain destroy request");
194
+ exactKeys(receipt.teardown.acl, ["beforeSha256", "afterSha256", "equal", "checked", "initramfsEntryRemoved"], "ACL teardown proof");
195
+ requireHash(receipt.teardown.acl.beforeSha256, "ACL before digest");
196
+ requireHash(receipt.teardown.acl.afterSha256, "ACL after digest");
197
+ if (receipt.teardown.acl.equal !== true || receipt.teardown.acl.checked !== true
198
+ || receipt.teardown.acl.initramfsEntryRemoved !== true
199
+ || receipt.teardown.acl.beforeSha256 !== receipt.teardown.acl.afterSha256) {
200
+ throw phaseError("teardown", "acl-proof-invalid", "temporary ACL restoration was not checked as equal");
201
+ }
202
+
203
+ exactKeys(receipt.context, ["filesystem", "network", "guestMounts"], "receipt context");
204
+ exactKeys(receipt.context.filesystem, ["summary", "disk", "hostShare", "credentials", "gpu", "sha256"], "filesystem context");
205
+ exactKeys(receipt.context.network, ["summary", "guest", "sha256"], "network context");
206
+ if (receipt.context.filesystem.summary !== "disk=absent host-share=absent credentials=absent gpu=absent"
207
+ || receipt.context.filesystem.disk !== false || receipt.context.filesystem.hostShare !== false
208
+ || receipt.context.filesystem.credentials !== false || receipt.context.filesystem.gpu !== false
209
+ || receipt.context.network.summary !== "network=absent" || receipt.context.network.guest !== false
210
+ || JSON.stringify(receipt.context.guestMounts) !== JSON.stringify(["proc", "sysfs", "devtmpfs"])) {
211
+ throw phaseError("evidence", "isolation-context-invalid", "guest isolation context is unexpected");
212
+ }
213
+ requireHash(receipt.context.filesystem.sha256, "filesystem context hash");
214
+ requireHash(receipt.context.network.sha256, "network context hash");
215
+ const expectedFilesystem = hash(JSON.stringify({ disk: false, hostShare: false, credentials: false, gpu: false,
216
+ initramfsSha256: receipt.initramfsSha256 }));
217
+ if (receipt.context.filesystem.sha256 !== expectedFilesystem
218
+ || receipt.context.network.sha256 !== hash(JSON.stringify({ network: false }))) {
219
+ throw phaseError("evidence", "context-mismatch", "context digests do not match the closed isolation summary");
220
+ }
221
+ return receipt;
222
+ }
223
+ function normalizedForwardedStderr(result, fallbackPhase, fallbackCode, fallbackDetail) {
224
+ const text = String(result?.stderr || "");
225
+ const lines = text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
226
+ const primary = lines.map((line) => line.match(/^microvm failure phase=([a-z0-9-]+) code=([a-z0-9-]+) detail=(.*)$/i))
227
+ .find(Boolean);
228
+ const cleanup = lines.map((line) => line.match(/^microvm cleanup failure code=([a-z0-9-]+) detail=(.*)$/i))
229
+ .filter(Boolean);
230
+ const details = [];
231
+ if (primary) details.push(primary[3]);
232
+ for (const match of cleanup) details.push(`cleanup: ${match[2]}`);
233
+ if (!details.length) details.push(text || result?.error || fallbackDetail);
234
+ return reason(primary?.[1] || (cleanup.length ? "teardown" : fallbackPhase),
235
+ primary?.[2] || (cleanup.length ? "cleanup-failed" : fallbackCode), details.join("; "));
236
+ }
237
+
238
+ export async function runLinuxMicroVMCutover(context, options = {}) {
239
+ if (!PLANNED_ISOLATION_MODES.has(options.workMode)) {
240
+ return denied("blocked", reason("policy", "planned-isolation-required", "Linux microVM execution is reserved for planned or automated work."));
241
+ }
242
+ const isolationEnabled = options.isolationEnabled ?? options.runtime?.isolationEnabled;
243
+ if (isolationEnabled !== true) {
244
+ return denied("blocked", reason("policy", "planned-isolation-not-enabled", "The planned isolation execution path is not enabled yet."));
245
+ }
246
+ if (!isNativeTuiContext(context) || typeof context?.ui?.confirm !== "function") {
247
+ return denied("blocked", reason("policy", "native-tui-required", "Open the Linux microVM cutover in the interactive Pi TUI."));
248
+ }
249
+ if (inFlight) return denied("denied", reason("execution", "already-active", "another Linux microVM cutover is active in this host session"));
250
+
251
+ const fixtureId = `microvm-${randomBytes(12).toString("hex")}`;
252
+ const fixtureDomain = fixtureDomainForId(fixtureId);
253
+ let script;
254
+ let scriptHash;
255
+ try {
256
+ script = readFileSync(REMOTE_FIXTURE, "utf8");
257
+ scriptHash = hash(script);
258
+ } catch (error) {
259
+ return denied("denied", reasonFromError(error, "payload", "payload-read-failed"));
260
+ }
261
+ const execute = options.execute || run;
262
+ const observe = options.observeFacts || ((executeArg, id) => sshProbe(executeArg, id));
263
+ let facts;
264
+ try { facts = validateFacts(observe(execute, fixtureId), fixtureId); }
265
+ catch (error) { return denied("denied", reasonFromError(error, "preflight", "facts-observation-failed")); }
266
+ const body = [
267
+ "Run one live transient QEMU/KVM microVM proof on linux-backend?",
268
+ `Remote host: ${facts.host} (${facts.arch}, kernel ${facts.kernel})`,
269
+ `Backend: ${facts.libvirt}; ${facts.qemu}`,
270
+ `Fixture: ${fixtureId}; domain: ${fixtureDomain} (preflight absent)`,
271
+ `Versioned fixture SHA-256: ${scriptHash}`,
272
+ "Writes: one generated fixture below ~/agentic-driver-state/cutover-fixtures/microvm/.",
273
+ "Guest: 1 vCPU, 128 MiB, BusyBox initramfs, no disk, network, host share, credentials, GPU, or serving access.",
274
+ "A temporary traverse-only ACL for libvirt-qemu is added to the remote home directory and the exact prior ACL is restored after exit or failure.",
275
+ "The transient domain prints one marker, powers off, and must disappear from libvirt.",
276
+ "No install, download, repository mutation, runtime authority, staging, commit, or push.",
277
+ ].join("\n");
278
+ let confirmed;
279
+ try { confirmed = await context.ui.confirm("Verify Linux microVM isolation", body); }
280
+ catch (error) { return denied("blocked", reason("confirmation", "confirmation-failed", `Native confirmation failed: ${error.message}`)); }
281
+ if (confirmed !== true) return denied("stopped", reason("confirmation", "not-granted", "Native confirmation was not granted."));
282
+
283
+ let current;
284
+ try { current = validateFacts(observe(execute, fixtureId), fixtureId); }
285
+ catch (error) { return denied("denied", reason("reobserve", "facts-observation-failed", `MicroVM facts changed: ${error.message}`)); }
286
+ if (JSON.stringify(current) !== JSON.stringify(facts)) {
287
+ return denied("denied", reason("reobserve", "facts-changed", "MicroVM facts changed after confirmation"));
288
+ }
289
+ inFlight = true;
290
+ try {
291
+ const result = execute("ssh", ["linux-backend", "bash", "-s", "--", fixtureId, scriptHash], { input: script, timeout: 180000 });
292
+ if (!result || result.code !== 0) {
293
+ return denied("blocked", normalizedForwardedStderr(result, "fixture", "execution-failed", "fixed microVM fixture failed"));
294
+ }
295
+ const receipt = parseReceipt(result.stdout);
296
+ return validateLinuxMicroVMReceipt(receipt, facts, fixtureId, scriptHash);
297
+ } catch (error) {
298
+ return denied("blocked", reasonFromError(error, "execution", "fixture-failed"));
299
+ } finally { inFlight = false; }
300
+ }
301
+ function commandArgumentsPresent(args) {
302
+ if (args === undefined || args === null) return false;
303
+ if (typeof args === "string") return args.trim().length > 0;
304
+ if (Array.isArray(args)) return args.some((value) => String(value).trim().length > 0);
305
+ return Object.keys(args).length > 0;
306
+ }
307
+ export function registerLinuxMicroVMCutoverInterface(pi, options = {}) {
308
+ if (typeof pi?.registerTool !== "function" || REGISTRATIONS.has(pi)) return;
309
+ REGISTRATIONS.add(pi);
310
+ const execute = async (_id, params, _signal, _update, context) => {
311
+ const value = params && Object.keys(params).length
312
+ ? denied("denied", reason("input", "model-parameters-not-allowed", "Linux microVM cutover accepts no model parameters"))
313
+ : await runLinuxMicroVMCutover(context, {
314
+ ...options,
315
+ workMode: options.runtime?.workMode,
316
+ isolationEnabled: options.runtime?.isolationEnabled,
317
+ });
318
+ return { content: [{ type: "text", text: JSON.stringify(value, null, 2) }], details: value };
319
+ };
320
+ pi.registerTool({ name: LINUX_MICROVM_CUTOVER_TOOL, label: "Verify Linux microVM",
321
+ description: "Run one native-confirmed transient QEMU/KVM microVM proof on linux-backend.",
322
+ parameters: { type: "object", additionalProperties: false, properties: {} }, execute });
323
+ pi.registerCommand?.("agentic-linux-microvm-cutover", { description: "Run the native-confirmed Linux microVM proof",
324
+ handler: async (args, context) => commandArgumentsPresent(args)
325
+ ? denied("denied", reason("input", "command-arguments-not-allowed", "Linux microVM cutover accepts no command arguments"))
326
+ : (await execute("command", {}, undefined, undefined, context)).details });
327
+ }
328
+ export default registerLinuxMicroVMCutoverInterface;