owlrunkit 0.25.0 → 0.25.2
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.
- package/CHANGELOG.md +21 -0
- package/NOTICE.md +17 -132
- package/README.md +35 -21
- package/SOURCE.md +6 -2
- package/docs/architecture/OWLCODA_RUNKIT_USAGE_MODE_SELECTION_PRODUCT_DECISION_20260811.md +1 -1
- package/integrations/codex/skills/owlcoda-runkit/SKILL.md +15 -13
- package/integrations/codex/skills/owlcoda-runkit/assets/templates/config.json +3 -3
- package/package.json +1 -1
- package/packages/attest/distribution-manifest-v1.json +10 -0
- package/packages/attest/src/quick.mjs +10 -0
- package/registry-release-policy-v2.json +5 -5
- package/sbom.spdx.json +4 -4
- package/scripts/runkit-contract/control-transaction-lock.mjs +108 -0
- package/scripts/runkit-contract/core-contract.mjs +5 -1
- package/scripts/runkit-contract/lease-lifecycle.mjs +14 -21
- package/scripts/runkit-contract/linux-runtime.mjs +13 -0
- package/scripts/runkit-contract/peer-linux-arm64 +0 -0
- package/scripts/runkit-contract/peer-linux-bwrap.mjs +109 -0
- package/scripts/runkit-contract/peer-linux-x64 +0 -0
- package/scripts/runkit-contract/peer-linux.MUSL-COPYRIGHT +193 -0
- package/scripts/runkit-contract/peer-linux.c +276 -0
- package/scripts/runkit-contract/peer-recovery.mjs +88 -15
- package/scripts/runkit-contract/project-cli-resolver.mjs +8 -0
- package/scripts/runkit-contract/resume-execution.mjs +2 -8
- package/scripts/runkit-contract/runkit-cli.mjs +8 -5
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
-
import { spawn } from "node:child_process";
|
|
2
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
3
3
|
import {
|
|
4
|
-
closeSync, constants, existsSync, fsyncSync, linkSync, lstatSync, mkdirSync,
|
|
4
|
+
accessSync, closeSync, constants, existsSync, fsyncSync, linkSync, lstatSync, mkdirSync,
|
|
5
5
|
mkdtempSync, openSync, readSync, readdirSync, realpathSync, unlinkSync,
|
|
6
6
|
watch, writeFileSync,
|
|
7
7
|
} from "node:fs";
|
|
@@ -9,6 +9,8 @@ import { tmpdir } from "node:os";
|
|
|
9
9
|
import path from "node:path";
|
|
10
10
|
|
|
11
11
|
import { RUNTIME_ROOT, currentCoreIdentity } from "./core-contract.mjs";
|
|
12
|
+
import { linuxRuntimeHelper } from "./linux-runtime.mjs";
|
|
13
|
+
import { BUBBLEWRAP_PEER_BACKEND, bubblewrapPeerArgs, probeBubblewrapPeer } from "./peer-linux-bwrap.mjs";
|
|
12
14
|
import { safeIdentifier } from "./provenance-common.mjs";
|
|
13
15
|
import { resolveVerificationEnvelopeBackendV1 } from "./verification-envelope.mjs";
|
|
14
16
|
|
|
@@ -195,9 +197,37 @@ function projection(task, state) {
|
|
|
195
197
|
};
|
|
196
198
|
}
|
|
197
199
|
|
|
200
|
+
export function resolvePeerRecoveryBackend() {
|
|
201
|
+
if (process.platform === "linux") {
|
|
202
|
+
try {
|
|
203
|
+
const executable = linuxRuntimeHelper();
|
|
204
|
+
if (!executable) return { available: false };
|
|
205
|
+
const result = spawnSync(executable, ["probe"], {
|
|
206
|
+
timeout: 3000, env: {}, stdio: ["ignore", "ignore", "pipe"], encoding: "utf8",
|
|
207
|
+
});
|
|
208
|
+
if (result.status === 0) {
|
|
209
|
+
return { available: true, id: "linux_landlock_seccomp_v1", executable,
|
|
210
|
+
digest: digest(regularBytes(executable, CHECKPOINT_LIMIT)) };
|
|
211
|
+
}
|
|
212
|
+
const reason = result.stderr?.trim() || result.error?.code;
|
|
213
|
+
if (result.status !== 77) return { available: false, reason };
|
|
214
|
+
const fallback = probeBubblewrapPeer(executable);
|
|
215
|
+
if (!fallback.available) {
|
|
216
|
+
return { available: false, reason: [reason, fallback.reason].filter(Boolean).join("; ") };
|
|
217
|
+
}
|
|
218
|
+
const helperHash = digest(regularBytes(executable, CHECKPOINT_LIMIT));
|
|
219
|
+
const sandboxHash = digest(regularBytes(fallback.executable, CHECKPOINT_LIMIT));
|
|
220
|
+
return { ...fallback, digest: digest(JSON.stringify([helperHash, sandboxHash])) };
|
|
221
|
+
} catch (error) { return { available: false, reason: error.code }; }
|
|
222
|
+
}
|
|
223
|
+
return resolveVerificationEnvelopeBackendV1();
|
|
224
|
+
}
|
|
225
|
+
|
|
198
226
|
function requireBackend() {
|
|
199
|
-
const backend =
|
|
200
|
-
if (!backend.available)
|
|
227
|
+
const backend = resolvePeerRecoveryBackend();
|
|
228
|
+
if (!backend.available) {
|
|
229
|
+
throw new Error(["peer_recovery_enforced_backend_unavailable", backend.reason].filter(Boolean).join(": "));
|
|
230
|
+
}
|
|
201
231
|
return backend;
|
|
202
232
|
}
|
|
203
233
|
|
|
@@ -357,35 +387,69 @@ function reportExit(task, attemptId, processExit) {
|
|
|
357
387
|
}
|
|
358
388
|
}
|
|
359
389
|
|
|
360
|
-
|
|
361
|
-
const backend = requireBackend();
|
|
362
|
-
safeIdentifier(agent, "current peer");
|
|
390
|
+
function workerExecutable(command) {
|
|
363
391
|
if (!Array.isArray(command) || !path.isAbsolute(command[0] ?? "")) {
|
|
364
392
|
throw new Error("peer_recovery_absolute_worker_required");
|
|
365
393
|
}
|
|
366
394
|
const executable = realpathSync(command[0]);
|
|
367
395
|
if (!lstatSync(executable).isFile()) throw new Error("peer_recovery_worker_invalid");
|
|
396
|
+
accessSync(executable, constants.X_OK);
|
|
397
|
+
return executable;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function existingHandoff(task, state, agent, generation) {
|
|
401
|
+
if (state.owner !== agent || state.generation !== generation || state.phase === "ready") return null;
|
|
402
|
+
const result = projection(task, state);
|
|
403
|
+
return state.phase === "exited" ? { ...result, exitCode: 2 } : result;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
export async function runPeerTask({ workspaceRoot, taskId, agent, generation, command, handoff = false }) {
|
|
407
|
+
const backend = requireBackend();
|
|
408
|
+
safeIdentifier(agent, "current peer");
|
|
409
|
+
const executable = workerExecutable(command);
|
|
368
410
|
const task = taskDirectory(workspaceRoot, taskId);
|
|
369
411
|
const state = current(task);
|
|
370
412
|
if (state.phase !== "ready" || state.owner !== agent || state.generation !== generation) {
|
|
413
|
+
const prior = handoff && existingHandoff(task, state, agent, generation);
|
|
414
|
+
if (prior) return prior;
|
|
371
415
|
throw new Error("peer_recovery_attempt_not_available");
|
|
372
416
|
}
|
|
373
417
|
const id = randomUUID();
|
|
374
418
|
const directory = mkdtempSync(path.join(realpathSync(tmpdir()), "owlrunkit-peer-"));
|
|
375
419
|
materialize(checkpoint(task, state).files, directory);
|
|
376
|
-
const attempt = { id, directory, backend: backend.id
|
|
420
|
+
const attempt = { id, directory, backend: backend.id,
|
|
421
|
+
...(backend.digest ? { backendSha256: backend.digest } : {}) };
|
|
377
422
|
const claimed = append(task, state, {
|
|
378
423
|
attempt, phase: "running", processExit: null, event: "peer_attempt_started",
|
|
379
424
|
});
|
|
380
|
-
if (!claimed)
|
|
425
|
+
if (!claimed) {
|
|
426
|
+
const prior = handoff && existingHandoff(task, current(task), agent, generation);
|
|
427
|
+
if (prior) return prior;
|
|
428
|
+
throw new Error("peer_recovery_attempt_already_claimed");
|
|
429
|
+
}
|
|
381
430
|
|
|
382
431
|
return new Promise((resolve, reject) => {
|
|
383
|
-
const
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
432
|
+
const environment = {
|
|
433
|
+
PATH: "/usr/bin:/bin", TMPDIR: directory, OWLRUNKIT_PEER_ATTEMPT: id,
|
|
434
|
+
OWLRUNKIT_PEER_PATHS: JSON.stringify(state.paths),
|
|
435
|
+
};
|
|
436
|
+
let args;
|
|
437
|
+
let env = environment;
|
|
438
|
+
if (backend.id === BUBBLEWRAP_PEER_BACKEND) {
|
|
439
|
+
args = bubblewrapPeerArgs({
|
|
440
|
+
helper: backend.helper, directory, worker: executable,
|
|
441
|
+
args: command.slice(1), environment,
|
|
442
|
+
});
|
|
443
|
+
env = {};
|
|
444
|
+
} else if (backend.id === "linux_landlock_seccomp_v1") {
|
|
445
|
+
args = ["run", String(process.pid), directory, executable, ...command.slice(1)];
|
|
446
|
+
// An isolated worker cannot read host OpenSSL configuration.
|
|
447
|
+
env.OPENSSL_CONF = "/dev/null";
|
|
448
|
+
} else {
|
|
449
|
+
args = ["-p", sandboxPolicy(executable, directory), executable, ...command.slice(1)];
|
|
450
|
+
}
|
|
451
|
+
const child = spawn(backend.executable, args, {
|
|
452
|
+
cwd: directory, env, stdio: ["pipe", "pipe", "pipe"],
|
|
389
453
|
});
|
|
390
454
|
let watcher, pending = Buffer.alloc(0), sentWake, protocolError = null;
|
|
391
455
|
let diagnostic = Buffer.alloc(0);
|
|
@@ -440,3 +504,12 @@ export async function runPeerTask({ workspaceRoot, taskId, agent, generation, co
|
|
|
440
504
|
} catch (error) { stop(error); }
|
|
441
505
|
});
|
|
442
506
|
}
|
|
507
|
+
|
|
508
|
+
export async function recoverAndRunPeerTask(options, command) {
|
|
509
|
+
workerExecutable(command);
|
|
510
|
+
const recovered = await recoverPeerTask(options);
|
|
511
|
+
if (recovered.generation !== options.generation + 1 || recovered.owner !== options.agent) {
|
|
512
|
+
return recovered;
|
|
513
|
+
}
|
|
514
|
+
return runPeerTask({ ...options, generation: recovered.generation, command, handoff: true });
|
|
515
|
+
}
|
|
@@ -27,10 +27,18 @@ const SELF_NORMALIZED_QUICK_CORE_VERSIONS = new Set([
|
|
|
27
27
|
"0.24.0",
|
|
28
28
|
"0.24.1",
|
|
29
29
|
"0.25.0",
|
|
30
|
+
"0.25.1",
|
|
31
|
+
"0.25.2",
|
|
30
32
|
]);
|
|
31
33
|
const EXACT_VERSION = /^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?$/;
|
|
32
34
|
const SHA256_REF = /^sha256:[a-f0-9]{64}$/;
|
|
33
35
|
const TRUSTED_PRIOR_CORE_IDENTITIES = new Map([
|
|
36
|
+
["0.25.1", new Set([
|
|
37
|
+
"sha256:0934a076b147847a4623bc89263b677c88088b493495fc74f11485985b7903b5",
|
|
38
|
+
])],
|
|
39
|
+
["0.25.0", new Set([
|
|
40
|
+
"sha256:20e75c386f1408f11e908a6d3ee1a39316d9728d5719fa06f8d6a5aa45abd83e",
|
|
41
|
+
])],
|
|
34
42
|
["0.24.1", new Set([
|
|
35
43
|
"sha256:94aaa2e3a6d504374fa78cfcd341e31b33087806c9ddf5f84b3d3b47a6328e08",
|
|
36
44
|
])],
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
validateExecutionPin,
|
|
21
21
|
validateLeaseOwnedPaths,
|
|
22
22
|
} from "./core-contract.mjs";
|
|
23
|
+
import { acquireRunKitControlLock } from "./control-transaction-lock.mjs";
|
|
23
24
|
import { buildCoverageIndex } from "./coverage-adoption.mjs";
|
|
24
25
|
import { validateVerificationReceiptGate } from "./verification-receipt-gate.mjs";
|
|
25
26
|
import {
|
|
@@ -103,14 +104,7 @@ function acquireResumeLock(workspaceRoot) {
|
|
|
103
104
|
const runtimeRoot = path.join(workspaceRoot, RUNTIME_ROOT);
|
|
104
105
|
assertSafeDirectory(workspaceRoot, runtimeRoot, "RunKit runtime root");
|
|
105
106
|
assertSafeDirectory(runtimeRoot, path.join(runtimeRoot, "executions"), "Executions directory");
|
|
106
|
-
|
|
107
|
-
try {
|
|
108
|
-
mkdirSync(lockPath);
|
|
109
|
-
} catch (error) {
|
|
110
|
-
if (error?.code === "EEXIST") throw new Error("Another RunKit control transaction is already active.");
|
|
111
|
-
throw error;
|
|
112
|
-
}
|
|
113
|
-
return () => rmSync(lockPath, { recursive: true, force: true });
|
|
107
|
+
return acquireRunKitControlLock(runtimeRoot);
|
|
114
108
|
}
|
|
115
109
|
|
|
116
110
|
function activeRunIds(workspaceRoot) {
|
|
@@ -95,6 +95,7 @@ import { buildHookRecoveryV1 } from "./hook-recovery.mjs";
|
|
|
95
95
|
import {
|
|
96
96
|
createPeerRecoveryTask,
|
|
97
97
|
inspectPeerRecoveryTask,
|
|
98
|
+
recoverAndRunPeerTask,
|
|
98
99
|
recoverPeerTask,
|
|
99
100
|
runPeerTask,
|
|
100
101
|
} from "./peer-recovery.mjs";
|
|
@@ -2454,7 +2455,7 @@ const PEER_HELP = `Usage:
|
|
|
2454
2455
|
owlrunkit peer create --workspace <dir> --task <id> --request <json>
|
|
2455
2456
|
owlrunkit peer inspect --workspace <dir> --task <id>
|
|
2456
2457
|
owlrunkit peer run --workspace <dir> --task <id> --agent <id> --generation <n> -- /absolute/worker [args]
|
|
2457
|
-
owlrunkit peer recover --workspace <dir> --task <id> --agent <id> --generation <n> --revision <n> [--wait-ms <n>]
|
|
2458
|
+
owlrunkit peer recover --workspace <dir> --task <id> --agent <id> --generation <n> --revision <n> [--wait-ms <n>] [-- /absolute/worker [args]]
|
|
2458
2459
|
`;
|
|
2459
2460
|
|
|
2460
2461
|
function runPeerCommand(rest) {
|
|
@@ -2468,10 +2469,10 @@ function runPeerCommand(rest) {
|
|
|
2468
2469
|
create: ["request"], inspect: [], run: ["agent", "generation"],
|
|
2469
2470
|
recover: ["agent", "generation", "revision", "wait-ms"],
|
|
2470
2471
|
};
|
|
2471
|
-
if (!Object.hasOwn(flags, action) || (separator !== -1 &&
|
|
2472
|
+
if (!Object.hasOwn(flags, action) || (separator !== -1 && !["run", "recover"].includes(action))) {
|
|
2472
2473
|
throw new Error(PEER_HELP);
|
|
2473
2474
|
}
|
|
2474
|
-
const options = parseOptions(optionArgs);
|
|
2475
|
+
const options = parseOptions(optionArgs, { boolean: ["json"] });
|
|
2475
2476
|
const allowed = new Set(["workspace", "task", "json", ...flags[action]]);
|
|
2476
2477
|
if (Object.keys(options).some(key => !allowed.has(key))) throw new Error(PEER_HELP);
|
|
2477
2478
|
const task = { workspaceRoot: workspace(options), taskId: requireOption(options, "task") };
|
|
@@ -2489,11 +2490,13 @@ function runPeerCommand(rest) {
|
|
|
2489
2490
|
if (separator === -1) throw new Error(PEER_HELP);
|
|
2490
2491
|
return runPeerTask({ ...peer, command: args.slice(separator + 1) });
|
|
2491
2492
|
}
|
|
2492
|
-
|
|
2493
|
+
const recovery = { ...peer,
|
|
2493
2494
|
revision: parseCanonicalNonnegativeSafeInteger(requireOption(options, "revision"), "revision"),
|
|
2494
2495
|
waitMs: options["wait-ms"] === undefined ? undefined
|
|
2495
2496
|
: parseCanonicalNonnegativeSafeInteger(options["wait-ms"], "wait-ms"),
|
|
2496
|
-
}
|
|
2497
|
+
};
|
|
2498
|
+
return separator === -1 ? recoverPeerTask(recovery)
|
|
2499
|
+
: recoverAndRunPeerTask(recovery, args.slice(separator + 1));
|
|
2497
2500
|
}
|
|
2498
2501
|
|
|
2499
2502
|
export async function runCli(argv = process.argv.slice(2), runtimeHooks = {}) {
|