@wenathlan/saddle 2.0.166 → 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 +144 -25
- 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/e2ughengine.md +8 -8
- package/docs/extension.md +1 -1
- package/docs/osdistributions.md +1 -1
- package/docs/todo1816.md +1 -1
- package/docs/viability.md +1 -1
- package/index.ts +4 -2
- package/modalities.ts +185 -38
- package/package.json +78 -77
- 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. */
|
|
@@ -1492,6 +1500,41 @@ function workspacepath(slug: string, name: string): string {
|
|
|
1492
1500
|
return `/sandbox/${slug}/${name}`;
|
|
1493
1501
|
}
|
|
1494
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
|
+
|
|
1495
1538
|
/** Clamps one requested deadline into the honest window (250ms to
|
|
1496
1539
|
* 60000ms — the same window the edge plane pins). */
|
|
1497
1540
|
function clampdeadline(requested?: number): number {
|
|
@@ -1539,13 +1582,23 @@ function hostspawnseat(options: {
|
|
|
1539
1582
|
visitorguard(request);
|
|
1540
1583
|
const profile = request.profile ?? DEFAULTPROFILE;
|
|
1541
1584
|
const deadline = clampdeadline(request.deadlinems);
|
|
1542
|
-
const
|
|
1543
|
-
|
|
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
|
+
}
|
|
1544
1596
|
const slice = cgroupslice(BASEENVELOPE, profile, options.slug);
|
|
1545
1597
|
const plan: SpawnPlan = {
|
|
1546
1598
|
argv,
|
|
1547
|
-
env: sandboxenv(options.env),
|
|
1548
|
-
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 },
|
|
1549
1602
|
cgroups: slice,
|
|
1550
1603
|
hardening: options.hardening ?? ["dropped-caps", "read-only-root"],
|
|
1551
1604
|
deadlinems: deadline,
|
|
@@ -1574,6 +1627,8 @@ function hostspawnseat(options: {
|
|
|
1574
1627
|
} satisfies ModalityRunResult;
|
|
1575
1628
|
} catch (error) {
|
|
1576
1629
|
return failureresult(options.slug, options.num, options.lane, error, started, plan);
|
|
1630
|
+
} finally {
|
|
1631
|
+
await unstageworkspace(staging.dir);
|
|
1577
1632
|
}
|
|
1578
1633
|
});
|
|
1579
1634
|
};
|
|
@@ -2134,31 +2189,63 @@ export const MODALITYENGINE: readonly ModalitySpec[] = [
|
|
|
2134
2189
|
archs: LINUXARCHS,
|
|
2135
2190
|
osprofiles: ALLOSPROFILES,
|
|
2136
2191
|
resources: MACHINEENVELOPE,
|
|
2137
|
-
execute:
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
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
|
+
},
|
|
2162
2249
|
},
|
|
2163
2250
|
{
|
|
2164
2251
|
num: 13,
|
|
@@ -4515,10 +4602,32 @@ export const MODALITYENGINE: readonly ModalitySpec[] = [
|
|
|
4515
4602
|
lane: "sandbox",
|
|
4516
4603
|
operate: async (request) => {
|
|
4517
4604
|
const inputs = (request.inputs ?? {}) as { sizebytes?: number; mountpoint?: string };
|
|
4518
|
-
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;
|
|
4519
4628
|
return {
|
|
4520
|
-
stdout: `tmpfs: the mount plan answered (${plan.argv.join(" ")}) — ${plan.oomguard}`,
|
|
4521
|
-
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 },
|
|
4522
4631
|
};
|
|
4523
4632
|
},
|
|
4524
4633
|
}),
|
|
@@ -4546,10 +4655,41 @@ export const MODALITYENGINE: readonly ModalitySpec[] = [
|
|
|
4546
4655
|
lane: "sandbox",
|
|
4547
4656
|
operate: async (request) => {
|
|
4548
4657
|
const inputs = (request.inputs ?? {}) as { offset?: number; length?: number };
|
|
4549
|
-
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;
|
|
4550
4682
|
return {
|
|
4551
|
-
stdout: `mmap: the aligned window answered (offset ${
|
|
4552
|
-
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
|
+
},
|
|
4553
4693
|
};
|
|
4554
4694
|
},
|
|
4555
4695
|
}),
|
|
@@ -4575,11 +4715,18 @@ export const MODALITYENGINE: readonly ModalitySpec[] = [
|
|
|
4575
4715
|
slug: "ramoverlayfs",
|
|
4576
4716
|
num: 84,
|
|
4577
4717
|
lane: "sandbox",
|
|
4578
|
-
operate: async () => {
|
|
4718
|
+
operate: async (request) => {
|
|
4579
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"];
|
|
4580
4727
|
return {
|
|
4581
|
-
stdout: `overlayfs: the overlay plan answered (${argv.join(" ")})`,
|
|
4582
|
-
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 },
|
|
4583
4730
|
};
|
|
4584
4731
|
},
|
|
4585
4732
|
}),
|