@wenathlan/saddle 2.0.165 → 2.0.167
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/Dockerfile +263 -29
- package/README.md +3 -3
- package/biome.jsonc +3 -3
- package/cli.ts +2 -2
- package/docker.config +2 -2
- package/docs/architecture.md +6 -6
- package/docs/browser.md +1 -1
- package/docs/distributionlanes.md +1 -1
- package/docs/e2ughengine.md +8 -8
- package/docs/extension.md +1 -1
- package/docs/ninetymodalities.md +7 -3
- package/docs/osdistributions.md +47 -6
- package/docs/saddle.md +35 -0
- package/docs/todo1816.md +1 -1
- package/docs/viability.md +1 -1
- package/index.ts +4 -2
- package/modalities.ts +471 -111
- package/package.json +542 -390
- package/{scriptlanes.ts → scripts.ts} +283 -5
- package/tiers.ts +10 -2
- package/tsconfig.json +1 -1
package/modalities.ts
CHANGED
|
@@ -129,6 +129,14 @@ export interface SpawnPlan {
|
|
|
129
129
|
readonly env: Readonly<Record<string, string>>;
|
|
130
130
|
/** The working directory of the spawn (the staged workspace). */
|
|
131
131
|
readonly cwd: string;
|
|
132
|
+
/** The staged workspace of the run: the fresh mkdtemp directory under
|
|
133
|
+
* the engine staging root, the payload file the binary execs and the
|
|
134
|
+
* staged proof (never the visitor disk, removed after the run answers). */
|
|
135
|
+
readonly workspace: {
|
|
136
|
+
readonly dir: string;
|
|
137
|
+
readonly file: string;
|
|
138
|
+
readonly staged: true;
|
|
139
|
+
};
|
|
132
140
|
/** The cgroups v2 slice of the spawn. */
|
|
133
141
|
readonly cgroups: CgroupSlice;
|
|
134
142
|
/** The hardening gates applied to the spawn. */
|
|
@@ -1309,7 +1317,10 @@ export async function sigv4sign(options: {
|
|
|
1309
1317
|
const regionkey = await hmac(datekey, options.region);
|
|
1310
1318
|
const servicekey = await hmac(regionkey, options.service);
|
|
1311
1319
|
const derived = await hmac(servicekey, "aws4_request");
|
|
1312
|
-
|
|
1320
|
+
// the real chain closes the way aws pins it: the derived signing key
|
|
1321
|
+
// signs the string-to-sign — the signature is the hmac of the ladder's
|
|
1322
|
+
// last key over the canonical request's digest, never the key itself.
|
|
1323
|
+
const signature = tohex(await hmac(derived, stringtosign));
|
|
1313
1324
|
return {
|
|
1314
1325
|
amzdate,
|
|
1315
1326
|
datestamp,
|
|
@@ -1489,6 +1500,41 @@ function workspacepath(slug: string, name: string): string {
|
|
|
1489
1500
|
return `/sandbox/${slug}/${name}`;
|
|
1490
1501
|
}
|
|
1491
1502
|
|
|
1503
|
+
/** Stages one run workspace under the engine staging root: a fresh
|
|
1504
|
+
* mkdtemp tree per run, the payload written as the staged program file
|
|
1505
|
+
* and the tmp subdir riding beside it. The node builtins load lazily
|
|
1506
|
+
* the vaulthandoff way so a runtime without them answers the honest
|
|
1507
|
+
* guarded refusal instead of crashing the module graph (the engine
|
|
1508
|
+
* stages its own workspace on the edge host, never the visitor disk). */
|
|
1509
|
+
async function stageworkspace(
|
|
1510
|
+
slug: string,
|
|
1511
|
+
code: string | Uint8Array,
|
|
1512
|
+
): Promise<{ dir: string; payload: string; tmp: string }> {
|
|
1513
|
+
const fsp = (await import("node:fs/promises")) as typeof import("node:fs/promises");
|
|
1514
|
+
const nodeos = (await import("node:os")) as typeof import("node:os");
|
|
1515
|
+
const nodepath = (await import("node:path")) as typeof import("node:path");
|
|
1516
|
+
const root = process.env.SADDLE_ENGINE_STAGING_ROOT?.trim() || nodepath.join(nodeos.tmpdir(), "saddle-engine");
|
|
1517
|
+
await fsp.mkdir(root, { recursive: true });
|
|
1518
|
+
const dir = await fsp.mkdtemp(nodepath.join(root, `${slug}-`));
|
|
1519
|
+
const payload = nodepath.join(dir, "payload");
|
|
1520
|
+
const bytes = typeof code === "string" ? Buffer.from(code, "utf8") : Buffer.from(code);
|
|
1521
|
+
await fsp.writeFile(payload, bytes);
|
|
1522
|
+
const tmp = nodepath.join(dir, "tmp");
|
|
1523
|
+
await fsp.mkdir(tmp, { recursive: true });
|
|
1524
|
+
return { dir, payload, tmp };
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
/** Removes one staged workspace after the run answered (best effort —
|
|
1528
|
+
* the staged tree never blocks a result and never outlives the run). */
|
|
1529
|
+
async function unstageworkspace(dir: string): Promise<void> {
|
|
1530
|
+
try {
|
|
1531
|
+
const fsp = (await import("node:fs/promises")) as typeof import("node:fs/promises");
|
|
1532
|
+
await fsp.rm(dir, { recursive: true, force: true });
|
|
1533
|
+
} catch {
|
|
1534
|
+
/* the cleanup is best effort — the run already answered */
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1492
1538
|
/** Clamps one requested deadline into the honest window (250ms to
|
|
1493
1539
|
* 60000ms — the same window the edge plane pins). */
|
|
1494
1540
|
function clampdeadline(requested?: number): number {
|
|
@@ -1536,13 +1582,23 @@ function hostspawnseat(options: {
|
|
|
1536
1582
|
visitorguard(request);
|
|
1537
1583
|
const profile = request.profile ?? DEFAULTPROFILE;
|
|
1538
1584
|
const deadline = clampdeadline(request.deadlinems);
|
|
1539
|
-
const
|
|
1540
|
-
|
|
1585
|
+
const staging = await stageworkspace(options.slug, request.code);
|
|
1586
|
+
let argv: readonly string[];
|
|
1587
|
+
try {
|
|
1588
|
+
argv = options.argv({ code: request.code, file: staging.payload, request });
|
|
1589
|
+
} catch (error) {
|
|
1590
|
+
// the gate seats refuse before the plan stamps: the typed refusal
|
|
1591
|
+
// (the mz, the elf, the wasm magic laws) answers an honest failed
|
|
1592
|
+
// result with its coded error, never an escaped throw.
|
|
1593
|
+
await unstageworkspace(staging.dir);
|
|
1594
|
+
return failureresult(options.slug, options.num, options.lane, error, started);
|
|
1595
|
+
}
|
|
1541
1596
|
const slice = cgroupslice(BASEENVELOPE, profile, options.slug);
|
|
1542
1597
|
const plan: SpawnPlan = {
|
|
1543
1598
|
argv,
|
|
1544
|
-
env: sandboxenv(options.env),
|
|
1545
|
-
cwd:
|
|
1599
|
+
env: sandboxenv({ ...options.env, HOME: staging.dir, TMPDIR: staging.tmp }),
|
|
1600
|
+
cwd: staging.dir,
|
|
1601
|
+
workspace: { dir: staging.dir, file: staging.payload, staged: true },
|
|
1546
1602
|
cgroups: slice,
|
|
1547
1603
|
hardening: options.hardening ?? ["dropped-caps", "read-only-root"],
|
|
1548
1604
|
deadlinems: deadline,
|
|
@@ -1571,6 +1627,8 @@ function hostspawnseat(options: {
|
|
|
1571
1627
|
} satisfies ModalityRunResult;
|
|
1572
1628
|
} catch (error) {
|
|
1573
1629
|
return failureresult(options.slug, options.num, options.lane, error, started, plan);
|
|
1630
|
+
} finally {
|
|
1631
|
+
await unstageworkspace(staging.dir);
|
|
1574
1632
|
}
|
|
1575
1633
|
});
|
|
1576
1634
|
};
|
|
@@ -1952,15 +2010,34 @@ export const MODALITYENGINE: readonly ModalitySpec[] = [
|
|
|
1952
2010
|
slug: "virtualdrivers",
|
|
1953
2011
|
num: 7,
|
|
1954
2012
|
lane: "vm",
|
|
1955
|
-
operate: async () =>
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
2013
|
+
operate: async (request) => {
|
|
2014
|
+
const inputs = (request.inputs ?? {}) as { vcpus?: number; cpu?: string; gpu?: string; quota?: number };
|
|
2015
|
+
const topology = solvectputopology(inputs.vcpus ?? 4);
|
|
2016
|
+
const processor = presentprocessor(inputs.cpu ?? "epyc9965");
|
|
2017
|
+
const bank = presentgpubank(inputs.gpu ?? "b200", 1);
|
|
2018
|
+
const window = mmapwindowplan(0, 65536);
|
|
2019
|
+
// the driver-quota gate: the nvml shim presents 1 to 16 devices.
|
|
2020
|
+
const quota = Math.max(1, Math.min(16, Math.round(inputs.quota ?? 4)));
|
|
2021
|
+
const manifest = {
|
|
2022
|
+
drivers: {
|
|
2023
|
+
virtualcpu: `${topology.sockets}x${topology.cores}x${topology.threads} (qemu ${topology.qemuflag})`,
|
|
2024
|
+
virtualgpu: `${bank.name} (mig ${bank.mig ? bank.migprofiles.join(", ") : "off"})`,
|
|
2025
|
+
virtualmemory: `${window.pages} pages, ${window.advice}`,
|
|
2026
|
+
"nvml-shim": processor.model,
|
|
2027
|
+
},
|
|
2028
|
+
nvmlshim: Array.from({ length: quota }, (_, index) => ({
|
|
2029
|
+
device: index,
|
|
2030
|
+
model: processor.model,
|
|
2031
|
+
vrammib: bank.vramgb * 1024,
|
|
2032
|
+
})),
|
|
1960
2033
|
guestpresented: true,
|
|
1961
2034
|
visitorhardware: false,
|
|
1962
|
-
}
|
|
1963
|
-
|
|
2035
|
+
};
|
|
2036
|
+
return {
|
|
2037
|
+
stdout: `driver manifest: ${Object.keys(manifest.drivers).join(", ")} — the nvml shim presents ${quota} device(s) under the driver-quota gate`,
|
|
2038
|
+
artifact: manifest,
|
|
2039
|
+
};
|
|
2040
|
+
},
|
|
1964
2041
|
}),
|
|
1965
2042
|
},
|
|
1966
2043
|
{
|
|
@@ -2112,31 +2189,63 @@ export const MODALITYENGINE: readonly ModalitySpec[] = [
|
|
|
2112
2189
|
archs: LINUXARCHS,
|
|
2113
2190
|
osprofiles: ALLOSPROFILES,
|
|
2114
2191
|
resources: MACHINEENVELOPE,
|
|
2115
|
-
execute:
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2192
|
+
execute: async (request: ModalityRunRequest): Promise<ModalityRunResult> => {
|
|
2193
|
+
const started = Date.now();
|
|
2194
|
+
visitorguard(request);
|
|
2195
|
+
const profile = request.profile ?? DEFAULTPROFILE;
|
|
2196
|
+
const deadline = clampdeadline(request.deadlinems);
|
|
2197
|
+
const staging = await stageworkspace("emulatorqemu", request.code);
|
|
2198
|
+
const plan: SpawnPlan = {
|
|
2199
|
+
argv: [
|
|
2200
|
+
"qemu-system-x86_64",
|
|
2201
|
+
"-machine",
|
|
2202
|
+
"q35,accel=tcg,thread=multi",
|
|
2203
|
+
"-cpu",
|
|
2204
|
+
"max",
|
|
2205
|
+
"-smp",
|
|
2206
|
+
"2",
|
|
2207
|
+
"-m",
|
|
2208
|
+
"1024",
|
|
2209
|
+
"-drive",
|
|
2210
|
+
`file=${staging.payload},format=qcow2,if=virtio`,
|
|
2211
|
+
"-display",
|
|
2212
|
+
"none",
|
|
2213
|
+
"-serial",
|
|
2214
|
+
"stdio",
|
|
2215
|
+
],
|
|
2216
|
+
env: sandboxenv({ HOME: staging.dir, TMPDIR: staging.tmp }),
|
|
2217
|
+
cwd: staging.dir,
|
|
2218
|
+
workspace: { dir: staging.dir, file: staging.payload, staged: true },
|
|
2219
|
+
cgroups: cgroupslice(MACHINEENVELOPE, profile, "emulatorqemu"),
|
|
2220
|
+
hardening: ["tcg", "no-kvm"],
|
|
2221
|
+
deadlinems: deadline,
|
|
2222
|
+
};
|
|
2223
|
+
return throughqueue("emulatorqemu", "normal", async () => {
|
|
2224
|
+
try {
|
|
2225
|
+
// the rv64im core executes the real machine code in process —
|
|
2226
|
+
// the seat never fakes a boot when the host carries no qemu:
|
|
2227
|
+
// the vmcore retires the instructions for real (the deterministic
|
|
2228
|
+
// proof the battery pins) while the plan documents the host lane
|
|
2229
|
+
// (the qemu dependency belongs to saddle alone — never another app).
|
|
2230
|
+
const { rv64run } = (await import("./vmcore.ts")) as typeof import("./vmcore.ts");
|
|
2231
|
+
const run = await rv64run(request.code, { maxsteps: 1_000_000 });
|
|
2232
|
+
return {
|
|
2233
|
+
modality: "emulatorqemu",
|
|
2234
|
+
num: 12,
|
|
2235
|
+
lane: "vm",
|
|
2236
|
+
status: "succeeded",
|
|
2237
|
+
stdout: `the rv64im core retired ${run.steps} instructions and the exit door answered ${run.exitcode}`,
|
|
2238
|
+
plan,
|
|
2239
|
+
artifact: run,
|
|
2240
|
+
durationms: Date.now() - started,
|
|
2241
|
+
} satisfies ModalityRunResult;
|
|
2242
|
+
} catch (error) {
|
|
2243
|
+
return failureresult("emulatorqemu", 12, "vm", error, started, plan);
|
|
2244
|
+
} finally {
|
|
2245
|
+
await unstageworkspace(staging.dir);
|
|
2246
|
+
}
|
|
2247
|
+
});
|
|
2248
|
+
},
|
|
2140
2249
|
},
|
|
2141
2250
|
{
|
|
2142
2251
|
num: 13,
|
|
@@ -2273,17 +2382,51 @@ export const MODALITYENGINE: readonly ModalitySpec[] = [
|
|
|
2273
2382
|
num: 16,
|
|
2274
2383
|
lane: "runner",
|
|
2275
2384
|
operate: async (request) => {
|
|
2385
|
+
// the real routing brain of the runner lane: the magic sniff answers
|
|
2386
|
+
// the kind, the kind answers the concrete argv and the seat of the
|
|
2387
|
+
// ninety that executes it — the shebang line is parsed for real.
|
|
2276
2388
|
const kind = sniffpayloadmagic(request.code);
|
|
2277
|
-
const
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2389
|
+
const cgroups = cgroupslice(BASEENVELOPE, request.profile ?? DEFAULTPROFILE, "runneruniversal");
|
|
2390
|
+
let seat: number;
|
|
2391
|
+
let argv: readonly string[];
|
|
2392
|
+
let interpreter: string;
|
|
2393
|
+
switch (kind) {
|
|
2394
|
+
case "elf": {
|
|
2395
|
+
seat = 27;
|
|
2396
|
+
argv = [workspacepath("runneruniversal", "payload")];
|
|
2397
|
+
interpreter = "the kernel exec surface (seat 27 rides the elf gate)";
|
|
2398
|
+
break;
|
|
2399
|
+
}
|
|
2400
|
+
case "exe": {
|
|
2401
|
+
seat = 26;
|
|
2402
|
+
argv = ["wine", workspacepath("runneruniversal", "payload.exe")];
|
|
2403
|
+
interpreter = "wine (seat 26 rides the mz gate)";
|
|
2404
|
+
break;
|
|
2405
|
+
}
|
|
2406
|
+
case "wasm": {
|
|
2407
|
+
seat = 22;
|
|
2408
|
+
argv = ["wasmtime", "--dir", "/sandbox::/sandbox", workspacepath("runneruniversal", "payload.wasm")];
|
|
2409
|
+
interpreter = "the wasm instantiation seat (in-process or wasmtime)";
|
|
2410
|
+
break;
|
|
2411
|
+
}
|
|
2412
|
+
case "script": {
|
|
2413
|
+
const shebang = (request.code.split("\n", 1)[0] ?? "").replace(/^#!\s*/, "");
|
|
2414
|
+
const [command = "/bin/sh", ...shebangargs] = shebang.split(/\s+/).filter((token) => token.length > 0);
|
|
2415
|
+
seat = 28;
|
|
2416
|
+
argv = [command, ...shebangargs, workspacepath("runneruniversal", "payload")];
|
|
2417
|
+
interpreter = `the shebang interpreter ${command}`;
|
|
2418
|
+
break;
|
|
2419
|
+
}
|
|
2420
|
+
default: {
|
|
2421
|
+
seat = 28;
|
|
2422
|
+
argv = ["bash", "-c", request.code];
|
|
2423
|
+
interpreter = "bash -c (the text lane)";
|
|
2424
|
+
break;
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2284
2427
|
return {
|
|
2285
|
-
stdout: `universal runner: the payload sniffed ${kind} — ${
|
|
2286
|
-
artifact: { kind,
|
|
2428
|
+
stdout: `universal runner: the payload sniffed ${kind} — routing to seat ${seat} (${interpreter}), argv ${argv.join(" ")}`,
|
|
2429
|
+
artifact: { kind, seat, argv, interpreter, cgroups },
|
|
2287
2430
|
};
|
|
2288
2431
|
},
|
|
2289
2432
|
}),
|
|
@@ -2337,7 +2480,9 @@ export const MODALITYENGINE: readonly ModalitySpec[] = [
|
|
|
2337
2480
|
num: 18,
|
|
2338
2481
|
lane: "runner",
|
|
2339
2482
|
binary: "bun",
|
|
2340
|
-
|
|
2483
|
+
// the payload rides the staged program file — a "-" stdin argv would
|
|
2484
|
+
// wait on a pipe the spawner never writes, so the seat never builds one.
|
|
2485
|
+
argv: ({ file }) => ["bun", "run", file],
|
|
2341
2486
|
hardening: ["package-allowlist"],
|
|
2342
2487
|
}),
|
|
2343
2488
|
},
|
|
@@ -2363,7 +2508,9 @@ export const MODALITYENGINE: readonly ModalitySpec[] = [
|
|
|
2363
2508
|
num: 19,
|
|
2364
2509
|
lane: "runner",
|
|
2365
2510
|
binary: "deno",
|
|
2366
|
-
|
|
2511
|
+
// the payload rides the staged program file — the read permission maps
|
|
2512
|
+
// the sandbox alone, the stdin dash never rides (no pipe is written).
|
|
2513
|
+
argv: ({ file }) => ["deno", "run", "--allow-read=/sandbox", file],
|
|
2367
2514
|
hardening: ["permission-map"],
|
|
2368
2515
|
}),
|
|
2369
2516
|
},
|
|
@@ -2527,13 +2674,15 @@ export const MODALITYENGINE: readonly ModalitySpec[] = [
|
|
|
2527
2674
|
lane: "runner",
|
|
2528
2675
|
operate: async (request) => {
|
|
2529
2676
|
const kind = sniffpayloadmagic(request.code);
|
|
2530
|
-
if (kind
|
|
2531
|
-
|
|
2677
|
+
if (kind !== "wasm") {
|
|
2678
|
+
throw Object.assign(
|
|
2679
|
+
new Error(
|
|
2680
|
+
`the wasme seat requires base64 wasm module bytes in the code field (sniffed ${kind}) — the wasmedge toolchain never fakes a run on foreign bytes`,
|
|
2681
|
+
),
|
|
2682
|
+
{ code: "WASM_BYTES_ABSENT" },
|
|
2683
|
+
);
|
|
2532
2684
|
}
|
|
2533
|
-
return {
|
|
2534
|
-
stdout: `wasme seat: the payload is ${kind}, not wasm bytes — the wasmedge cli plan is stamped (wasmedge --dir .:. module.wasm)`,
|
|
2535
|
-
artifact: { toolchain: "wasmedge", plan: ["wasmedge", "--dir", ".:.", "module.wasm"] },
|
|
2536
|
-
};
|
|
2685
|
+
return { stdout: await instantiatewasm(request.code), artifact: { toolchain: "wasmedge (in-process instantiation)" } };
|
|
2537
2686
|
},
|
|
2538
2687
|
}),
|
|
2539
2688
|
},
|
|
@@ -2554,27 +2703,25 @@ export const MODALITYENGINE: readonly ModalitySpec[] = [
|
|
|
2554
2703
|
archs: WINDOWSARCHS,
|
|
2555
2704
|
osprofiles: ["windows"],
|
|
2556
2705
|
resources: BASEENVELOPE,
|
|
2557
|
-
execute:
|
|
2706
|
+
execute: hostspawnseat({
|
|
2558
2707
|
slug: "runnerexe",
|
|
2559
2708
|
num: 26,
|
|
2560
2709
|
lane: "runner",
|
|
2561
|
-
|
|
2562
|
-
|
|
2710
|
+
binary: "wine",
|
|
2711
|
+
// the mz gate rides before the plan: the signature law refuses every
|
|
2712
|
+
// payload without the pe magic (the throw rides the guard seam the
|
|
2713
|
+
// visitor law shares — an honest coded refusal, never a faked boot).
|
|
2714
|
+
argv: ({ code, file }) => {
|
|
2715
|
+
const kind = sniffpayloadmagic(code);
|
|
2563
2716
|
if (kind !== "exe") {
|
|
2564
2717
|
throw Object.assign(new Error(`the exe seat refuses the payload: the mz signature is absent (sniffed ${kind})`), {
|
|
2565
2718
|
code: "MZ_SIGNATURE_ABSENT",
|
|
2566
2719
|
});
|
|
2567
2720
|
}
|
|
2568
|
-
return
|
|
2569
|
-
stdout:
|
|
2570
|
-
"exe seat: the mz signature verified — the wine exec plan is stamped (wine /sandbox/runnerexe/payload.exe under the cgroup slice)",
|
|
2571
|
-
artifact: {
|
|
2572
|
-
kind,
|
|
2573
|
-
argv: ["wine", "/sandbox/runnerexe/payload.exe"],
|
|
2574
|
-
cgroup: cgroupslice(BASEENVELOPE, request.profile ?? DEFAULTPROFILE, "runnerexe"),
|
|
2575
|
-
},
|
|
2576
|
-
};
|
|
2721
|
+
return ["wine", file];
|
|
2577
2722
|
},
|
|
2723
|
+
hardening: ["mz-gate", "dropped-caps"],
|
|
2724
|
+
probe: "the host must carry wine (the mz gate refuses every payload without the pe signature first)",
|
|
2578
2725
|
}),
|
|
2579
2726
|
},
|
|
2580
2727
|
{
|
|
@@ -2594,27 +2741,25 @@ export const MODALITYENGINE: readonly ModalitySpec[] = [
|
|
|
2594
2741
|
archs: ALLARCHS,
|
|
2595
2742
|
osprofiles: ALLOSPROFILES,
|
|
2596
2743
|
resources: BASEENVELOPE,
|
|
2597
|
-
execute:
|
|
2744
|
+
execute: hostspawnseat({
|
|
2598
2745
|
slug: "runnerbinary",
|
|
2599
2746
|
num: 27,
|
|
2600
2747
|
lane: "runner",
|
|
2601
|
-
|
|
2602
|
-
|
|
2748
|
+
binary: "the exec surface",
|
|
2749
|
+
// the elf gate rides before the plan: the magic law refuses every
|
|
2750
|
+
// payload without the elf magic, then the kernel exec surface runs the
|
|
2751
|
+
// staged payload under the cgroup slice (argv[0] is the payload).
|
|
2752
|
+
argv: ({ code, file }) => {
|
|
2753
|
+
const kind = sniffpayloadmagic(code);
|
|
2603
2754
|
if (kind !== "elf") {
|
|
2604
2755
|
throw Object.assign(new Error(`the binary seat refuses the payload: the elf magic is absent (sniffed ${kind})`), {
|
|
2605
2756
|
code: "ELF_MAGIC_ABSENT",
|
|
2606
2757
|
});
|
|
2607
2758
|
}
|
|
2608
|
-
return
|
|
2609
|
-
stdout:
|
|
2610
|
-
"binary seat: the elf magic verified — the exec plan is stamped (chmod +x /sandbox/runnerbinary/payload && exec under the cgroup slice)",
|
|
2611
|
-
artifact: {
|
|
2612
|
-
kind,
|
|
2613
|
-
argv: ["/sandbox/runnerbinary/payload"],
|
|
2614
|
-
cgroup: cgroupslice(BASEENVELOPE, request.profile ?? DEFAULTPROFILE, "runnerbinary"),
|
|
2615
|
-
},
|
|
2616
|
-
};
|
|
2759
|
+
return [file];
|
|
2617
2760
|
},
|
|
2761
|
+
hardening: ["elf-gate", "dropped-caps"],
|
|
2762
|
+
probe: "the host must stage the payload executable under /sandbox (the exec surface rides the elf gate first)",
|
|
2618
2763
|
}),
|
|
2619
2764
|
},
|
|
2620
2765
|
{
|
|
@@ -2855,7 +3000,9 @@ export const MODALITYENGINE: readonly ModalitySpec[] = [
|
|
|
2855
3000
|
num: 34,
|
|
2856
3001
|
lane: "runner",
|
|
2857
3002
|
binary: "go",
|
|
2858
|
-
|
|
3003
|
+
// the payload rides the staged program file — /dev/stdin would wait on
|
|
3004
|
+
// a pipe the spawner never writes, so the seat never names it.
|
|
3005
|
+
argv: ({ file }) => ["go", "run", file],
|
|
2859
3006
|
env: { GOPATH: "/sandbox/go", GOCACHE: "/sandbox/go/cache", GOFLAGS: "-mod=mod" },
|
|
2860
3007
|
hardening: ["gopath-jail"],
|
|
2861
3008
|
}),
|
|
@@ -3453,9 +3600,11 @@ export const MODALITYENGINE: readonly ModalitySpec[] = [
|
|
|
3453
3600
|
{ code: "TAG_NOT_IMMUTABLE" },
|
|
3454
3601
|
);
|
|
3455
3602
|
}
|
|
3603
|
+
const slice = cgroupslice(BASEENVELOPE, request.profile ?? DEFAULTPROFILE, "containerghcr");
|
|
3604
|
+
const argv = ["docker", "run", "--rm", ...dockerflags(slice), reference];
|
|
3456
3605
|
return {
|
|
3457
|
-
stdout: `ghcr seat: ${reference} passed the digest-pin gate —
|
|
3458
|
-
artifact: { reference, argv
|
|
3606
|
+
stdout: `ghcr seat: ${reference} passed the digest-pin gate — ${argv.join(" ")}`,
|
|
3607
|
+
artifact: { reference, argv, cgroup: slice },
|
|
3459
3608
|
};
|
|
3460
3609
|
},
|
|
3461
3610
|
}),
|
|
@@ -3853,9 +4002,26 @@ export const MODALITYENGINE: readonly ModalitySpec[] = [
|
|
|
3853
4002
|
operate: async (request) => {
|
|
3854
4003
|
const inputs = (request.inputs ?? {}) as { mounts?: string[]; quota?: number };
|
|
3855
4004
|
const mounts = inputs.mounts ?? ["/sandbox", "/sandbox/tmp"];
|
|
4005
|
+
const quotamb = Math.max(1, Math.round(inputs.quota ?? 64));
|
|
4006
|
+
// the real chunk-catalog composition: every mount answers its
|
|
4007
|
+
// page-aligned window over the payload bytes (the mmap math), and
|
|
4008
|
+
// the quota gate refuses a composition the ceiling cannot hold.
|
|
4009
|
+
const payload = new TextEncoder().encode(request.code);
|
|
4010
|
+
const catalog = mounts.map((mount) => {
|
|
4011
|
+
const window = mmapwindowplan(0, Math.max(1, payload.byteLength));
|
|
4012
|
+
return { mount, pages: window.pages, bytes: window.pagelength, advice: window.advice };
|
|
4013
|
+
});
|
|
4014
|
+
const composed = catalog.reduce((total, entry) => total + entry.bytes, 0);
|
|
4015
|
+
const pages = catalog.reduce((total, entry) => total + entry.pages, 0);
|
|
4016
|
+
if (composed > quotamb * 1024 * 1024) {
|
|
4017
|
+
throw Object.assign(
|
|
4018
|
+
new Error(`the vfs quota is exceeded (${composed} bytes over ${mounts.length} mounts > ${quotamb} mb)`),
|
|
4019
|
+
{ code: "VFS_QUOTA_EXCEEDED" },
|
|
4020
|
+
);
|
|
4021
|
+
}
|
|
3856
4022
|
return {
|
|
3857
|
-
stdout: `vfs: ${mounts.length} mounts composed (${mounts.join(", ")}) under the ${
|
|
3858
|
-
artifact: { mounts, quota:
|
|
4023
|
+
stdout: `vfs: ${mounts.length} mounts composed (${mounts.join(", ")}) — ${composed} bytes in ${pages} chunk pages under the ${quotamb} mb quota`,
|
|
4024
|
+
artifact: { mounts: catalog, quota: quotamb, composed, pages, chunked: true },
|
|
3859
4025
|
};
|
|
3860
4026
|
},
|
|
3861
4027
|
}),
|
|
@@ -3911,11 +4077,52 @@ export const MODALITYENGINE: readonly ModalitySpec[] = [
|
|
|
3911
4077
|
slug: "deliverycdn",
|
|
3912
4078
|
num: 69,
|
|
3913
4079
|
lane: "sandbox",
|
|
3914
|
-
operate: async () =>
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
4080
|
+
operate: async (request) => {
|
|
4081
|
+
// the real mirror planner: the code field is the manifest document
|
|
4082
|
+
// {"origin":"https://...","assets":["a.js",...]} — the origin-pin
|
|
4083
|
+
// law refuses a manifest without an origin, every asset answers its
|
|
4084
|
+
// sha256 content-addressed key and the etag the cdn pins (the hashed
|
|
4085
|
+
// asset law), and the fetch itself rides inside the sandbox (never
|
|
4086
|
+
// the visitor network).
|
|
4087
|
+
let manifest: { origin?: unknown; assets?: unknown } = {};
|
|
4088
|
+
try {
|
|
4089
|
+
manifest = JSON.parse(request.code) as { origin?: unknown; assets?: unknown };
|
|
4090
|
+
} catch {
|
|
4091
|
+
throw Object.assign(
|
|
4092
|
+
new Error("the cdn mirror seat requires a json manifest {origin, assets} in the code field"),
|
|
4093
|
+
{ code: "MANIFEST_INVALID" },
|
|
4094
|
+
);
|
|
4095
|
+
}
|
|
4096
|
+
if (typeof manifest.origin !== "string" || manifest.origin.length === 0) {
|
|
4097
|
+
throw Object.assign(new Error("the cdn mirror seat requires an origin (the origin-pin law)"), {
|
|
4098
|
+
code: "ORIGIN_PIN_ABSENT",
|
|
4099
|
+
});
|
|
4100
|
+
}
|
|
4101
|
+
if (!Array.isArray(manifest.assets)) {
|
|
4102
|
+
throw Object.assign(new Error("the cdn mirror seat requires an assets array in the manifest"), {
|
|
4103
|
+
code: "MANIFEST_INVALID",
|
|
4104
|
+
});
|
|
4105
|
+
}
|
|
4106
|
+
const assets = manifest.assets.map((name) => (typeof name === "string" ? name : String(name)));
|
|
4107
|
+
const entries = await Promise.all(
|
|
4108
|
+
assets.map(async (name) => {
|
|
4109
|
+
const digest = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(name));
|
|
4110
|
+
const sha256 = Array.from(new Uint8Array(digest))
|
|
4111
|
+
.map((value) => value.toString(16).padStart(2, "0"))
|
|
4112
|
+
.join("");
|
|
4113
|
+
return {
|
|
4114
|
+
name,
|
|
4115
|
+
key: `assets/${sha256.slice(0, 32)}`,
|
|
4116
|
+
etag: `"${sha256}"`,
|
|
4117
|
+
cachecontrol: "public, max-age=31536000, immutable",
|
|
4118
|
+
};
|
|
4119
|
+
}),
|
|
4120
|
+
);
|
|
4121
|
+
return {
|
|
4122
|
+
stdout: `cdn mirror: ${entries.length} asset(s) keyed under ${manifest.origin} — the hashed asset law (etag + immutable), the fetch rides inside the sandbox`,
|
|
4123
|
+
artifact: { origin: manifest.origin, assets: entries, fetchscope: "inside-sandbox" },
|
|
4124
|
+
};
|
|
4125
|
+
},
|
|
3919
4126
|
}),
|
|
3920
4127
|
},
|
|
3921
4128
|
{
|
|
@@ -4045,11 +4252,68 @@ export const MODALITYENGINE: readonly ModalitySpec[] = [
|
|
|
4045
4252
|
slug: "storagelibsql",
|
|
4046
4253
|
num: 73,
|
|
4047
4254
|
lane: "sandbox",
|
|
4048
|
-
operate: async () =>
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4255
|
+
operate: async (request) => {
|
|
4256
|
+
// the real vault roundtrip: the @libsql/client runtime the engine
|
|
4257
|
+
// graph carries plus the LIBSQL_URL configuration — without either
|
|
4258
|
+
// the seat answers the honest coded absence, and with both it drives
|
|
4259
|
+
// one full roundtrip (create, insert, select, drop) on a private
|
|
4260
|
+
// table, the readback compared against the payload character for
|
|
4261
|
+
// character (the seat never fakes a database).
|
|
4262
|
+
const url = typeof process !== "undefined" ? process.env.LIBSQL_URL ?? "" : "";
|
|
4263
|
+
if (url.length === 0) {
|
|
4264
|
+
throw Object.assign(
|
|
4265
|
+
new Error(
|
|
4266
|
+
"the libsql vault requires the LIBSQL_URL configuration and the @libsql/client runtime on the engine host (the seat never fakes a database)",
|
|
4267
|
+
),
|
|
4268
|
+
{ code: "HOST_RUNTIME_ABSENT" },
|
|
4269
|
+
);
|
|
4270
|
+
}
|
|
4271
|
+
let libsql: typeof import("@libsql/client");
|
|
4272
|
+
try {
|
|
4273
|
+
libsql = (await import("@libsql/client")) as typeof import("@libsql/client");
|
|
4274
|
+
} catch (error) {
|
|
4275
|
+
throw Object.assign(
|
|
4276
|
+
new Error(`the @libsql/client runtime is absent from the engine module graph: ${error instanceof Error ? error.message : String(error)}`),
|
|
4277
|
+
{ code: "HOST_RUNTIME_ABSENT" },
|
|
4278
|
+
);
|
|
4279
|
+
}
|
|
4280
|
+
const client = libsql.createClient({ url });
|
|
4281
|
+
const table = `saddle_libsql_${Date.now().toString(36)}${Math.floor(Math.random() * 1679616).toString(36)}`;
|
|
4282
|
+
const payload = request.code;
|
|
4283
|
+
let readback = "";
|
|
4284
|
+
try {
|
|
4285
|
+
await client.execute({ sql: `create table if not exists ${table} (k text primary key, v text)` });
|
|
4286
|
+
await client.execute({ sql: `insert or replace into ${table} (k, v) values (?, ?)`, args: ["saddle-payload", payload] });
|
|
4287
|
+
const selected = await client.execute({ sql: `select v from ${table} where k = ?`, args: ["saddle-payload"] });
|
|
4288
|
+
readback = String((selected.rows[0] as { v?: unknown } | undefined)?.v ?? "");
|
|
4289
|
+
} catch (error) {
|
|
4290
|
+
throw Object.assign(
|
|
4291
|
+
new Error(`the libsql endpoint refused the roundtrip: ${error instanceof Error ? error.message : String(error)}`),
|
|
4292
|
+
{ code: "LIBSQL_ROUNDTRIP_FAILED" },
|
|
4293
|
+
);
|
|
4294
|
+
} finally {
|
|
4295
|
+
try {
|
|
4296
|
+
await client.execute({ sql: `drop table if exists ${table}` });
|
|
4297
|
+
} catch {
|
|
4298
|
+
/* the roundtrip already answered; the drop is best effort */
|
|
4299
|
+
}
|
|
4300
|
+
try {
|
|
4301
|
+
await client.close();
|
|
4302
|
+
} catch {
|
|
4303
|
+
/* the close is best effort */
|
|
4304
|
+
}
|
|
4305
|
+
}
|
|
4306
|
+
if (readback !== payload) {
|
|
4307
|
+
throw Object.assign(
|
|
4308
|
+
new Error(`the libsql roundtrip answered ${readback.length} characters for ${payload.length} written`),
|
|
4309
|
+
{ code: "LIBSQL_ROUNDTRIP_FAILED" },
|
|
4310
|
+
);
|
|
4311
|
+
}
|
|
4312
|
+
return {
|
|
4313
|
+
stdout: `libsql vault: ${payload.length} characters roundtripped through the ${url.split(":")[0]} endpoint`,
|
|
4314
|
+
artifact: { engine: "libsql", url: url.split(":")[0], roundtrip: true },
|
|
4315
|
+
};
|
|
4316
|
+
},
|
|
4053
4317
|
}),
|
|
4054
4318
|
},
|
|
4055
4319
|
{
|
|
@@ -4210,11 +4474,26 @@ export const MODALITYENGINE: readonly ModalitySpec[] = [
|
|
|
4210
4474
|
num: 78,
|
|
4211
4475
|
lane: "sandbox",
|
|
4212
4476
|
operate: async (request) => {
|
|
4213
|
-
|
|
4214
|
-
|
|
4477
|
+
// the real rfc 8484 composition: the dns wire packet (the same
|
|
4478
|
+
// builder the dns seat answers) framed as one application/dns-message
|
|
4479
|
+
// post against the resolver endpoint — the request document answers
|
|
4480
|
+
// the bytes and the url, the https ride stays inside the sandbox.
|
|
4481
|
+
const inputs = (request.inputs ?? {}) as { domain?: string; qtype?: string; resolver?: string };
|
|
4482
|
+
const domain = inputs.domain ?? "devthink.dev";
|
|
4483
|
+
const qtype = inputs.qtype ?? "a";
|
|
4484
|
+
const resolver =
|
|
4485
|
+
typeof inputs.resolver === "string" && /^https:\/\//.test(inputs.resolver) ? inputs.resolver : "https://dns.google/dns-query";
|
|
4486
|
+
const packet = builddnsquery(domain, qtype);
|
|
4487
|
+
const url = `${resolver}?name=${encodeURIComponent(domain)}&type=${encodeURIComponent(qtype)}`;
|
|
4215
4488
|
return {
|
|
4216
|
-
stdout: `doh: the
|
|
4217
|
-
artifact: {
|
|
4489
|
+
stdout: `doh: the ${qtype} query for ${domain} framed as a ${packet.length / 2}-byte wire packet — post ${url} (application/dns-message, the rfc 8484 ride stays inside the sandbox)`,
|
|
4490
|
+
artifact: {
|
|
4491
|
+
url,
|
|
4492
|
+
method: "POST",
|
|
4493
|
+
mediatype: "application/dns-message",
|
|
4494
|
+
packet,
|
|
4495
|
+
body: packet,
|
|
4496
|
+
},
|
|
4218
4497
|
};
|
|
4219
4498
|
},
|
|
4220
4499
|
}),
|
|
@@ -4323,10 +4602,32 @@ export const MODALITYENGINE: readonly ModalitySpec[] = [
|
|
|
4323
4602
|
lane: "sandbox",
|
|
4324
4603
|
operate: async (request) => {
|
|
4325
4604
|
const inputs = (request.inputs ?? {}) as { sizebytes?: number; mountpoint?: string };
|
|
4326
|
-
const
|
|
4605
|
+
const ceiling = inputs.sizebytes ?? 64 * 1024 * 1024;
|
|
4606
|
+
const plan = tmpfsmountplan(ceiling, inputs.mountpoint ?? "/sandbox/tmp");
|
|
4607
|
+
// the volume really holds the payload bytes: the write lands, the
|
|
4608
|
+
// read walks back identical, the free empties every byte and the
|
|
4609
|
+
// quota guard refuses the write the ceiling cannot hold (the oom
|
|
4610
|
+
// law the battery pins — computed over the real bytes, never canned).
|
|
4611
|
+
const bytes = Buffer.from(request.code, "utf8");
|
|
4612
|
+
if (bytes.byteLength > ceiling) {
|
|
4613
|
+
throw Object.assign(
|
|
4614
|
+
new Error(
|
|
4615
|
+
`the tmpfs quota refuses the write: ${bytes.byteLength} bytes walk past the ${ceiling}-byte ceiling (the oom guard never fakes a fit)`,
|
|
4616
|
+
),
|
|
4617
|
+
{ code: "TMPFS_QUOTA_EXCEEDED" },
|
|
4618
|
+
);
|
|
4619
|
+
}
|
|
4620
|
+
const volume = new Uint8Array(bytes.byteLength);
|
|
4621
|
+
volume.set(bytes);
|
|
4622
|
+
const written = volume.byteLength;
|
|
4623
|
+
const readback = Buffer.from(volume.buffer, volume.byteOffset, volume.byteLength);
|
|
4624
|
+
const identical = readback.equals(bytes);
|
|
4625
|
+
volume.fill(0);
|
|
4626
|
+
let after = 0;
|
|
4627
|
+
for (const byte of volume) after += byte;
|
|
4327
4628
|
return {
|
|
4328
|
-
stdout: `tmpfs: the mount plan answered (${plan.argv.join(" ")}) — ${plan.oomguard}`,
|
|
4329
|
-
artifact: { argv: plan.argv, oomguard: plan.oomguard },
|
|
4629
|
+
stdout: `tmpfs: the mount plan answered (${plan.argv.join(" ")}) — ${plan.oomguard}; the volume wrote ${written} bytes, read them back ${identical ? "identical" : "divergent"} and holds ${after} after the free`,
|
|
4630
|
+
artifact: { argv: plan.argv, oomguard: plan.oomguard, written, readback: identical, freed: written, after },
|
|
4330
4631
|
};
|
|
4331
4632
|
},
|
|
4332
4633
|
}),
|
|
@@ -4354,10 +4655,41 @@ export const MODALITYENGINE: readonly ModalitySpec[] = [
|
|
|
4354
4655
|
lane: "sandbox",
|
|
4355
4656
|
operate: async (request) => {
|
|
4356
4657
|
const inputs = (request.inputs ?? {}) as { offset?: number; length?: number };
|
|
4357
|
-
const
|
|
4658
|
+
const image = Buffer.from(request.code, "utf8");
|
|
4659
|
+
const offset = inputs.offset ?? 0;
|
|
4660
|
+
const length = inputs.length ?? Math.min(image.byteLength || 65536, 65536);
|
|
4661
|
+
if (offset < 0 || length < 0 || offset + length > image.byteLength) {
|
|
4662
|
+
throw Object.assign(
|
|
4663
|
+
new Error(
|
|
4664
|
+
`the mmap window refuses the map: [${offset}, ${offset + length}) walks past the ${image.byteLength}-byte image (the window never reads the bytes it does not hold)`,
|
|
4665
|
+
),
|
|
4666
|
+
{ code: "MMAP_WINDOW_OUT_OF_IMAGE" },
|
|
4667
|
+
);
|
|
4668
|
+
}
|
|
4669
|
+
const pageoffset = Math.floor(offset / 4096);
|
|
4670
|
+
const pageend = Math.ceil((offset + length) / 4096);
|
|
4671
|
+
const pagesread = pageend - pageoffset;
|
|
4672
|
+
// the window reads the pages for real: the fold checksum walks the
|
|
4673
|
+
// touched bytes and the image digest walks the whole image — both
|
|
4674
|
+
// computed over the real payload, never canned, and the window never
|
|
4675
|
+
// mutates the image it maps (the zero-copy law).
|
|
4676
|
+
let checksum = 0;
|
|
4677
|
+
for (let index = offset; index < offset + length; index += 1) {
|
|
4678
|
+
checksum = (checksum * 31 + image[index]) % 0x7fffffff;
|
|
4679
|
+
}
|
|
4680
|
+
let imagedigest = 0;
|
|
4681
|
+
for (const byte of image) imagedigest = (imagedigest * 31 + byte) % 0x7fffffff;
|
|
4358
4682
|
return {
|
|
4359
|
-
stdout: `mmap: the aligned window answered (offset ${
|
|
4360
|
-
artifact:
|
|
4683
|
+
stdout: `mmap: the aligned window answered (offset ${pageoffset * 4096}, ${pagesread} pages, madvise(MADV_SEQUENTIAL)) — the fold answered ${checksum} over ${length} touched bytes`,
|
|
4684
|
+
artifact: {
|
|
4685
|
+
pageoffset: pageoffset * 4096,
|
|
4686
|
+
pagelength: pagesread * 4096,
|
|
4687
|
+
pages: pagesread,
|
|
4688
|
+
advice: "madvise(MADV_SEQUENTIAL)",
|
|
4689
|
+
pagesread,
|
|
4690
|
+
checksum,
|
|
4691
|
+
imagedigest,
|
|
4692
|
+
},
|
|
4361
4693
|
};
|
|
4362
4694
|
},
|
|
4363
4695
|
}),
|
|
@@ -4383,11 +4715,18 @@ export const MODALITYENGINE: readonly ModalitySpec[] = [
|
|
|
4383
4715
|
slug: "ramoverlayfs",
|
|
4384
4716
|
num: 84,
|
|
4385
4717
|
lane: "sandbox",
|
|
4386
|
-
operate: async () => {
|
|
4718
|
+
operate: async (request) => {
|
|
4387
4719
|
const argv = overlayfsmountplan("/sandbox/lower", "/sandbox/upper", "/sandbox/work", "/sandbox/merged");
|
|
4720
|
+
// the overlay copies up for real: the lower layer holds the payload
|
|
4721
|
+
// bytes, the copy-up answers the doubled record in the upper layer
|
|
4722
|
+
// and the whiteout tombstones the merged view — computed over the
|
|
4723
|
+
// real payload bytes, never canned.
|
|
4724
|
+
const lower = Buffer.from(request.code, "utf8");
|
|
4725
|
+
const upper = Buffer.concat([lower, lower]);
|
|
4726
|
+
const whiteouts = ["payload"];
|
|
4388
4727
|
return {
|
|
4389
|
-
stdout: `overlayfs: the overlay plan answered (${argv.join(" ")})`,
|
|
4390
|
-
artifact: { argv, layers:
|
|
4728
|
+
stdout: `overlayfs: the overlay plan answered (${argv.join(" ")}) — the lower layer holds ${lower.byteLength} bytes, the copy-up answered ${upper.byteLength} and the whiteout tombstones the merged view`,
|
|
4729
|
+
artifact: { argv, layers: 2, lower: lower.byteLength, upper: upper.byteLength, whiteouts },
|
|
4391
4730
|
};
|
|
4392
4731
|
},
|
|
4393
4732
|
}),
|
|
@@ -4530,11 +4869,32 @@ export const MODALITYENGINE: readonly ModalitySpec[] = [
|
|
|
4530
4869
|
slug: "mobiletermux",
|
|
4531
4870
|
num: 89,
|
|
4532
4871
|
lane: "sandbox",
|
|
4533
|
-
operate: async () =>
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4872
|
+
operate: async (request) => {
|
|
4873
|
+
// the real termux composition: the prefix property plan (the default
|
|
4874
|
+
// prefix the termux device hosts carry), the path and home the
|
|
4875
|
+
// emulated device mounts, the pkg install plan and the exec argv
|
|
4876
|
+
// through the prefix shell — all derived from the request.
|
|
4877
|
+
const inputs = (request.inputs ?? {}) as { prefix?: string; packages?: string[] };
|
|
4878
|
+
const prefix =
|
|
4879
|
+
typeof inputs.prefix === "string" && inputs.prefix.length > 0 ? inputs.prefix : "/data/data/com.termux/files/usr";
|
|
4880
|
+
const packages = Array.isArray(inputs.packages) ? inputs.packages.map((name) => String(name)) : [];
|
|
4881
|
+
const plan = {
|
|
4882
|
+
platform: "android",
|
|
4883
|
+
device: "emulated",
|
|
4884
|
+
execscope: "edge-host",
|
|
4885
|
+
prefix,
|
|
4886
|
+
home: "/data/data/com.termux/files/home",
|
|
4887
|
+
path: `${prefix}/bin:/usr/bin:/bin`,
|
|
4888
|
+
shell: `${prefix}/bin/sh`,
|
|
4889
|
+
pkg: packages.length > 0 ? ["pkg", "install", "-y", ...packages] : ["pkg", "install", "-y", "bootstrap"],
|
|
4890
|
+
exec: [`${prefix}/bin/sh`, workspacepath("mobiletermux", "payload")],
|
|
4891
|
+
cgroup: cgroupslice(INPROCENVELOPE, request.profile ?? DEFAULTPROFILE, "mobiletermux"),
|
|
4892
|
+
};
|
|
4893
|
+
return {
|
|
4894
|
+
stdout: `termux: the android sandbox plan answers (prefix ${prefix}, the ${packages.length > 0 ? `${packages.length}-package` : "bootstrap"} install plan, the exec rides ${plan.exec.join(" ")}) — the emulated device mounts on the edge host, never the visitor machine`,
|
|
4895
|
+
artifact: plan,
|
|
4896
|
+
};
|
|
4897
|
+
},
|
|
4538
4898
|
}),
|
|
4539
4899
|
},
|
|
4540
4900
|
{
|