@wenathlan/saddle 2.0.163 → 2.0.165
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 +44 -17
- package/README.md +1 -1
- package/cli.ts +99 -2
- package/communication.ts +455 -0
- package/docker.config +55 -0
- package/docs/distributionlanes.md +19 -0
- package/docs/doc.go +25 -0
- package/docs/ninetymodalities.md +8 -0
- package/docs/osdistributions.md +149 -0
- package/integration.ts +221 -0
- package/modalities.ts +4665 -0
- package/package.json +11 -5
- package/scriptlanes.ts +774 -0
package/modalities.ts
ADDED
|
@@ -0,0 +1,4665 @@
|
|
|
1
|
+
// # modalities.ts — the master virtualization engine of saddle: the
|
|
2
|
+
// ninety modalities from the first to the ninetieth (the pr15 law),
|
|
3
|
+
// every one a real and functional seat of the engine.
|
|
4
|
+
//
|
|
5
|
+
// THE NINETY LAW (pr15): saddle is the master virtualization engine of
|
|
6
|
+
// the family — the vm, the sandbox, the microvm, the runner and the
|
|
7
|
+
// hardware virtual seats — and the registry this file owns counts
|
|
8
|
+
// exactly ninety entries: the numeric id (1 to 90), the name, the lane
|
|
9
|
+
// (sandbox, vm, runner or hardware) and the real executable logic of
|
|
10
|
+
// the seat (the spawn plans, the cgroups-style resource flags and the
|
|
11
|
+
// queue orchestration every entry carries). no fake stubs: every entry
|
|
12
|
+
// owns a working implementation function — the host seats build the
|
|
13
|
+
// real argv the engine host executes (docker, qemu, firecracker, the
|
|
14
|
+
// language runtimes), the in-process seats do the real work in
|
|
15
|
+
// typescript (the yaml interpreter, the babel compilation bridge, the
|
|
16
|
+
// virtual file store, the vfs, the port dock, the wasm instantiation,
|
|
17
|
+
// the wire packet builders, the sigv4 signing chain) and the provider
|
|
18
|
+
// seats (aws, daytona, 2b) build the real signed requests the edge
|
|
19
|
+
// infrastructure answers.
|
|
20
|
+
//
|
|
21
|
+
// THE CRITICAL LAW (the visitor law — the law that governs every line
|
|
22
|
+
// of this file): the engine NEVER touches the person's machine — not
|
|
23
|
+
// the visitor gpu, not the visitor cpu, not the visitor browser, not
|
|
24
|
+
// the visitor local storage, not localforage, nothing. everything the
|
|
25
|
+
// engine executes runs on our own twenty-four hour internet
|
|
26
|
+
// infrastructure through edge processing (saddle owns the compute,
|
|
27
|
+
// vault owns the storage). the visitorguard function enforces the law
|
|
28
|
+
// at the dispatch gate: a request that asks for a visitor scope, a
|
|
29
|
+
// browser scope, a local storage scope or a local device answers the
|
|
30
|
+
// coded refusal before one instruction rides, and the engine spawner
|
|
31
|
+
// refuses to run inside any browser context (the engine host is the
|
|
32
|
+
// self-hosted edge alone).
|
|
33
|
+
//
|
|
34
|
+
// THE ENGINE LIMITS LAW (pr15): the engines are unlimited — the
|
|
35
|
+
// engine graph carries no ceiling on workloads, sessions, modalities
|
|
36
|
+
// or artifacts. the limits that exist are plan limits (for example the
|
|
37
|
+
// 50 gb per plan storage quota) and they are devthink system plan
|
|
38
|
+
// logic, never engine limits: they arrive from outside through the
|
|
39
|
+
// plan envelope seam (see engineplans.ts) and scope one run only.
|
|
40
|
+
//
|
|
41
|
+
// THE LANE LAW: every entry rides one of the four lanes — vm (the
|
|
42
|
+
// machine seats), hardware (the virtual hardware presentation seats),
|
|
43
|
+
// runner (the program execution seats) and sandbox (the isolation,
|
|
44
|
+
// port, file, storage, network and delivery seats the sandbox mounts).
|
|
45
|
+
//
|
|
46
|
+
// THE ARCHITECTURE STANDARD: every entry declares the architecture
|
|
47
|
+
// seats of the 11-architecture standard it answers (containerarch.ts:
|
|
48
|
+
// linux-x64, darwin-arm64, windows-x64, linux-arm64, darwin-x64,
|
|
49
|
+
// windows-arm64, linux-x64-musl, linux-riscv64, wasm32,
|
|
50
|
+
// darwin-arm64-v8, windows-x64-gnu) and the os host profiles it may
|
|
51
|
+
// host (osprofiles.ts: linux, grapheneos, omarchy, maruos, vanillaos,
|
|
52
|
+
// v, windows, android — catalog only).
|
|
53
|
+
//
|
|
54
|
+
// Section map:
|
|
55
|
+
// section 1 — the contracts (lanes, requests, results, plans, flags)
|
|
56
|
+
// section 2 — the visitor guard (the critical law, executable)
|
|
57
|
+
// section 3 — the resource flag builders (cgroups v2, docker, microvm)
|
|
58
|
+
// section 4 — the queue orchestration (express, normal, batch)
|
|
59
|
+
// section 5 — the engine spawner (the self-hosted host alone)
|
|
60
|
+
// section 6 — the in-process engines (yaml, babel, wasm, files, wire)
|
|
61
|
+
// section 7 — the executor builders (host spawn, in-process, provider)
|
|
62
|
+
// section 8 — the ninety (the table of record)
|
|
63
|
+
// section 9 — the dispatch (executemodality and the resolvers)
|
|
64
|
+
//
|
|
65
|
+
// Pure orchestration and pure functions at load: the node-only
|
|
66
|
+
// surfaces (child_process, vm, sqlite) ride lazy dynamic imports inside
|
|
67
|
+
// the executors so the browser-pure bundle keeps carrying the table.
|
|
68
|
+
|
|
69
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
70
|
+
/* Section 1: the contracts — the vocabulary of the ninety. */
|
|
71
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
72
|
+
|
|
73
|
+
/** The four lanes of the engine (the lane law): vm — the machine
|
|
74
|
+
* seats; hardware — the virtual hardware presentation seats; runner —
|
|
75
|
+
* the program execution seats; sandbox — the isolation and the mount
|
|
76
|
+
* seats (ports, files, storage, network, delivery). */
|
|
77
|
+
export type ModalityLane = "sandbox" | "vm" | "runner" | "hardware";
|
|
78
|
+
|
|
79
|
+
/** The boot cost classes the ladder measures (the same ladder the
|
|
80
|
+
* registry of record pins). */
|
|
81
|
+
export type ModalityBootClass = "hot" | "warm" | "cold";
|
|
82
|
+
|
|
83
|
+
/** The honest wiring state of one seat (the report never lies): live —
|
|
84
|
+
* the engine drives it today; host — the lane probes the host runtime
|
|
85
|
+
* or its configuration first; planned — the declared promise. */
|
|
86
|
+
export type ModalityWiringState = "live" | "host" | "planned";
|
|
87
|
+
|
|
88
|
+
/** The resource envelope one seat applies to a run — the numbers the
|
|
89
|
+
* cgroups-style flags derive from (scaled by the engine profile). */
|
|
90
|
+
export interface ResourceEnvelope {
|
|
91
|
+
/** The cpu weight (1 to 10000, the cgroups v2 cpu.weight scale). */
|
|
92
|
+
readonly cpuweight: number;
|
|
93
|
+
/** The cpu ceiling in percent of one core (the cpu.max quota). */
|
|
94
|
+
readonly cpupercent: number;
|
|
95
|
+
/** The memory ceiling in megabytes (the memory.max quota). */
|
|
96
|
+
readonly memorymb: number;
|
|
97
|
+
/** The process ceiling (the pids.max quota). */
|
|
98
|
+
readonly pids: number;
|
|
99
|
+
/** The io ceiling in mbps (the io.max rbps/wbps quota). */
|
|
100
|
+
readonly iombps: number;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** The cgroups v2 slice one run rides — the resource flags the engine
|
|
104
|
+
* stamps for every spawn (the cgroups-style resource law). */
|
|
105
|
+
export interface CgroupSlice {
|
|
106
|
+
/** The cpu.max pair: quota and period ("max 100000" = no quota). */
|
|
107
|
+
readonly cpumax: string;
|
|
108
|
+
/** The cpu.weight (1 to 10000). */
|
|
109
|
+
readonly cpuweight: number;
|
|
110
|
+
/** The memory.max ceiling in bytes. */
|
|
111
|
+
readonly memorymax: number;
|
|
112
|
+
/** The memory.high throttle in bytes. */
|
|
113
|
+
readonly memoryhigh: number;
|
|
114
|
+
/** The pids.max ceiling. */
|
|
115
|
+
readonly pidsmax: number;
|
|
116
|
+
/** The io.max line (device rbps/wbps limits). */
|
|
117
|
+
readonly iomax: string;
|
|
118
|
+
/** The slice name the flags ride under. */
|
|
119
|
+
readonly slice: string;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** The spawn plan one seat builds — the real executable logic of the
|
|
123
|
+
* host seats: the argv, the sandbox environment, the working
|
|
124
|
+
* directory, the cgroup slice and the hardening gates applied. */
|
|
125
|
+
export interface SpawnPlan {
|
|
126
|
+
/** The real command the engine host executes (argv[0] is the binary). */
|
|
127
|
+
readonly argv: readonly string[];
|
|
128
|
+
/** The sandbox environment the spawn carries (never the host env). */
|
|
129
|
+
readonly env: Readonly<Record<string, string>>;
|
|
130
|
+
/** The working directory of the spawn (the staged workspace). */
|
|
131
|
+
readonly cwd: string;
|
|
132
|
+
/** The cgroups v2 slice of the spawn. */
|
|
133
|
+
readonly cgroups: CgroupSlice;
|
|
134
|
+
/** The hardening gates applied to the spawn. */
|
|
135
|
+
readonly hardening: readonly string[];
|
|
136
|
+
/** The run deadline in milliseconds. */
|
|
137
|
+
readonly deadlinems: number;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** One run request the dispatch answers (the engine api vocabulary):
|
|
141
|
+
* the code payload, the caller inputs, the deadline, the engine
|
|
142
|
+
* profile, the architecture seat, the os host profile and the explicit
|
|
143
|
+
* scope declaration (the visitor law reads it — the scope may never
|
|
144
|
+
* name the visitor machine). */
|
|
145
|
+
export interface ModalityRunRequest {
|
|
146
|
+
/** The program payload: script text, base64 bytes or a plan document. */
|
|
147
|
+
readonly code: string;
|
|
148
|
+
/** The caller inputs surfaced to the program when given. */
|
|
149
|
+
readonly inputs?: unknown;
|
|
150
|
+
/** The run deadline in milliseconds (clamped between 250 and 60000). */
|
|
151
|
+
readonly deadlinems?: number;
|
|
152
|
+
/** The engine profile of the run (max, balanced or lite). */
|
|
153
|
+
readonly profile?: string;
|
|
154
|
+
/** The architecture seat of the 11-architecture standard. */
|
|
155
|
+
readonly arch?: string;
|
|
156
|
+
/** The os host profile id of the run (the catalog rows). */
|
|
157
|
+
readonly osprofile?: string;
|
|
158
|
+
/** The explicit execution scope — "engine" (the default and the only
|
|
159
|
+
* honest value: everything runs on the self-hosted edge). the guard
|
|
160
|
+
* refuses "visitor", "browser", "local" and every synonym. */
|
|
161
|
+
readonly scope?: string;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** One run result the dispatch answers: the seat identity, the honest
|
|
165
|
+
* status, the captured streams, the spawn plan the seat built, the
|
|
166
|
+
* in-process artifact and the coded error. */
|
|
167
|
+
export interface ModalityRunResult {
|
|
168
|
+
/** The slug of the seat that ran. */
|
|
169
|
+
readonly modality: string;
|
|
170
|
+
/** The numeric id of the seat that ran (1 to 90). */
|
|
171
|
+
readonly num: number;
|
|
172
|
+
/** The lane of the seat that ran. */
|
|
173
|
+
readonly lane: ModalityLane;
|
|
174
|
+
/** The honest status of the run. */
|
|
175
|
+
readonly status: "succeeded" | "failed";
|
|
176
|
+
/** The captured stdout when the run produced any. */
|
|
177
|
+
readonly stdout?: string;
|
|
178
|
+
/** The captured stderr when the run produced any. */
|
|
179
|
+
readonly stderr?: string;
|
|
180
|
+
/** The spawn plan the seat built (the host seats always answer it). */
|
|
181
|
+
readonly plan?: SpawnPlan;
|
|
182
|
+
/** The artifact the in-process seats answer (documents, manifests). */
|
|
183
|
+
readonly artifact?: unknown;
|
|
184
|
+
/** The coded error when the run failed. */
|
|
185
|
+
readonly error?: { readonly code?: string; readonly message: string };
|
|
186
|
+
/** The measured duration of the run in milliseconds. */
|
|
187
|
+
readonly durationms: number;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** The executor contract every seat of the ninety implements — the
|
|
191
|
+
* working implementation function the registry law demands (no fake
|
|
192
|
+
* stubs: a seat without real logic never enters the table). */
|
|
193
|
+
export type ModalityExecutor = (request: ModalityRunRequest) => Promise<ModalityRunResult>;
|
|
194
|
+
|
|
195
|
+
/** The full record of one modality of the ninety — the table row the
|
|
196
|
+
* registry of record derives its catalog from. */
|
|
197
|
+
export interface ModalitySpec {
|
|
198
|
+
/** The numeric id of the seat: 1 (the first) to 90 (the ninetieth). */
|
|
199
|
+
readonly num: number;
|
|
200
|
+
/** The display name of the seat (short, lowercase). */
|
|
201
|
+
readonly name: string;
|
|
202
|
+
/** The stable slug the api, the config rows and the lane tables reference. */
|
|
203
|
+
readonly slug: string;
|
|
204
|
+
/** The lane of the seat (sandbox, vm, runner or hardware). */
|
|
205
|
+
readonly lane: ModalityLane;
|
|
206
|
+
/** The isolation depth the seat reaches (process, namespace, vm, hardware). */
|
|
207
|
+
readonly isolation: "process" | "namespace" | "vm" | "hardware";
|
|
208
|
+
/** The honest wiring state (the report never lies). */
|
|
209
|
+
readonly state: ModalityWiringState;
|
|
210
|
+
/** The driver vocabulary the seat mounts when it boots. */
|
|
211
|
+
readonly drivers: readonly string[];
|
|
212
|
+
/** The hardening gates the seat enforces by default. */
|
|
213
|
+
readonly hardening: readonly string[];
|
|
214
|
+
/** The boot cost class. */
|
|
215
|
+
readonly boot: ModalityBootClass;
|
|
216
|
+
/** The measured boot budget in milliseconds. */
|
|
217
|
+
readonly bootms: number;
|
|
218
|
+
/** Whether the seat presents a gpu through the virtual drivers. */
|
|
219
|
+
readonly gpu: boolean;
|
|
220
|
+
/** Whether the seat opens a network path (the hardened default is off). */
|
|
221
|
+
readonly network: boolean;
|
|
222
|
+
/** The mime type the seat's api surface answers. */
|
|
223
|
+
readonly mime: string;
|
|
224
|
+
/** The architecture seats of the 11-architecture standard the entry
|
|
225
|
+
* answers (the containerarch ids). */
|
|
226
|
+
readonly archs: readonly string[];
|
|
227
|
+
/** The os host profiles the seat may host (the osprofiles catalog ids). */
|
|
228
|
+
readonly osprofiles: readonly string[];
|
|
229
|
+
/** The resource envelope the seat applies (the cgroup flag source). */
|
|
230
|
+
readonly resources: ResourceEnvelope;
|
|
231
|
+
/** The real executable logic of the seat (the no-stub law). */
|
|
232
|
+
readonly execute: ModalityExecutor;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** The boot budgets of the ladder, per class (the same ladder the
|
|
236
|
+
* registry of record exports). */
|
|
237
|
+
export const BOOTLADDER: Readonly<Record<ModalityBootClass, number>> = Object.freeze({
|
|
238
|
+
hot: 25,
|
|
239
|
+
warm: 800,
|
|
240
|
+
cold: 1500,
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
/** The ninety modality plan: the count the owner charged and this
|
|
244
|
+
* table answers (the tests pin both stay equal). */
|
|
245
|
+
export const MODALITYPLANCOUNT = 90;
|
|
246
|
+
|
|
247
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
248
|
+
/* Section 2: the visitor guard — the critical law, executable. */
|
|
249
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
250
|
+
|
|
251
|
+
/** The visitor scope vocabulary the guard refuses — every synonym of
|
|
252
|
+
* "run on the person's machine" the law names (the critical law: not
|
|
253
|
+
* the gpu, not the cpu, not the browser, not the local storage, not
|
|
254
|
+
* localforage, nothing). */
|
|
255
|
+
const VISITORSCOPES: ReadonlySet<string> = new Set([
|
|
256
|
+
"visitor",
|
|
257
|
+
"browser",
|
|
258
|
+
"local",
|
|
259
|
+
"client",
|
|
260
|
+
"user",
|
|
261
|
+
"host",
|
|
262
|
+
"device",
|
|
263
|
+
"gpu",
|
|
264
|
+
"cpu",
|
|
265
|
+
"localstorage",
|
|
266
|
+
"localforage",
|
|
267
|
+
"indexeddb",
|
|
268
|
+
"websql",
|
|
269
|
+
"sessionstorage",
|
|
270
|
+
]);
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* The visitor guard — the critical law of the engine made executable:
|
|
274
|
+
* every run request passes this gate before one instruction rides, and
|
|
275
|
+
* any request that asks for a visitor scope (the person's gpu, cpu,
|
|
276
|
+
* browser, local storage, localforage or any local device) answers the
|
|
277
|
+
* coded refusal. the only honest scope is "engine": everything the
|
|
278
|
+
* engine executes runs on our own twenty-four hour internet
|
|
279
|
+
* infrastructure via saddle and vault edge processing.
|
|
280
|
+
*
|
|
281
|
+
* @param request the run request the dispatch received.
|
|
282
|
+
* @throws a typed VISITOR_SCOPE_REFUSED error naming the law when the
|
|
283
|
+
* request asks for a scope the engine never serves.
|
|
284
|
+
*/
|
|
285
|
+
export function visitorguard(request: ModalityRunRequest): void {
|
|
286
|
+
const scope = (request.scope ?? "engine").trim().toLowerCase();
|
|
287
|
+
if (scope !== "engine") {
|
|
288
|
+
if (VISITORSCOPES.has(scope)) {
|
|
289
|
+
throw Object.assign(
|
|
290
|
+
new Error(
|
|
291
|
+
`the critical law: the engine never touches the visitor machine (the "${scope}" scope is refused) — everything runs on the self-hosted edge, never the visitor gpu, cpu, browser, local storage or localforage`,
|
|
292
|
+
),
|
|
293
|
+
{ code: "VISITOR_SCOPE_REFUSED" },
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
throw Object.assign(
|
|
297
|
+
new Error(`the engine answers no scope "${request.scope}" — the only honest scope is "engine" (the self-hosted edge)`),
|
|
298
|
+
{ code: "SCOPE_UNKNOWN" },
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Answers whether the current runtime is a browser context — the
|
|
305
|
+
* engine spawner refuses to run inside one (the engine host is the
|
|
306
|
+
* self-hosted edge alone, never the visitor browser).
|
|
307
|
+
*/
|
|
308
|
+
function inbrowsercontext(): boolean {
|
|
309
|
+
return typeof globalThis.window !== "undefined" && typeof globalThis.document !== "undefined";
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
313
|
+
/* Section 3: the resource flag builders — the cgroups-style law. */
|
|
314
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
315
|
+
|
|
316
|
+
/** The profile envelope scale of the run (engineplans owns the tier
|
|
317
|
+
* law; this local read keeps the table self-contained: max 1.0,
|
|
318
|
+
* balanced 0.5, lite 0.25 — the same scale the engine plans table
|
|
319
|
+
* declares). */
|
|
320
|
+
const PROFILESCALE: Readonly<Record<string, number>> = Object.freeze({ max: 1, balanced: 0.5, lite: 0.25 });
|
|
321
|
+
|
|
322
|
+
/** The default profile of the run (the balanced tier — the container
|
|
323
|
+
* lane's documented default). */
|
|
324
|
+
const DEFAULTPROFILE = "balanced";
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Builds the cgroups v2 slice of one run — the resource flags every
|
|
328
|
+
* spawn carries (the cgroups-style resource law of the ninety): the
|
|
329
|
+
* cpu.max quota pair, the cpu.weight, the memory.max and memory.high
|
|
330
|
+
* ceilings, the pids.max ceiling and the io.max line, scaled by the
|
|
331
|
+
* engine profile of the run.
|
|
332
|
+
*
|
|
333
|
+
* @param envelope the resource envelope of the seat.
|
|
334
|
+
* @param profile the engine profile id of the run.
|
|
335
|
+
* @param slug the seat slug (the slice name derives from it).
|
|
336
|
+
* @returns the cgroups v2 slice of the run.
|
|
337
|
+
*/
|
|
338
|
+
export function cgroupslice(envelope: ResourceEnvelope, profile = DEFAULTPROFILE, slug = "seat"): CgroupSlice {
|
|
339
|
+
const scale = PROFILESCALE[profile] ?? PROFILESCALE[DEFAULTPROFILE];
|
|
340
|
+
const cpupercent = Math.max(10, Math.round(envelope.cpupercent * scale));
|
|
341
|
+
const memorymb = Math.max(32, Math.round(envelope.memorymb * scale));
|
|
342
|
+
const memorymax = memorymb * 1024 * 1024;
|
|
343
|
+
const period = 100000;
|
|
344
|
+
const quota = Math.round((cpupercent / 100) * period);
|
|
345
|
+
const iombps = Math.max(10, Math.round(envelope.iombps * scale));
|
|
346
|
+
return Object.freeze({
|
|
347
|
+
cpumax: `${quota} ${period}`,
|
|
348
|
+
cpuweight: Math.max(1, Math.round(envelope.cpuweight * (0.5 + scale / 2))),
|
|
349
|
+
memorymax,
|
|
350
|
+
memoryhigh: Math.round(memorymax * 0.8),
|
|
351
|
+
pidsmax: Math.max(16, Math.round(envelope.pids * scale)),
|
|
352
|
+
iomax: `major:minor rbps=${iombps * 1024 * 1024} wbps=${iombps * 1024 * 1024}`,
|
|
353
|
+
slice: `saddle-${slug.replace(/[^a-z0-9]/g, "")}.slice`,
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Renders the docker hardening flags of one run — the namespace seats'
|
|
359
|
+
* resource and isolation flags (the same hardening vocabulary the
|
|
360
|
+
* container seats of the table declare).
|
|
361
|
+
*
|
|
362
|
+
* @param slice the cgroups slice of the run.
|
|
363
|
+
* @returns the docker flag array (memory, cpus, pids-limit and the
|
|
364
|
+
* isolation flags the container law pins).
|
|
365
|
+
*/
|
|
366
|
+
export function dockerflags(slice: CgroupSlice): readonly string[] {
|
|
367
|
+
const quota = slice.cpumax.split(" ")[0];
|
|
368
|
+
const cpus = quota === "max" ? "0" : String(Number(quota) / 100000);
|
|
369
|
+
return [
|
|
370
|
+
"--memory",
|
|
371
|
+
`${Math.round(slice.memorymax / (1024 * 1024))}m`,
|
|
372
|
+
"--memory-swap",
|
|
373
|
+
`${Math.round(slice.memorymax / (1024 * 1024))}m`,
|
|
374
|
+
"--cpus",
|
|
375
|
+
cpus,
|
|
376
|
+
"--pids-limit",
|
|
377
|
+
String(slice.pidsmax),
|
|
378
|
+
"--network",
|
|
379
|
+
"none",
|
|
380
|
+
"--read-only",
|
|
381
|
+
"--cap-drop",
|
|
382
|
+
"ALL",
|
|
383
|
+
"--security-opt",
|
|
384
|
+
"no-new-privileges",
|
|
385
|
+
];
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
389
|
+
/* Section 4: the queue orchestration — express, normal, batch. */
|
|
390
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
391
|
+
|
|
392
|
+
/** The priority classes of the queue (the orchestration law): express
|
|
393
|
+
* rides first (interactive runs), normal answers the default lanes and
|
|
394
|
+
* batch rides last (the background executions the yaml seat plans). */
|
|
395
|
+
export type ModalityPriority = "express" | "normal" | "batch";
|
|
396
|
+
|
|
397
|
+
/** One entry of the modality queue. */
|
|
398
|
+
interface ModalityQueueEntry {
|
|
399
|
+
readonly id: number;
|
|
400
|
+
readonly modality: string;
|
|
401
|
+
readonly priority: ModalityPriority;
|
|
402
|
+
readonly enqueuedat: number;
|
|
403
|
+
readonly run: () => Promise<ModalityRunResult>;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/** The modality queue of the engine — the orchestration every spawn
|
|
407
|
+
* rides through (priority order, bounded concurrency from the profile,
|
|
408
|
+
* honest stats). */
|
|
409
|
+
class ModalityQueue {
|
|
410
|
+
private readonly entries: ModalityQueueEntry[] = [];
|
|
411
|
+
private nextid = 1;
|
|
412
|
+
private running = 0;
|
|
413
|
+
private completed = 0;
|
|
414
|
+
private readonly width: number;
|
|
415
|
+
|
|
416
|
+
constructor(width = 32) {
|
|
417
|
+
this.width = Math.max(1, width);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/** the priority order weight (express 0, normal 1, batch 2). */
|
|
421
|
+
private static weight(priority: ModalityPriority): number {
|
|
422
|
+
return priority === "express" ? 0 : priority === "normal" ? 1 : 2;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/** pushes one entry, keeps the priority order, answers the entry. */
|
|
426
|
+
push(modality: string, priority: ModalityPriority, run: () => Promise<ModalityRunResult>): ModalityQueueEntry {
|
|
427
|
+
const entry: ModalityQueueEntry = { id: this.nextid++, modality, priority, enqueuedat: Date.now(), run };
|
|
428
|
+
this.entries.push(entry);
|
|
429
|
+
this.entries.sort((a, b) => ModalityQueue.weight(a.priority) - ModalityQueue.weight(b.priority) || a.id - b.id);
|
|
430
|
+
return entry;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/** pops the next entry the priority order answers (undefined when the
|
|
434
|
+
* queue is empty or at capacity). */
|
|
435
|
+
pop(): ModalityQueueEntry | undefined {
|
|
436
|
+
if (this.entries.length === 0 || this.running >= this.width) return undefined;
|
|
437
|
+
return this.entries.shift();
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/** marks one entry as started (the concurrency counter). */
|
|
441
|
+
started(): void {
|
|
442
|
+
this.running += 1;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/** marks one entry as completed (the counters the stats read). */
|
|
446
|
+
finished(): void {
|
|
447
|
+
this.running = Math.max(0, this.running - 1);
|
|
448
|
+
this.completed += 1;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/** the honest stats of the queue (the report surface). */
|
|
452
|
+
stats(): { waiting: number; running: number; completed: number; width: number } {
|
|
453
|
+
return { waiting: this.entries.length, running: this.running, completed: this.completed, width: this.width };
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/** The modality queue of record (the balanced profile width — see
|
|
458
|
+
* engineplans.ts for the tier law). */
|
|
459
|
+
const queue = new ModalityQueue(32);
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Answers the honest stats of the modality queue (the orchestration
|
|
463
|
+
* report surface the console reads).
|
|
464
|
+
*/
|
|
465
|
+
export function modalityqueuestats(): { waiting: number; running: number; completed: number; width: number } {
|
|
466
|
+
return queue.stats();
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Runs one operation through the queue orchestration — the entry rides
|
|
471
|
+
* its priority class and the drain loop executes it under the profile
|
|
472
|
+
* width (every spawn of the ninety rides this seam, so the
|
|
473
|
+
* orchestration is real for every seat).
|
|
474
|
+
*
|
|
475
|
+
* @param modality the seat slug the entry rides.
|
|
476
|
+
* @param priority the priority class of the entry.
|
|
477
|
+
* @param run the deferred operation of the entry.
|
|
478
|
+
* @returns the operation's result.
|
|
479
|
+
*/
|
|
480
|
+
async function throughqueue(
|
|
481
|
+
modality: string,
|
|
482
|
+
priority: ModalityPriority,
|
|
483
|
+
run: () => Promise<ModalityRunResult>,
|
|
484
|
+
): Promise<ModalityRunResult> {
|
|
485
|
+
const entry = queue.push(modality, priority, run);
|
|
486
|
+
// the direct drain: the priority order answers this entry as soon as
|
|
487
|
+
// the width allows, and the await keeps the api honest.
|
|
488
|
+
for (;;) {
|
|
489
|
+
const next = queue.pop();
|
|
490
|
+
if (next === undefined) {
|
|
491
|
+
// at capacity: wait for a slot (the width gate).
|
|
492
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
queue.started();
|
|
496
|
+
try {
|
|
497
|
+
const result = await next.run();
|
|
498
|
+
if (next.id === entry.id) return result;
|
|
499
|
+
} catch (error) {
|
|
500
|
+
if (next.id === entry.id) throw error;
|
|
501
|
+
} finally {
|
|
502
|
+
queue.finished();
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
508
|
+
/* Section 5: the engine spawner — the self-hosted host alone. */
|
|
509
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
510
|
+
|
|
511
|
+
/** The captured output of one spawn. */
|
|
512
|
+
interface SpawnOutput {
|
|
513
|
+
stdout: string;
|
|
514
|
+
stderr: string;
|
|
515
|
+
code: number | null;
|
|
516
|
+
signal: string | null;
|
|
517
|
+
timedout: boolean;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/** The output capture ceiling of every spawn (bytes). */
|
|
521
|
+
const OUTPUTCAPBYTES = 262144;
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* Spawns one plan on the engine host — the real execution surface of
|
|
525
|
+
* the host seats: the argv runs under the sandbox environment with the
|
|
526
|
+
* deadline kill and the output capture. the spawner refuses to run
|
|
527
|
+
* inside a browser context (the critical law: the engine host is the
|
|
528
|
+
* self-hosted edge alone) and answers the honest coded absence when
|
|
529
|
+
* the host carries no spawner or the spawn env is sealed.
|
|
530
|
+
*
|
|
531
|
+
* @param plan the spawn plan the seat built.
|
|
532
|
+
* @returns the captured output of the spawn.
|
|
533
|
+
*/
|
|
534
|
+
async function spawnonengine(plan: SpawnPlan): Promise<SpawnOutput> {
|
|
535
|
+
if (inbrowsercontext()) {
|
|
536
|
+
throw Object.assign(
|
|
537
|
+
new Error("the engine spawner refuses a browser context — the engine host is the self-hosted edge alone (the critical law)"),
|
|
538
|
+
{ code: "ENGINE_SPAWNER_ABSENT" },
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
if (typeof process !== "undefined" && process.env.SADDLE_ENGINE_SPAWNER === "0") {
|
|
542
|
+
throw Object.assign(
|
|
543
|
+
new Error("the engine spawner is sealed on this host (SADDLE_ENGINE_SPAWNER=0) — the plan answers, the spawn waits for the edge host"),
|
|
544
|
+
{ code: "ENGINE_SPAWNER_ABSENT" },
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
let childprocess: typeof import("node:child_process");
|
|
548
|
+
try {
|
|
549
|
+
childprocess = await import("node:child_process");
|
|
550
|
+
} catch {
|
|
551
|
+
throw Object.assign(new Error("the engine spawner is absent from this host"), { code: "ENGINE_SPAWNER_ABSENT" });
|
|
552
|
+
}
|
|
553
|
+
return await new Promise<SpawnOutput>((resolve) => {
|
|
554
|
+
const child = childprocess.spawn(plan.argv[0], plan.argv.slice(1) as string[], {
|
|
555
|
+
env: { ...plan.env, PATH: process.env.PATH ?? "/usr/local/bin:/usr/bin:/bin" },
|
|
556
|
+
cwd: plan.cwd,
|
|
557
|
+
});
|
|
558
|
+
const stdoutchunks: Buffer[] = [];
|
|
559
|
+
const stderrchunks: Buffer[] = [];
|
|
560
|
+
let bytessofar = 0;
|
|
561
|
+
let timedout = false;
|
|
562
|
+
const timer = setTimeout(() => {
|
|
563
|
+
timedout = true;
|
|
564
|
+
child.kill("SIGKILL");
|
|
565
|
+
}, plan.deadlinems);
|
|
566
|
+
child.stdout.on("data", (chunk: Buffer) => {
|
|
567
|
+
bytessofar += chunk.byteLength;
|
|
568
|
+
if (bytessofar <= OUTPUTCAPBYTES) stdoutchunks.push(chunk);
|
|
569
|
+
});
|
|
570
|
+
child.stderr.on("data", (chunk: Buffer) => {
|
|
571
|
+
bytessofar += chunk.byteLength;
|
|
572
|
+
if (bytessofar <= OUTPUTCAPBYTES) stderrchunks.push(chunk);
|
|
573
|
+
});
|
|
574
|
+
child.on("error", (error: Error) => {
|
|
575
|
+
clearTimeout(timer);
|
|
576
|
+
resolve({
|
|
577
|
+
stdout: Buffer.concat(stdoutchunks).toString("utf8"),
|
|
578
|
+
stderr: `${Buffer.concat(stderrchunks).toString("utf8")}\n${error.message}`,
|
|
579
|
+
code: null,
|
|
580
|
+
signal: null,
|
|
581
|
+
timedout,
|
|
582
|
+
});
|
|
583
|
+
});
|
|
584
|
+
child.on("close", (code: number | null, signal: NodeJS.Signals | null) => {
|
|
585
|
+
clearTimeout(timer);
|
|
586
|
+
resolve({
|
|
587
|
+
stdout: Buffer.concat(stdoutchunks).toString("utf8"),
|
|
588
|
+
stderr: Buffer.concat(stderrchunks).toString("utf8"),
|
|
589
|
+
code,
|
|
590
|
+
signal: signal ?? null,
|
|
591
|
+
timedout,
|
|
592
|
+
});
|
|
593
|
+
});
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
598
|
+
/* Section 6: the in-process engines — the real work in typescript. */
|
|
599
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
600
|
+
|
|
601
|
+
/* ---- 6.1 the virtual file store (the virtual file seat) ---- */
|
|
602
|
+
|
|
603
|
+
/** One virtual file record of the store. */
|
|
604
|
+
interface VirtualFileRecord {
|
|
605
|
+
readonly handle: string;
|
|
606
|
+
readonly path: string;
|
|
607
|
+
readonly bytes: Uint8Array;
|
|
608
|
+
readonly createdat: number;
|
|
609
|
+
readonly sha256: string;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/** The virtual file store — the in-memory file records the virtual
|
|
613
|
+
* file seat owns (handles, quotas, hashing; nothing ever touches a
|
|
614
|
+
* visitor disk — the critical law). */
|
|
615
|
+
class VirtualFileStore {
|
|
616
|
+
private readonly files = new Map<string, VirtualFileRecord>();
|
|
617
|
+
private readonly quotaof = new Map<string, number>();
|
|
618
|
+
private readonly quotabytes = 64 * 1024 * 1024;
|
|
619
|
+
|
|
620
|
+
/** the sha256 of one payload (the hash seal of every record). */
|
|
621
|
+
private static async hash(bytes: Uint8Array): Promise<string> {
|
|
622
|
+
const digest = await globalThis.crypto.subtle.digest("SHA-256", bytes.slice().buffer as ArrayBuffer);
|
|
623
|
+
return Array.from(new Uint8Array(digest))
|
|
624
|
+
.map((value) => value.toString(16).padStart(2, "0"))
|
|
625
|
+
.join("");
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/** writes one file (the quota answers first — the honest refusal). */
|
|
629
|
+
async write(owner: string, path: string, data: string | Uint8Array): Promise<VirtualFileRecord> {
|
|
630
|
+
const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
631
|
+
const used = this.quotaof.get(owner) ?? 0;
|
|
632
|
+
if (used + bytes.byteLength > this.quotabytes) {
|
|
633
|
+
throw Object.assign(
|
|
634
|
+
new Error(`the virtual file quota of ${owner} is exceeded (${used + bytes.byteLength} > ${this.quotabytes} bytes)`),
|
|
635
|
+
{ code: "VIRTUAL_FILE_QUOTA_EXCEEDED" },
|
|
636
|
+
);
|
|
637
|
+
}
|
|
638
|
+
for (const [handle, file] of this.files) {
|
|
639
|
+
if (file.path === path) this.files.delete(handle);
|
|
640
|
+
}
|
|
641
|
+
const handle = `vf${Date.now().toString(36)}${this.files.size.toString(36)}`;
|
|
642
|
+
const record: VirtualFileRecord = { handle, path, bytes, createdat: Date.now(), sha256: await VirtualFileStore.hash(bytes) };
|
|
643
|
+
this.files.set(handle, record);
|
|
644
|
+
this.quotaof.set(owner, used + bytes.byteLength);
|
|
645
|
+
return record;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/** reads one file by path (undefined when absent). */
|
|
649
|
+
read(path: string): VirtualFileRecord | undefined {
|
|
650
|
+
for (const file of this.files.values()) {
|
|
651
|
+
if (file.path === path) return file;
|
|
652
|
+
}
|
|
653
|
+
return undefined;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/** stats the store (the report surface). */
|
|
657
|
+
stats(): { files: number; bytes: number } {
|
|
658
|
+
let bytes = 0;
|
|
659
|
+
for (const file of this.files.values()) bytes += file.bytes.byteLength;
|
|
660
|
+
return { files: this.files.size, bytes };
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
/** The virtual file store of record (the virtual file seat reads it). */
|
|
665
|
+
const virtualfilestore = new VirtualFileStore();
|
|
666
|
+
|
|
667
|
+
/* ---- 6.2 the yaml interpreter (the yaml seat) ---- */
|
|
668
|
+
|
|
669
|
+
/** The yaml value tree the interpreter answers (mappings, sequences
|
|
670
|
+
* and scalars — the honest subset the seat compiles). */
|
|
671
|
+
export type YamlValue = string | number | boolean | null | YamlValue[] | { [key: string]: YamlValue };
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* Parses one scalar of the yaml grammar — the real scalar rules:
|
|
675
|
+
* quotes, booleans, null, numbers and plain strings.
|
|
676
|
+
*/
|
|
677
|
+
function parsescalar(raw: string): YamlValue {
|
|
678
|
+
const text = raw.trim();
|
|
679
|
+
if (text.length === 0) return null;
|
|
680
|
+
if ((text.startsWith('"') && text.endsWith('"')) || (text.startsWith("'") && text.endsWith("'"))) {
|
|
681
|
+
return text.slice(1, -1);
|
|
682
|
+
}
|
|
683
|
+
if (text === "true" || text === "yes") return true;
|
|
684
|
+
if (text === "false" || text === "no") return false;
|
|
685
|
+
if (text === "null" || text === "~") return null;
|
|
686
|
+
if (/^-?\d+$/.test(text)) return Number.parseInt(text, 10);
|
|
687
|
+
if (/^-?\d+\.\d+$/.test(text)) return Number.parseFloat(text);
|
|
688
|
+
return text;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
/**
|
|
692
|
+
* The yaml interpreter — a real indentation-based parser of the yaml
|
|
693
|
+
* subset the seat compiles: mappings, nested mappings, sequences,
|
|
694
|
+
* scalars (quoted, boolean, null, numeric) and comments. this is
|
|
695
|
+
* genuine interpretation (no dependency): the yaml seat rides it
|
|
696
|
+
* before the babel compilation and the background execution plan.
|
|
697
|
+
*
|
|
698
|
+
* @param source the yaml document text.
|
|
699
|
+
* @returns the parsed value tree.
|
|
700
|
+
* @throws a typed YAML_PARSE_FAILED error naming the line on a break.
|
|
701
|
+
*/
|
|
702
|
+
export function interpretyaml(source: string): YamlValue {
|
|
703
|
+
const lines = source
|
|
704
|
+
.split("\n")
|
|
705
|
+
.map((text, index) => ({ text: text.replace(/\t/g, " "), index: index + 1 }))
|
|
706
|
+
.filter((line) => line.text.trim().length > 0 && !line.text.trim().startsWith("#"));
|
|
707
|
+
let cursor = 0;
|
|
708
|
+
|
|
709
|
+
function indentof(text: string): number {
|
|
710
|
+
let count = 0;
|
|
711
|
+
while (count < text.length && text[count] === " ") count += 1;
|
|
712
|
+
return count;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
function parseblock(indent: number): YamlValue {
|
|
716
|
+
if (cursor >= lines.length) return null;
|
|
717
|
+
const current = lines[cursor];
|
|
718
|
+
const currentindent = indentof(current.text);
|
|
719
|
+
if (currentindent < indent) return null;
|
|
720
|
+
if (current.text.trim().startsWith("- ")) {
|
|
721
|
+
const sequence: YamlValue[] = [];
|
|
722
|
+
while (cursor < lines.length) {
|
|
723
|
+
const entry = lines[cursor];
|
|
724
|
+
if (indentof(entry.text) !== currentindent || !entry.text.trim().startsWith("- ")) break;
|
|
725
|
+
const inline = entry.text.trim().slice(2).trim();
|
|
726
|
+
cursor += 1;
|
|
727
|
+
if (inline.includes(":")) {
|
|
728
|
+
const nested: Record<string, YamlValue> = {};
|
|
729
|
+
const [key, ...rest] = inline.split(":");
|
|
730
|
+
const value = rest.join(":").trim();
|
|
731
|
+
nested[key.trim()] = value.length > 0 ? parsescalar(value) : parseblock(currentindent + 2);
|
|
732
|
+
while (cursor < lines.length && indentof(lines[cursor].text) > currentindent) {
|
|
733
|
+
const deeper = lines[cursor].text.trim();
|
|
734
|
+
if (deeper.startsWith("- ")) {
|
|
735
|
+
nested[key.trim()] = parseblock(currentindent + 2);
|
|
736
|
+
break;
|
|
737
|
+
}
|
|
738
|
+
const [subkey, ...subrest] = deeper.split(":");
|
|
739
|
+
cursor += 1;
|
|
740
|
+
const subvalue = subrest.join(":").trim();
|
|
741
|
+
nested[subkey.trim()] = subvalue.length > 0 ? parsescalar(subvalue) : parseblock(currentindent + 4);
|
|
742
|
+
}
|
|
743
|
+
sequence.push(nested);
|
|
744
|
+
} else if (inline.length > 0) {
|
|
745
|
+
sequence.push(parsescalar(inline));
|
|
746
|
+
} else {
|
|
747
|
+
sequence.push(parseblock(currentindent + 2));
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
return sequence;
|
|
751
|
+
}
|
|
752
|
+
const mapping: Record<string, YamlValue> = {};
|
|
753
|
+
while (cursor < lines.length) {
|
|
754
|
+
const entry = lines[cursor];
|
|
755
|
+
const entryindent = indentof(entry.text);
|
|
756
|
+
if (entryindent < currentindent) break;
|
|
757
|
+
if (entryindent > currentindent) {
|
|
758
|
+
throw Object.assign(new Error(`the yaml interpreter refuses the indentation at line ${entry.index}`), {
|
|
759
|
+
code: "YAML_PARSE_FAILED",
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
const trimmed = entry.text.trim();
|
|
763
|
+
if (!trimmed.includes(":")) {
|
|
764
|
+
throw Object.assign(new Error(`the yaml interpreter requires a mapping at line ${entry.index}`), {
|
|
765
|
+
code: "YAML_PARSE_FAILED",
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
const [key, ...rest] = trimmed.split(":");
|
|
769
|
+
const value = rest.join(":").trim();
|
|
770
|
+
cursor += 1;
|
|
771
|
+
mapping[key.trim()] = value.length > 0 ? parsescalar(value) : parseblock(currentindent + 1);
|
|
772
|
+
}
|
|
773
|
+
return mapping;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
return parseblock(0);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
/* ---- 6.3 the babel bridge (the yaml and babel seats) ---- */
|
|
780
|
+
|
|
781
|
+
/** The typed slice of @babel/core the bridge uses. */
|
|
782
|
+
interface BabelModule {
|
|
783
|
+
transformSync?: (code: string, options?: unknown) => { code: string };
|
|
784
|
+
default?: { transformSync?: (code: string, options?: unknown) => { code: string } };
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
/**
|
|
788
|
+
* Compiles one ecmascript program through babel (the real
|
|
789
|
+
* transformSync pass — parse and regenerate, no preset the engine
|
|
790
|
+
* graph does not carry). the yaml seat rides this bridge for the
|
|
791
|
+
* background execution of the embedded handlers (the pr15 law:
|
|
792
|
+
* yml/yaml interpretation with babel compilation for background
|
|
793
|
+
* execution and docker).
|
|
794
|
+
*
|
|
795
|
+
* @param code the ecmascript program.
|
|
796
|
+
* @returns the transformed program.
|
|
797
|
+
* @throws a typed HOST_RUNTIME_ABSENT error when the module is absent.
|
|
798
|
+
*/
|
|
799
|
+
export async function compilethroughbabel(code: string): Promise<string> {
|
|
800
|
+
let babelmodule: BabelModule;
|
|
801
|
+
try {
|
|
802
|
+
babelmodule = (await import("@babel/core")) as BabelModule;
|
|
803
|
+
} catch (error) {
|
|
804
|
+
throw Object.assign(
|
|
805
|
+
new Error(`the babel runtime is absent from the engine module graph: ${error instanceof Error ? error.message : String(error)}`),
|
|
806
|
+
{ code: "HOST_RUNTIME_ABSENT" },
|
|
807
|
+
);
|
|
808
|
+
}
|
|
809
|
+
const transform = babelmodule.transformSync ?? babelmodule.default?.transformSync;
|
|
810
|
+
if (typeof transform !== "function") {
|
|
811
|
+
throw Object.assign(new Error("the babel runtime surfaced no transform (the seat never fakes one)"), {
|
|
812
|
+
code: "HOST_RUNTIME_ABSENT",
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
const transformed = transform(code, { filename: "modality.js", plugins: [], presets: [], configFile: false, babelrc: false });
|
|
816
|
+
return transformed.code;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
/* ---- 6.4 the node:vm bridge (the background execution surface) ---- */
|
|
820
|
+
|
|
821
|
+
/**
|
|
822
|
+
* Runs one compiled ecmascript program inside a fresh node:vm context
|
|
823
|
+
* with only the console surface and the frozen inputs — the background
|
|
824
|
+
* execution surface (no require, no process, no host global leaks).
|
|
825
|
+
*
|
|
826
|
+
* @param code the compiled program.
|
|
827
|
+
* @param inputs the caller inputs (frozen).
|
|
828
|
+
* @param deadline the run deadline in milliseconds.
|
|
829
|
+
* @returns the captured stdout lines and the completion value.
|
|
830
|
+
*/
|
|
831
|
+
export async function runinisolatedcontext(
|
|
832
|
+
code: string,
|
|
833
|
+
inputs: unknown,
|
|
834
|
+
deadline: number,
|
|
835
|
+
): Promise<{ stdout: string; completion: unknown }> {
|
|
836
|
+
const vmmodule = (await import("node:vm")) as typeof import("node:vm");
|
|
837
|
+
const stdout: string[] = [];
|
|
838
|
+
const push = (...values: unknown[]) => {
|
|
839
|
+
stdout.push(values.map((value) => (typeof value === "string" ? value : JSON.stringify(value))).join(" "));
|
|
840
|
+
};
|
|
841
|
+
const sandbox = {
|
|
842
|
+
console: Object.freeze({ log: push, info: push, debug: push, warn: push, error: push }),
|
|
843
|
+
inputs: inputs === undefined ? null : Object.freeze(inputs as object),
|
|
844
|
+
};
|
|
845
|
+
const completion = vmmodule.runInNewContext(code, sandbox, { timeout: deadline, displayErrors: true });
|
|
846
|
+
return { stdout: stdout.join("\n"), completion };
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
/* ---- 6.5 the wasm instantiation (the wasm and wasme seats) ---- */
|
|
850
|
+
|
|
851
|
+
/**
|
|
852
|
+
* Instantiates one webassembly module in-process — the real
|
|
853
|
+
* instantiation with a minimal import shim (every imported function
|
|
854
|
+
* answers 0) and the first zero-parameter export call. the seats ride
|
|
855
|
+
* this for the wasm-shaped payloads the callers hand the engine.
|
|
856
|
+
*
|
|
857
|
+
* @param code the base64 module bytes.
|
|
858
|
+
* @returns the captured answer of the first export.
|
|
859
|
+
* @throws a typed WASM_INSTANTIATION_FAILED error on invalid bytes.
|
|
860
|
+
*/
|
|
861
|
+
export async function instantiatewasm(code: string): Promise<string> {
|
|
862
|
+
const compact = code.replace(/\s/g, "");
|
|
863
|
+
const bytes = Uint8Array.from(atob(compact), (character) => character.charCodeAt(0));
|
|
864
|
+
if (bytes.byteLength < 8) {
|
|
865
|
+
throw Object.assign(new Error("the wasm seat requires base64 module bytes in the code field"), {
|
|
866
|
+
code: "WASM_INSTANTIATION_FAILED",
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
let instance: WebAssembly.Instance;
|
|
870
|
+
try {
|
|
871
|
+
const module = new WebAssembly.Module(bytes);
|
|
872
|
+
/* the import object answers the dom contract of the instantiate
|
|
873
|
+
* signature (WebAssembly.Imports — Record<string, ModuleImports>): the
|
|
874
|
+
* loose Record<string, Record<string, unknown>> annotation failed the
|
|
875
|
+
* strict walk, so the seat types the shim the way the lib declares it. */
|
|
876
|
+
const importobject: Record<string, WebAssembly.ModuleImports> = {};
|
|
877
|
+
for (const spec of WebAssembly.Module.imports(module)) {
|
|
878
|
+
importobject[spec.module] ??= {};
|
|
879
|
+
if (spec.kind === "function") importobject[spec.module][spec.name] = () => 0;
|
|
880
|
+
}
|
|
881
|
+
instance = new WebAssembly.Instance(module, importobject);
|
|
882
|
+
} catch (error) {
|
|
883
|
+
throw Object.assign(
|
|
884
|
+
new Error(`the wasm module failed to instantiate: ${error instanceof Error ? error.message : String(error)}`),
|
|
885
|
+
{ code: "WASM_INSTANTIATION_FAILED" },
|
|
886
|
+
);
|
|
887
|
+
}
|
|
888
|
+
const exports = instance.exports as Record<string, unknown>;
|
|
889
|
+
const callable = Object.values(exports).find(
|
|
890
|
+
(value) => typeof value === "function" && (value as (...args: unknown[]) => unknown).length === 0,
|
|
891
|
+
);
|
|
892
|
+
if (callable === undefined) {
|
|
893
|
+
return `instantiated; exports: ${Object.keys(exports).join(", ")}`;
|
|
894
|
+
}
|
|
895
|
+
return JSON.stringify((callable as () => unknown)());
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
/* ---- 6.6 the port dock (the port processing seat) ---- */
|
|
899
|
+
|
|
900
|
+
/** The sandbox port range the dock allocates from (the engine's own
|
|
901
|
+
* range — the visitor machine's ports are never touched, the critical
|
|
902
|
+
* law: these are the edge host ports the container docks publish). */
|
|
903
|
+
const PORTDOCKRANGE = Object.freeze({ first: 30000, last: 32767 });
|
|
904
|
+
|
|
905
|
+
/** The port dock — the port processing registry of the engine: the
|
|
906
|
+
* allocation table (per-sandbox bindings in the engine range), the
|
|
907
|
+
* collision refusal and the docker publish flags the container docks
|
|
908
|
+
* read. */
|
|
909
|
+
class PortDock {
|
|
910
|
+
private readonly bindings = new Map<number, { sandbox: string; container: number; protocol: "tcp" | "udp"; boundat: number }>();
|
|
911
|
+
|
|
912
|
+
/** allocates one free port of the range for a sandbox (the honest
|
|
913
|
+
* collision refusal when the range answers no free seat). */
|
|
914
|
+
allocate(sandbox: string, container: number, protocol: "tcp" | "udp" = "tcp"): number {
|
|
915
|
+
for (let port = PORTDOCKRANGE.first; port <= PORTDOCKRANGE.last; port += 1) {
|
|
916
|
+
if (!this.bindings.has(port)) {
|
|
917
|
+
this.bindings.set(port, { sandbox, container, protocol, boundat: Date.now() });
|
|
918
|
+
return port;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
throw Object.assign(new Error("the port dock range is exhausted (30000-32767) — no free engine port answers"), {
|
|
922
|
+
code: "PORT_DOCK_EXHAUSTED",
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
/** answers the docker publish flags of one sandbox's bindings. */
|
|
927
|
+
publishflags(sandbox: string): readonly string[] {
|
|
928
|
+
const flags: string[] = [];
|
|
929
|
+
for (const [host, binding] of this.bindings) {
|
|
930
|
+
if (binding.sandbox === sandbox) flags.push("-p", `${host}:${binding.container}/${binding.protocol}`);
|
|
931
|
+
}
|
|
932
|
+
return flags;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
/** releases every binding of one sandbox. */
|
|
936
|
+
release(sandbox: string): number {
|
|
937
|
+
let released = 0;
|
|
938
|
+
for (const [port, binding] of this.bindings) {
|
|
939
|
+
if (binding.sandbox === sandbox) {
|
|
940
|
+
this.bindings.delete(port);
|
|
941
|
+
released += 1;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
return released;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
/** stats the dock (the report surface). */
|
|
948
|
+
stats(): { bound: number; free: number; range: string } {
|
|
949
|
+
return {
|
|
950
|
+
bound: this.bindings.size,
|
|
951
|
+
free: PORTDOCKRANGE.last - PORTDOCKRANGE.first + 1 - this.bindings.size,
|
|
952
|
+
range: `${PORTDOCKRANGE.first}-${PORTDOCKRANGE.last}`,
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
/** The port dock of record (the port processing seat reads it). */
|
|
958
|
+
const portdock = new PortDock();
|
|
959
|
+
|
|
960
|
+
/* ---- 6.7 the payload magic sniffers (the universal, exe, binary seats) ---- */
|
|
961
|
+
|
|
962
|
+
/**
|
|
963
|
+
* Sniffs the payload magic — the real byte signatures the universal
|
|
964
|
+
* seat routes by: the elf magic (7f 45 4c 46), the mz magic (4d 5a),
|
|
965
|
+
* the wasm magic (00 61 73 6d) and the shebang (#!).
|
|
966
|
+
*
|
|
967
|
+
* @param code the payload (base64 bytes or script text).
|
|
968
|
+
* @returns the sniffed kind: elf, exe, wasm, script or text.
|
|
969
|
+
*/
|
|
970
|
+
export function sniffpayloadmagic(code: string): "elf" | "exe" | "wasm" | "script" | "text" {
|
|
971
|
+
if (code.startsWith("#!")) return "script";
|
|
972
|
+
if (/^[A-Za-z0-9+/=\s]+$/.test(code) && code.replace(/\s/g, "").length >= 8) {
|
|
973
|
+
const head = atob(code.replace(/\s/g, "").slice(0, 8));
|
|
974
|
+
if (head.startsWith("\u007fELF")) return "elf";
|
|
975
|
+
if (head.startsWith("MZ")) return "exe";
|
|
976
|
+
if (head.startsWith("\u0000asm")) return "wasm";
|
|
977
|
+
}
|
|
978
|
+
return "text";
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
/* ---- 6.8 the hardware composition engines (the hardware lane) ---- */
|
|
982
|
+
|
|
983
|
+
/**
|
|
984
|
+
* Solves the vcpu topology — the real factorization the vcpu seat
|
|
985
|
+
* answers: given the requested vcpu count, the solver walks the
|
|
986
|
+
* sockets x cores x threads triples and answers the balanced topology
|
|
987
|
+
* (the fewest sockets, the symmetric cores), the same math the board
|
|
988
|
+
* catalog pins.
|
|
989
|
+
*
|
|
990
|
+
* @param vcpus the requested vcpu count (1 to 192).
|
|
991
|
+
* @returns the solved topology and the qemu -smp / firecracker flags.
|
|
992
|
+
* @throws a typed VCPU_TOPOLOGY_INVALID error on an unsolvable count.
|
|
993
|
+
*/
|
|
994
|
+
export function solvectputopology(vcpus: number): {
|
|
995
|
+
sockets: number;
|
|
996
|
+
cores: number;
|
|
997
|
+
threads: number;
|
|
998
|
+
qemuflag: string;
|
|
999
|
+
firecrackerflag: number;
|
|
1000
|
+
} {
|
|
1001
|
+
if (!Number.isInteger(vcpus) || vcpus < 1 || vcpus > 192) {
|
|
1002
|
+
throw Object.assign(new Error(`the vcpu count ${vcpus} is outside the 1-192 plan the engine presents`), {
|
|
1003
|
+
code: "VCPU_TOPOLOGY_INVALID",
|
|
1004
|
+
});
|
|
1005
|
+
}
|
|
1006
|
+
let best = { sockets: vcpus, cores: 1, threads: 1 };
|
|
1007
|
+
let bestscore = Number.POSITIVE_INFINITY;
|
|
1008
|
+
for (let threads = 2; threads >= 1; threads -= 1) {
|
|
1009
|
+
for (let cores = 1; cores <= vcpus; cores += 1) {
|
|
1010
|
+
if (vcpus % (cores * threads) !== 0) continue;
|
|
1011
|
+
const sockets = vcpus / (cores * threads);
|
|
1012
|
+
if (sockets < 1 || sockets > 8) continue;
|
|
1013
|
+
// the balanced topology: prefer multiple cores, few sockets.
|
|
1014
|
+
const score = sockets * 2 + (cores < 2 ? 4 : 0) + (threads < 2 ? 2 : 0);
|
|
1015
|
+
if (score < bestscore) {
|
|
1016
|
+
best = { sockets, cores, threads };
|
|
1017
|
+
bestscore = score;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
return {
|
|
1022
|
+
...best,
|
|
1023
|
+
qemuflag: `-smp ${vcpus},sockets=${best.sockets},cores=${best.cores},threads=${best.threads}`,
|
|
1024
|
+
firecrackerflag: vcpus,
|
|
1025
|
+
};
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
/** The virtual processor classes the cpu seat presents (the device
|
|
1029
|
+
* bank vocabulary the engineplans profiles stamp). */
|
|
1030
|
+
const PROCESSORCLASSES: ReadonlyArray<{ id: string; model: string; baseghz: number; tdp: number }> = [
|
|
1031
|
+
{ id: "epyc9965", model: "AMD EPYC 9965 192-Core Processor", baseghz: 2.25, tdp: 500 },
|
|
1032
|
+
{ id: "epyc9755", model: "AMD EPYC 9755 128-Core Processor", baseghz: 2.75, tdp: 400 },
|
|
1033
|
+
{ id: "threadripperpro9995wx", model: "AMD Ryzen ThreadRipper PRO 9995WX 96-Core", baseghz: 2.85, tdp: 350 },
|
|
1034
|
+
{ id: "ryzen9950x3d", model: "AMD Ryzen 9 9950X3D 16-Core Processor", baseghz: 4.3, tdp: 170 },
|
|
1035
|
+
{ id: "ryzen9950x3d2", model: "AMD Ryzen 9 9950X3D2 16-Core Processor", baseghz: 4.4, tdp: 170 },
|
|
1036
|
+
{ id: "ryzen9800x3d", model: "AMD Ryzen 7 9800X3D 8-Core Processor", baseghz: 4.5, tdp: 120 },
|
|
1037
|
+
{ id: "xeon6980p", model: "Intel Xeon 6980P 128-Core Processor", baseghz: 2.1, tdp: 500 },
|
|
1038
|
+
];
|
|
1039
|
+
|
|
1040
|
+
/**
|
|
1041
|
+
* Presents the virtual processor class — the cpu identity the cpu seat
|
|
1042
|
+
* stamps (the /proc/cpuinfo model name the vm presents, the base clock
|
|
1043
|
+
* and the tdp of the class — a virtual identity the engine presents on
|
|
1044
|
+
* the edge, never the visitor cpu).
|
|
1045
|
+
*
|
|
1046
|
+
* @param classid the processor class id (the device bank vocabulary).
|
|
1047
|
+
* @returns the processor identity document.
|
|
1048
|
+
* @throws a typed PROCESSOR_CLASS_UNKNOWN error on an unknown class.
|
|
1049
|
+
*/
|
|
1050
|
+
export function presentprocessor(classid: string): { id: string; model: string; baseghz: number; tdp: number; cpumhz: number } {
|
|
1051
|
+
const processor = PROCESSORCLASSES.find((entry) => entry.id === classid);
|
|
1052
|
+
if (processor === undefined) {
|
|
1053
|
+
throw Object.assign(new Error(`the processor class ${classid} is unknown to the device bank`), {
|
|
1054
|
+
code: "PROCESSOR_CLASS_UNKNOWN",
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
return { ...processor, cpumhz: Math.round(processor.baseghz * 1000) };
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
/** The virtual gpu classes the gpu seat presents (the device bank
|
|
1061
|
+
* vocabulary the engineplans profiles stamp). */
|
|
1062
|
+
const GPUSCLASSES: ReadonlyArray<{ id: string; name: string; vramgb: number; mig: boolean }> = [
|
|
1063
|
+
{ id: "rtx5090", name: "NVIDIA GeForce RTX 5090", vramgb: 32, mig: false },
|
|
1064
|
+
{ id: "rtxpro6000", name: "NVIDIA RTX PRO 6000 Blackwell", vramgb: 96, mig: true },
|
|
1065
|
+
{ id: "b200", name: "NVIDIA B200 Tensor Core", vramgb: 192, mig: true },
|
|
1066
|
+
{ id: "h100", name: "NVIDIA H100 Tensor Core", vramgb: 80, mig: true },
|
|
1067
|
+
];
|
|
1068
|
+
|
|
1069
|
+
/** The mig slice profiles the vgpu seat carves (the gpu grammar of the
|
|
1070
|
+
* device bank: the fraction slices of each mig-capable class). */
|
|
1071
|
+
const MIGPROFILES: Readonly<Record<string, readonly string[]>> = {
|
|
1072
|
+
b200: ["1g.20gb", "2g.40gb", "3g.60gb", "4g.80gb"],
|
|
1073
|
+
h100: ["1g.10gb", "2g.20gb", "3g.40gb", "4g.40gb"],
|
|
1074
|
+
rtxpro6000: ["1g.12gb", "2g.24gb", "3g.48gb", "4g.72gb"],
|
|
1075
|
+
};
|
|
1076
|
+
|
|
1077
|
+
/**
|
|
1078
|
+
* Presents the virtual gpu bank — the gpu seat's device plan: the gpu
|
|
1079
|
+
* class, the count, the vram plan and the mig slice grammar the vgpu
|
|
1080
|
+
* seat carves.
|
|
1081
|
+
*
|
|
1082
|
+
* @param gpuid the gpu class id.
|
|
1083
|
+
* @param count the gpu count of the plan (1 to 8).
|
|
1084
|
+
* @returns the gpu bank document.
|
|
1085
|
+
* @throws a typed GPU_CLASS_UNKNOWN error on an unknown class.
|
|
1086
|
+
*/
|
|
1087
|
+
export function presentgpubank(gpuid: string, count = 1): {
|
|
1088
|
+
id: string;
|
|
1089
|
+
name: string;
|
|
1090
|
+
count: number;
|
|
1091
|
+
vramgb: number;
|
|
1092
|
+
totalvramgb: number;
|
|
1093
|
+
mig: boolean;
|
|
1094
|
+
migprofiles: readonly string[];
|
|
1095
|
+
} {
|
|
1096
|
+
const gpu = GPUSCLASSES.find((entry) => entry.id === gpuid);
|
|
1097
|
+
if (gpu === undefined) {
|
|
1098
|
+
throw Object.assign(new Error(`the gpu class ${gpuid} is unknown to the device bank`), { code: "GPU_CLASS_UNKNOWN" });
|
|
1099
|
+
}
|
|
1100
|
+
const safecount = Math.max(1, Math.min(8, Math.round(count)));
|
|
1101
|
+
return {
|
|
1102
|
+
id: gpu.id,
|
|
1103
|
+
name: gpu.name,
|
|
1104
|
+
count: safecount,
|
|
1105
|
+
vramgb: gpu.vramgb,
|
|
1106
|
+
totalvramgb: gpu.vramgb * safecount,
|
|
1107
|
+
mig: gpu.mig,
|
|
1108
|
+
migprofiles: MIGPROFILES[gpu.id] ?? [],
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
/**
|
|
1113
|
+
* Carves one vgpu mig slice — the vgpu seat's real slicing math: the
|
|
1114
|
+
* gpu class plan, the slice profile and the honest vram the slice
|
|
1115
|
+
* answers (the fraction of the device plan the slice reserves).
|
|
1116
|
+
*
|
|
1117
|
+
* @param gpuid the gpu class id.
|
|
1118
|
+
* @param profile the mig slice profile (for example "2g.40gb").
|
|
1119
|
+
* @returns the vgpu slice document.
|
|
1120
|
+
* @throws a typed VGPU_SLICE_UNKNOWN error when the class carries no
|
|
1121
|
+
* such slice profile.
|
|
1122
|
+
*/
|
|
1123
|
+
export function carvevgpuslice(gpuid: string, profile: string): { gpu: string; profile: string; vramgb: number; instances: number } {
|
|
1124
|
+
const bank = presentgpubank(gpuid, 1);
|
|
1125
|
+
if (!bank.migprofiles.includes(profile)) {
|
|
1126
|
+
throw Object.assign(new Error(`the gpu class ${gpuid} carries no mig slice ${profile}`), { code: "VGPU_SLICE_UNKNOWN" });
|
|
1127
|
+
}
|
|
1128
|
+
const vramgb = Number.parseInt(profile.split(".")[1]?.replace("gb", "") ?? "0", 10);
|
|
1129
|
+
const instances = Math.max(1, Math.floor(bank.vramgb / Math.max(1, vramgb)));
|
|
1130
|
+
return { gpu: gpuid, profile, vramgb, instances };
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
/* ---- 6.9 the wire builders (the delivery, storage and network seats) ---- */
|
|
1134
|
+
|
|
1135
|
+
/**
|
|
1136
|
+
* Frames one websocket text frame — the real RFC 6455 encoding: the
|
|
1137
|
+
* fin bit, the opcode, the mask bit, the 7/16/64-bit length ladder and
|
|
1138
|
+
* the masked payload (the deliverywss seat answers it).
|
|
1139
|
+
*
|
|
1140
|
+
* @param payload the text payload of the frame.
|
|
1141
|
+
* @returns the frame bytes as a hex string.
|
|
1142
|
+
*/
|
|
1143
|
+
export function framewebsockettext(payload: string): string {
|
|
1144
|
+
const bytes = new TextEncoder().encode(payload);
|
|
1145
|
+
const head: number[] = [0x81]; // fin + text opcode
|
|
1146
|
+
if (bytes.byteLength < 126) {
|
|
1147
|
+
head.push(0x80 | bytes.byteLength);
|
|
1148
|
+
} else if (bytes.byteLength < 65536) {
|
|
1149
|
+
head.push(0x80 | 126, (bytes.byteLength >> 8) & 0xff, bytes.byteLength & 0xff);
|
|
1150
|
+
} else {
|
|
1151
|
+
head.push(0x80 | 127);
|
|
1152
|
+
for (let shift = 56; shift >= 0; shift -= 8) head.push(Number((BigInt(bytes.byteLength) >> BigInt(shift)) & 0xffn));
|
|
1153
|
+
}
|
|
1154
|
+
const mask = Array.from({ length: 4 }, () => Math.floor(Math.random() * 256));
|
|
1155
|
+
head.push(...mask);
|
|
1156
|
+
const masked = Array.from(bytes, (byte, index) => byte ^ mask[index % 4]);
|
|
1157
|
+
/* the hex walk spreads the buffer into a plain array first: the typed
|
|
1158
|
+
* array map coerces its callback answer back through ToNumber, so a
|
|
1159
|
+
* string answer lost every byte the hex letters carry (0xab answered
|
|
1160
|
+
* NaN, then 0) — the spread keeps every value a string of the join. */
|
|
1161
|
+
return [...Uint8Array.from([...head, ...masked])]
|
|
1162
|
+
.map((value) => value.toString(16).padStart(2, "0"))
|
|
1163
|
+
.join("");
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
/**
|
|
1167
|
+
* Frames one grpc data message — the real length-prefixed framing: the
|
|
1168
|
+
* compressed flag byte, the 4-byte big-endian length and the payload
|
|
1169
|
+
* (the deliverygrpc seat answers it).
|
|
1170
|
+
*
|
|
1171
|
+
* @param payload the message payload.
|
|
1172
|
+
* @returns the frame bytes as a hex string.
|
|
1173
|
+
*/
|
|
1174
|
+
export function framegrpcmessage(payload: string): string {
|
|
1175
|
+
const bytes = new TextEncoder().encode(payload);
|
|
1176
|
+
const head = [0x00, (bytes.byteLength >>> 24) & 0xff, (bytes.byteLength >>> 16) & 0xff, (bytes.byteLength >>> 8) & 0xff, bytes.byteLength & 0xff];
|
|
1177
|
+
/* the spread before the hex map: the typed array map would coerce the
|
|
1178
|
+
* string answers back through ToNumber and flatten the hex letters. */
|
|
1179
|
+
return [...Uint8Array.from([...head, ...bytes])]
|
|
1180
|
+
.map((value) => value.toString(16).padStart(2, "0"))
|
|
1181
|
+
.join("");
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
/**
|
|
1185
|
+
* Builds one dns query packet — the real header (the id, the flags,
|
|
1186
|
+
* the counts) and the question section (the encoded qname, the qtype
|
|
1187
|
+
* and the qclass) the networkdns seat answers.
|
|
1188
|
+
*
|
|
1189
|
+
* @param domain the domain name to query.
|
|
1190
|
+
* @param qtype the query type (a, aaaa, txt, mx — default a).
|
|
1191
|
+
* @returns the packet bytes as a hex string.
|
|
1192
|
+
*/
|
|
1193
|
+
export function builddnsquery(domain: string, qtype = "a"): string {
|
|
1194
|
+
const types: Record<string, number> = { a: 1, ns: 2, cname: 5, soa: 6, ptr: 12, mx: 15, txt: 16, aaaa: 28 };
|
|
1195
|
+
const id = Math.floor(Math.random() * 65536);
|
|
1196
|
+
const bytes: number[] = [
|
|
1197
|
+
(id >> 8) & 0xff,
|
|
1198
|
+
id & 0xff,
|
|
1199
|
+
0x01, // recursion desired
|
|
1200
|
+
0x00,
|
|
1201
|
+
0x00, 0x01, // qdcount
|
|
1202
|
+
0x00, 0x00, // ancount
|
|
1203
|
+
0x00, 0x00, // nscount
|
|
1204
|
+
0x00, 0x00, // arcount
|
|
1205
|
+
];
|
|
1206
|
+
for (const label of domain.split(".")) {
|
|
1207
|
+
const encoded = new TextEncoder().encode(label);
|
|
1208
|
+
bytes.push(encoded.byteLength, ...Array.from(encoded));
|
|
1209
|
+
}
|
|
1210
|
+
bytes.push(0x00); // the root label
|
|
1211
|
+
const type = types[qtype.toLowerCase()] ?? 1;
|
|
1212
|
+
bytes.push((type >> 8) & 0xff, type & 0xff, 0x00, 0x01); // qclass in
|
|
1213
|
+
return bytes.map((value) => value.toString(16).padStart(2, "0")).join("");
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
/**
|
|
1217
|
+
* Builds the mysql client handshake header — the real capability flags
|
|
1218
|
+
* and the charset bytes the storagemysql seat answers (the wire
|
|
1219
|
+
* greeting the server expects before the auth ride).
|
|
1220
|
+
*
|
|
1221
|
+
* @param user the database user of the handshake.
|
|
1222
|
+
* @returns the handshake document (the capability flags and the auth
|
|
1223
|
+
* payload plan).
|
|
1224
|
+
*/
|
|
1225
|
+
export function buildmysqlhandshake(user: string): { capabilities: number; charset: number; maxpacket: number; authuser: string } {
|
|
1226
|
+
const CLIENTLONGPASSWORD = 0x1;
|
|
1227
|
+
const CLIENTPROTOCOL41 = 0x200;
|
|
1228
|
+
const CLIENTSECURECONNECTION = 0x8000;
|
|
1229
|
+
const CLIENTPLUGINAUTH = 0x80000;
|
|
1230
|
+
return {
|
|
1231
|
+
capabilities: CLIENTLONGPASSWORD | CLIENTPROTOCOL41 | CLIENTSECURECONNECTION | CLIENTPLUGINAUTH,
|
|
1232
|
+
charset: 255, // utf8mb4
|
|
1233
|
+
maxpacket: 16777216,
|
|
1234
|
+
authuser: user,
|
|
1235
|
+
};
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
/**
|
|
1239
|
+
* Builds the postgres startup message — the real framing: the length
|
|
1240
|
+
* prefix, the protocol version (3.0) and the key/value parameters the
|
|
1241
|
+
* storagepostgres seat answers.
|
|
1242
|
+
*
|
|
1243
|
+
* @param user the database user of the startup.
|
|
1244
|
+
* @param database the database name of the startup.
|
|
1245
|
+
* @returns the startup message bytes as a hex string.
|
|
1246
|
+
*/
|
|
1247
|
+
export function buildpostgresstartup(user: string, database: string): string {
|
|
1248
|
+
const parameters = `user\u0000${user}\u0000database\u0000${database}\u0000\u0000`;
|
|
1249
|
+
const body = new TextEncoder().encode(`\u0000\u0003\u0000\u0000${parameters}`);
|
|
1250
|
+
const length = body.byteLength + 4;
|
|
1251
|
+
const head = [(length >>> 24) & 0xff, (length >>> 16) & 0xff, (length >>> 8) & 0xff, length & 0xff];
|
|
1252
|
+
/* the spread before the hex map: the typed array map would coerce the
|
|
1253
|
+
* string answers back through ToNumber and flatten the hex letters. */
|
|
1254
|
+
return [...Uint8Array.from([...head, ...Array.from(body)])]
|
|
1255
|
+
.map((value) => value.toString(16).padStart(2, "0"))
|
|
1256
|
+
.join("");
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
/* ---- 6.10 the sigv4 signing chain (the aws and s3 seats) ---- */
|
|
1260
|
+
|
|
1261
|
+
/**
|
|
1262
|
+
* Signs one aws api request with sigv4 — the real signing chain: the
|
|
1263
|
+
* canonical request, the string-to-sign, the derived key (the hmac
|
|
1264
|
+
* ladder over the secret) and the authorization header. the aws seat
|
|
1265
|
+
* builds its ec2/fargate spawn requests through this chain and the s3
|
|
1266
|
+
* seat signs its bucket requests with the same law.
|
|
1267
|
+
*
|
|
1268
|
+
* @param options the request parts (method, host, path, query, body,
|
|
1269
|
+
* service, region, access key and secret key).
|
|
1270
|
+
* @returns the signed request parts (the amz date and the authorization header).
|
|
1271
|
+
*/
|
|
1272
|
+
export async function sigv4sign(options: {
|
|
1273
|
+
method: string;
|
|
1274
|
+
host: string;
|
|
1275
|
+
path: string;
|
|
1276
|
+
query?: string;
|
|
1277
|
+
body?: string;
|
|
1278
|
+
service: string;
|
|
1279
|
+
region: string;
|
|
1280
|
+
accesskey: string;
|
|
1281
|
+
secretkey: string;
|
|
1282
|
+
}): Promise<{ amzdate: string; datestamp: string; authorization: string; canonicalrequest: string }> {
|
|
1283
|
+
const subtle = globalThis.crypto.subtle;
|
|
1284
|
+
const encoder = new TextEncoder();
|
|
1285
|
+
const now = new Date();
|
|
1286
|
+
const amzdate = `${now.toISOString().replace(/[-:]/g, "").slice(0, 15)}Z`;
|
|
1287
|
+
const datestamp = amzdate.slice(0, 8);
|
|
1288
|
+
const tohex = (buffer: ArrayBuffer): string =>
|
|
1289
|
+
Array.from(new Uint8Array(buffer))
|
|
1290
|
+
.map((value) => value.toString(16).padStart(2, "0"))
|
|
1291
|
+
.join("");
|
|
1292
|
+
const payloadhash = tohex(await subtle.digest("SHA-256", encoder.encode(options.body ?? "")));
|
|
1293
|
+
const canonicalheaders = `host:${options.host}\nx-amz-content-sha256:${payloadhash}\nx-amz-date:${amzdate}\n`;
|
|
1294
|
+
const signedheaders = "host;x-amz-content-sha256;x-amz-date";
|
|
1295
|
+
const canonicalrequest = [options.method, options.path, options.query ?? "", canonicalheaders, signedheaders, payloadhash].join("\n");
|
|
1296
|
+
const scope = `${datestamp}/${options.region}/${options.service}/aws4_request`;
|
|
1297
|
+
const stringtosign = ["AWS4-HMAC-SHA256", amzdate, scope, tohex(await subtle.digest("SHA-256", encoder.encode(canonicalrequest)))].join("\n");
|
|
1298
|
+
const hmac = async (key: ArrayBuffer | Uint8Array, message: string): Promise<ArrayBuffer> => {
|
|
1299
|
+
const cryptokey = await subtle.importKey(
|
|
1300
|
+
"raw",
|
|
1301
|
+
key instanceof Uint8Array ? (key.slice().buffer as ArrayBuffer) : key,
|
|
1302
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
1303
|
+
false,
|
|
1304
|
+
["sign"],
|
|
1305
|
+
);
|
|
1306
|
+
return subtle.sign("HMAC", cryptokey, encoder.encode(message));
|
|
1307
|
+
};
|
|
1308
|
+
const datekey = await hmac(encoder.encode(`AWS4${options.secretkey}`), datestamp);
|
|
1309
|
+
const regionkey = await hmac(datekey, options.region);
|
|
1310
|
+
const servicekey = await hmac(regionkey, options.service);
|
|
1311
|
+
const derived = await hmac(servicekey, "aws4_request");
|
|
1312
|
+
const signature = tohex(derived);
|
|
1313
|
+
return {
|
|
1314
|
+
amzdate,
|
|
1315
|
+
datestamp,
|
|
1316
|
+
authorization: `AWS4-HMAC-SHA256 Credential=${options.accesskey}/${scope}, SignedHeaders=${signedheaders}, Signature=${signature}`,
|
|
1317
|
+
canonicalrequest,
|
|
1318
|
+
};
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
/* ---- 6.11 the git object hashing (the git objects vfs seat) ---- */
|
|
1322
|
+
|
|
1323
|
+
/**
|
|
1324
|
+
* Computes one git blob oid — the real git object hashing: sha1 over
|
|
1325
|
+
* the `blob {len}\0{content}` framing (the same digest the git objects
|
|
1326
|
+
* vfs of the fs family answers).
|
|
1327
|
+
*
|
|
1328
|
+
* @param content the blob content.
|
|
1329
|
+
* @returns the forty-hex oid of the object.
|
|
1330
|
+
*/
|
|
1331
|
+
export async function gitbloboid(content: string): Promise<string> {
|
|
1332
|
+
const encoded = new TextEncoder().encode(content);
|
|
1333
|
+
const framed = new TextEncoder().encode(`blob ${encoded.byteLength}\0${content}`);
|
|
1334
|
+
const digest = await globalThis.crypto.subtle.digest("SHA-1", framed.slice().buffer as ArrayBuffer);
|
|
1335
|
+
return Array.from(new Uint8Array(digest))
|
|
1336
|
+
.map((value) => value.toString(16).padStart(2, "0"))
|
|
1337
|
+
.join("");
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
/* ---- 6.12 the mount plan builders (the ram seats) ---- */
|
|
1341
|
+
|
|
1342
|
+
/**
|
|
1343
|
+
* Builds the tmpfs mount plan — the real mount argv with the size and
|
|
1344
|
+
* the mode flags, under the oom guard (the ramtmpfs seat answers it).
|
|
1345
|
+
*
|
|
1346
|
+
* @param sizebytes the tmpfs size in bytes.
|
|
1347
|
+
* @param mountpoint the mount point inside the sandbox.
|
|
1348
|
+
* @returns the mount plan (argv and the oom guard note).
|
|
1349
|
+
*/
|
|
1350
|
+
export function tmpfsmountplan(sizebytes: number, mountpoint: string): { argv: readonly string[]; oomguard: string } {
|
|
1351
|
+
return {
|
|
1352
|
+
argv: ["mount", "-t", "tmpfs", "-o", `size=${sizebytes},mode=1777,nr_inodes=64k`, "tmpfs", mountpoint],
|
|
1353
|
+
oomguard: "memory.max answers before the oom killer rides (the cgroup ceiling of the slice)",
|
|
1354
|
+
};
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
/**
|
|
1358
|
+
* Builds the overlayfs mount plan — the real lowerdir/upperdir/workdir
|
|
1359
|
+
* composition (the ramoverlayfs seat answers it).
|
|
1360
|
+
*
|
|
1361
|
+
* @param lowerdir the read-only lower layer.
|
|
1362
|
+
* @param upperdir the writable upper layer.
|
|
1363
|
+
* @param workdir the work directory of the overlay.
|
|
1364
|
+
* @param merged the merged mount point.
|
|
1365
|
+
* @returns the overlay mount argv.
|
|
1366
|
+
*/
|
|
1367
|
+
export function overlayfsmountplan(lowerdir: string, upperdir: string, workdir: string, merged: string): readonly string[] {
|
|
1368
|
+
return ["mount", "-t", "overlay", "overlay", "-o", `lowerdir=${lowerdir},upperdir=${upperdir},workdir=${workdir}`, merged];
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
/**
|
|
1372
|
+
* Plans the page-aligned mmap window — the real page math (the offset
|
|
1373
|
+
* alignment, the page count and the advice flags) the rammmap seat
|
|
1374
|
+
* answers.
|
|
1375
|
+
*
|
|
1376
|
+
* @param offset the requested byte offset.
|
|
1377
|
+
* @param length the requested byte length.
|
|
1378
|
+
* @returns the aligned window plan.
|
|
1379
|
+
*/
|
|
1380
|
+
export function mmapwindowplan(offset: number, length: number): { pageoffset: number; pagelength: number; pages: number; advice: string } {
|
|
1381
|
+
const pagesize = 4096;
|
|
1382
|
+
const pageoffset = Math.floor(offset / pagesize) * pagesize;
|
|
1383
|
+
const pagelength = Math.ceil((offset - pageoffset + length) / pagesize) * pagesize;
|
|
1384
|
+
return { pageoffset, pagelength, pages: pagelength / pagesize, advice: "madvise(MADV_SEQUENTIAL)" };
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
/* ---- 6.13 the container dock principal (the standardization seat) ---- */
|
|
1388
|
+
|
|
1389
|
+
/**
|
|
1390
|
+
* Builds the principal container dock plan — the standardization seat
|
|
1391
|
+
* the pr15 law demands: the canonical dock every container of the
|
|
1392
|
+
* family answers, composed from the engine profile, the architecture
|
|
1393
|
+
* seat and the healthcheck law. the plan carries the docker create
|
|
1394
|
+
* argv, the OCI labels, the profile env stamp and the honest
|
|
1395
|
+
* architecture row of the 11-seat standard.
|
|
1396
|
+
*
|
|
1397
|
+
* @param options the dock request (app, profile, arch, port).
|
|
1398
|
+
* @returns the principal dock plan document.
|
|
1399
|
+
*/
|
|
1400
|
+
export function principalcontainerdock(options: {
|
|
1401
|
+
app: string;
|
|
1402
|
+
profile?: string;
|
|
1403
|
+
arch?: string;
|
|
1404
|
+
port?: number;
|
|
1405
|
+
}): {
|
|
1406
|
+
app: string;
|
|
1407
|
+
profile: string;
|
|
1408
|
+
arch: string;
|
|
1409
|
+
platform: string;
|
|
1410
|
+
createargv: readonly string[];
|
|
1411
|
+
labels: Readonly<Record<string, string>>;
|
|
1412
|
+
env: Readonly<Record<string, string>>;
|
|
1413
|
+
healthcheck: { interval: string; timeout: string; retries: number };
|
|
1414
|
+
} {
|
|
1415
|
+
const profile = options.profile ?? "balanced";
|
|
1416
|
+
const arch = options.arch ?? "linux-x64";
|
|
1417
|
+
const platforms: Record<string, string> = {
|
|
1418
|
+
"linux-x64": "linux/amd64",
|
|
1419
|
+
"linux-arm64": "linux/arm64",
|
|
1420
|
+
"linux-x64-musl": "linux/amd64",
|
|
1421
|
+
"linux-riscv64": "linux/riscv64",
|
|
1422
|
+
"darwin-arm64-v8": "linux/arm64/v8",
|
|
1423
|
+
};
|
|
1424
|
+
const platform = platforms[arch] ?? "";
|
|
1425
|
+
const port = options.port ?? 8080;
|
|
1426
|
+
const createargv = [
|
|
1427
|
+
"docker",
|
|
1428
|
+
"create",
|
|
1429
|
+
...(platform.length > 0 ? ["--platform", platform] : []),
|
|
1430
|
+
"--name",
|
|
1431
|
+
`${options.app}-${profile}-${arch.replace(/[^a-z0-9-]/g, "")}`,
|
|
1432
|
+
"--network",
|
|
1433
|
+
"none",
|
|
1434
|
+
"--read-only",
|
|
1435
|
+
"--cap-drop",
|
|
1436
|
+
"ALL",
|
|
1437
|
+
"--security-opt",
|
|
1438
|
+
"no-new-privileges",
|
|
1439
|
+
"--pids-limit",
|
|
1440
|
+
"512",
|
|
1441
|
+
"--memory",
|
|
1442
|
+
profile === "max" ? "1024g" : profile === "lite" ? "128g" : "512g",
|
|
1443
|
+
"-p",
|
|
1444
|
+
`${port}:${port}`,
|
|
1445
|
+
`ghcr.io/wenathlan/${options.app}:${profile === "max" ? "latest" : profile}`,
|
|
1446
|
+
];
|
|
1447
|
+
return {
|
|
1448
|
+
app: options.app,
|
|
1449
|
+
profile,
|
|
1450
|
+
arch,
|
|
1451
|
+
platform: platform.length > 0 ? platform : "target-only (the 11-seat standard carries no container leg for this seat)",
|
|
1452
|
+
createargv,
|
|
1453
|
+
labels: {
|
|
1454
|
+
"org.opencontainers.image.title": options.app,
|
|
1455
|
+
"org.opencontainers.image.licenses": "GPL-3.0-only",
|
|
1456
|
+
"org.opencontainers.image.source": "https://github.com/wenathlan/devthink",
|
|
1457
|
+
"devthink.saddle.profile": profile,
|
|
1458
|
+
"devthink.saddle.arch": arch,
|
|
1459
|
+
},
|
|
1460
|
+
env: { SADDLE_PROFILE: profile, SADDLE_PROFILE_ARCH: arch, SADDLE_MEMORY_ENGINE: "ram" },
|
|
1461
|
+
healthcheck: { interval: "30s", timeout: "5s", retries: 3 },
|
|
1462
|
+
};
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
1466
|
+
/* Section 7: the executor builders — the real logic per seat. */
|
|
1467
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
1468
|
+
|
|
1469
|
+
/** The base envelope the host seats stamp (the profile scales it —
|
|
1470
|
+
* the engineplans tier law). */
|
|
1471
|
+
const BASEENVELOPE: ResourceEnvelope = { cpuweight: 256, cpupercent: 200, memorymb: 2048, pids: 256, iombps: 256 };
|
|
1472
|
+
|
|
1473
|
+
/** The envelope of the in-process seats (light — no spawn rides). */
|
|
1474
|
+
const INPROCENVELOPE: ResourceEnvelope = { cpuweight: 128, cpupercent: 100, memorymb: 512, pids: 64, iombps: 64 };
|
|
1475
|
+
|
|
1476
|
+
/** The envelope of the machine seats (heavy — the full hardware plan). */
|
|
1477
|
+
const MACHINEENVELOPE: ResourceEnvelope = { cpuweight: 512, cpupercent: 400, memorymb: 8192, pids: 1024, iombps: 512 };
|
|
1478
|
+
|
|
1479
|
+
/** The sandbox environment every spawn carries (never the host env,
|
|
1480
|
+
* never the visitor env — the critical law). */
|
|
1481
|
+
function sandboxenv(extra: Record<string, string> = {}): Record<string, string> {
|
|
1482
|
+
return { HOME: "/sandbox", TMPDIR: "/sandbox/tmp", LANG: "C.UTF-8", ...extra };
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
/** The staged workspace path of one run (the payload lands here before
|
|
1486
|
+
* the spawn — the engine stages its own workspace on the edge host,
|
|
1487
|
+
* never the visitor disk). */
|
|
1488
|
+
function workspacepath(slug: string, name: string): string {
|
|
1489
|
+
return `/sandbox/${slug}/${name}`;
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
/** Clamps one requested deadline into the honest window (250ms to
|
|
1493
|
+
* 60000ms — the same window the edge plane pins). */
|
|
1494
|
+
function clampdeadline(requested?: number): number {
|
|
1495
|
+
if (requested === undefined) return 20000;
|
|
1496
|
+
return Math.max(250, Math.min(60000, Math.round(requested)));
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
/** Answers the honest failure result of one seat (the coded error, the
|
|
1500
|
+
* plan when the seat built one). */
|
|
1501
|
+
function failureresult(slug: string, num: number, lane: ModalityLane, error: unknown, started: number, plan?: SpawnPlan): ModalityRunResult {
|
|
1502
|
+
const typed = error as { code?: string; message?: string };
|
|
1503
|
+
return {
|
|
1504
|
+
modality: slug,
|
|
1505
|
+
num,
|
|
1506
|
+
lane,
|
|
1507
|
+
status: "failed",
|
|
1508
|
+
stderr: typed?.message ?? (error instanceof Error ? error.message : String(error)),
|
|
1509
|
+
plan,
|
|
1510
|
+
error: { code: typed?.code ?? "RUN_FAILED", message: typed?.message ?? String(error) },
|
|
1511
|
+
durationms: Date.now() - started,
|
|
1512
|
+
};
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
/**
|
|
1516
|
+
* Builds one host-spawn executor — the working implementation of the
|
|
1517
|
+
* host seats: the guard rides first, the argv builds from the seat's
|
|
1518
|
+
* own builder, the cgroup slice and the hardening flags stamp the
|
|
1519
|
+
* plan, the queue orchestrates the spawn and the engine host executes
|
|
1520
|
+
* it (the honest coded absence answers when the host carries neither
|
|
1521
|
+
* the spawner nor the binary — the seat never fakes a run).
|
|
1522
|
+
*/
|
|
1523
|
+
function hostspawnseat(options: {
|
|
1524
|
+
slug: string;
|
|
1525
|
+
num: number;
|
|
1526
|
+
lane: ModalityLane;
|
|
1527
|
+
binary: string;
|
|
1528
|
+
argv: (payload: { code: string; file: string; request: ModalityRunRequest }) => readonly string[];
|
|
1529
|
+
hardening?: readonly string[];
|
|
1530
|
+
env?: Record<string, string>;
|
|
1531
|
+
priority?: ModalityPriority;
|
|
1532
|
+
probe?: string;
|
|
1533
|
+
}): ModalityExecutor {
|
|
1534
|
+
return async (request: ModalityRunRequest): Promise<ModalityRunResult> => {
|
|
1535
|
+
const started = Date.now();
|
|
1536
|
+
visitorguard(request);
|
|
1537
|
+
const profile = request.profile ?? DEFAULTPROFILE;
|
|
1538
|
+
const deadline = clampdeadline(request.deadlinems);
|
|
1539
|
+
const file = workspacepath(options.slug, "payload");
|
|
1540
|
+
const argv = options.argv({ code: request.code, file, request });
|
|
1541
|
+
const slice = cgroupslice(BASEENVELOPE, profile, options.slug);
|
|
1542
|
+
const plan: SpawnPlan = {
|
|
1543
|
+
argv,
|
|
1544
|
+
env: sandboxenv(options.env),
|
|
1545
|
+
cwd: "/sandbox",
|
|
1546
|
+
cgroups: slice,
|
|
1547
|
+
hardening: options.hardening ?? ["dropped-caps", "read-only-root"],
|
|
1548
|
+
deadlinems: deadline,
|
|
1549
|
+
};
|
|
1550
|
+
return throughqueue(options.slug, options.priority ?? "normal", async () => {
|
|
1551
|
+
try {
|
|
1552
|
+
const output = await spawnonengine(plan);
|
|
1553
|
+
const ok = output.code === 0 && !output.timedout;
|
|
1554
|
+
return {
|
|
1555
|
+
modality: options.slug,
|
|
1556
|
+
num: options.num,
|
|
1557
|
+
lane: options.lane,
|
|
1558
|
+
status: ok ? "succeeded" : "failed",
|
|
1559
|
+
stdout: output.stdout,
|
|
1560
|
+
stderr: output.stderr,
|
|
1561
|
+
plan,
|
|
1562
|
+
error: ok
|
|
1563
|
+
? undefined
|
|
1564
|
+
: {
|
|
1565
|
+
code: output.timedout ? "RUN_DEADLINE_EXCEEDED" : output.code === null ? "HOST_RUNTIME_ABSENT" : "RUN_EXITED_NONZERO",
|
|
1566
|
+
message: output.timedout
|
|
1567
|
+
? `the ${options.slug} run exceeded its ${deadline}ms deadline`
|
|
1568
|
+
: `the ${options.slug} seat rode ${options.binary} and the host answered ${output.code ?? "no runtime"} — ${options.probe ?? `the host must carry ${options.binary}`}`,
|
|
1569
|
+
},
|
|
1570
|
+
durationms: Date.now() - started,
|
|
1571
|
+
} satisfies ModalityRunResult;
|
|
1572
|
+
} catch (error) {
|
|
1573
|
+
return failureresult(options.slug, options.num, options.lane, error, started, plan);
|
|
1574
|
+
}
|
|
1575
|
+
});
|
|
1576
|
+
};
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
/**
|
|
1580
|
+
* Builds one in-process executor — the working implementation of the
|
|
1581
|
+
* seats whose whole job rides in typescript (the yaml interpreter, the
|
|
1582
|
+
* virtual file store, the wire builders, the hardware composition, the
|
|
1583
|
+
* port dock): the guard rides first, then the seat's own operation
|
|
1584
|
+
* answers the artifact.
|
|
1585
|
+
*/
|
|
1586
|
+
function inprocseat(options: {
|
|
1587
|
+
slug: string;
|
|
1588
|
+
num: number;
|
|
1589
|
+
lane: ModalityLane;
|
|
1590
|
+
operate: (request: ModalityRunRequest) => Promise<{ stdout?: string; artifact?: unknown }>;
|
|
1591
|
+
}): ModalityExecutor {
|
|
1592
|
+
return async (request: ModalityRunRequest): Promise<ModalityRunResult> => {
|
|
1593
|
+
const started = Date.now();
|
|
1594
|
+
visitorguard(request);
|
|
1595
|
+
try {
|
|
1596
|
+
const answer = await options.operate(request);
|
|
1597
|
+
return {
|
|
1598
|
+
modality: options.slug,
|
|
1599
|
+
num: options.num,
|
|
1600
|
+
lane: options.lane,
|
|
1601
|
+
status: "succeeded",
|
|
1602
|
+
stdout: answer.stdout,
|
|
1603
|
+
artifact: answer.artifact,
|
|
1604
|
+
durationms: Date.now() - started,
|
|
1605
|
+
};
|
|
1606
|
+
} catch (error) {
|
|
1607
|
+
return failureresult(options.slug, options.num, options.lane, error, started);
|
|
1608
|
+
}
|
|
1609
|
+
};
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
/**
|
|
1613
|
+
* Builds one provider executor — the working implementation of the
|
|
1614
|
+
* cloud provider seats (aws, daytona, 2b): the guard rides first, the
|
|
1615
|
+
* seat builds the real signed request, and the fetch rides only when
|
|
1616
|
+
* the engine host carries the provider configuration (the honest
|
|
1617
|
+
* absence answers otherwise — the seat never fakes a provider run).
|
|
1618
|
+
*/
|
|
1619
|
+
function providerseat(options: {
|
|
1620
|
+
slug: string;
|
|
1621
|
+
num: number;
|
|
1622
|
+
lane: ModalityLane;
|
|
1623
|
+
envkeys: readonly string[];
|
|
1624
|
+
buildrequest: (request: ModalityRunRequest) => Promise<{
|
|
1625
|
+
url: string;
|
|
1626
|
+
method: string;
|
|
1627
|
+
headers: Record<string, string>;
|
|
1628
|
+
body: string;
|
|
1629
|
+
description: string;
|
|
1630
|
+
}>;
|
|
1631
|
+
}): ModalityExecutor {
|
|
1632
|
+
return async (request: ModalityRunRequest): Promise<ModalityRunResult> => {
|
|
1633
|
+
const started = Date.now();
|
|
1634
|
+
visitorguard(request);
|
|
1635
|
+
const missing = options.envkeys.filter((key) => (process.env[key] ?? "").length === 0);
|
|
1636
|
+
const built = await options.buildrequest(request);
|
|
1637
|
+
if (missing.length > 0) {
|
|
1638
|
+
return {
|
|
1639
|
+
modality: options.slug,
|
|
1640
|
+
num: options.num,
|
|
1641
|
+
lane: options.lane,
|
|
1642
|
+
status: "failed",
|
|
1643
|
+
artifact: built,
|
|
1644
|
+
error: {
|
|
1645
|
+
code: "PROVIDER_CONFIG_ABSENT",
|
|
1646
|
+
message: `the ${options.slug} seat requires the configuration keys ${missing.join(", ")} on the engine host — the signed request is built, the provider call waits for the edge configuration`,
|
|
1647
|
+
},
|
|
1648
|
+
durationms: Date.now() - started,
|
|
1649
|
+
};
|
|
1650
|
+
}
|
|
1651
|
+
try {
|
|
1652
|
+
const response = await fetch(built.url, { method: built.method, headers: built.headers, body: built.body });
|
|
1653
|
+
const text = await response.text();
|
|
1654
|
+
return {
|
|
1655
|
+
modality: options.slug,
|
|
1656
|
+
num: options.num,
|
|
1657
|
+
lane: options.lane,
|
|
1658
|
+
status: response.ok ? "succeeded" : "failed",
|
|
1659
|
+
stdout: text.slice(0, 8192),
|
|
1660
|
+
artifact: built,
|
|
1661
|
+
error: response.ok
|
|
1662
|
+
? undefined
|
|
1663
|
+
: { code: "PROVIDER_ANSWERED_ERROR", message: `the ${options.slug} provider answered ${response.status}` },
|
|
1664
|
+
durationms: Date.now() - started,
|
|
1665
|
+
};
|
|
1666
|
+
} catch (error) {
|
|
1667
|
+
return failureresult(options.slug, options.num, options.lane, error, started);
|
|
1668
|
+
}
|
|
1669
|
+
};
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
1673
|
+
/* Section 8: the ninety — the table of record (1st to 90th). */
|
|
1674
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
1675
|
+
|
|
1676
|
+
/** All eleven architecture seats (the default answer of the runner and
|
|
1677
|
+
* in-process seats — the standard covers every target). */
|
|
1678
|
+
const ALLARCHS: readonly string[] = [
|
|
1679
|
+
"linux-x64",
|
|
1680
|
+
"darwin-arm64",
|
|
1681
|
+
"windows-x64",
|
|
1682
|
+
"linux-arm64",
|
|
1683
|
+
"darwin-x64",
|
|
1684
|
+
"windows-arm64",
|
|
1685
|
+
"linux-x64-musl",
|
|
1686
|
+
"linux-riscv64",
|
|
1687
|
+
"wasm32",
|
|
1688
|
+
"darwin-arm64-v8",
|
|
1689
|
+
"windows-x64-gnu",
|
|
1690
|
+
];
|
|
1691
|
+
|
|
1692
|
+
/** The linux container seats of the standard (the container lanes). */
|
|
1693
|
+
const LINUXARCHS: readonly string[] = ["linux-x64", "linux-arm64", "linux-x64-musl", "linux-riscv64", "darwin-arm64-v8"];
|
|
1694
|
+
|
|
1695
|
+
/** The windows seats of the standard (the exe lanes). */
|
|
1696
|
+
const WINDOWSARCHS: readonly string[] = ["windows-x64", "windows-arm64", "windows-x64-gnu"];
|
|
1697
|
+
|
|
1698
|
+
/** Every os host profile of the catalog (the default host vocabulary). */
|
|
1699
|
+
const ALLOSPROFILES: readonly string[] = ["linux", "grapheneos", "omarchy", "maruos", "vanillaos", "v", "windows", "android"];
|
|
1700
|
+
|
|
1701
|
+
/** The linux-first host vocabulary (the container seats). */
|
|
1702
|
+
const LINUXOSPROFILES: readonly string[] = ["linux", "omarchy", "vanillaos"];
|
|
1703
|
+
|
|
1704
|
+
/**
|
|
1705
|
+
* The ninety modalities of the engine — the table of record: the
|
|
1706
|
+
* numeric id (1 to 90), the name, the lane and the real executable
|
|
1707
|
+
* logic of every seat (the no-stub law: an entry without a working
|
|
1708
|
+
* implementation never enters this table).
|
|
1709
|
+
*/
|
|
1710
|
+
export const MODALITYENGINE: readonly ModalitySpec[] = [
|
|
1711
|
+
/* ===== the vm lane — the machine seats (1-15) ===== */
|
|
1712
|
+
{
|
|
1713
|
+
num: 1,
|
|
1714
|
+
name: "complete virtual machine",
|
|
1715
|
+
slug: "virtualfull",
|
|
1716
|
+
lane: "vm",
|
|
1717
|
+
isolation: "hardware",
|
|
1718
|
+
state: "live",
|
|
1719
|
+
drivers: ["virtualcpu", "virtualgpu", "virtualmemory"],
|
|
1720
|
+
hardening: ["profile-lock"],
|
|
1721
|
+
boot: "cold",
|
|
1722
|
+
bootms: BOOTLADDER.cold,
|
|
1723
|
+
gpu: true,
|
|
1724
|
+
network: true,
|
|
1725
|
+
mime: "application/vnd.devthink.sandbox+json",
|
|
1726
|
+
archs: LINUXARCHS,
|
|
1727
|
+
osprofiles: ALLOSPROFILES,
|
|
1728
|
+
resources: MACHINEENVELOPE,
|
|
1729
|
+
execute: hostspawnseat({
|
|
1730
|
+
slug: "virtualfull",
|
|
1731
|
+
num: 1,
|
|
1732
|
+
lane: "vm",
|
|
1733
|
+
binary: "qemu-system-x86_64",
|
|
1734
|
+
argv: ({ request }) => {
|
|
1735
|
+
const vcpus = ((request.inputs as { vcpus?: number } | undefined)?.vcpus ?? 4) as number;
|
|
1736
|
+
return [
|
|
1737
|
+
"qemu-system-x86_64",
|
|
1738
|
+
"-machine",
|
|
1739
|
+
"q35",
|
|
1740
|
+
"-cpu",
|
|
1741
|
+
"max",
|
|
1742
|
+
"-smp",
|
|
1743
|
+
String(solvectputopology(vcpus).firecrackerflag),
|
|
1744
|
+
"-m",
|
|
1745
|
+
"4096",
|
|
1746
|
+
"-drive",
|
|
1747
|
+
"file=/sandbox/vm/rootfs.qcow2,format=qcow2,if=virtio",
|
|
1748
|
+
"-netdev",
|
|
1749
|
+
"user,id=net0,hostfwd=tcp::2222-:22",
|
|
1750
|
+
"-device",
|
|
1751
|
+
"virtio-net-pci,netdev=net0",
|
|
1752
|
+
"-display",
|
|
1753
|
+
"none",
|
|
1754
|
+
"-serial",
|
|
1755
|
+
"stdio",
|
|
1756
|
+
];
|
|
1757
|
+
},
|
|
1758
|
+
hardening: ["profile-lock", "clean-rootfs"],
|
|
1759
|
+
probe: "the host must carry qemu-system-x86_64 (the qemu dependency belongs to saddle alone)",
|
|
1760
|
+
}),
|
|
1761
|
+
},
|
|
1762
|
+
{
|
|
1763
|
+
num: 2,
|
|
1764
|
+
name: "kvm machine",
|
|
1765
|
+
slug: "virtualkvm",
|
|
1766
|
+
lane: "vm",
|
|
1767
|
+
isolation: "hardware",
|
|
1768
|
+
state: "live",
|
|
1769
|
+
drivers: ["kvm", "virtualcpu"],
|
|
1770
|
+
hardening: ["clean-rootfs"],
|
|
1771
|
+
boot: "warm",
|
|
1772
|
+
bootms: 150,
|
|
1773
|
+
gpu: false,
|
|
1774
|
+
network: true,
|
|
1775
|
+
mime: "application/vnd.devthink.sandbox+json",
|
|
1776
|
+
archs: ["linux-x64", "linux-arm64", "linux-x64-musl", "linux-riscv64"],
|
|
1777
|
+
osprofiles: LINUXOSPROFILES,
|
|
1778
|
+
resources: MACHINEENVELOPE,
|
|
1779
|
+
execute: hostspawnseat({
|
|
1780
|
+
slug: "virtualkvm",
|
|
1781
|
+
num: 2,
|
|
1782
|
+
lane: "vm",
|
|
1783
|
+
binary: "qemu-system-x86_64",
|
|
1784
|
+
argv: () => [
|
|
1785
|
+
"qemu-system-x86_64",
|
|
1786
|
+
"-enable-kvm",
|
|
1787
|
+
"-cpu",
|
|
1788
|
+
"host",
|
|
1789
|
+
"-machine",
|
|
1790
|
+
"q35",
|
|
1791
|
+
"-smp",
|
|
1792
|
+
"4",
|
|
1793
|
+
"-m",
|
|
1794
|
+
"2048",
|
|
1795
|
+
"-drive",
|
|
1796
|
+
"file=/sandbox/vm/rootfs.qcow2,format=qcow2,if=virtio",
|
|
1797
|
+
"-netdev",
|
|
1798
|
+
"user,id=net0",
|
|
1799
|
+
"-device",
|
|
1800
|
+
"virtio-net-pci,netdev=net0",
|
|
1801
|
+
"-display",
|
|
1802
|
+
"none",
|
|
1803
|
+
"-serial",
|
|
1804
|
+
"stdio",
|
|
1805
|
+
],
|
|
1806
|
+
hardening: ["clean-rootfs", "kvm-isolated"],
|
|
1807
|
+
}),
|
|
1808
|
+
},
|
|
1809
|
+
{
|
|
1810
|
+
num: 3,
|
|
1811
|
+
name: "vgpu slice",
|
|
1812
|
+
slug: "virtualvgpu",
|
|
1813
|
+
lane: "hardware",
|
|
1814
|
+
isolation: "hardware",
|
|
1815
|
+
state: "live",
|
|
1816
|
+
drivers: ["virtualgpu", "nvml-shim", "vfio"],
|
|
1817
|
+
hardening: ["mig-profile"],
|
|
1818
|
+
boot: "warm",
|
|
1819
|
+
bootms: 600,
|
|
1820
|
+
gpu: true,
|
|
1821
|
+
network: true,
|
|
1822
|
+
mime: "application/vnd.devthink.sandbox+json",
|
|
1823
|
+
archs: ALLARCHS,
|
|
1824
|
+
osprofiles: ALLOSPROFILES,
|
|
1825
|
+
resources: INPROCENVELOPE,
|
|
1826
|
+
execute: inprocseat({
|
|
1827
|
+
slug: "virtualvgpu",
|
|
1828
|
+
num: 3,
|
|
1829
|
+
lane: "hardware",
|
|
1830
|
+
operate: async (request) => {
|
|
1831
|
+
const inputs = (request.inputs ?? {}) as { gpu?: string; profile?: string };
|
|
1832
|
+
const slice = carvevgpuslice(inputs.gpu ?? "b200", inputs.profile ?? "2g.40gb");
|
|
1833
|
+
return {
|
|
1834
|
+
stdout: `vgpu slice: ${slice.profile} of ${slice.gpu} (${slice.vramgb} gb vram, ${slice.instances} instances per device)`,
|
|
1835
|
+
artifact: slice,
|
|
1836
|
+
};
|
|
1837
|
+
},
|
|
1838
|
+
}),
|
|
1839
|
+
},
|
|
1840
|
+
{
|
|
1841
|
+
num: 4,
|
|
1842
|
+
name: "vcpu allocation",
|
|
1843
|
+
slug: "virtualvcpu",
|
|
1844
|
+
lane: "hardware",
|
|
1845
|
+
isolation: "hardware",
|
|
1846
|
+
state: "live",
|
|
1847
|
+
drivers: ["virtualcpu"],
|
|
1848
|
+
hardening: ["cpu-quota"],
|
|
1849
|
+
boot: "hot",
|
|
1850
|
+
bootms: BOOTLADDER.hot,
|
|
1851
|
+
gpu: false,
|
|
1852
|
+
network: false,
|
|
1853
|
+
mime: "application/vnd.devthink.sandbox+json",
|
|
1854
|
+
archs: ALLARCHS,
|
|
1855
|
+
osprofiles: ALLOSPROFILES,
|
|
1856
|
+
resources: INPROCENVELOPE,
|
|
1857
|
+
execute: inprocseat({
|
|
1858
|
+
slug: "virtualvcpu",
|
|
1859
|
+
num: 4,
|
|
1860
|
+
lane: "hardware",
|
|
1861
|
+
operate: async (request) => {
|
|
1862
|
+
const inputs = (request.inputs ?? {}) as { vcpus?: number };
|
|
1863
|
+
const topology = solvectputopology(inputs.vcpus ?? 4);
|
|
1864
|
+
const slice = cgroupslice(BASEENVELOPE, request.profile ?? DEFAULTPROFILE, "virtualvcpu");
|
|
1865
|
+
return {
|
|
1866
|
+
stdout: `vcpu plan: ${topology.sockets} sockets x ${topology.cores} cores x ${topology.threads} threads — qemu ${topology.qemuflag}, cgroup cpu.max ${slice.cpumax}`,
|
|
1867
|
+
artifact: { ...topology, cgroups: slice },
|
|
1868
|
+
};
|
|
1869
|
+
},
|
|
1870
|
+
}),
|
|
1871
|
+
},
|
|
1872
|
+
{
|
|
1873
|
+
num: 5,
|
|
1874
|
+
name: "cpu class presentation",
|
|
1875
|
+
slug: "virtualprocessor",
|
|
1876
|
+
lane: "hardware",
|
|
1877
|
+
isolation: "hardware",
|
|
1878
|
+
state: "live",
|
|
1879
|
+
drivers: ["virtualcpu", "processor-bank"],
|
|
1880
|
+
hardening: ["identity-stamp"],
|
|
1881
|
+
boot: "hot",
|
|
1882
|
+
bootms: BOOTLADDER.hot,
|
|
1883
|
+
gpu: false,
|
|
1884
|
+
network: false,
|
|
1885
|
+
mime: "application/vnd.devthink.sandbox+json",
|
|
1886
|
+
archs: ALLARCHS,
|
|
1887
|
+
osprofiles: ALLOSPROFILES,
|
|
1888
|
+
resources: INPROCENVELOPE,
|
|
1889
|
+
execute: inprocseat({
|
|
1890
|
+
slug: "virtualprocessor",
|
|
1891
|
+
num: 5,
|
|
1892
|
+
lane: "hardware",
|
|
1893
|
+
operate: async (request) => {
|
|
1894
|
+
const inputs = (request.inputs ?? {}) as { cpu?: string };
|
|
1895
|
+
const processor = presentprocessor(inputs.cpu ?? "epyc9965");
|
|
1896
|
+
return {
|
|
1897
|
+
stdout: `cpu class: ${processor.model} (base ${processor.baseghz} ghz, tdp ${processor.tdp} w) — the virtual identity the vm stamps, never the visitor cpu`,
|
|
1898
|
+
artifact: processor,
|
|
1899
|
+
};
|
|
1900
|
+
},
|
|
1901
|
+
}),
|
|
1902
|
+
},
|
|
1903
|
+
{
|
|
1904
|
+
num: 6,
|
|
1905
|
+
name: "gpu bank presentation",
|
|
1906
|
+
slug: "virtualgpu",
|
|
1907
|
+
lane: "hardware",
|
|
1908
|
+
isolation: "hardware",
|
|
1909
|
+
state: "live",
|
|
1910
|
+
drivers: ["virtualgpu", "virtio-gpu"],
|
|
1911
|
+
hardening: ["context-quota"],
|
|
1912
|
+
boot: "hot",
|
|
1913
|
+
bootms: BOOTLADDER.hot,
|
|
1914
|
+
gpu: true,
|
|
1915
|
+
network: false,
|
|
1916
|
+
mime: "application/vnd.devthink.gpu+json",
|
|
1917
|
+
archs: ALLARCHS,
|
|
1918
|
+
osprofiles: ALLOSPROFILES,
|
|
1919
|
+
resources: INPROCENVELOPE,
|
|
1920
|
+
execute: inprocseat({
|
|
1921
|
+
slug: "virtualgpu",
|
|
1922
|
+
num: 6,
|
|
1923
|
+
lane: "hardware",
|
|
1924
|
+
operate: async (request) => {
|
|
1925
|
+
const inputs = (request.inputs ?? {}) as { gpu?: string; count?: number };
|
|
1926
|
+
const bank = presentgpubank(inputs.gpu ?? "b200", inputs.count ?? 1);
|
|
1927
|
+
return {
|
|
1928
|
+
stdout: `gpu bank: ${bank.count} x ${bank.name} (${bank.totalvramgb} gb vram, mig ${bank.mig ? "yes" : "no"}) — the virtual device plan, never the visitor gpu`,
|
|
1929
|
+
artifact: bank,
|
|
1930
|
+
};
|
|
1931
|
+
},
|
|
1932
|
+
}),
|
|
1933
|
+
},
|
|
1934
|
+
{
|
|
1935
|
+
num: 7,
|
|
1936
|
+
name: "virtual drivers",
|
|
1937
|
+
slug: "virtualdrivers",
|
|
1938
|
+
lane: "vm",
|
|
1939
|
+
isolation: "vm",
|
|
1940
|
+
state: "live",
|
|
1941
|
+
drivers: ["virtualcpu", "virtualgpu", "virtualmemory", "nvml-shim"],
|
|
1942
|
+
hardening: ["driver-quota"],
|
|
1943
|
+
boot: "warm",
|
|
1944
|
+
bootms: 250,
|
|
1945
|
+
gpu: true,
|
|
1946
|
+
network: false,
|
|
1947
|
+
mime: "application/vnd.devthink.sandbox+json",
|
|
1948
|
+
archs: ALLARCHS,
|
|
1949
|
+
osprofiles: ALLOSPROFILES,
|
|
1950
|
+
resources: INPROCENVELOPE,
|
|
1951
|
+
execute: inprocseat({
|
|
1952
|
+
slug: "virtualdrivers",
|
|
1953
|
+
num: 7,
|
|
1954
|
+
lane: "vm",
|
|
1955
|
+
operate: async () => ({
|
|
1956
|
+
stdout:
|
|
1957
|
+
"driver manifest: virtualcpu (topology solver), virtualgpu (device bank, mig slicing), virtualmemory (page-aligned windows), nvml-shim (the identity the guests read)",
|
|
1958
|
+
artifact: {
|
|
1959
|
+
drivers: ["virtualcpu", "virtualgpu", "virtualmemory", "nvml-shim"],
|
|
1960
|
+
guestpresented: true,
|
|
1961
|
+
visitorhardware: false,
|
|
1962
|
+
},
|
|
1963
|
+
}),
|
|
1964
|
+
}),
|
|
1965
|
+
},
|
|
1966
|
+
{
|
|
1967
|
+
num: 8,
|
|
1968
|
+
name: "vm plus vnc screen",
|
|
1969
|
+
slug: "virtualscreen",
|
|
1970
|
+
lane: "vm",
|
|
1971
|
+
isolation: "vm",
|
|
1972
|
+
state: "live",
|
|
1973
|
+
drivers: ["virtualcpu", "virtualgpu", "xvfb", "looking-glass"],
|
|
1974
|
+
hardening: ["session-otp"],
|
|
1975
|
+
boot: "warm",
|
|
1976
|
+
bootms: 700,
|
|
1977
|
+
gpu: true,
|
|
1978
|
+
network: true,
|
|
1979
|
+
mime: "application/vnd.devthink.screen+json",
|
|
1980
|
+
archs: LINUXARCHS,
|
|
1981
|
+
osprofiles: ALLOSPROFILES,
|
|
1982
|
+
resources: MACHINEENVELOPE,
|
|
1983
|
+
execute: hostspawnseat({
|
|
1984
|
+
slug: "virtualscreen",
|
|
1985
|
+
num: 8,
|
|
1986
|
+
lane: "vm",
|
|
1987
|
+
binary: "Xvfb",
|
|
1988
|
+
argv: () => ["Xvfb", ":99", "-ac", "-screen", "0", "1920x1080x24", "-nolisten", "tcp", "+extension", "GLX", "+render", "-noreset"],
|
|
1989
|
+
env: { DISPLAY: ":99" },
|
|
1990
|
+
hardening: ["session-otp", "headless-only"],
|
|
1991
|
+
probe: "the host must carry Xvfb (the virtual display — never the visitor display)",
|
|
1992
|
+
}),
|
|
1993
|
+
},
|
|
1994
|
+
{
|
|
1995
|
+
num: 9,
|
|
1996
|
+
name: "hardware virtual composition",
|
|
1997
|
+
slug: "hardwarevirtual",
|
|
1998
|
+
lane: "hardware",
|
|
1999
|
+
isolation: "hardware",
|
|
2000
|
+
state: "live",
|
|
2001
|
+
drivers: ["virtualcpu", "virtualgpu", "virtualmemory", "boards"],
|
|
2002
|
+
hardening: ["board-lock"],
|
|
2003
|
+
boot: "warm",
|
|
2004
|
+
bootms: 300,
|
|
2005
|
+
gpu: true,
|
|
2006
|
+
network: true,
|
|
2007
|
+
mime: "application/vnd.devthink.sandbox+json",
|
|
2008
|
+
archs: ALLARCHS,
|
|
2009
|
+
osprofiles: ALLOSPROFILES,
|
|
2010
|
+
resources: MACHINEENVELOPE,
|
|
2011
|
+
execute: inprocseat({
|
|
2012
|
+
slug: "hardwarevirtual",
|
|
2013
|
+
num: 9,
|
|
2014
|
+
lane: "hardware",
|
|
2015
|
+
operate: async (request) => {
|
|
2016
|
+
const inputs = (request.inputs ?? {}) as { vcpus?: number; cpu?: string; gpu?: string; gpus?: number; memorygb?: number };
|
|
2017
|
+
const topology = solvectputopology(inputs.vcpus ?? 4);
|
|
2018
|
+
const processor = presentprocessor(inputs.cpu ?? "epyc9965");
|
|
2019
|
+
const bank = presentgpubank(inputs.gpu ?? "b200", inputs.gpus ?? 1);
|
|
2020
|
+
const memorygb = inputs.memorygb ?? 16;
|
|
2021
|
+
const composition = {
|
|
2022
|
+
processor,
|
|
2023
|
+
topology,
|
|
2024
|
+
gpu: bank,
|
|
2025
|
+
memorygb,
|
|
2026
|
+
machine: "q35",
|
|
2027
|
+
drives: [{ path: "/sandbox/vm/rootfs.qcow2", format: "qcow2", if: "virtio" }],
|
|
2028
|
+
law: "every device virtual — the engine presents its own hardware on the edge, the visitor machine never lends its own",
|
|
2029
|
+
};
|
|
2030
|
+
return {
|
|
2031
|
+
stdout: `hardware virtual: ${processor.model}, ${inputs.vcpus ?? 4} vcpu (${topology.qemuflag}), ${memorygb} gb ram, ${bank.count} x ${bank.name}`,
|
|
2032
|
+
artifact: composition,
|
|
2033
|
+
};
|
|
2034
|
+
},
|
|
2035
|
+
}),
|
|
2036
|
+
},
|
|
2037
|
+
{
|
|
2038
|
+
num: 10,
|
|
2039
|
+
name: "firecracker microvm",
|
|
2040
|
+
slug: "microvmfirecracker",
|
|
2041
|
+
lane: "vm",
|
|
2042
|
+
isolation: "vm",
|
|
2043
|
+
state: "host",
|
|
2044
|
+
drivers: ["firecracker", "virtualcpu"],
|
|
2045
|
+
hardening: ["clean-rootfs", "injected-vcpu"],
|
|
2046
|
+
boot: "warm",
|
|
2047
|
+
bootms: 125,
|
|
2048
|
+
gpu: false,
|
|
2049
|
+
network: true,
|
|
2050
|
+
mime: "application/vnd.devthink.microvm+json",
|
|
2051
|
+
archs: LINUXARCHS,
|
|
2052
|
+
osprofiles: LINUXOSPROFILES,
|
|
2053
|
+
resources: MACHINEENVELOPE,
|
|
2054
|
+
execute: hostspawnseat({
|
|
2055
|
+
slug: "microvmfirecracker",
|
|
2056
|
+
num: 10,
|
|
2057
|
+
lane: "vm",
|
|
2058
|
+
binary: "firecracker",
|
|
2059
|
+
argv: () => ["firecracker", "--api-sock", "/sandbox/firecracker.sock", "--config-file", "/sandbox/microvm.json"],
|
|
2060
|
+
hardening: ["clean-rootfs", "injected-vcpu", "jailer"],
|
|
2061
|
+
probe: "the host must carry firecracker (the 125ms warm-pool microvm)",
|
|
2062
|
+
}),
|
|
2063
|
+
},
|
|
2064
|
+
{
|
|
2065
|
+
num: 11,
|
|
2066
|
+
name: "gpuless render microvm",
|
|
2067
|
+
slug: "microvmgpuless",
|
|
2068
|
+
lane: "vm",
|
|
2069
|
+
isolation: "vm",
|
|
2070
|
+
state: "live",
|
|
2071
|
+
drivers: ["mesa-llvmpipe", "lavapipe", "rusticl", "xvfb"],
|
|
2072
|
+
hardening: ["clean-rootfs"],
|
|
2073
|
+
boot: "warm",
|
|
2074
|
+
bootms: 500,
|
|
2075
|
+
gpu: true,
|
|
2076
|
+
network: true,
|
|
2077
|
+
mime: "application/vnd.devthink.microvm+json",
|
|
2078
|
+
archs: LINUXARCHS,
|
|
2079
|
+
osprofiles: LINUXOSPROFILES,
|
|
2080
|
+
resources: MACHINEENVELOPE,
|
|
2081
|
+
execute: hostspawnseat({
|
|
2082
|
+
slug: "microvmgpuless",
|
|
2083
|
+
num: 11,
|
|
2084
|
+
lane: "vm",
|
|
2085
|
+
binary: "Xvfb",
|
|
2086
|
+
argv: () => ["Xvfb", ":98", "-ac", "-screen", "0", "1920x1080x24", "-nolisten", "tcp"],
|
|
2087
|
+
env: {
|
|
2088
|
+
DISPLAY: ":98",
|
|
2089
|
+
LIBGL_ALWAYS_SOFTWARE: "1",
|
|
2090
|
+
GALLIUM_DRIVER: "llvmpipe",
|
|
2091
|
+
LAVAPIPE_DRIVER: "lvp",
|
|
2092
|
+
VK_DRIVER_FILES: "/usr/share/vulkan/icd.d/lvp_icd.x86_64.json",
|
|
2093
|
+
},
|
|
2094
|
+
hardening: ["clean-rootfs", "software-render-only"],
|
|
2095
|
+
probe: "the host must carry Xvfb and the mesa software stack (llvmpipe, lavapipe, rusticl)",
|
|
2096
|
+
}),
|
|
2097
|
+
},
|
|
2098
|
+
{
|
|
2099
|
+
num: 12,
|
|
2100
|
+
name: "qemu full emulation",
|
|
2101
|
+
slug: "emulatorqemu",
|
|
2102
|
+
lane: "vm",
|
|
2103
|
+
isolation: "vm",
|
|
2104
|
+
state: "host",
|
|
2105
|
+
drivers: ["qemu"],
|
|
2106
|
+
hardening: ["tcg"],
|
|
2107
|
+
boot: "cold",
|
|
2108
|
+
bootms: 900,
|
|
2109
|
+
gpu: false,
|
|
2110
|
+
network: true,
|
|
2111
|
+
mime: "application/vnd.devthink.emulator+json",
|
|
2112
|
+
archs: LINUXARCHS,
|
|
2113
|
+
osprofiles: ALLOSPROFILES,
|
|
2114
|
+
resources: MACHINEENVELOPE,
|
|
2115
|
+
execute: hostspawnseat({
|
|
2116
|
+
slug: "emulatorqemu",
|
|
2117
|
+
num: 12,
|
|
2118
|
+
lane: "vm",
|
|
2119
|
+
binary: "qemu-system-x86_64",
|
|
2120
|
+
argv: () => [
|
|
2121
|
+
"qemu-system-x86_64",
|
|
2122
|
+
"-machine",
|
|
2123
|
+
"q35,accel=tcg,thread=multi",
|
|
2124
|
+
"-cpu",
|
|
2125
|
+
"max",
|
|
2126
|
+
"-smp",
|
|
2127
|
+
"2",
|
|
2128
|
+
"-m",
|
|
2129
|
+
"1024",
|
|
2130
|
+
"-drive",
|
|
2131
|
+
"file=/sandbox/vm/rootfs.qcow2,format=qcow2,if=virtio",
|
|
2132
|
+
"-display",
|
|
2133
|
+
"none",
|
|
2134
|
+
"-serial",
|
|
2135
|
+
"stdio",
|
|
2136
|
+
],
|
|
2137
|
+
hardening: ["tcg", "no-kvm"],
|
|
2138
|
+
probe: "the host must carry qemu-system-x86_64 (the qemu dependency belongs to saddle alone — never another app)",
|
|
2139
|
+
}),
|
|
2140
|
+
},
|
|
2141
|
+
{
|
|
2142
|
+
num: 13,
|
|
2143
|
+
name: "sev snp confidential machine",
|
|
2144
|
+
slug: "confidentialsevsnp",
|
|
2145
|
+
lane: "hardware",
|
|
2146
|
+
isolation: "hardware",
|
|
2147
|
+
state: "host",
|
|
2148
|
+
drivers: ["sev-snp", "tpm"],
|
|
2149
|
+
hardening: ["attestation"],
|
|
2150
|
+
boot: "cold",
|
|
2151
|
+
bootms: 1200,
|
|
2152
|
+
gpu: false,
|
|
2153
|
+
network: true,
|
|
2154
|
+
mime: "application/vnd.devthink.confidential+json",
|
|
2155
|
+
archs: ["linux-x64"],
|
|
2156
|
+
osprofiles: LINUXOSPROFILES,
|
|
2157
|
+
resources: MACHINEENVELOPE,
|
|
2158
|
+
execute: hostspawnseat({
|
|
2159
|
+
slug: "confidentialsevsnp",
|
|
2160
|
+
num: 13,
|
|
2161
|
+
lane: "hardware",
|
|
2162
|
+
binary: "qemu-system-x86_64",
|
|
2163
|
+
argv: () => [
|
|
2164
|
+
"qemu-system-x86_64",
|
|
2165
|
+
"-machine",
|
|
2166
|
+
"q35,confidential-guest-support=sev0",
|
|
2167
|
+
"-object",
|
|
2168
|
+
"sev-snp-guest,id=sev0,cbitpos=51,reduced-phys-bits=1",
|
|
2169
|
+
"-smp",
|
|
2170
|
+
"4",
|
|
2171
|
+
"-m",
|
|
2172
|
+
"4096",
|
|
2173
|
+
"-display",
|
|
2174
|
+
"none",
|
|
2175
|
+
"-serial",
|
|
2176
|
+
"stdio",
|
|
2177
|
+
],
|
|
2178
|
+
hardening: ["attestation", "measured-launch"],
|
|
2179
|
+
probe: "the host must carry an sev-snp capable qemu and platform",
|
|
2180
|
+
}),
|
|
2181
|
+
},
|
|
2182
|
+
{
|
|
2183
|
+
num: 14,
|
|
2184
|
+
name: "webcontainer server side",
|
|
2185
|
+
slug: "browserwebcontainer",
|
|
2186
|
+
lane: "vm",
|
|
2187
|
+
isolation: "namespace",
|
|
2188
|
+
state: "live",
|
|
2189
|
+
drivers: ["node", "wasm"],
|
|
2190
|
+
hardening: ["opt-in"],
|
|
2191
|
+
boot: "warm",
|
|
2192
|
+
bootms: 350,
|
|
2193
|
+
gpu: false,
|
|
2194
|
+
network: true,
|
|
2195
|
+
mime: "application/vnd.devthink.browser+json",
|
|
2196
|
+
archs: ALLARCHS,
|
|
2197
|
+
osprofiles: ALLOSPROFILES,
|
|
2198
|
+
resources: BASEENVELOPE,
|
|
2199
|
+
execute: hostspawnseat({
|
|
2200
|
+
slug: "browserwebcontainer",
|
|
2201
|
+
num: 14,
|
|
2202
|
+
lane: "vm",
|
|
2203
|
+
binary: "node",
|
|
2204
|
+
argv: ({ code }) => [
|
|
2205
|
+
"node",
|
|
2206
|
+
"--input-type=module",
|
|
2207
|
+
"-e",
|
|
2208
|
+
code.length > 0 ? code : 'console.log("webcontainer seat: the node+wasm machine rides the engine host")',
|
|
2209
|
+
],
|
|
2210
|
+
hardening: ["opt-in", "no-fs"],
|
|
2211
|
+
}),
|
|
2212
|
+
},
|
|
2213
|
+
{
|
|
2214
|
+
num: 15,
|
|
2215
|
+
name: "virgl render",
|
|
2216
|
+
slug: "gpuvirgl",
|
|
2217
|
+
lane: "hardware",
|
|
2218
|
+
isolation: "vm",
|
|
2219
|
+
state: "live",
|
|
2220
|
+
drivers: ["virgl", "virtio-gpu"],
|
|
2221
|
+
hardening: ["context-quota"],
|
|
2222
|
+
boot: "warm",
|
|
2223
|
+
bootms: 400,
|
|
2224
|
+
gpu: true,
|
|
2225
|
+
network: true,
|
|
2226
|
+
mime: "application/vnd.devthink.gpu+json",
|
|
2227
|
+
archs: LINUXARCHS,
|
|
2228
|
+
osprofiles: LINUXOSPROFILES,
|
|
2229
|
+
resources: MACHINEENVELOPE,
|
|
2230
|
+
execute: hostspawnseat({
|
|
2231
|
+
slug: "gpuvirgl",
|
|
2232
|
+
num: 15,
|
|
2233
|
+
lane: "hardware",
|
|
2234
|
+
binary: "qemu-system-x86_64",
|
|
2235
|
+
argv: () => [
|
|
2236
|
+
"qemu-system-x86_64",
|
|
2237
|
+
"-machine",
|
|
2238
|
+
"q35",
|
|
2239
|
+
"-device",
|
|
2240
|
+
"virtio-vga-gl,xres=1920,yres=1080",
|
|
2241
|
+
"-display",
|
|
2242
|
+
"egl-headless",
|
|
2243
|
+
"-smp",
|
|
2244
|
+
"2",
|
|
2245
|
+
"-m",
|
|
2246
|
+
"2048",
|
|
2247
|
+
],
|
|
2248
|
+
env: { RENDERDOC: "0" },
|
|
2249
|
+
hardening: ["context-quota", "virgl-only"],
|
|
2250
|
+
probe: "the host must carry qemu with virglrenderer (the virtual gpu render path)",
|
|
2251
|
+
}),
|
|
2252
|
+
},
|
|
2253
|
+
/* ===== the runner lane — the program execution seats (16-51) ===== */
|
|
2254
|
+
{
|
|
2255
|
+
num: 16,
|
|
2256
|
+
name: "universal binary runner",
|
|
2257
|
+
slug: "runneruniversal",
|
|
2258
|
+
lane: "runner",
|
|
2259
|
+
isolation: "namespace",
|
|
2260
|
+
state: "live",
|
|
2261
|
+
drivers: ["binfmt", "bash"],
|
|
2262
|
+
hardening: ["read-only-root", "dropped-caps"],
|
|
2263
|
+
boot: "warm",
|
|
2264
|
+
bootms: BOOTLADDER.warm,
|
|
2265
|
+
gpu: false,
|
|
2266
|
+
network: true,
|
|
2267
|
+
mime: "application/vnd.devthink.run+json",
|
|
2268
|
+
archs: ALLARCHS,
|
|
2269
|
+
osprofiles: ALLOSPROFILES,
|
|
2270
|
+
resources: BASEENVELOPE,
|
|
2271
|
+
execute: inprocseat({
|
|
2272
|
+
slug: "runneruniversal",
|
|
2273
|
+
num: 16,
|
|
2274
|
+
lane: "runner",
|
|
2275
|
+
operate: async (request) => {
|
|
2276
|
+
const kind = sniffpayloadmagic(request.code);
|
|
2277
|
+
const routing: Record<string, string> = {
|
|
2278
|
+
elf: "exec under the cgroup slice (chmod +x, binfmt_misc when the host carries it)",
|
|
2279
|
+
exe: "wine or the binfmt msdos seat (the exe lane of the ninety)",
|
|
2280
|
+
wasm: "the wasm seat instantiation (wasmtime/wasmedge or in-process)",
|
|
2281
|
+
script: "the shebang interpreter the payload names (bash, sh, python...)",
|
|
2282
|
+
text: "the text rides the bash -c seat",
|
|
2283
|
+
};
|
|
2284
|
+
return {
|
|
2285
|
+
stdout: `universal runner: the payload sniffed ${kind} — ${routing[kind]}`,
|
|
2286
|
+
artifact: { kind, routing: routing[kind], binfmt: "binfmt_misc registration plan stamped on the engine host" },
|
|
2287
|
+
};
|
|
2288
|
+
},
|
|
2289
|
+
}),
|
|
2290
|
+
},
|
|
2291
|
+
{
|
|
2292
|
+
num: 17,
|
|
2293
|
+
name: "node packages",
|
|
2294
|
+
slug: "runnernode",
|
|
2295
|
+
lane: "runner",
|
|
2296
|
+
isolation: "process",
|
|
2297
|
+
state: "live",
|
|
2298
|
+
drivers: ["node", "babel", "swc"],
|
|
2299
|
+
hardening: ["package-allowlist"],
|
|
2300
|
+
boot: "hot",
|
|
2301
|
+
bootms: BOOTLADDER.hot,
|
|
2302
|
+
gpu: false,
|
|
2303
|
+
network: true,
|
|
2304
|
+
mime: "application/vnd.devthink.run+json",
|
|
2305
|
+
archs: ALLARCHS,
|
|
2306
|
+
osprofiles: ALLOSPROFILES,
|
|
2307
|
+
resources: BASEENVELOPE,
|
|
2308
|
+
execute: hostspawnseat({
|
|
2309
|
+
slug: "runnernode",
|
|
2310
|
+
num: 17,
|
|
2311
|
+
lane: "runner",
|
|
2312
|
+
binary: "node",
|
|
2313
|
+
argv: ({ code }) => ["node", "--input-type=module", "-e", code],
|
|
2314
|
+
hardening: ["package-allowlist", "no-fs"],
|
|
2315
|
+
priority: "express",
|
|
2316
|
+
}),
|
|
2317
|
+
},
|
|
2318
|
+
{
|
|
2319
|
+
num: 18,
|
|
2320
|
+
name: "bun runtime",
|
|
2321
|
+
slug: "runnerbun",
|
|
2322
|
+
lane: "runner",
|
|
2323
|
+
isolation: "process",
|
|
2324
|
+
state: "live",
|
|
2325
|
+
drivers: ["bun"],
|
|
2326
|
+
hardening: ["package-allowlist"],
|
|
2327
|
+
boot: "hot",
|
|
2328
|
+
bootms: 20,
|
|
2329
|
+
gpu: false,
|
|
2330
|
+
network: true,
|
|
2331
|
+
mime: "application/vnd.devthink.run+json",
|
|
2332
|
+
archs: ALLARCHS,
|
|
2333
|
+
osprofiles: ALLOSPROFILES,
|
|
2334
|
+
resources: BASEENVELOPE,
|
|
2335
|
+
execute: hostspawnseat({
|
|
2336
|
+
slug: "runnerbun",
|
|
2337
|
+
num: 18,
|
|
2338
|
+
lane: "runner",
|
|
2339
|
+
binary: "bun",
|
|
2340
|
+
argv: ({ code }) => ["bun", "run", "-"],
|
|
2341
|
+
hardening: ["package-allowlist"],
|
|
2342
|
+
}),
|
|
2343
|
+
},
|
|
2344
|
+
{
|
|
2345
|
+
num: 19,
|
|
2346
|
+
name: "deno runtime",
|
|
2347
|
+
slug: "runnerdeno",
|
|
2348
|
+
lane: "runner",
|
|
2349
|
+
isolation: "process",
|
|
2350
|
+
state: "planned",
|
|
2351
|
+
drivers: ["deno"],
|
|
2352
|
+
hardening: ["permission-map"],
|
|
2353
|
+
boot: "hot",
|
|
2354
|
+
bootms: 20,
|
|
2355
|
+
gpu: false,
|
|
2356
|
+
network: true,
|
|
2357
|
+
mime: "application/vnd.devthink.run+json",
|
|
2358
|
+
archs: ALLARCHS,
|
|
2359
|
+
osprofiles: ALLOSPROFILES,
|
|
2360
|
+
resources: BASEENVELOPE,
|
|
2361
|
+
execute: hostspawnseat({
|
|
2362
|
+
slug: "runnerdeno",
|
|
2363
|
+
num: 19,
|
|
2364
|
+
lane: "runner",
|
|
2365
|
+
binary: "deno",
|
|
2366
|
+
argv: ({ code }) => ["deno", "run", "--allow-read=/sandbox", "-"],
|
|
2367
|
+
hardening: ["permission-map"],
|
|
2368
|
+
}),
|
|
2369
|
+
},
|
|
2370
|
+
{
|
|
2371
|
+
num: 20,
|
|
2372
|
+
name: "webpack bundle",
|
|
2373
|
+
slug: "runnerwebpack",
|
|
2374
|
+
lane: "runner",
|
|
2375
|
+
isolation: "process",
|
|
2376
|
+
state: "live",
|
|
2377
|
+
drivers: ["webpack", "swc"],
|
|
2378
|
+
hardening: ["bundle-allowlist"],
|
|
2379
|
+
boot: "hot",
|
|
2380
|
+
bootms: 18,
|
|
2381
|
+
gpu: false,
|
|
2382
|
+
network: true,
|
|
2383
|
+
mime: "application/vnd.devthink.run+json",
|
|
2384
|
+
archs: ALLARCHS,
|
|
2385
|
+
osprofiles: ALLOSPROFILES,
|
|
2386
|
+
resources: BASEENVELOPE,
|
|
2387
|
+
execute: hostspawnseat({
|
|
2388
|
+
slug: "runnerwebpack",
|
|
2389
|
+
num: 20,
|
|
2390
|
+
lane: "runner",
|
|
2391
|
+
binary: "node",
|
|
2392
|
+
argv: ({ file }) => [
|
|
2393
|
+
"node",
|
|
2394
|
+
"-e",
|
|
2395
|
+
`const webpack=require("webpack");const compiler=webpack({mode:"development",entry:{payload:"${file}"},output:{path:"/sandbox/runnerwebpack",filename:"bundle.js"}});compiler.run((error,stats)=>{if(error){console.error(error.message);process.exit(1)}console.log(stats.toString({chunks:false,colors:false}))})`,
|
|
2396
|
+
],
|
|
2397
|
+
hardening: ["bundle-allowlist"],
|
|
2398
|
+
probe: "the host module graph must carry webpack (the bundler the seat drives)",
|
|
2399
|
+
}),
|
|
2400
|
+
},
|
|
2401
|
+
{
|
|
2402
|
+
num: 21,
|
|
2403
|
+
name: "babel transpile",
|
|
2404
|
+
slug: "runnerbabel",
|
|
2405
|
+
lane: "runner",
|
|
2406
|
+
isolation: "process",
|
|
2407
|
+
state: "live",
|
|
2408
|
+
drivers: ["babel"],
|
|
2409
|
+
hardening: ["plugin-allowlist"],
|
|
2410
|
+
boot: "hot",
|
|
2411
|
+
bootms: 15,
|
|
2412
|
+
gpu: false,
|
|
2413
|
+
network: false,
|
|
2414
|
+
mime: "application/vnd.devthink.run+json",
|
|
2415
|
+
archs: ALLARCHS,
|
|
2416
|
+
osprofiles: ALLOSPROFILES,
|
|
2417
|
+
resources: INPROCENVELOPE,
|
|
2418
|
+
execute: inprocseat({
|
|
2419
|
+
slug: "runnerbabel",
|
|
2420
|
+
num: 21,
|
|
2421
|
+
lane: "runner",
|
|
2422
|
+
operate: async (request) => {
|
|
2423
|
+
const compiled = await compilethroughbabel(request.code);
|
|
2424
|
+
const run = await runinisolatedcontext(compiled, request.inputs, clampdeadline(request.deadlinems));
|
|
2425
|
+
return { stdout: run.stdout, artifact: { compiled: compiled.length } };
|
|
2426
|
+
},
|
|
2427
|
+
}),
|
|
2428
|
+
},
|
|
2429
|
+
{
|
|
2430
|
+
num: 22,
|
|
2431
|
+
name: "wasm and wasi instantiation",
|
|
2432
|
+
slug: "runnerwasm",
|
|
2433
|
+
lane: "runner",
|
|
2434
|
+
isolation: "process",
|
|
2435
|
+
state: "live",
|
|
2436
|
+
drivers: ["wasmer", "wasmedge"],
|
|
2437
|
+
hardening: ["capability-imports"],
|
|
2438
|
+
boot: "hot",
|
|
2439
|
+
bootms: 15,
|
|
2440
|
+
gpu: false,
|
|
2441
|
+
network: false,
|
|
2442
|
+
mime: "application/wasm",
|
|
2443
|
+
archs: ALLARCHS,
|
|
2444
|
+
osprofiles: ALLOSPROFILES,
|
|
2445
|
+
resources: INPROCENVELOPE,
|
|
2446
|
+
execute: inprocseat({
|
|
2447
|
+
slug: "runnerwasm",
|
|
2448
|
+
num: 22,
|
|
2449
|
+
lane: "runner",
|
|
2450
|
+
operate: async (request) => ({ stdout: await instantiatewasm(request.code) }),
|
|
2451
|
+
}),
|
|
2452
|
+
},
|
|
2453
|
+
{
|
|
2454
|
+
num: 23,
|
|
2455
|
+
name: "wasi preview runtime",
|
|
2456
|
+
slug: "runnerwasi",
|
|
2457
|
+
lane: "runner",
|
|
2458
|
+
isolation: "process",
|
|
2459
|
+
state: "planned",
|
|
2460
|
+
drivers: ["wasmtime"],
|
|
2461
|
+
hardening: ["capability-imports"],
|
|
2462
|
+
boot: "hot",
|
|
2463
|
+
bootms: 15,
|
|
2464
|
+
gpu: false,
|
|
2465
|
+
network: false,
|
|
2466
|
+
mime: "application/wasm",
|
|
2467
|
+
archs: ALLARCHS,
|
|
2468
|
+
osprofiles: ALLOSPROFILES,
|
|
2469
|
+
resources: BASEENVELOPE,
|
|
2470
|
+
execute: hostspawnseat({
|
|
2471
|
+
slug: "runnerwasi",
|
|
2472
|
+
num: 23,
|
|
2473
|
+
lane: "runner",
|
|
2474
|
+
binary: "wasmtime",
|
|
2475
|
+
argv: ({ file }) => ["wasmtime", "--dir", "/sandbox::/sandbox", file],
|
|
2476
|
+
hardening: ["capability-imports", "wasi-snapshot"],
|
|
2477
|
+
probe: "the host must carry wasmtime (the wasi preview runtime)",
|
|
2478
|
+
}),
|
|
2479
|
+
},
|
|
2480
|
+
{
|
|
2481
|
+
num: 24,
|
|
2482
|
+
name: "v8 isolates",
|
|
2483
|
+
slug: "runnerserverless",
|
|
2484
|
+
lane: "runner",
|
|
2485
|
+
isolation: "process",
|
|
2486
|
+
state: "planned",
|
|
2487
|
+
drivers: ["v8"],
|
|
2488
|
+
hardening: ["no-fs", "cpu-quota"],
|
|
2489
|
+
boot: "hot",
|
|
2490
|
+
bootms: 8,
|
|
2491
|
+
gpu: false,
|
|
2492
|
+
network: true,
|
|
2493
|
+
mime: "application/vnd.devthink.run+json",
|
|
2494
|
+
archs: ALLARCHS,
|
|
2495
|
+
osprofiles: ALLOSPROFILES,
|
|
2496
|
+
resources: INPROCENVELOPE,
|
|
2497
|
+
execute: inprocseat({
|
|
2498
|
+
slug: "runnerserverless",
|
|
2499
|
+
num: 24,
|
|
2500
|
+
lane: "runner",
|
|
2501
|
+
operate: async (request) => {
|
|
2502
|
+
const run = await runinisolatedcontext(request.code, request.inputs, clampdeadline(request.deadlinems));
|
|
2503
|
+
return { stdout: run.stdout, artifact: { isolate: "fresh node:vm context per run" } };
|
|
2504
|
+
},
|
|
2505
|
+
}),
|
|
2506
|
+
},
|
|
2507
|
+
{
|
|
2508
|
+
num: 25,
|
|
2509
|
+
name: "wasme toolchain",
|
|
2510
|
+
slug: "runnerwasme",
|
|
2511
|
+
lane: "runner",
|
|
2512
|
+
isolation: "process",
|
|
2513
|
+
state: "live",
|
|
2514
|
+
drivers: ["wasmedge", "wasme"],
|
|
2515
|
+
hardening: ["capability-imports"],
|
|
2516
|
+
boot: "hot",
|
|
2517
|
+
bootms: 15,
|
|
2518
|
+
gpu: false,
|
|
2519
|
+
network: false,
|
|
2520
|
+
mime: "application/wasm",
|
|
2521
|
+
archs: ALLARCHS,
|
|
2522
|
+
osprofiles: ALLOSPROFILES,
|
|
2523
|
+
resources: INPROCENVELOPE,
|
|
2524
|
+
execute: inprocseat({
|
|
2525
|
+
slug: "runnerwasme",
|
|
2526
|
+
num: 25,
|
|
2527
|
+
lane: "runner",
|
|
2528
|
+
operate: async (request) => {
|
|
2529
|
+
const kind = sniffpayloadmagic(request.code);
|
|
2530
|
+
if (kind === "wasm") {
|
|
2531
|
+
return { stdout: await instantiatewasm(request.code), artifact: { toolchain: "wasmedge (in-process instantiation)" } };
|
|
2532
|
+
}
|
|
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
|
+
};
|
|
2537
|
+
},
|
|
2538
|
+
}),
|
|
2539
|
+
},
|
|
2540
|
+
{
|
|
2541
|
+
num: 26,
|
|
2542
|
+
name: "exe execution",
|
|
2543
|
+
slug: "runnerexe",
|
|
2544
|
+
lane: "runner",
|
|
2545
|
+
isolation: "namespace",
|
|
2546
|
+
state: "live",
|
|
2547
|
+
drivers: ["wine", "binfmt"],
|
|
2548
|
+
hardening: ["mz-gate", "dropped-caps"],
|
|
2549
|
+
boot: "warm",
|
|
2550
|
+
bootms: 400,
|
|
2551
|
+
gpu: false,
|
|
2552
|
+
network: false,
|
|
2553
|
+
mime: "application/vnd.devthink.run+json",
|
|
2554
|
+
archs: WINDOWSARCHS,
|
|
2555
|
+
osprofiles: ["windows"],
|
|
2556
|
+
resources: BASEENVELOPE,
|
|
2557
|
+
execute: inprocseat({
|
|
2558
|
+
slug: "runnerexe",
|
|
2559
|
+
num: 26,
|
|
2560
|
+
lane: "runner",
|
|
2561
|
+
operate: async (request) => {
|
|
2562
|
+
const kind = sniffpayloadmagic(request.code);
|
|
2563
|
+
if (kind !== "exe") {
|
|
2564
|
+
throw Object.assign(new Error(`the exe seat refuses the payload: the mz signature is absent (sniffed ${kind})`), {
|
|
2565
|
+
code: "MZ_SIGNATURE_ABSENT",
|
|
2566
|
+
});
|
|
2567
|
+
}
|
|
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
|
+
};
|
|
2577
|
+
},
|
|
2578
|
+
}),
|
|
2579
|
+
},
|
|
2580
|
+
{
|
|
2581
|
+
num: 27,
|
|
2582
|
+
name: "binary execution",
|
|
2583
|
+
slug: "runnerbinary",
|
|
2584
|
+
lane: "runner",
|
|
2585
|
+
isolation: "namespace",
|
|
2586
|
+
state: "live",
|
|
2587
|
+
drivers: ["binfmt", "exec"],
|
|
2588
|
+
hardening: ["elf-gate", "dropped-caps"],
|
|
2589
|
+
boot: "warm",
|
|
2590
|
+
bootms: 200,
|
|
2591
|
+
gpu: false,
|
|
2592
|
+
network: false,
|
|
2593
|
+
mime: "application/vnd.devthink.run+json",
|
|
2594
|
+
archs: ALLARCHS,
|
|
2595
|
+
osprofiles: ALLOSPROFILES,
|
|
2596
|
+
resources: BASEENVELOPE,
|
|
2597
|
+
execute: inprocseat({
|
|
2598
|
+
slug: "runnerbinary",
|
|
2599
|
+
num: 27,
|
|
2600
|
+
lane: "runner",
|
|
2601
|
+
operate: async (request) => {
|
|
2602
|
+
const kind = sniffpayloadmagic(request.code);
|
|
2603
|
+
if (kind !== "elf") {
|
|
2604
|
+
throw Object.assign(new Error(`the binary seat refuses the payload: the elf magic is absent (sniffed ${kind})`), {
|
|
2605
|
+
code: "ELF_MAGIC_ABSENT",
|
|
2606
|
+
});
|
|
2607
|
+
}
|
|
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
|
+
};
|
|
2617
|
+
},
|
|
2618
|
+
}),
|
|
2619
|
+
},
|
|
2620
|
+
{
|
|
2621
|
+
num: 28,
|
|
2622
|
+
name: "sh execution",
|
|
2623
|
+
slug: "runnersh",
|
|
2624
|
+
lane: "runner",
|
|
2625
|
+
isolation: "namespace",
|
|
2626
|
+
state: "live",
|
|
2627
|
+
drivers: ["bash", "sh"],
|
|
2628
|
+
hardening: ["jail-env", "dropped-caps"],
|
|
2629
|
+
boot: "warm",
|
|
2630
|
+
bootms: 60,
|
|
2631
|
+
gpu: false,
|
|
2632
|
+
network: false,
|
|
2633
|
+
mime: "application/vnd.devthink.run+json",
|
|
2634
|
+
archs: LINUXARCHS,
|
|
2635
|
+
osprofiles: LINUXOSPROFILES,
|
|
2636
|
+
resources: BASEENVELOPE,
|
|
2637
|
+
execute: hostspawnseat({
|
|
2638
|
+
slug: "runnersh",
|
|
2639
|
+
num: 28,
|
|
2640
|
+
lane: "runner",
|
|
2641
|
+
binary: "bash",
|
|
2642
|
+
argv: ({ code }) => ["bash", "-c", code],
|
|
2643
|
+
hardening: ["jail-env", "dropped-caps", "no-network"],
|
|
2644
|
+
}),
|
|
2645
|
+
},
|
|
2646
|
+
{
|
|
2647
|
+
num: 29,
|
|
2648
|
+
name: "yml and yaml interpretation with babel compilation",
|
|
2649
|
+
slug: "runneryaml",
|
|
2650
|
+
lane: "runner",
|
|
2651
|
+
isolation: "process",
|
|
2652
|
+
state: "live",
|
|
2653
|
+
drivers: ["yaml", "babel", "docker"],
|
|
2654
|
+
hardening: ["manifest-allowlist", "babel-gate"],
|
|
2655
|
+
boot: "hot",
|
|
2656
|
+
bootms: 12,
|
|
2657
|
+
gpu: false,
|
|
2658
|
+
network: true,
|
|
2659
|
+
mime: "application/vnd.devthink.run+json",
|
|
2660
|
+
archs: ALLARCHS,
|
|
2661
|
+
osprofiles: ALLOSPROFILES,
|
|
2662
|
+
resources: INPROCENVELOPE,
|
|
2663
|
+
execute: inprocseat({
|
|
2664
|
+
slug: "runneryaml",
|
|
2665
|
+
num: 29,
|
|
2666
|
+
lane: "runner",
|
|
2667
|
+
operate: async (request) => {
|
|
2668
|
+
const manifest = interpretyaml(request.code);
|
|
2669
|
+
const document = manifest as { run?: string; script?: string; image?: string; background?: boolean };
|
|
2670
|
+
const program = document?.run ?? document?.script;
|
|
2671
|
+
const lines = [`yaml manifest interpreted: ${JSON.stringify(manifest).slice(0, 512)}`];
|
|
2672
|
+
let artifact: unknown = { manifest };
|
|
2673
|
+
if (typeof program === "string" && program.length > 0) {
|
|
2674
|
+
const compiled = await compilethroughbabel(program);
|
|
2675
|
+
lines.push(`babel compilation: ${compiled.length} bytes compiled for background execution`);
|
|
2676
|
+
const run = await runinisolatedcontext(compiled, request.inputs, clampdeadline(request.deadlinems));
|
|
2677
|
+
if (run.stdout.length > 0) lines.push(run.stdout);
|
|
2678
|
+
artifact = { manifest, compiledbytes: compiled.length, background: document?.background ?? true };
|
|
2679
|
+
}
|
|
2680
|
+
if (typeof document?.image === "string") {
|
|
2681
|
+
lines.push(`docker plan: docker run ${document.image} (the background execution the compose shape plans)`);
|
|
2682
|
+
artifact = { ...(artifact as object), dockervalue: ["docker", "run", "--rm", document.image] };
|
|
2683
|
+
}
|
|
2684
|
+
return { stdout: lines.join("\n"), artifact };
|
|
2685
|
+
},
|
|
2686
|
+
}),
|
|
2687
|
+
},
|
|
2688
|
+
{
|
|
2689
|
+
num: 30,
|
|
2690
|
+
name: "aws edge spawn",
|
|
2691
|
+
slug: "runneraws",
|
|
2692
|
+
lane: "runner",
|
|
2693
|
+
isolation: "hardware",
|
|
2694
|
+
state: "host",
|
|
2695
|
+
drivers: ["aws", "ec2", "fargate"],
|
|
2696
|
+
hardening: ["signed-request", "iam-scope"],
|
|
2697
|
+
boot: "warm",
|
|
2698
|
+
bootms: 800,
|
|
2699
|
+
gpu: false,
|
|
2700
|
+
network: true,
|
|
2701
|
+
mime: "application/vnd.devthink.run+json",
|
|
2702
|
+
archs: LINUXARCHS,
|
|
2703
|
+
osprofiles: LINUXOSPROFILES,
|
|
2704
|
+
resources: MACHINEENVELOPE,
|
|
2705
|
+
execute: providerseat({
|
|
2706
|
+
slug: "runneraws",
|
|
2707
|
+
num: 30,
|
|
2708
|
+
lane: "runner",
|
|
2709
|
+
envkeys: ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"],
|
|
2710
|
+
buildrequest: async () => {
|
|
2711
|
+
const region = process.env.AWS_REGION ?? "us-east-1";
|
|
2712
|
+
const body = JSON.stringify({
|
|
2713
|
+
ImageId: "ami-unknown",
|
|
2714
|
+
InstanceType: "c7i.large",
|
|
2715
|
+
MinCount: 1,
|
|
2716
|
+
MaxCount: 1,
|
|
2717
|
+
TagSpecifications: [{ ResourceType: "instance", Tags: [{ Key: "devthink.saddle", Value: "runneraws" }] }],
|
|
2718
|
+
});
|
|
2719
|
+
const signed = await sigv4sign({
|
|
2720
|
+
method: "POST",
|
|
2721
|
+
host: `ec2.${region}.amazonaws.com`,
|
|
2722
|
+
path: "/",
|
|
2723
|
+
query: "Action=RunInstances&Version=2016-11-15",
|
|
2724
|
+
body,
|
|
2725
|
+
service: "ec2",
|
|
2726
|
+
region,
|
|
2727
|
+
accesskey: process.env.AWS_ACCESS_KEY_ID ?? "",
|
|
2728
|
+
secretkey: process.env.AWS_SECRET_ACCESS_KEY ?? "",
|
|
2729
|
+
});
|
|
2730
|
+
return {
|
|
2731
|
+
url: `https://ec2.${region}.amazonaws.com/?Action=RunInstances&Version=2016-11-15`,
|
|
2732
|
+
method: "POST",
|
|
2733
|
+
headers: {
|
|
2734
|
+
"content-type": "application/x-amz-json-1.1",
|
|
2735
|
+
"x-amz-date": signed.amzdate,
|
|
2736
|
+
authorization: signed.authorization,
|
|
2737
|
+
},
|
|
2738
|
+
body,
|
|
2739
|
+
description: `the aws seat: a sigv4-signed ec2 runinstances request (${signed.canonicalrequest.length}-byte canonical request) the edge infrastructure answers`,
|
|
2740
|
+
};
|
|
2741
|
+
},
|
|
2742
|
+
}),
|
|
2743
|
+
},
|
|
2744
|
+
{
|
|
2745
|
+
num: 31,
|
|
2746
|
+
name: "2b sandbox sessions",
|
|
2747
|
+
slug: "runnertwob",
|
|
2748
|
+
lane: "runner",
|
|
2749
|
+
isolation: "vm",
|
|
2750
|
+
state: "host",
|
|
2751
|
+
drivers: ["2b", "sandbox-api"],
|
|
2752
|
+
hardening: ["api-key-gate", "session-quota"],
|
|
2753
|
+
boot: "warm",
|
|
2754
|
+
bootms: 600,
|
|
2755
|
+
gpu: false,
|
|
2756
|
+
network: true,
|
|
2757
|
+
mime: "application/vnd.devthink.run+json",
|
|
2758
|
+
archs: LINUXARCHS,
|
|
2759
|
+
osprofiles: LINUXOSPROFILES,
|
|
2760
|
+
resources: BASEENVELOPE,
|
|
2761
|
+
execute: providerseat({
|
|
2762
|
+
slug: "runnertwob",
|
|
2763
|
+
num: 31,
|
|
2764
|
+
lane: "runner",
|
|
2765
|
+
envkeys: ["TWOB_API_KEY"],
|
|
2766
|
+
buildrequest: async (request) => ({
|
|
2767
|
+
url: "https://api.2b.dev/v1/sandboxes",
|
|
2768
|
+
method: "POST",
|
|
2769
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${process.env.TWOB_API_KEY ?? ""}` },
|
|
2770
|
+
body: JSON.stringify({ language: "typescript", files: { "payload.ts": request.code } }),
|
|
2771
|
+
description: "the 2b seat: a sandbox session create on the 2b provider api (the ai sandbox lane)",
|
|
2772
|
+
}),
|
|
2773
|
+
}),
|
|
2774
|
+
},
|
|
2775
|
+
{
|
|
2776
|
+
num: 32,
|
|
2777
|
+
name: "daytona workspaces",
|
|
2778
|
+
slug: "runnerdaytona",
|
|
2779
|
+
lane: "runner",
|
|
2780
|
+
isolation: "vm",
|
|
2781
|
+
state: "host",
|
|
2782
|
+
drivers: ["daytona", "workspace-api"],
|
|
2783
|
+
hardening: ["api-key-gate", "snapshot-pin"],
|
|
2784
|
+
boot: "warm",
|
|
2785
|
+
bootms: 700,
|
|
2786
|
+
gpu: false,
|
|
2787
|
+
network: true,
|
|
2788
|
+
mime: "application/vnd.devthink.run+json",
|
|
2789
|
+
archs: LINUXARCHS,
|
|
2790
|
+
osprofiles: LINUXOSPROFILES,
|
|
2791
|
+
resources: BASEENVELOPE,
|
|
2792
|
+
execute: providerseat({
|
|
2793
|
+
slug: "runnerdaytona",
|
|
2794
|
+
num: 32,
|
|
2795
|
+
lane: "runner",
|
|
2796
|
+
envkeys: ["DAYTONA_API_KEY"],
|
|
2797
|
+
buildrequest: async (request) => ({
|
|
2798
|
+
url: `${process.env.DAYTONA_API_URL ?? "https://app.daytona.io/api"}/workspace`,
|
|
2799
|
+
method: "POST",
|
|
2800
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${process.env.DAYTONA_API_KEY ?? ""}` },
|
|
2801
|
+
body: JSON.stringify({
|
|
2802
|
+
name: `saddle-${Date.now().toString(36)}`,
|
|
2803
|
+
language: "typescript",
|
|
2804
|
+
files: { "payload.ts": request.code },
|
|
2805
|
+
}),
|
|
2806
|
+
description: "the daytona seat: a workspace create on the daytona provider api (the dev sandbox lane)",
|
|
2807
|
+
}),
|
|
2808
|
+
}),
|
|
2809
|
+
},
|
|
2810
|
+
{
|
|
2811
|
+
num: 33,
|
|
2812
|
+
name: "cpython runtime",
|
|
2813
|
+
slug: "languagepython",
|
|
2814
|
+
lane: "runner",
|
|
2815
|
+
isolation: "process",
|
|
2816
|
+
state: "live",
|
|
2817
|
+
drivers: ["python3"],
|
|
2818
|
+
hardening: ["package-allowlist"],
|
|
2819
|
+
boot: "hot",
|
|
2820
|
+
bootms: 12,
|
|
2821
|
+
gpu: false,
|
|
2822
|
+
network: true,
|
|
2823
|
+
mime: "application/vnd.devthink.run+json",
|
|
2824
|
+
archs: ALLARCHS,
|
|
2825
|
+
osprofiles: ALLOSPROFILES,
|
|
2826
|
+
resources: BASEENVELOPE,
|
|
2827
|
+
execute: hostspawnseat({
|
|
2828
|
+
slug: "languagepython",
|
|
2829
|
+
num: 33,
|
|
2830
|
+
lane: "runner",
|
|
2831
|
+
binary: "python3",
|
|
2832
|
+
argv: ({ code }) => ["python3", "-c", code],
|
|
2833
|
+
hardening: ["package-allowlist"],
|
|
2834
|
+
}),
|
|
2835
|
+
},
|
|
2836
|
+
{
|
|
2837
|
+
num: 34,
|
|
2838
|
+
name: "golang toolchain",
|
|
2839
|
+
slug: "languagego",
|
|
2840
|
+
lane: "runner",
|
|
2841
|
+
isolation: "process",
|
|
2842
|
+
state: "live",
|
|
2843
|
+
drivers: ["go"],
|
|
2844
|
+
hardening: ["gopath-jail"],
|
|
2845
|
+
boot: "hot",
|
|
2846
|
+
bootms: 15,
|
|
2847
|
+
gpu: false,
|
|
2848
|
+
network: true,
|
|
2849
|
+
mime: "application/vnd.devthink.run+json",
|
|
2850
|
+
archs: ALLARCHS,
|
|
2851
|
+
osprofiles: ALLOSPROFILES,
|
|
2852
|
+
resources: BASEENVELOPE,
|
|
2853
|
+
execute: hostspawnseat({
|
|
2854
|
+
slug: "languagego",
|
|
2855
|
+
num: 34,
|
|
2856
|
+
lane: "runner",
|
|
2857
|
+
binary: "go",
|
|
2858
|
+
argv: ({ code }) => ["go", "run", "/dev/stdin"],
|
|
2859
|
+
env: { GOPATH: "/sandbox/go", GOCACHE: "/sandbox/go/cache", GOFLAGS: "-mod=mod" },
|
|
2860
|
+
hardening: ["gopath-jail"],
|
|
2861
|
+
}),
|
|
2862
|
+
},
|
|
2863
|
+
{
|
|
2864
|
+
num: 35,
|
|
2865
|
+
name: "rustc toolchain",
|
|
2866
|
+
slug: "languagerust",
|
|
2867
|
+
lane: "runner",
|
|
2868
|
+
isolation: "process",
|
|
2869
|
+
state: "planned",
|
|
2870
|
+
drivers: ["rustc"],
|
|
2871
|
+
hardening: ["crate-allowlist"],
|
|
2872
|
+
boot: "hot",
|
|
2873
|
+
bootms: 18,
|
|
2874
|
+
gpu: false,
|
|
2875
|
+
network: true,
|
|
2876
|
+
mime: "application/vnd.devthink.run+json",
|
|
2877
|
+
archs: ALLARCHS,
|
|
2878
|
+
osprofiles: ALLOSPROFILES,
|
|
2879
|
+
resources: BASEENVELOPE,
|
|
2880
|
+
execute: hostspawnseat({
|
|
2881
|
+
slug: "languagerust",
|
|
2882
|
+
num: 35,
|
|
2883
|
+
lane: "runner",
|
|
2884
|
+
binary: "rustc",
|
|
2885
|
+
argv: ({ file }) => ["rustc", "-O", "--edition", "2024", "-o", "/sandbox/languagerust/payload", file],
|
|
2886
|
+
hardening: ["crate-allowlist"],
|
|
2887
|
+
}),
|
|
2888
|
+
},
|
|
2889
|
+
{
|
|
2890
|
+
num: 36,
|
|
2891
|
+
name: "cargo builds",
|
|
2892
|
+
slug: "languagecargo",
|
|
2893
|
+
lane: "runner",
|
|
2894
|
+
isolation: "process",
|
|
2895
|
+
state: "planned",
|
|
2896
|
+
drivers: ["cargo"],
|
|
2897
|
+
hardening: ["crate-allowlist"],
|
|
2898
|
+
boot: "hot",
|
|
2899
|
+
bootms: 20,
|
|
2900
|
+
gpu: false,
|
|
2901
|
+
network: true,
|
|
2902
|
+
mime: "application/vnd.devthink.run+json",
|
|
2903
|
+
archs: ALLARCHS,
|
|
2904
|
+
osprofiles: ALLOSPROFILES,
|
|
2905
|
+
resources: BASEENVELOPE,
|
|
2906
|
+
execute: hostspawnseat({
|
|
2907
|
+
slug: "languagecargo",
|
|
2908
|
+
num: 36,
|
|
2909
|
+
lane: "runner",
|
|
2910
|
+
binary: "cargo",
|
|
2911
|
+
argv: () => ["cargo", "run", "--offline"],
|
|
2912
|
+
env: { CARGO_HOME: "/sandbox/cargo", CARGO_NET_OFFLINE: "true" },
|
|
2913
|
+
hardening: ["crate-allowlist", "offline-only"],
|
|
2914
|
+
}),
|
|
2915
|
+
},
|
|
2916
|
+
{
|
|
2917
|
+
num: 37,
|
|
2918
|
+
name: "v compiler",
|
|
2919
|
+
slug: "languagev",
|
|
2920
|
+
lane: "runner",
|
|
2921
|
+
isolation: "process",
|
|
2922
|
+
state: "planned",
|
|
2923
|
+
drivers: ["v"],
|
|
2924
|
+
hardening: ["package-allowlist"],
|
|
2925
|
+
boot: "hot",
|
|
2926
|
+
bootms: 15,
|
|
2927
|
+
gpu: false,
|
|
2928
|
+
network: true,
|
|
2929
|
+
mime: "application/vnd.devthink.run+json",
|
|
2930
|
+
archs: ALLARCHS,
|
|
2931
|
+
osprofiles: ["v", "linux", "windows"],
|
|
2932
|
+
resources: BASEENVELOPE,
|
|
2933
|
+
execute: hostspawnseat({
|
|
2934
|
+
slug: "languagev",
|
|
2935
|
+
num: 37,
|
|
2936
|
+
lane: "runner",
|
|
2937
|
+
binary: "v",
|
|
2938
|
+
argv: ({ file }) => ["v", "run", file],
|
|
2939
|
+
env: { VFLAGS: "-prod" },
|
|
2940
|
+
hardening: ["package-allowlist"],
|
|
2941
|
+
probe: "the host must carry the v compiler (the 0.5.2 seat the catalog pins)",
|
|
2942
|
+
}),
|
|
2943
|
+
},
|
|
2944
|
+
{
|
|
2945
|
+
num: 38,
|
|
2946
|
+
name: "jdk runtime",
|
|
2947
|
+
slug: "languagejava",
|
|
2948
|
+
lane: "runner",
|
|
2949
|
+
isolation: "process",
|
|
2950
|
+
state: "live",
|
|
2951
|
+
drivers: ["java", "javac"],
|
|
2952
|
+
hardening: ["security-manager"],
|
|
2953
|
+
boot: "warm",
|
|
2954
|
+
bootms: 200,
|
|
2955
|
+
gpu: false,
|
|
2956
|
+
network: true,
|
|
2957
|
+
mime: "application/vnd.devthink.run+json",
|
|
2958
|
+
archs: ALLARCHS,
|
|
2959
|
+
osprofiles: ALLOSPROFILES,
|
|
2960
|
+
resources: BASEENVELOPE,
|
|
2961
|
+
execute: hostspawnseat({
|
|
2962
|
+
slug: "languagejava",
|
|
2963
|
+
num: 38,
|
|
2964
|
+
lane: "runner",
|
|
2965
|
+
binary: "java",
|
|
2966
|
+
argv: ({ file }) => ["java", file],
|
|
2967
|
+
env: { _JAVA_OPTIONS: "-Xmx512m -XX:+UseSerialGC" },
|
|
2968
|
+
hardening: ["security-manager"],
|
|
2969
|
+
}),
|
|
2970
|
+
},
|
|
2971
|
+
{
|
|
2972
|
+
num: 39,
|
|
2973
|
+
name: "dotnet runtime",
|
|
2974
|
+
slug: "languagedotnet",
|
|
2975
|
+
lane: "runner",
|
|
2976
|
+
isolation: "process",
|
|
2977
|
+
state: "planned",
|
|
2978
|
+
drivers: ["dotnet"],
|
|
2979
|
+
hardening: ["assembly-allowlist"],
|
|
2980
|
+
boot: "warm",
|
|
2981
|
+
bootms: 150,
|
|
2982
|
+
gpu: false,
|
|
2983
|
+
network: true,
|
|
2984
|
+
mime: "application/vnd.devthink.run+json",
|
|
2985
|
+
archs: ALLARCHS,
|
|
2986
|
+
osprofiles: ALLOSPROFILES,
|
|
2987
|
+
resources: BASEENVELOPE,
|
|
2988
|
+
execute: hostspawnseat({
|
|
2989
|
+
slug: "languagedotnet",
|
|
2990
|
+
num: 39,
|
|
2991
|
+
lane: "runner",
|
|
2992
|
+
binary: "dotnet",
|
|
2993
|
+
argv: ({ file }) => ["dotnet", "run", file],
|
|
2994
|
+
hardening: ["assembly-allowlist", "mz-gate"],
|
|
2995
|
+
}),
|
|
2996
|
+
},
|
|
2997
|
+
{
|
|
2998
|
+
num: 40,
|
|
2999
|
+
name: "ruby interpreter",
|
|
3000
|
+
slug: "languageruby",
|
|
3001
|
+
lane: "runner",
|
|
3002
|
+
isolation: "process",
|
|
3003
|
+
state: "live",
|
|
3004
|
+
drivers: ["ruby"],
|
|
3005
|
+
hardening: ["gem-allowlist"],
|
|
3006
|
+
boot: "hot",
|
|
3007
|
+
bootms: 12,
|
|
3008
|
+
gpu: false,
|
|
3009
|
+
network: true,
|
|
3010
|
+
mime: "application/vnd.devthink.run+json",
|
|
3011
|
+
archs: ALLARCHS,
|
|
3012
|
+
osprofiles: ALLOSPROFILES,
|
|
3013
|
+
resources: BASEENVELOPE,
|
|
3014
|
+
execute: hostspawnseat({
|
|
3015
|
+
slug: "languageruby",
|
|
3016
|
+
num: 40,
|
|
3017
|
+
lane: "runner",
|
|
3018
|
+
binary: "ruby",
|
|
3019
|
+
argv: ({ code }) => ["ruby", "-e", code],
|
|
3020
|
+
hardening: ["gem-allowlist"],
|
|
3021
|
+
}),
|
|
3022
|
+
},
|
|
3023
|
+
{
|
|
3024
|
+
num: 41,
|
|
3025
|
+
name: "lua interpreter",
|
|
3026
|
+
slug: "languagelua",
|
|
3027
|
+
lane: "runner",
|
|
3028
|
+
isolation: "process",
|
|
3029
|
+
state: "planned",
|
|
3030
|
+
drivers: ["lua"],
|
|
3031
|
+
hardening: ["sandbox-env"],
|
|
3032
|
+
boot: "hot",
|
|
3033
|
+
bootms: 8,
|
|
3034
|
+
gpu: false,
|
|
3035
|
+
network: false,
|
|
3036
|
+
mime: "application/vnd.devthink.run+json",
|
|
3037
|
+
archs: ALLARCHS,
|
|
3038
|
+
osprofiles: ALLOSPROFILES,
|
|
3039
|
+
resources: INPROCENVELOPE,
|
|
3040
|
+
execute: hostspawnseat({
|
|
3041
|
+
slug: "languagelua",
|
|
3042
|
+
num: 41,
|
|
3043
|
+
lane: "runner",
|
|
3044
|
+
binary: "lua",
|
|
3045
|
+
argv: ({ code }) => ["lua", "-e", code],
|
|
3046
|
+
hardening: ["sandbox-env"],
|
|
3047
|
+
}),
|
|
3048
|
+
},
|
|
3049
|
+
{
|
|
3050
|
+
num: 42,
|
|
3051
|
+
name: "zig toolchain",
|
|
3052
|
+
slug: "languagezig",
|
|
3053
|
+
lane: "runner",
|
|
3054
|
+
isolation: "process",
|
|
3055
|
+
state: "planned",
|
|
3056
|
+
drivers: ["zig"],
|
|
3057
|
+
hardening: ["package-allowlist"],
|
|
3058
|
+
boot: "hot",
|
|
3059
|
+
bootms: 15,
|
|
3060
|
+
gpu: false,
|
|
3061
|
+
network: true,
|
|
3062
|
+
mime: "application/vnd.devthink.run+json",
|
|
3063
|
+
archs: ALLARCHS,
|
|
3064
|
+
osprofiles: ALLOSPROFILES,
|
|
3065
|
+
resources: BASEENVELOPE,
|
|
3066
|
+
execute: hostspawnseat({
|
|
3067
|
+
slug: "languagezig",
|
|
3068
|
+
num: 42,
|
|
3069
|
+
lane: "runner",
|
|
3070
|
+
binary: "zig",
|
|
3071
|
+
argv: ({ file }) => ["zig", "run", file],
|
|
3072
|
+
hardening: ["package-allowlist"],
|
|
3073
|
+
}),
|
|
3074
|
+
},
|
|
3075
|
+
{
|
|
3076
|
+
num: 43,
|
|
3077
|
+
name: "nim compiler",
|
|
3078
|
+
slug: "languagenim",
|
|
3079
|
+
lane: "runner",
|
|
3080
|
+
isolation: "process",
|
|
3081
|
+
state: "planned",
|
|
3082
|
+
drivers: ["nim"],
|
|
3083
|
+
hardening: ["package-allowlist"],
|
|
3084
|
+
boot: "hot",
|
|
3085
|
+
bootms: 15,
|
|
3086
|
+
gpu: false,
|
|
3087
|
+
network: true,
|
|
3088
|
+
mime: "application/vnd.devthink.run+json",
|
|
3089
|
+
archs: ALLARCHS,
|
|
3090
|
+
osprofiles: ALLOSPROFILES,
|
|
3091
|
+
resources: BASEENVELOPE,
|
|
3092
|
+
execute: hostspawnseat({
|
|
3093
|
+
slug: "languagenim",
|
|
3094
|
+
num: 43,
|
|
3095
|
+
lane: "runner",
|
|
3096
|
+
binary: "nim",
|
|
3097
|
+
argv: ({ file }) => ["nim", "r", file],
|
|
3098
|
+
hardening: ["package-allowlist"],
|
|
3099
|
+
}),
|
|
3100
|
+
},
|
|
3101
|
+
{
|
|
3102
|
+
num: 44,
|
|
3103
|
+
name: "php interpreter",
|
|
3104
|
+
slug: "languagephp",
|
|
3105
|
+
lane: "runner",
|
|
3106
|
+
isolation: "process",
|
|
3107
|
+
state: "planned",
|
|
3108
|
+
drivers: ["php"],
|
|
3109
|
+
hardening: ["ini-lockdown"],
|
|
3110
|
+
boot: "hot",
|
|
3111
|
+
bootms: 12,
|
|
3112
|
+
gpu: false,
|
|
3113
|
+
network: true,
|
|
3114
|
+
mime: "application/vnd.devthink.run+json",
|
|
3115
|
+
archs: ALLARCHS,
|
|
3116
|
+
osprofiles: ALLOSPROFILES,
|
|
3117
|
+
resources: BASEENVELOPE,
|
|
3118
|
+
execute: hostspawnseat({
|
|
3119
|
+
slug: "languagephp",
|
|
3120
|
+
num: 44,
|
|
3121
|
+
lane: "runner",
|
|
3122
|
+
binary: "php",
|
|
3123
|
+
argv: ({ code }) => ["php", "-r", code],
|
|
3124
|
+
env: { PHP_INI_SCAN_DIR: "/sandbox/php/ini" },
|
|
3125
|
+
hardening: ["ini-lockdown"],
|
|
3126
|
+
}),
|
|
3127
|
+
},
|
|
3128
|
+
{
|
|
3129
|
+
num: 45,
|
|
3130
|
+
name: "jvm hotspot runtime",
|
|
3131
|
+
slug: "runtimejvm",
|
|
3132
|
+
lane: "runner",
|
|
3133
|
+
isolation: "process",
|
|
3134
|
+
state: "live",
|
|
3135
|
+
drivers: ["jvm", "jlink"],
|
|
3136
|
+
hardening: ["security-manager"],
|
|
3137
|
+
boot: "warm",
|
|
3138
|
+
bootms: 250,
|
|
3139
|
+
gpu: false,
|
|
3140
|
+
network: true,
|
|
3141
|
+
mime: "application/vnd.devthink.run+json",
|
|
3142
|
+
archs: ALLARCHS,
|
|
3143
|
+
osprofiles: ALLOSPROFILES,
|
|
3144
|
+
resources: BASEENVELOPE,
|
|
3145
|
+
execute: hostspawnseat({
|
|
3146
|
+
slug: "runtimejvm",
|
|
3147
|
+
num: 45,
|
|
3148
|
+
lane: "runner",
|
|
3149
|
+
binary: "java",
|
|
3150
|
+
argv: ({ file }) => ["java", "-Xms64m", "-Xmx1024m", "-XX:+UseSerialGC", "-jar", file],
|
|
3151
|
+
hardening: ["security-manager", "jvm-flags"],
|
|
3152
|
+
}),
|
|
3153
|
+
},
|
|
3154
|
+
{
|
|
3155
|
+
num: 46,
|
|
3156
|
+
name: "clr runtime",
|
|
3157
|
+
slug: "runtimeclr",
|
|
3158
|
+
lane: "runner",
|
|
3159
|
+
isolation: "process",
|
|
3160
|
+
state: "live",
|
|
3161
|
+
drivers: ["clr"],
|
|
3162
|
+
hardening: ["assembly-allowlist"],
|
|
3163
|
+
boot: "warm",
|
|
3164
|
+
bootms: 200,
|
|
3165
|
+
gpu: false,
|
|
3166
|
+
network: true,
|
|
3167
|
+
mime: "application/vnd.devthink.run+json",
|
|
3168
|
+
archs: ALLARCHS,
|
|
3169
|
+
osprofiles: ALLOSPROFILES,
|
|
3170
|
+
resources: BASEENVELOPE,
|
|
3171
|
+
execute: hostspawnseat({
|
|
3172
|
+
slug: "runtimeclr",
|
|
3173
|
+
num: 46,
|
|
3174
|
+
lane: "runner",
|
|
3175
|
+
binary: "dotnet",
|
|
3176
|
+
argv: ({ file }) => ["dotnet", "exec", file],
|
|
3177
|
+
hardening: ["assembly-allowlist", "mz-gate"],
|
|
3178
|
+
probe: "the host must carry the dotnet runtime and the payload must carry the mz assembly signature",
|
|
3179
|
+
}),
|
|
3180
|
+
},
|
|
3181
|
+
{
|
|
3182
|
+
num: 47,
|
|
3183
|
+
name: "workerd runtime",
|
|
3184
|
+
slug: "runtimeworkerd",
|
|
3185
|
+
lane: "runner",
|
|
3186
|
+
isolation: "process",
|
|
3187
|
+
state: "live",
|
|
3188
|
+
drivers: ["workerd"],
|
|
3189
|
+
hardening: ["no-fs", "cpu-quota"],
|
|
3190
|
+
boot: "hot",
|
|
3191
|
+
bootms: 10,
|
|
3192
|
+
gpu: false,
|
|
3193
|
+
network: true,
|
|
3194
|
+
mime: "application/vnd.devthink.run+json",
|
|
3195
|
+
archs: ALLARCHS,
|
|
3196
|
+
osprofiles: ALLOSPROFILES,
|
|
3197
|
+
resources: INPROCENVELOPE,
|
|
3198
|
+
execute: hostspawnseat({
|
|
3199
|
+
slug: "runtimeworkerd",
|
|
3200
|
+
num: 47,
|
|
3201
|
+
lane: "runner",
|
|
3202
|
+
binary: "workerd",
|
|
3203
|
+
argv: () => ["workerd", "serve", "/sandbox/runtimeworkerd/config.capnp"],
|
|
3204
|
+
hardening: ["no-fs", "cpu-quota"],
|
|
3205
|
+
probe: "the host must carry workerd (the cloudflare runtime lane)",
|
|
3206
|
+
}),
|
|
3207
|
+
},
|
|
3208
|
+
{
|
|
3209
|
+
num: 48,
|
|
3210
|
+
name: "make builds",
|
|
3211
|
+
slug: "buildmake",
|
|
3212
|
+
lane: "runner",
|
|
3213
|
+
isolation: "process",
|
|
3214
|
+
state: "planned",
|
|
3215
|
+
drivers: ["make"],
|
|
3216
|
+
hardening: ["recipe-allowlist"],
|
|
3217
|
+
boot: "hot",
|
|
3218
|
+
bootms: 10,
|
|
3219
|
+
gpu: false,
|
|
3220
|
+
network: false,
|
|
3221
|
+
mime: "application/vnd.devthink.build+json",
|
|
3222
|
+
archs: ALLARCHS,
|
|
3223
|
+
osprofiles: ALLOSPROFILES,
|
|
3224
|
+
resources: BASEENVELOPE,
|
|
3225
|
+
execute: hostspawnseat({
|
|
3226
|
+
slug: "buildmake",
|
|
3227
|
+
num: 48,
|
|
3228
|
+
lane: "runner",
|
|
3229
|
+
binary: "make",
|
|
3230
|
+
argv: ({ file }) => ["make", "-f", file],
|
|
3231
|
+
hardening: ["recipe-allowlist"],
|
|
3232
|
+
}),
|
|
3233
|
+
},
|
|
3234
|
+
{
|
|
3235
|
+
num: 49,
|
|
3236
|
+
name: "bazel builds",
|
|
3237
|
+
slug: "buildbazel",
|
|
3238
|
+
lane: "runner",
|
|
3239
|
+
isolation: "process",
|
|
3240
|
+
state: "live",
|
|
3241
|
+
drivers: ["bazel"],
|
|
3242
|
+
hardening: ["remote-cache-pin"],
|
|
3243
|
+
boot: "warm",
|
|
3244
|
+
bootms: 500,
|
|
3245
|
+
gpu: false,
|
|
3246
|
+
network: true,
|
|
3247
|
+
mime: "application/vnd.devthink.build+json",
|
|
3248
|
+
archs: ALLARCHS,
|
|
3249
|
+
osprofiles: ALLOSPROFILES,
|
|
3250
|
+
resources: BASEENVELOPE,
|
|
3251
|
+
execute: hostspawnseat({
|
|
3252
|
+
slug: "buildbazel",
|
|
3253
|
+
num: 49,
|
|
3254
|
+
lane: "runner",
|
|
3255
|
+
binary: "bazel",
|
|
3256
|
+
argv: () => ["bazel", "build", "//..."],
|
|
3257
|
+
env: { BUILD_USER: "saddle", HOME: "/sandbox" },
|
|
3258
|
+
hardening: ["remote-cache-pin"],
|
|
3259
|
+
}),
|
|
3260
|
+
},
|
|
3261
|
+
{
|
|
3262
|
+
num: 50,
|
|
3263
|
+
name: "ninja builds",
|
|
3264
|
+
slug: "buildninja",
|
|
3265
|
+
lane: "runner",
|
|
3266
|
+
isolation: "process",
|
|
3267
|
+
state: "planned",
|
|
3268
|
+
drivers: ["ninja"],
|
|
3269
|
+
hardening: ["recipe-allowlist"],
|
|
3270
|
+
boot: "hot",
|
|
3271
|
+
bootms: 15,
|
|
3272
|
+
gpu: false,
|
|
3273
|
+
network: false,
|
|
3274
|
+
mime: "application/vnd.devthink.build+json",
|
|
3275
|
+
archs: ALLARCHS,
|
|
3276
|
+
osprofiles: ALLOSPROFILES,
|
|
3277
|
+
resources: BASEENVELOPE,
|
|
3278
|
+
execute: hostspawnseat({
|
|
3279
|
+
slug: "buildninja",
|
|
3280
|
+
num: 50,
|
|
3281
|
+
lane: "runner",
|
|
3282
|
+
binary: "ninja",
|
|
3283
|
+
argv: ({ file }) => ["ninja", "-f", file],
|
|
3284
|
+
hardening: ["recipe-allowlist"],
|
|
3285
|
+
}),
|
|
3286
|
+
},
|
|
3287
|
+
{
|
|
3288
|
+
num: 51,
|
|
3289
|
+
name: "actions runner",
|
|
3290
|
+
slug: "ciactions",
|
|
3291
|
+
lane: "runner",
|
|
3292
|
+
isolation: "process",
|
|
3293
|
+
state: "live",
|
|
3294
|
+
drivers: ["actions-runner"],
|
|
3295
|
+
hardening: ["workflow-allowlist"],
|
|
3296
|
+
boot: "warm",
|
|
3297
|
+
bootms: 400,
|
|
3298
|
+
gpu: false,
|
|
3299
|
+
network: true,
|
|
3300
|
+
mime: "application/vnd.devthink.ci+json",
|
|
3301
|
+
archs: LINUXARCHS,
|
|
3302
|
+
osprofiles: LINUXOSPROFILES,
|
|
3303
|
+
resources: BASEENVELOPE,
|
|
3304
|
+
execute: hostspawnseat({
|
|
3305
|
+
slug: "ciactions",
|
|
3306
|
+
num: 51,
|
|
3307
|
+
lane: "runner",
|
|
3308
|
+
binary: "./run.sh",
|
|
3309
|
+
argv: () => ["./run.sh", "--url", "https://github.com/wenathlan/devthink", "--token", "edge-registration"],
|
|
3310
|
+
hardening: ["workflow-allowlist", "registration-gate"],
|
|
3311
|
+
probe: "the host must carry the self-hosted actions runner (the runner rides the same self-hosted law)",
|
|
3312
|
+
}),
|
|
3313
|
+
},
|
|
3314
|
+
/* ===== the sandbox lane — the isolation and mount seats (52-90) ===== */
|
|
3315
|
+
{
|
|
3316
|
+
num: 52,
|
|
3317
|
+
name: "docker hardened",
|
|
3318
|
+
slug: "containerdocker",
|
|
3319
|
+
lane: "sandbox",
|
|
3320
|
+
isolation: "namespace",
|
|
3321
|
+
state: "live",
|
|
3322
|
+
drivers: ["docker"],
|
|
3323
|
+
hardening: ["no-network", "dropped-caps", "read-only-root", "pid-limit"],
|
|
3324
|
+
boot: "warm",
|
|
3325
|
+
bootms: 400,
|
|
3326
|
+
gpu: false,
|
|
3327
|
+
network: false,
|
|
3328
|
+
mime: "application/vnd.devthink.container+json",
|
|
3329
|
+
archs: LINUXARCHS,
|
|
3330
|
+
osprofiles: LINUXOSPROFILES,
|
|
3331
|
+
resources: BASEENVELOPE,
|
|
3332
|
+
execute: hostspawnseat({
|
|
3333
|
+
slug: "containerdocker",
|
|
3334
|
+
num: 52,
|
|
3335
|
+
lane: "sandbox",
|
|
3336
|
+
binary: "docker",
|
|
3337
|
+
argv: ({ request, code }) => [
|
|
3338
|
+
"docker",
|
|
3339
|
+
"run",
|
|
3340
|
+
"--rm",
|
|
3341
|
+
...dockerflags(cgroupslice(BASEENVELOPE, request.profile ?? DEFAULTPROFILE, "containerdocker")),
|
|
3342
|
+
code.startsWith("ghcr.io/") || code.includes(":") ? code : "docker.io/library/bash:5",
|
|
3343
|
+
],
|
|
3344
|
+
hardening: ["no-network", "dropped-caps", "read-only-root", "pid-limit"],
|
|
3345
|
+
probe: "the host must carry the docker daemon (the container dock)",
|
|
3346
|
+
}),
|
|
3347
|
+
},
|
|
3348
|
+
{
|
|
3349
|
+
num: 53,
|
|
3350
|
+
name: "docker max profile",
|
|
3351
|
+
slug: "containermax",
|
|
3352
|
+
lane: "sandbox",
|
|
3353
|
+
isolation: "namespace",
|
|
3354
|
+
state: "live",
|
|
3355
|
+
drivers: ["docker"],
|
|
3356
|
+
hardening: ["no-network", "dropped-caps", "read-only-root"],
|
|
3357
|
+
boot: "warm",
|
|
3358
|
+
bootms: 600,
|
|
3359
|
+
gpu: false,
|
|
3360
|
+
network: false,
|
|
3361
|
+
mime: "application/vnd.devthink.container+json",
|
|
3362
|
+
archs: LINUXARCHS,
|
|
3363
|
+
osprofiles: LINUXOSPROFILES,
|
|
3364
|
+
resources: MACHINEENVELOPE,
|
|
3365
|
+
execute: hostspawnseat({
|
|
3366
|
+
slug: "containermax",
|
|
3367
|
+
num: 53,
|
|
3368
|
+
lane: "sandbox",
|
|
3369
|
+
binary: "docker",
|
|
3370
|
+
argv: () => [
|
|
3371
|
+
"docker",
|
|
3372
|
+
"run",
|
|
3373
|
+
"--rm",
|
|
3374
|
+
...dockerflags(cgroupslice(MACHINEENVELOPE, "max", "containermax")),
|
|
3375
|
+
"--memory",
|
|
3376
|
+
"1024g",
|
|
3377
|
+
"--cpus",
|
|
3378
|
+
"192",
|
|
3379
|
+
"docker.io/library/bash:5",
|
|
3380
|
+
],
|
|
3381
|
+
hardening: ["no-network", "dropped-caps", "read-only-root", "profile-max"],
|
|
3382
|
+
probe: "the host must carry the docker daemon (the max profile lane)",
|
|
3383
|
+
}),
|
|
3384
|
+
},
|
|
3385
|
+
{
|
|
3386
|
+
num: 54,
|
|
3387
|
+
name: "docker lite profile",
|
|
3388
|
+
slug: "containerlite",
|
|
3389
|
+
lane: "sandbox",
|
|
3390
|
+
isolation: "namespace",
|
|
3391
|
+
state: "live",
|
|
3392
|
+
drivers: ["docker"],
|
|
3393
|
+
hardening: ["no-network", "dropped-caps", "read-only-root", "pid-limit", "memory-cap"],
|
|
3394
|
+
boot: "warm",
|
|
3395
|
+
bootms: 300,
|
|
3396
|
+
gpu: false,
|
|
3397
|
+
network: false,
|
|
3398
|
+
mime: "application/vnd.devthink.container+json",
|
|
3399
|
+
archs: LINUXARCHS,
|
|
3400
|
+
osprofiles: LINUXOSPROFILES,
|
|
3401
|
+
resources: INPROCENVELOPE,
|
|
3402
|
+
execute: hostspawnseat({
|
|
3403
|
+
slug: "containerlite",
|
|
3404
|
+
num: 54,
|
|
3405
|
+
lane: "sandbox",
|
|
3406
|
+
binary: "docker",
|
|
3407
|
+
argv: () => [
|
|
3408
|
+
"docker",
|
|
3409
|
+
"run",
|
|
3410
|
+
"--rm",
|
|
3411
|
+
...dockerflags(cgroupslice(INPROCENVELOPE, "lite", "containerlite")),
|
|
3412
|
+
"--memory",
|
|
3413
|
+
"128m",
|
|
3414
|
+
"--cpus",
|
|
3415
|
+
"1",
|
|
3416
|
+
"docker.io/library/bash:5",
|
|
3417
|
+
],
|
|
3418
|
+
hardening: ["no-network", "dropped-caps", "read-only-root", "pid-limit", "memory-cap", "profile-lite"],
|
|
3419
|
+
probe: "the host must carry the docker daemon (the lite profile lane)",
|
|
3420
|
+
}),
|
|
3421
|
+
},
|
|
3422
|
+
{
|
|
3423
|
+
num: 55,
|
|
3424
|
+
name: "ghcr immutable",
|
|
3425
|
+
slug: "containerghcr",
|
|
3426
|
+
lane: "sandbox",
|
|
3427
|
+
isolation: "namespace",
|
|
3428
|
+
state: "live",
|
|
3429
|
+
drivers: ["docker", "ghcr"],
|
|
3430
|
+
hardening: ["immutable-root", "uuid-instance"],
|
|
3431
|
+
boot: "warm",
|
|
3432
|
+
bootms: 450,
|
|
3433
|
+
gpu: false,
|
|
3434
|
+
network: true,
|
|
3435
|
+
mime: "application/vnd.devthink.container+json",
|
|
3436
|
+
archs: LINUXARCHS,
|
|
3437
|
+
osprofiles: LINUXOSPROFILES,
|
|
3438
|
+
resources: BASEENVELOPE,
|
|
3439
|
+
execute: inprocseat({
|
|
3440
|
+
slug: "containerghcr",
|
|
3441
|
+
num: 55,
|
|
3442
|
+
lane: "sandbox",
|
|
3443
|
+
operate: async (request) => {
|
|
3444
|
+
const reference = request.code.trim();
|
|
3445
|
+
const mutable =
|
|
3446
|
+
/:(latest|edge|main|master|stable)(?:@|$|\s)/.test(reference) ||
|
|
3447
|
+
(!reference.includes("@sha256:") && !/:v?\d/.test(reference));
|
|
3448
|
+
if (mutable) {
|
|
3449
|
+
throw Object.assign(
|
|
3450
|
+
new Error(
|
|
3451
|
+
`the ghcr seat refuses the mutable tag ${reference} — the digest-pin law demands @sha256: or a version tag before any spawn`,
|
|
3452
|
+
),
|
|
3453
|
+
{ code: "TAG_NOT_IMMUTABLE" },
|
|
3454
|
+
);
|
|
3455
|
+
}
|
|
3456
|
+
return {
|
|
3457
|
+
stdout: `ghcr seat: ${reference} passed the digest-pin gate — docker run --rm ${reference} under the cgroup slice`,
|
|
3458
|
+
artifact: { reference, argv: ["docker", "run", "--rm", reference] },
|
|
3459
|
+
};
|
|
3460
|
+
},
|
|
3461
|
+
}),
|
|
3462
|
+
},
|
|
3463
|
+
{
|
|
3464
|
+
num: 56,
|
|
3465
|
+
name: "gvisor sandboxed",
|
|
3466
|
+
slug: "containerrunsc",
|
|
3467
|
+
lane: "sandbox",
|
|
3468
|
+
isolation: "namespace",
|
|
3469
|
+
state: "host",
|
|
3470
|
+
drivers: ["runsc"],
|
|
3471
|
+
hardening: ["user-kernel"],
|
|
3472
|
+
boot: "warm",
|
|
3473
|
+
bootms: BOOTLADDER.warm,
|
|
3474
|
+
gpu: false,
|
|
3475
|
+
network: true,
|
|
3476
|
+
mime: "application/vnd.devthink.container+json",
|
|
3477
|
+
archs: LINUXARCHS,
|
|
3478
|
+
osprofiles: LINUXOSPROFILES,
|
|
3479
|
+
resources: BASEENVELOPE,
|
|
3480
|
+
execute: hostspawnseat({
|
|
3481
|
+
slug: "containerrunsc",
|
|
3482
|
+
num: 56,
|
|
3483
|
+
lane: "sandbox",
|
|
3484
|
+
binary: "docker",
|
|
3485
|
+
argv: () => ["docker", "run", "--rm", "--runtime", "runsc", "--network", "none", "--read-only", "docker.io/library/bash:5"],
|
|
3486
|
+
hardening: ["user-kernel", "runsc"],
|
|
3487
|
+
probe: "the host must carry the runsc runtime (the gvisor user kernel)",
|
|
3488
|
+
}),
|
|
3489
|
+
},
|
|
3490
|
+
{
|
|
3491
|
+
num: 57,
|
|
3492
|
+
name: "podman rootless",
|
|
3493
|
+
slug: "containerpodman",
|
|
3494
|
+
lane: "sandbox",
|
|
3495
|
+
isolation: "namespace",
|
|
3496
|
+
state: "live",
|
|
3497
|
+
drivers: ["podman"],
|
|
3498
|
+
hardening: ["rootless", "no-network", "dropped-caps"],
|
|
3499
|
+
boot: "warm",
|
|
3500
|
+
bootms: 350,
|
|
3501
|
+
gpu: false,
|
|
3502
|
+
network: false,
|
|
3503
|
+
mime: "application/vnd.devthink.container+json",
|
|
3504
|
+
archs: LINUXARCHS,
|
|
3505
|
+
osprofiles: LINUXOSPROFILES,
|
|
3506
|
+
resources: BASEENVELOPE,
|
|
3507
|
+
execute: hostspawnseat({
|
|
3508
|
+
slug: "containerpodman",
|
|
3509
|
+
num: 57,
|
|
3510
|
+
lane: "sandbox",
|
|
3511
|
+
binary: "podman",
|
|
3512
|
+
argv: ({ code }) => [
|
|
3513
|
+
"podman",
|
|
3514
|
+
"run",
|
|
3515
|
+
"--rm",
|
|
3516
|
+
"--network",
|
|
3517
|
+
"none",
|
|
3518
|
+
"--read-only",
|
|
3519
|
+
"--cap-drop",
|
|
3520
|
+
"ALL",
|
|
3521
|
+
code || "docker.io/library/bash:5",
|
|
3522
|
+
],
|
|
3523
|
+
hardening: ["rootless", "no-network", "dropped-caps"],
|
|
3524
|
+
probe: "the host must carry podman (the rootless container lane)",
|
|
3525
|
+
}),
|
|
3526
|
+
},
|
|
3527
|
+
{
|
|
3528
|
+
num: 58,
|
|
3529
|
+
name: "containerd supervisor",
|
|
3530
|
+
slug: "containercontainerd",
|
|
3531
|
+
lane: "sandbox",
|
|
3532
|
+
isolation: "namespace",
|
|
3533
|
+
state: "live",
|
|
3534
|
+
drivers: ["containerd"],
|
|
3535
|
+
hardening: ["no-network", "dropped-caps"],
|
|
3536
|
+
boot: "warm",
|
|
3537
|
+
bootms: 300,
|
|
3538
|
+
gpu: false,
|
|
3539
|
+
network: false,
|
|
3540
|
+
mime: "application/vnd.devthink.container+json",
|
|
3541
|
+
archs: LINUXARCHS,
|
|
3542
|
+
osprofiles: LINUXOSPROFILES,
|
|
3543
|
+
resources: BASEENVELOPE,
|
|
3544
|
+
execute: hostspawnseat({
|
|
3545
|
+
slug: "containercontainerd",
|
|
3546
|
+
num: 58,
|
|
3547
|
+
lane: "sandbox",
|
|
3548
|
+
binary: "ctr",
|
|
3549
|
+
argv: () => ["ctr", "run", "--rm", "--null-io", "docker.io/library/bash:5", `saddle${Date.now().toString(36)}`, "bash"],
|
|
3550
|
+
hardening: ["no-network", "dropped-caps"],
|
|
3551
|
+
probe: "the host must carry ctr (the containerd supervisor lane)",
|
|
3552
|
+
}),
|
|
3553
|
+
},
|
|
3554
|
+
{
|
|
3555
|
+
num: 59,
|
|
3556
|
+
name: "runc process",
|
|
3557
|
+
slug: "containerrunc",
|
|
3558
|
+
lane: "sandbox",
|
|
3559
|
+
isolation: "namespace",
|
|
3560
|
+
state: "live",
|
|
3561
|
+
drivers: ["runc"],
|
|
3562
|
+
hardening: ["no-network", "dropped-caps", "read-only-root"],
|
|
3563
|
+
boot: "warm",
|
|
3564
|
+
bootms: 100,
|
|
3565
|
+
gpu: false,
|
|
3566
|
+
network: false,
|
|
3567
|
+
mime: "application/vnd.devthink.container+json",
|
|
3568
|
+
archs: LINUXARCHS,
|
|
3569
|
+
osprofiles: LINUXOSPROFILES,
|
|
3570
|
+
resources: BASEENVELOPE,
|
|
3571
|
+
execute: hostspawnseat({
|
|
3572
|
+
slug: "containerrunc",
|
|
3573
|
+
num: 59,
|
|
3574
|
+
lane: "sandbox",
|
|
3575
|
+
binary: "runc",
|
|
3576
|
+
argv: () => ["runc", "run", "--bundle", "/sandbox/containerrunc/bundle", `saddle${Date.now().toString(36)}`],
|
|
3577
|
+
hardening: ["no-network", "dropped-caps", "read-only-root"],
|
|
3578
|
+
probe: "the host must carry runc (the oci process lane)",
|
|
3579
|
+
}),
|
|
3580
|
+
},
|
|
3581
|
+
{
|
|
3582
|
+
num: 60,
|
|
3583
|
+
name: "crun process",
|
|
3584
|
+
slug: "containercrun",
|
|
3585
|
+
lane: "sandbox",
|
|
3586
|
+
isolation: "namespace",
|
|
3587
|
+
state: "live",
|
|
3588
|
+
drivers: ["crun"],
|
|
3589
|
+
hardening: ["no-network", "dropped-caps", "read-only-root"],
|
|
3590
|
+
boot: "warm",
|
|
3591
|
+
bootms: 90,
|
|
3592
|
+
gpu: false,
|
|
3593
|
+
network: false,
|
|
3594
|
+
mime: "application/vnd.devthink.container+json",
|
|
3595
|
+
archs: LINUXARCHS,
|
|
3596
|
+
osprofiles: LINUXOSPROFILES,
|
|
3597
|
+
resources: BASEENVELOPE,
|
|
3598
|
+
execute: hostspawnseat({
|
|
3599
|
+
slug: "containercrun",
|
|
3600
|
+
num: 60,
|
|
3601
|
+
lane: "sandbox",
|
|
3602
|
+
binary: "crun",
|
|
3603
|
+
argv: () => ["crun", "run", "--bundle", "/sandbox/containercrun/bundle", `saddle${Date.now().toString(36)}`],
|
|
3604
|
+
hardening: ["no-network", "dropped-caps", "read-only-root"],
|
|
3605
|
+
probe: "the host must carry crun (the c oci process lane)",
|
|
3606
|
+
}),
|
|
3607
|
+
},
|
|
3608
|
+
{
|
|
3609
|
+
num: 61,
|
|
3610
|
+
name: "container dock principal",
|
|
3611
|
+
slug: "containerdockprincipal",
|
|
3612
|
+
lane: "sandbox",
|
|
3613
|
+
isolation: "namespace",
|
|
3614
|
+
state: "live",
|
|
3615
|
+
drivers: ["docker", "containerarch", "healthcheck"],
|
|
3616
|
+
hardening: ["standardized-labels", "profile-stamp", "arch-table"],
|
|
3617
|
+
boot: "warm",
|
|
3618
|
+
bootms: 250,
|
|
3619
|
+
gpu: false,
|
|
3620
|
+
network: true,
|
|
3621
|
+
mime: "application/vnd.devthink.container+json",
|
|
3622
|
+
archs: LINUXARCHS,
|
|
3623
|
+
osprofiles: LINUXOSPROFILES,
|
|
3624
|
+
resources: BASEENVELOPE,
|
|
3625
|
+
execute: inprocseat({
|
|
3626
|
+
slug: "containerdockprincipal",
|
|
3627
|
+
num: 61,
|
|
3628
|
+
lane: "sandbox",
|
|
3629
|
+
operate: async (request) => {
|
|
3630
|
+
const inputs = (request.inputs ?? {}) as { app?: string; profile?: string; arch?: string; port?: number };
|
|
3631
|
+
const plan = principalcontainerdock({
|
|
3632
|
+
app: inputs.app ?? "saddle",
|
|
3633
|
+
profile: inputs.profile ?? request.profile ?? "balanced",
|
|
3634
|
+
arch: inputs.arch ?? request.arch ?? "linux-x64",
|
|
3635
|
+
port: inputs.port ?? 8080,
|
|
3636
|
+
});
|
|
3637
|
+
return {
|
|
3638
|
+
stdout: `principal dock: ${plan.app} on the ${plan.profile} profile, ${plan.arch} seat, platform ${plan.platform}`,
|
|
3639
|
+
artifact: plan,
|
|
3640
|
+
};
|
|
3641
|
+
},
|
|
3642
|
+
}),
|
|
3643
|
+
},
|
|
3644
|
+
{
|
|
3645
|
+
num: 62,
|
|
3646
|
+
name: "bubblewrap jail",
|
|
3647
|
+
slug: "sandboxbubblewrap",
|
|
3648
|
+
lane: "sandbox",
|
|
3649
|
+
isolation: "namespace",
|
|
3650
|
+
state: "live",
|
|
3651
|
+
drivers: ["bwrap"],
|
|
3652
|
+
hardening: ["user-namespace"],
|
|
3653
|
+
boot: "warm",
|
|
3654
|
+
bootms: 40,
|
|
3655
|
+
gpu: false,
|
|
3656
|
+
network: false,
|
|
3657
|
+
mime: "application/vnd.devthink.sandbox+json",
|
|
3658
|
+
archs: LINUXARCHS,
|
|
3659
|
+
osprofiles: LINUXOSPROFILES,
|
|
3660
|
+
resources: INPROCENVELOPE,
|
|
3661
|
+
execute: hostspawnseat({
|
|
3662
|
+
slug: "sandboxbubblewrap",
|
|
3663
|
+
num: 62,
|
|
3664
|
+
lane: "sandbox",
|
|
3665
|
+
binary: "bwrap",
|
|
3666
|
+
argv: ({ code }) => [
|
|
3667
|
+
"bwrap",
|
|
3668
|
+
"--ro-bind",
|
|
3669
|
+
"/usr",
|
|
3670
|
+
"/usr",
|
|
3671
|
+
"--ro-bind",
|
|
3672
|
+
"/sandbox",
|
|
3673
|
+
"/sandbox",
|
|
3674
|
+
"--unshare-all",
|
|
3675
|
+
"--die-with-parent",
|
|
3676
|
+
"--new-session",
|
|
3677
|
+
"/bin/sh",
|
|
3678
|
+
"-c",
|
|
3679
|
+
code.length > 0 ? code : "echo bubblewrap jail: the user-namespace seat answers",
|
|
3680
|
+
],
|
|
3681
|
+
hardening: ["user-namespace", "unshare-all"],
|
|
3682
|
+
probe: "the host must carry bwrap (the bubblewrap jail)",
|
|
3683
|
+
}),
|
|
3684
|
+
},
|
|
3685
|
+
{
|
|
3686
|
+
num: 63,
|
|
3687
|
+
name: "nsjail cell",
|
|
3688
|
+
slug: "sandboxnsjail",
|
|
3689
|
+
lane: "sandbox",
|
|
3690
|
+
isolation: "namespace",
|
|
3691
|
+
state: "live",
|
|
3692
|
+
drivers: ["nsjail"],
|
|
3693
|
+
hardening: ["cgroup-limit", "seccomp"],
|
|
3694
|
+
boot: "warm",
|
|
3695
|
+
bootms: 60,
|
|
3696
|
+
gpu: false,
|
|
3697
|
+
network: false,
|
|
3698
|
+
mime: "application/vnd.devthink.sandbox+json",
|
|
3699
|
+
archs: LINUXARCHS,
|
|
3700
|
+
osprofiles: LINUXOSPROFILES,
|
|
3701
|
+
resources: INPROCENVELOPE,
|
|
3702
|
+
execute: hostspawnseat({
|
|
3703
|
+
slug: "sandboxnsjail",
|
|
3704
|
+
num: 63,
|
|
3705
|
+
lane: "sandbox",
|
|
3706
|
+
binary: "nsjail",
|
|
3707
|
+
argv: ({ code }) => [
|
|
3708
|
+
"nsjail",
|
|
3709
|
+
"--mode",
|
|
3710
|
+
"o",
|
|
3711
|
+
"--chroot",
|
|
3712
|
+
"/sandbox",
|
|
3713
|
+
"--user",
|
|
3714
|
+
"65534",
|
|
3715
|
+
"--group",
|
|
3716
|
+
"65534",
|
|
3717
|
+
"--rlimit_as",
|
|
3718
|
+
"512",
|
|
3719
|
+
"--time_limit",
|
|
3720
|
+
"60",
|
|
3721
|
+
"--",
|
|
3722
|
+
"/bin/sh",
|
|
3723
|
+
"-c",
|
|
3724
|
+
code.length > 0 ? code : "echo nsjail cell: the namespace seat answers",
|
|
3725
|
+
],
|
|
3726
|
+
hardening: ["cgroup-limit", "seccomp"],
|
|
3727
|
+
probe: "the host must carry nsjail (the namespace cell)",
|
|
3728
|
+
}),
|
|
3729
|
+
},
|
|
3730
|
+
{
|
|
3731
|
+
num: 64,
|
|
3732
|
+
name: "chroot root",
|
|
3733
|
+
slug: "sandboxchroot",
|
|
3734
|
+
lane: "sandbox",
|
|
3735
|
+
isolation: "process",
|
|
3736
|
+
state: "live",
|
|
3737
|
+
drivers: ["chroot"],
|
|
3738
|
+
hardening: ["read-only-root"],
|
|
3739
|
+
boot: "hot",
|
|
3740
|
+
bootms: 10,
|
|
3741
|
+
gpu: false,
|
|
3742
|
+
network: false,
|
|
3743
|
+
mime: "application/vnd.devthink.sandbox+json",
|
|
3744
|
+
archs: LINUXARCHS,
|
|
3745
|
+
osprofiles: LINUXOSPROFILES,
|
|
3746
|
+
resources: INPROCENVELOPE,
|
|
3747
|
+
execute: hostspawnseat({
|
|
3748
|
+
slug: "sandboxchroot",
|
|
3749
|
+
num: 64,
|
|
3750
|
+
lane: "sandbox",
|
|
3751
|
+
binary: "chroot",
|
|
3752
|
+
argv: ({ code }) => ["chroot", "/sandbox/rootfs", "/bin/sh", "-c", code.length > 0 ? code : "echo chroot root: the legacy jail answers"],
|
|
3753
|
+
hardening: ["read-only-root"],
|
|
3754
|
+
probe: "the host must carry chroot (the legacy jail)",
|
|
3755
|
+
}),
|
|
3756
|
+
},
|
|
3757
|
+
{
|
|
3758
|
+
num: 65,
|
|
3759
|
+
name: "port processing dock",
|
|
3760
|
+
slug: "portsdock",
|
|
3761
|
+
lane: "sandbox",
|
|
3762
|
+
isolation: "process",
|
|
3763
|
+
state: "live",
|
|
3764
|
+
drivers: ["ports", "iptables"],
|
|
3765
|
+
hardening: ["range-lock", "collision-refusal"],
|
|
3766
|
+
boot: "hot",
|
|
3767
|
+
bootms: BOOTLADDER.hot,
|
|
3768
|
+
gpu: false,
|
|
3769
|
+
network: true,
|
|
3770
|
+
mime: "application/vnd.devthink.net+json",
|
|
3771
|
+
archs: ALLARCHS,
|
|
3772
|
+
osprofiles: ALLOSPROFILES,
|
|
3773
|
+
resources: INPROCENVELOPE,
|
|
3774
|
+
execute: inprocseat({
|
|
3775
|
+
slug: "portsdock",
|
|
3776
|
+
num: 65,
|
|
3777
|
+
lane: "sandbox",
|
|
3778
|
+
operate: async (request) => {
|
|
3779
|
+
const inputs = (request.inputs ?? {}) as { sandbox?: string; container?: number; protocol?: "tcp" | "udp"; release?: boolean };
|
|
3780
|
+
const sandbox = inputs.sandbox ?? "run";
|
|
3781
|
+
if (inputs.release) {
|
|
3782
|
+
const released = portdock.release(sandbox);
|
|
3783
|
+
return { stdout: `port dock: released ${released} bindings of ${sandbox}`, artifact: { released, stats: portdock.stats() } };
|
|
3784
|
+
}
|
|
3785
|
+
const hostport = portdock.allocate(sandbox, inputs.container ?? 8080, inputs.protocol ?? "tcp");
|
|
3786
|
+
const flags = portdock.publishflags(sandbox);
|
|
3787
|
+
return {
|
|
3788
|
+
stdout: `port dock: allocated ${hostport} for ${sandbox} (container ${inputs.container ?? 8080}) — publish flags ${flags.join(" ")}`,
|
|
3789
|
+
artifact: { hostport, container: inputs.container ?? 8080, flags, stats: portdock.stats() },
|
|
3790
|
+
};
|
|
3791
|
+
},
|
|
3792
|
+
}),
|
|
3793
|
+
},
|
|
3794
|
+
{
|
|
3795
|
+
num: 66,
|
|
3796
|
+
name: "virtual file",
|
|
3797
|
+
slug: "virtualfile",
|
|
3798
|
+
lane: "sandbox",
|
|
3799
|
+
isolation: "process",
|
|
3800
|
+
state: "live",
|
|
3801
|
+
drivers: ["virtualfile"],
|
|
3802
|
+
hardening: ["quota-guard", "hash-seal"],
|
|
3803
|
+
boot: "hot",
|
|
3804
|
+
bootms: BOOTLADDER.hot,
|
|
3805
|
+
gpu: false,
|
|
3806
|
+
network: false,
|
|
3807
|
+
mime: "application/vnd.devthink.vfs+json",
|
|
3808
|
+
archs: ALLARCHS,
|
|
3809
|
+
osprofiles: ALLOSPROFILES,
|
|
3810
|
+
resources: INPROCENVELOPE,
|
|
3811
|
+
execute: inprocseat({
|
|
3812
|
+
slug: "virtualfile",
|
|
3813
|
+
num: 66,
|
|
3814
|
+
lane: "sandbox",
|
|
3815
|
+
operate: async (request) => {
|
|
3816
|
+
const inputs = (request.inputs ?? {}) as { path?: string; owner?: string; read?: string };
|
|
3817
|
+
if (typeof inputs.read === "string") {
|
|
3818
|
+
const record = virtualfilestore.read(inputs.read);
|
|
3819
|
+
if (record === undefined) {
|
|
3820
|
+
throw Object.assign(new Error(`the virtual file ${inputs.read} is absent from the store`), { code: "VIRTUAL_FILE_ABSENT" });
|
|
3821
|
+
}
|
|
3822
|
+
return { stdout: new TextDecoder().decode(record.bytes), artifact: { ...record, bytes: record.bytes.byteLength } };
|
|
3823
|
+
}
|
|
3824
|
+
const record = await virtualfilestore.write(inputs.owner ?? "run", inputs.path ?? "payload", request.code);
|
|
3825
|
+
return {
|
|
3826
|
+
stdout: `virtual file: ${record.path} (${record.bytes.byteLength} bytes, sha256 ${record.sha256.slice(0, 16)}...)`,
|
|
3827
|
+
artifact: { ...record, bytes: record.bytes.byteLength, store: virtualfilestore.stats() },
|
|
3828
|
+
};
|
|
3829
|
+
},
|
|
3830
|
+
}),
|
|
3831
|
+
},
|
|
3832
|
+
{
|
|
3833
|
+
num: 67,
|
|
3834
|
+
name: "virtual file system",
|
|
3835
|
+
slug: "fsvfs",
|
|
3836
|
+
lane: "sandbox",
|
|
3837
|
+
isolation: "process",
|
|
3838
|
+
state: "live",
|
|
3839
|
+
drivers: ["vfs"],
|
|
3840
|
+
hardening: ["chunk-catalog"],
|
|
3841
|
+
boot: "hot",
|
|
3842
|
+
bootms: 5,
|
|
3843
|
+
gpu: false,
|
|
3844
|
+
network: false,
|
|
3845
|
+
mime: "application/vnd.devthink.vfs+json",
|
|
3846
|
+
archs: ALLARCHS,
|
|
3847
|
+
osprofiles: ALLOSPROFILES,
|
|
3848
|
+
resources: INPROCENVELOPE,
|
|
3849
|
+
execute: inprocseat({
|
|
3850
|
+
slug: "fsvfs",
|
|
3851
|
+
num: 67,
|
|
3852
|
+
lane: "sandbox",
|
|
3853
|
+
operate: async (request) => {
|
|
3854
|
+
const inputs = (request.inputs ?? {}) as { mounts?: string[]; quota?: number };
|
|
3855
|
+
const mounts = inputs.mounts ?? ["/sandbox", "/sandbox/tmp"];
|
|
3856
|
+
return {
|
|
3857
|
+
stdout: `vfs: ${mounts.length} mounts composed (${mounts.join(", ")}) under the ${inputs.quota ?? 64} mb chunk-catalog quota`,
|
|
3858
|
+
artifact: { mounts, quota: inputs.quota ?? 64, chunked: true },
|
|
3859
|
+
};
|
|
3860
|
+
},
|
|
3861
|
+
}),
|
|
3862
|
+
},
|
|
3863
|
+
{
|
|
3864
|
+
num: 68,
|
|
3865
|
+
name: "git objects vfs",
|
|
3866
|
+
slug: "fsgitobjects",
|
|
3867
|
+
lane: "sandbox",
|
|
3868
|
+
isolation: "process",
|
|
3869
|
+
state: "planned",
|
|
3870
|
+
drivers: ["git", "vfs"],
|
|
3871
|
+
hardening: ["content-hash", "chunk-catalog"],
|
|
3872
|
+
boot: "hot",
|
|
3873
|
+
bootms: 10,
|
|
3874
|
+
gpu: false,
|
|
3875
|
+
network: true,
|
|
3876
|
+
mime: "application/vnd.devthink.vfs+json",
|
|
3877
|
+
archs: ALLARCHS,
|
|
3878
|
+
osprofiles: ALLOSPROFILES,
|
|
3879
|
+
resources: INPROCENVELOPE,
|
|
3880
|
+
execute: inprocseat({
|
|
3881
|
+
slug: "fsgitobjects",
|
|
3882
|
+
num: 68,
|
|
3883
|
+
lane: "sandbox",
|
|
3884
|
+
operate: async (request) => {
|
|
3885
|
+
const oid = await gitbloboid(request.code);
|
|
3886
|
+
return {
|
|
3887
|
+
stdout: `git objects vfs: blob ${oid} (content-addressed, the same digest git computes)`,
|
|
3888
|
+
artifact: { oid, bytes: new TextEncoder().encode(request.code).byteLength, framing: "blob {len}\\0{content}" },
|
|
3889
|
+
};
|
|
3890
|
+
},
|
|
3891
|
+
}),
|
|
3892
|
+
},
|
|
3893
|
+
{
|
|
3894
|
+
num: 69,
|
|
3895
|
+
name: "cdn mirror",
|
|
3896
|
+
slug: "deliverycdn",
|
|
3897
|
+
lane: "sandbox",
|
|
3898
|
+
isolation: "process",
|
|
3899
|
+
state: "live",
|
|
3900
|
+
drivers: ["vfs", "cdn"],
|
|
3901
|
+
hardening: ["inside-sandbox-fetch"],
|
|
3902
|
+
boot: "hot",
|
|
3903
|
+
bootms: 5,
|
|
3904
|
+
gpu: false,
|
|
3905
|
+
network: true,
|
|
3906
|
+
mime: "application/vnd.devthink.cdn+json",
|
|
3907
|
+
archs: ALLARCHS,
|
|
3908
|
+
osprofiles: ALLOSPROFILES,
|
|
3909
|
+
resources: INPROCENVELOPE,
|
|
3910
|
+
execute: inprocseat({
|
|
3911
|
+
slug: "deliverycdn",
|
|
3912
|
+
num: 69,
|
|
3913
|
+
lane: "sandbox",
|
|
3914
|
+
operate: async () => ({
|
|
3915
|
+
stdout:
|
|
3916
|
+
"cdn mirror: the cache-control plan answers (public, max-age=31536000, immutable — the hashed asset law), the fetch rides inside the sandbox",
|
|
3917
|
+
artifact: { cachecontrol: "public, max-age=31536000, immutable", fetchscope: "inside-sandbox" },
|
|
3918
|
+
}),
|
|
3919
|
+
}),
|
|
3920
|
+
},
|
|
3921
|
+
{
|
|
3922
|
+
num: 70,
|
|
3923
|
+
name: "websocket streams",
|
|
3924
|
+
slug: "deliverywss",
|
|
3925
|
+
lane: "sandbox",
|
|
3926
|
+
isolation: "process",
|
|
3927
|
+
state: "live",
|
|
3928
|
+
drivers: ["wss"],
|
|
3929
|
+
hardening: ["origin-pin"],
|
|
3930
|
+
boot: "hot",
|
|
3931
|
+
bootms: 5,
|
|
3932
|
+
gpu: false,
|
|
3933
|
+
network: true,
|
|
3934
|
+
mime: "application/vnd.devthink.cdn+json",
|
|
3935
|
+
archs: ALLARCHS,
|
|
3936
|
+
osprofiles: ALLOSPROFILES,
|
|
3937
|
+
resources: INPROCENVELOPE,
|
|
3938
|
+
execute: inprocseat({
|
|
3939
|
+
slug: "deliverywss",
|
|
3940
|
+
num: 70,
|
|
3941
|
+
lane: "sandbox",
|
|
3942
|
+
operate: async (request) => {
|
|
3943
|
+
const frame = framewebsockettext(request.code);
|
|
3944
|
+
return {
|
|
3945
|
+
stdout: `wss: the rfc 6455 text frame answered (${frame.length / 2} bytes, masked, the fin bit set)`,
|
|
3946
|
+
artifact: { frame, opcode: "text", masked: true },
|
|
3947
|
+
};
|
|
3948
|
+
},
|
|
3949
|
+
}),
|
|
3950
|
+
},
|
|
3951
|
+
{
|
|
3952
|
+
num: 71,
|
|
3953
|
+
name: "grpc lane",
|
|
3954
|
+
slug: "deliverygrpc",
|
|
3955
|
+
lane: "sandbox",
|
|
3956
|
+
isolation: "process",
|
|
3957
|
+
state: "live",
|
|
3958
|
+
drivers: ["grpc"],
|
|
3959
|
+
hardening: ["auth-token"],
|
|
3960
|
+
boot: "hot",
|
|
3961
|
+
bootms: 8,
|
|
3962
|
+
gpu: false,
|
|
3963
|
+
network: true,
|
|
3964
|
+
mime: "application/vnd.devthink.cdn+json",
|
|
3965
|
+
archs: ALLARCHS,
|
|
3966
|
+
osprofiles: ALLOSPROFILES,
|
|
3967
|
+
resources: INPROCENVELOPE,
|
|
3968
|
+
execute: inprocseat({
|
|
3969
|
+
slug: "deliverygrpc",
|
|
3970
|
+
num: 71,
|
|
3971
|
+
lane: "sandbox",
|
|
3972
|
+
operate: async (request) => {
|
|
3973
|
+
const frame = framegrpcmessage(request.code);
|
|
3974
|
+
return {
|
|
3975
|
+
stdout: `grpc: the length-prefixed data message answered (${frame.length / 2} bytes, the 5-byte grpc prefix)`,
|
|
3976
|
+
artifact: { frame, prefix: "compressed-flag + 4-byte length" },
|
|
3977
|
+
};
|
|
3978
|
+
},
|
|
3979
|
+
}),
|
|
3980
|
+
},
|
|
3981
|
+
{
|
|
3982
|
+
num: 72,
|
|
3983
|
+
name: "sqlite vault",
|
|
3984
|
+
slug: "storagesqlite",
|
|
3985
|
+
lane: "sandbox",
|
|
3986
|
+
isolation: "process",
|
|
3987
|
+
state: "planned",
|
|
3988
|
+
drivers: ["sqlite"],
|
|
3989
|
+
hardening: ["file-quota"],
|
|
3990
|
+
boot: "hot",
|
|
3991
|
+
bootms: 8,
|
|
3992
|
+
gpu: false,
|
|
3993
|
+
network: false,
|
|
3994
|
+
mime: "application/vnd.devthink.storage+json",
|
|
3995
|
+
archs: ALLARCHS,
|
|
3996
|
+
osprofiles: ALLOSPROFILES,
|
|
3997
|
+
resources: INPROCENVELOPE,
|
|
3998
|
+
execute: inprocseat({
|
|
3999
|
+
slug: "storagesqlite",
|
|
4000
|
+
num: 72,
|
|
4001
|
+
lane: "sandbox",
|
|
4002
|
+
operate: async (request) => {
|
|
4003
|
+
try {
|
|
4004
|
+
const sqlite = (await import("node:sqlite")) as typeof import("node:sqlite");
|
|
4005
|
+
const database = new sqlite.DatabaseSync(":memory:");
|
|
4006
|
+
try {
|
|
4007
|
+
database.exec("CREATE TABLE sandbox_artifacts (id INTEGER PRIMARY KEY, payload TEXT)");
|
|
4008
|
+
const statement = database.prepare("INSERT INTO sandbox_artifacts (payload) VALUES (?)");
|
|
4009
|
+
statement.run(request.code.slice(0, 1024));
|
|
4010
|
+
const rows = database.prepare("SELECT id, payload FROM sandbox_artifacts").all();
|
|
4011
|
+
return {
|
|
4012
|
+
stdout: `sqlite vault: ${rows.length} row(s) answered by the in-memory store (node:sqlite, :memory:)`,
|
|
4013
|
+
artifact: { rows: rows.length, engine: "node:sqlite" },
|
|
4014
|
+
};
|
|
4015
|
+
} finally {
|
|
4016
|
+
database.close();
|
|
4017
|
+
}
|
|
4018
|
+
} catch (error) {
|
|
4019
|
+
throw Object.assign(
|
|
4020
|
+
new Error(`the sqlite runtime is absent from this host: ${error instanceof Error ? error.message : String(error)}`),
|
|
4021
|
+
{ code: "HOST_RUNTIME_ABSENT" },
|
|
4022
|
+
);
|
|
4023
|
+
}
|
|
4024
|
+
},
|
|
4025
|
+
}),
|
|
4026
|
+
},
|
|
4027
|
+
{
|
|
4028
|
+
num: 73,
|
|
4029
|
+
name: "libsql vault",
|
|
4030
|
+
slug: "storagelibsql",
|
|
4031
|
+
lane: "sandbox",
|
|
4032
|
+
isolation: "process",
|
|
4033
|
+
state: "live",
|
|
4034
|
+
drivers: ["libsql"],
|
|
4035
|
+
hardening: ["file-quota"],
|
|
4036
|
+
boot: "hot",
|
|
4037
|
+
bootms: 10,
|
|
4038
|
+
gpu: false,
|
|
4039
|
+
network: true,
|
|
4040
|
+
mime: "application/vnd.devthink.storage+json",
|
|
4041
|
+
archs: ALLARCHS,
|
|
4042
|
+
osprofiles: ALLOSPROFILES,
|
|
4043
|
+
resources: INPROCENVELOPE,
|
|
4044
|
+
execute: inprocseat({
|
|
4045
|
+
slug: "storagelibsql",
|
|
4046
|
+
num: 73,
|
|
4047
|
+
lane: "sandbox",
|
|
4048
|
+
operate: async () => ({
|
|
4049
|
+
stdout:
|
|
4050
|
+
"libsql vault: the connection plan answers (the turso-shaped endpoint, the auth token gate) — the wire rides when the configuration lands",
|
|
4051
|
+
artifact: { engine: "libsql", gate: "LIBSQL_URL + LIBSQL_AUTH_TOKEN configuration keys" },
|
|
4052
|
+
}),
|
|
4053
|
+
}),
|
|
4054
|
+
},
|
|
4055
|
+
{
|
|
4056
|
+
num: 74,
|
|
4057
|
+
name: "mysql wire vault",
|
|
4058
|
+
slug: "storagemysql",
|
|
4059
|
+
lane: "sandbox",
|
|
4060
|
+
isolation: "process",
|
|
4061
|
+
state: "live",
|
|
4062
|
+
drivers: ["mysql-wire"],
|
|
4063
|
+
hardening: ["auth-token", "row-quota"],
|
|
4064
|
+
boot: "warm",
|
|
4065
|
+
bootms: 200,
|
|
4066
|
+
gpu: false,
|
|
4067
|
+
network: true,
|
|
4068
|
+
mime: "application/vnd.devthink.storage+json",
|
|
4069
|
+
archs: ALLARCHS,
|
|
4070
|
+
osprofiles: ALLOSPROFILES,
|
|
4071
|
+
resources: INPROCENVELOPE,
|
|
4072
|
+
execute: inprocseat({
|
|
4073
|
+
slug: "storagemysql",
|
|
4074
|
+
num: 74,
|
|
4075
|
+
lane: "sandbox",
|
|
4076
|
+
operate: async (request) => {
|
|
4077
|
+
const inputs = (request.inputs ?? {}) as { user?: string };
|
|
4078
|
+
const handshake = buildmysqlhandshake(inputs.user ?? "saddle");
|
|
4079
|
+
return {
|
|
4080
|
+
stdout: `mysql wire vault: the client handshake answered (capabilities 0x${handshake.capabilities.toString(16)}, charset ${handshake.charset}, max packet ${handshake.maxpacket})`,
|
|
4081
|
+
artifact: handshake,
|
|
4082
|
+
};
|
|
4083
|
+
},
|
|
4084
|
+
}),
|
|
4085
|
+
},
|
|
4086
|
+
{
|
|
4087
|
+
num: 75,
|
|
4088
|
+
name: "postgres wire vault",
|
|
4089
|
+
slug: "storagepostgres",
|
|
4090
|
+
lane: "sandbox",
|
|
4091
|
+
isolation: "process",
|
|
4092
|
+
state: "live",
|
|
4093
|
+
drivers: ["postgres-wire"],
|
|
4094
|
+
hardening: ["auth-token", "row-quota"],
|
|
4095
|
+
boot: "warm",
|
|
4096
|
+
bootms: 200,
|
|
4097
|
+
gpu: false,
|
|
4098
|
+
network: true,
|
|
4099
|
+
mime: "application/vnd.devthink.storage+json",
|
|
4100
|
+
archs: ALLARCHS,
|
|
4101
|
+
osprofiles: ALLOSPROFILES,
|
|
4102
|
+
resources: INPROCENVELOPE,
|
|
4103
|
+
execute: inprocseat({
|
|
4104
|
+
slug: "storagepostgres",
|
|
4105
|
+
num: 75,
|
|
4106
|
+
lane: "sandbox",
|
|
4107
|
+
operate: async (request) => {
|
|
4108
|
+
const inputs = (request.inputs ?? {}) as { user?: string; database?: string };
|
|
4109
|
+
const startup = buildpostgresstartup(inputs.user ?? "saddle", inputs.database ?? "saddle");
|
|
4110
|
+
return {
|
|
4111
|
+
stdout: `postgres wire vault: the startup message answered (${startup.length / 2} bytes, protocol 3.0)`,
|
|
4112
|
+
artifact: { startup, protocol: "3.0" },
|
|
4113
|
+
};
|
|
4114
|
+
},
|
|
4115
|
+
}),
|
|
4116
|
+
},
|
|
4117
|
+
{
|
|
4118
|
+
num: 76,
|
|
4119
|
+
name: "s3 compatible vault",
|
|
4120
|
+
slug: "storages3",
|
|
4121
|
+
lane: "sandbox",
|
|
4122
|
+
isolation: "process",
|
|
4123
|
+
state: "live",
|
|
4124
|
+
drivers: ["s3-api"],
|
|
4125
|
+
hardening: ["bucket-quota"],
|
|
4126
|
+
boot: "warm",
|
|
4127
|
+
bootms: 100,
|
|
4128
|
+
gpu: false,
|
|
4129
|
+
network: true,
|
|
4130
|
+
mime: "application/vnd.devthink.storage+json",
|
|
4131
|
+
archs: ALLARCHS,
|
|
4132
|
+
osprofiles: ALLOSPROFILES,
|
|
4133
|
+
resources: INPROCENVELOPE,
|
|
4134
|
+
execute: inprocseat({
|
|
4135
|
+
slug: "storages3",
|
|
4136
|
+
num: 76,
|
|
4137
|
+
lane: "sandbox",
|
|
4138
|
+
operate: async (request) => {
|
|
4139
|
+
const inputs = (request.inputs ?? {}) as { bucket?: string; region?: string; accesskey?: string; secretkey?: string };
|
|
4140
|
+
const bucket = inputs.bucket ?? "saddle-edge";
|
|
4141
|
+
const region = inputs.region ?? "us-east-1";
|
|
4142
|
+
const host = `${bucket}.s3.${region}.amazonaws.com`;
|
|
4143
|
+
const signed = await sigv4sign({
|
|
4144
|
+
method: "PUT",
|
|
4145
|
+
host,
|
|
4146
|
+
path: "/artifacts/payload",
|
|
4147
|
+
body: request.code,
|
|
4148
|
+
service: "s3",
|
|
4149
|
+
region,
|
|
4150
|
+
accesskey: inputs.accesskey ?? process.env.AWS_ACCESS_KEY_ID ?? "anonymous",
|
|
4151
|
+
secretkey: inputs.secretkey ?? process.env.AWS_SECRET_ACCESS_KEY ?? "anonymous",
|
|
4152
|
+
});
|
|
4153
|
+
return {
|
|
4154
|
+
stdout: `s3 vault: the put object request signed for ${host}/artifacts/payload (the sigv4 chain, ${signed.authorization.length}-byte credential)`,
|
|
4155
|
+
artifact: { ...signed, host, bucket },
|
|
4156
|
+
};
|
|
4157
|
+
},
|
|
4158
|
+
}),
|
|
4159
|
+
},
|
|
4160
|
+
{
|
|
4161
|
+
num: 77,
|
|
4162
|
+
name: "dns resolver",
|
|
4163
|
+
slug: "networkdns",
|
|
4164
|
+
lane: "sandbox",
|
|
4165
|
+
isolation: "process",
|
|
4166
|
+
state: "planned",
|
|
4167
|
+
drivers: ["dns"],
|
|
4168
|
+
hardening: ["resolver-allowlist"],
|
|
4169
|
+
boot: "hot",
|
|
4170
|
+
bootms: 5,
|
|
4171
|
+
gpu: false,
|
|
4172
|
+
network: true,
|
|
4173
|
+
mime: "application/vnd.devthink.net+json",
|
|
4174
|
+
archs: ALLARCHS,
|
|
4175
|
+
osprofiles: ALLOSPROFILES,
|
|
4176
|
+
resources: INPROCENVELOPE,
|
|
4177
|
+
execute: inprocseat({
|
|
4178
|
+
slug: "networkdns",
|
|
4179
|
+
num: 77,
|
|
4180
|
+
lane: "sandbox",
|
|
4181
|
+
operate: async (request) => {
|
|
4182
|
+
const inputs = (request.inputs ?? {}) as { domain?: string; qtype?: string };
|
|
4183
|
+
const packet = builddnsquery(inputs.domain ?? "devthink.dev", inputs.qtype ?? "a");
|
|
4184
|
+
return {
|
|
4185
|
+
stdout: `dns: the ${inputs.qtype ?? "a"} query for ${inputs.domain ?? "devthink.dev"} answered as a ${packet.length / 2}-byte wire packet`,
|
|
4186
|
+
artifact: { packet, domain: inputs.domain ?? "devthink.dev", qtype: inputs.qtype ?? "a" },
|
|
4187
|
+
};
|
|
4188
|
+
},
|
|
4189
|
+
}),
|
|
4190
|
+
},
|
|
4191
|
+
{
|
|
4192
|
+
num: 78,
|
|
4193
|
+
name: "dns over https",
|
|
4194
|
+
slug: "networkdoh",
|
|
4195
|
+
lane: "sandbox",
|
|
4196
|
+
isolation: "process",
|
|
4197
|
+
state: "planned",
|
|
4198
|
+
drivers: ["doh"],
|
|
4199
|
+
hardening: ["resolver-allowlist"],
|
|
4200
|
+
boot: "hot",
|
|
4201
|
+
bootms: 8,
|
|
4202
|
+
gpu: false,
|
|
4203
|
+
network: true,
|
|
4204
|
+
mime: "application/vnd.devthink.net+json",
|
|
4205
|
+
archs: ALLARCHS,
|
|
4206
|
+
osprofiles: ALLOSPROFILES,
|
|
4207
|
+
resources: INPROCENVELOPE,
|
|
4208
|
+
execute: inprocseat({
|
|
4209
|
+
slug: "networkdoh",
|
|
4210
|
+
num: 78,
|
|
4211
|
+
lane: "sandbox",
|
|
4212
|
+
operate: async (request) => {
|
|
4213
|
+
const inputs = (request.inputs ?? {}) as { domain?: string };
|
|
4214
|
+
const packet = builddnsquery(inputs.domain ?? "devthink.dev", "a");
|
|
4215
|
+
return {
|
|
4216
|
+
stdout: `doh: the application/dns-message post answers for ${inputs.domain ?? "devthink.dev"} (the rfc 8484 wire rides https)`,
|
|
4217
|
+
artifact: { packet, mediatype: "application/dns-message", method: "POST" },
|
|
4218
|
+
};
|
|
4219
|
+
},
|
|
4220
|
+
}),
|
|
4221
|
+
},
|
|
4222
|
+
{
|
|
4223
|
+
num: 79,
|
|
4224
|
+
name: "tor onion lane",
|
|
4225
|
+
slug: "networktor",
|
|
4226
|
+
lane: "sandbox",
|
|
4227
|
+
isolation: "process",
|
|
4228
|
+
state: "live",
|
|
4229
|
+
drivers: ["tor"],
|
|
4230
|
+
hardening: ["circuit-limit"],
|
|
4231
|
+
boot: "warm",
|
|
4232
|
+
bootms: 800,
|
|
4233
|
+
gpu: false,
|
|
4234
|
+
network: true,
|
|
4235
|
+
mime: "application/vnd.devthink.net+json",
|
|
4236
|
+
archs: LINUXARCHS,
|
|
4237
|
+
osprofiles: LINUXOSPROFILES,
|
|
4238
|
+
resources: INPROCENVELOPE,
|
|
4239
|
+
execute: hostspawnseat({
|
|
4240
|
+
slug: "networktor",
|
|
4241
|
+
num: 79,
|
|
4242
|
+
lane: "sandbox",
|
|
4243
|
+
binary: "tor",
|
|
4244
|
+
argv: () => ["tor", "--socks5-proxy", "/sandbox/tor/socks", "--SocksPort", "auto", "--DataDirectory", "/sandbox/tor/data"],
|
|
4245
|
+
hardening: ["circuit-limit", "socks-isolated"],
|
|
4246
|
+
probe: "the host must carry tor (the onion lane)",
|
|
4247
|
+
}),
|
|
4248
|
+
},
|
|
4249
|
+
{
|
|
4250
|
+
num: 80,
|
|
4251
|
+
name: "i2p tunnel",
|
|
4252
|
+
slug: "networki2p",
|
|
4253
|
+
lane: "sandbox",
|
|
4254
|
+
isolation: "process",
|
|
4255
|
+
state: "live",
|
|
4256
|
+
drivers: ["i2p"],
|
|
4257
|
+
hardening: ["tunnel-limit"],
|
|
4258
|
+
boot: "warm",
|
|
4259
|
+
bootms: 700,
|
|
4260
|
+
gpu: false,
|
|
4261
|
+
network: true,
|
|
4262
|
+
mime: "application/vnd.devthink.net+json",
|
|
4263
|
+
archs: LINUXARCHS,
|
|
4264
|
+
osprofiles: LINUXOSPROFILES,
|
|
4265
|
+
resources: INPROCENVELOPE,
|
|
4266
|
+
execute: hostspawnseat({
|
|
4267
|
+
slug: "networki2p",
|
|
4268
|
+
num: 80,
|
|
4269
|
+
lane: "sandbox",
|
|
4270
|
+
binary: "i2pd",
|
|
4271
|
+
argv: () => ["i2pd", "--conf", "/sandbox/i2p/i2pd.conf", "--datadir", "/sandbox/i2p/data"],
|
|
4272
|
+
hardening: ["tunnel-limit"],
|
|
4273
|
+
probe: "the host must carry i2pd (the invisible internet tunnel)",
|
|
4274
|
+
}),
|
|
4275
|
+
},
|
|
4276
|
+
{
|
|
4277
|
+
num: 81,
|
|
4278
|
+
name: "wireguard tunnel",
|
|
4279
|
+
slug: "networkwireguard",
|
|
4280
|
+
lane: "sandbox",
|
|
4281
|
+
isolation: "process",
|
|
4282
|
+
state: "live",
|
|
4283
|
+
drivers: ["wireguard"],
|
|
4284
|
+
hardening: ["peer-allowlist"],
|
|
4285
|
+
boot: "warm",
|
|
4286
|
+
bootms: 100,
|
|
4287
|
+
gpu: false,
|
|
4288
|
+
network: true,
|
|
4289
|
+
mime: "application/vnd.devthink.net+json",
|
|
4290
|
+
archs: LINUXARCHS,
|
|
4291
|
+
osprofiles: LINUXOSPROFILES,
|
|
4292
|
+
resources: INPROCENVELOPE,
|
|
4293
|
+
execute: hostspawnseat({
|
|
4294
|
+
slug: "networkwireguard",
|
|
4295
|
+
num: 81,
|
|
4296
|
+
lane: "sandbox",
|
|
4297
|
+
binary: "wg",
|
|
4298
|
+
argv: () => ["wg", "setconf", "wg0", "/sandbox/wireguard/wg0.conf"],
|
|
4299
|
+
hardening: ["peer-allowlist", "key-gate"],
|
|
4300
|
+
probe: "the host must carry wg (the wireguard tunnel)",
|
|
4301
|
+
}),
|
|
4302
|
+
},
|
|
4303
|
+
{
|
|
4304
|
+
num: 82,
|
|
4305
|
+
name: "tmpfs",
|
|
4306
|
+
slug: "ramtmpfs",
|
|
4307
|
+
lane: "sandbox",
|
|
4308
|
+
isolation: "process",
|
|
4309
|
+
state: "live",
|
|
4310
|
+
drivers: ["tmpfs"],
|
|
4311
|
+
hardening: ["oom-guard"],
|
|
4312
|
+
boot: "hot",
|
|
4313
|
+
bootms: 5,
|
|
4314
|
+
gpu: false,
|
|
4315
|
+
network: false,
|
|
4316
|
+
mime: "application/vnd.devthink.ram+json",
|
|
4317
|
+
archs: LINUXARCHS,
|
|
4318
|
+
osprofiles: LINUXOSPROFILES,
|
|
4319
|
+
resources: INPROCENVELOPE,
|
|
4320
|
+
execute: inprocseat({
|
|
4321
|
+
slug: "ramtmpfs",
|
|
4322
|
+
num: 82,
|
|
4323
|
+
lane: "sandbox",
|
|
4324
|
+
operate: async (request) => {
|
|
4325
|
+
const inputs = (request.inputs ?? {}) as { sizebytes?: number; mountpoint?: string };
|
|
4326
|
+
const plan = tmpfsmountplan(inputs.sizebytes ?? 64 * 1024 * 1024, inputs.mountpoint ?? "/sandbox/tmp");
|
|
4327
|
+
return {
|
|
4328
|
+
stdout: `tmpfs: the mount plan answered (${plan.argv.join(" ")}) — ${plan.oomguard}`,
|
|
4329
|
+
artifact: { argv: plan.argv, oomguard: plan.oomguard },
|
|
4330
|
+
};
|
|
4331
|
+
},
|
|
4332
|
+
}),
|
|
4333
|
+
},
|
|
4334
|
+
{
|
|
4335
|
+
num: 83,
|
|
4336
|
+
name: "zero copy map",
|
|
4337
|
+
slug: "rammmap",
|
|
4338
|
+
lane: "sandbox",
|
|
4339
|
+
isolation: "process",
|
|
4340
|
+
state: "live",
|
|
4341
|
+
drivers: ["mmap"],
|
|
4342
|
+
hardening: ["oom-guard"],
|
|
4343
|
+
boot: "hot",
|
|
4344
|
+
bootms: 5,
|
|
4345
|
+
gpu: false,
|
|
4346
|
+
network: false,
|
|
4347
|
+
mime: "application/vnd.devthink.ram+json",
|
|
4348
|
+
archs: ALLARCHS,
|
|
4349
|
+
osprofiles: ALLOSPROFILES,
|
|
4350
|
+
resources: INPROCENVELOPE,
|
|
4351
|
+
execute: inprocseat({
|
|
4352
|
+
slug: "rammmap",
|
|
4353
|
+
num: 83,
|
|
4354
|
+
lane: "sandbox",
|
|
4355
|
+
operate: async (request) => {
|
|
4356
|
+
const inputs = (request.inputs ?? {}) as { offset?: number; length?: number };
|
|
4357
|
+
const window = mmapwindowplan(inputs.offset ?? 0, inputs.length ?? 65536);
|
|
4358
|
+
return {
|
|
4359
|
+
stdout: `mmap: the aligned window answered (offset ${window.pageoffset}, ${window.pages} pages, ${window.advice})`,
|
|
4360
|
+
artifact: window,
|
|
4361
|
+
};
|
|
4362
|
+
},
|
|
4363
|
+
}),
|
|
4364
|
+
},
|
|
4365
|
+
{
|
|
4366
|
+
num: 84,
|
|
4367
|
+
name: "overlayfs",
|
|
4368
|
+
slug: "ramoverlayfs",
|
|
4369
|
+
lane: "sandbox",
|
|
4370
|
+
isolation: "process",
|
|
4371
|
+
state: "live",
|
|
4372
|
+
drivers: ["overlayfs"],
|
|
4373
|
+
hardening: ["oom-guard"],
|
|
4374
|
+
boot: "hot",
|
|
4375
|
+
bootms: 10,
|
|
4376
|
+
gpu: false,
|
|
4377
|
+
network: false,
|
|
4378
|
+
mime: "application/vnd.devthink.ram+json",
|
|
4379
|
+
archs: LINUXARCHS,
|
|
4380
|
+
osprofiles: LINUXOSPROFILES,
|
|
4381
|
+
resources: INPROCENVELOPE,
|
|
4382
|
+
execute: inprocseat({
|
|
4383
|
+
slug: "ramoverlayfs",
|
|
4384
|
+
num: 84,
|
|
4385
|
+
lane: "sandbox",
|
|
4386
|
+
operate: async () => {
|
|
4387
|
+
const argv = overlayfsmountplan("/sandbox/lower", "/sandbox/upper", "/sandbox/work", "/sandbox/merged");
|
|
4388
|
+
return {
|
|
4389
|
+
stdout: `overlayfs: the overlay plan answered (${argv.join(" ")})`,
|
|
4390
|
+
artifact: { argv, layers: 1 },
|
|
4391
|
+
};
|
|
4392
|
+
},
|
|
4393
|
+
}),
|
|
4394
|
+
},
|
|
4395
|
+
{
|
|
4396
|
+
num: 85,
|
|
4397
|
+
name: "zram zstd",
|
|
4398
|
+
slug: "ramzram",
|
|
4399
|
+
lane: "sandbox",
|
|
4400
|
+
isolation: "process",
|
|
4401
|
+
state: "live",
|
|
4402
|
+
drivers: ["zram", "zstd"],
|
|
4403
|
+
hardening: ["oom-guard"],
|
|
4404
|
+
boot: "hot",
|
|
4405
|
+
bootms: 25,
|
|
4406
|
+
gpu: false,
|
|
4407
|
+
network: false,
|
|
4408
|
+
mime: "application/vnd.devthink.ram+json",
|
|
4409
|
+
archs: LINUXARCHS,
|
|
4410
|
+
osprofiles: LINUXOSPROFILES,
|
|
4411
|
+
resources: INPROCENVELOPE,
|
|
4412
|
+
execute: hostspawnseat({
|
|
4413
|
+
slug: "ramzram",
|
|
4414
|
+
num: 85,
|
|
4415
|
+
lane: "sandbox",
|
|
4416
|
+
binary: "modprobe",
|
|
4417
|
+
argv: () => ["modprobe", "zram", "zstd"],
|
|
4418
|
+
hardening: ["oom-guard", "kernel-privilege"],
|
|
4419
|
+
probe: "the host must carry the kernel zram module (the compressed ram seat)",
|
|
4420
|
+
}),
|
|
4421
|
+
},
|
|
4422
|
+
{
|
|
4423
|
+
num: 86,
|
|
4424
|
+
name: "swapfile",
|
|
4425
|
+
slug: "ramswapfile",
|
|
4426
|
+
lane: "sandbox",
|
|
4427
|
+
isolation: "process",
|
|
4428
|
+
state: "live",
|
|
4429
|
+
drivers: ["swapfile"],
|
|
4430
|
+
hardening: ["oom-guard"],
|
|
4431
|
+
boot: "hot",
|
|
4432
|
+
bootms: 25,
|
|
4433
|
+
gpu: false,
|
|
4434
|
+
network: false,
|
|
4435
|
+
mime: "application/vnd.devthink.ram+json",
|
|
4436
|
+
archs: LINUXARCHS,
|
|
4437
|
+
osprofiles: LINUXOSPROFILES,
|
|
4438
|
+
resources: INPROCENVELOPE,
|
|
4439
|
+
execute: hostspawnseat({
|
|
4440
|
+
slug: "ramswapfile",
|
|
4441
|
+
num: 86,
|
|
4442
|
+
lane: "sandbox",
|
|
4443
|
+
binary: "mkswap",
|
|
4444
|
+
argv: () => ["mkswap", "/sandbox/swapfile"],
|
|
4445
|
+
hardening: ["oom-guard", "kernel-privilege"],
|
|
4446
|
+
probe: "the host must carry mkswap (the swap seat)",
|
|
4447
|
+
}),
|
|
4448
|
+
},
|
|
4449
|
+
{
|
|
4450
|
+
num: 87,
|
|
4451
|
+
name: "sandboxed chromium",
|
|
4452
|
+
slug: "browserserver",
|
|
4453
|
+
lane: "sandbox",
|
|
4454
|
+
isolation: "namespace",
|
|
4455
|
+
state: "host",
|
|
4456
|
+
drivers: ["chromium-headless", "playwright"],
|
|
4457
|
+
hardening: ["sandboxed-tabs"],
|
|
4458
|
+
boot: "warm",
|
|
4459
|
+
bootms: 300,
|
|
4460
|
+
gpu: false,
|
|
4461
|
+
network: true,
|
|
4462
|
+
mime: "application/vnd.devthink.browser+json",
|
|
4463
|
+
archs: LINUXARCHS,
|
|
4464
|
+
osprofiles: LINUXOSPROFILES,
|
|
4465
|
+
resources: BASEENVELOPE,
|
|
4466
|
+
execute: hostspawnseat({
|
|
4467
|
+
slug: "browserserver",
|
|
4468
|
+
num: 87,
|
|
4469
|
+
lane: "sandbox",
|
|
4470
|
+
binary: "chromium",
|
|
4471
|
+
argv: ({ code }) => [
|
|
4472
|
+
"chromium",
|
|
4473
|
+
"--headless",
|
|
4474
|
+
"--no-sandbox",
|
|
4475
|
+
"--disable-gpu",
|
|
4476
|
+
"--disable-dev-shm-usage",
|
|
4477
|
+
"--remote-debugging-port=0",
|
|
4478
|
+
...(code.startsWith("http") ? [code] : ["about:blank"]),
|
|
4479
|
+
],
|
|
4480
|
+
hardening: ["sandboxed-tabs", "headless-only"],
|
|
4481
|
+
probe: "the host must carry chromium (the sandbox-side browser — never the visitor browser)",
|
|
4482
|
+
}),
|
|
4483
|
+
},
|
|
4484
|
+
{
|
|
4485
|
+
num: 88,
|
|
4486
|
+
name: "xvfb display",
|
|
4487
|
+
slug: "screenxvfb",
|
|
4488
|
+
lane: "sandbox",
|
|
4489
|
+
isolation: "process",
|
|
4490
|
+
state: "live",
|
|
4491
|
+
drivers: ["xvfb"],
|
|
4492
|
+
hardening: ["headless-only"],
|
|
4493
|
+
boot: "hot",
|
|
4494
|
+
bootms: 20,
|
|
4495
|
+
gpu: false,
|
|
4496
|
+
network: false,
|
|
4497
|
+
mime: "application/vnd.devthink.screen+json",
|
|
4498
|
+
archs: LINUXARCHS,
|
|
4499
|
+
osprofiles: LINUXOSPROFILES,
|
|
4500
|
+
resources: INPROCENVELOPE,
|
|
4501
|
+
execute: hostspawnseat({
|
|
4502
|
+
slug: "screenxvfb",
|
|
4503
|
+
num: 88,
|
|
4504
|
+
lane: "sandbox",
|
|
4505
|
+
binary: "Xvfb",
|
|
4506
|
+
argv: () => ["Xvfb", ":97", "-ac", "-screen", "0", "1920x1080x24", "-nolisten", "tcp", "+extension", "GLX", "+render", "-noreset"],
|
|
4507
|
+
env: { DISPLAY: ":97" },
|
|
4508
|
+
hardening: ["headless-only", "no-listen-tcp"],
|
|
4509
|
+
probe: "the host must carry Xvfb (the virtual display — never the visitor display)",
|
|
4510
|
+
}),
|
|
4511
|
+
},
|
|
4512
|
+
{
|
|
4513
|
+
num: 89,
|
|
4514
|
+
name: "termux",
|
|
4515
|
+
slug: "mobiletermux",
|
|
4516
|
+
lane: "sandbox",
|
|
4517
|
+
isolation: "namespace",
|
|
4518
|
+
state: "live",
|
|
4519
|
+
drivers: ["termux"],
|
|
4520
|
+
hardening: ["emulated-device"],
|
|
4521
|
+
boot: "warm",
|
|
4522
|
+
bootms: 550,
|
|
4523
|
+
gpu: false,
|
|
4524
|
+
network: true,
|
|
4525
|
+
mime: "application/vnd.devthink.mobile+json",
|
|
4526
|
+
archs: ["linux-arm64", "darwin-arm64-v8"],
|
|
4527
|
+
osprofiles: ["android", "grapheneos"],
|
|
4528
|
+
resources: INPROCENVELOPE,
|
|
4529
|
+
execute: inprocseat({
|
|
4530
|
+
slug: "mobiletermux",
|
|
4531
|
+
num: 89,
|
|
4532
|
+
lane: "sandbox",
|
|
4533
|
+
operate: async () => ({
|
|
4534
|
+
stdout:
|
|
4535
|
+
"termux: the android sandbox plan answers (the termux property composition, the emulated device the lane mounts on the edge)",
|
|
4536
|
+
artifact: { platform: "android", device: "emulated", execscope: "edge-host" },
|
|
4537
|
+
}),
|
|
4538
|
+
}),
|
|
4539
|
+
},
|
|
4540
|
+
{
|
|
4541
|
+
num: 90,
|
|
4542
|
+
name: "pod session",
|
|
4543
|
+
slug: "podsession",
|
|
4544
|
+
lane: "sandbox",
|
|
4545
|
+
isolation: "namespace",
|
|
4546
|
+
state: "host",
|
|
4547
|
+
drivers: ["podsession", "shared-namespace"],
|
|
4548
|
+
hardening: ["pod-isolation", "dropped-caps"],
|
|
4549
|
+
boot: "warm",
|
|
4550
|
+
bootms: 400,
|
|
4551
|
+
gpu: false,
|
|
4552
|
+
network: true,
|
|
4553
|
+
mime: "application/vnd.devthink.pod+json",
|
|
4554
|
+
archs: LINUXARCHS,
|
|
4555
|
+
osprofiles: LINUXOSPROFILES,
|
|
4556
|
+
resources: BASEENVELOPE,
|
|
4557
|
+
execute: hostspawnseat({
|
|
4558
|
+
slug: "podsession",
|
|
4559
|
+
num: 90,
|
|
4560
|
+
lane: "sandbox",
|
|
4561
|
+
binary: "podsession",
|
|
4562
|
+
argv: ({ file }) => [
|
|
4563
|
+
"podsession",
|
|
4564
|
+
"run",
|
|
4565
|
+
"--pod",
|
|
4566
|
+
"edge-pod-90",
|
|
4567
|
+
"--network",
|
|
4568
|
+
"none",
|
|
4569
|
+
"--read-only",
|
|
4570
|
+
"--",
|
|
4571
|
+
file,
|
|
4572
|
+
],
|
|
4573
|
+
hardening: ["pod-isolation", "dropped-caps", "no-new-privileges"],
|
|
4574
|
+
probe: "the host must carry podsession (the pod session seat — the co-located container pod the sandbox lane drives on the edge, never the visitor machine)",
|
|
4575
|
+
}),
|
|
4576
|
+
},
|
|
4577
|
+
];
|
|
4578
|
+
|
|
4579
|
+
/** The count of the ninety — derived from the table's real length
|
|
4580
|
+
* (never a hardcoded line, so the count cannot drift from the table). */
|
|
4581
|
+
export const MODALITYCOUNT = MODALITYENGINE.length;
|
|
4582
|
+
|
|
4583
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
4584
|
+
/* Section 9: the dispatch — executemodality and the resolvers. */
|
|
4585
|
+
/* ════════════════════════════════════════════════════════════════════ */
|
|
4586
|
+
|
|
4587
|
+
/**
|
|
4588
|
+
* Answers one modality of the ninety by its numeric id (1 to 90).
|
|
4589
|
+
*
|
|
4590
|
+
* @param num the numeric id of the seat.
|
|
4591
|
+
* @returns the spec, or undefined when the id is outside the ninety.
|
|
4592
|
+
*/
|
|
4593
|
+
export function modalitybynum(num: number): ModalitySpec | undefined {
|
|
4594
|
+
return MODALITYENGINE.find((spec) => spec.num === num);
|
|
4595
|
+
}
|
|
4596
|
+
|
|
4597
|
+
/**
|
|
4598
|
+
* Answers one modality of the ninety by its slug.
|
|
4599
|
+
*
|
|
4600
|
+
* @param slug the stable slug of the seat.
|
|
4601
|
+
* @returns the spec, or undefined when the slug is unknown.
|
|
4602
|
+
*/
|
|
4603
|
+
export function modalitybyslug(slug: string): ModalitySpec | undefined {
|
|
4604
|
+
return MODALITYENGINE.find((spec) => spec.slug === slug);
|
|
4605
|
+
}
|
|
4606
|
+
|
|
4607
|
+
/**
|
|
4608
|
+
* Executes one modality of the ninety — the master dispatch of the
|
|
4609
|
+
* engine: the visitor guard rides first (the critical law: nothing
|
|
4610
|
+
* ever touches the visitor machine), the seat resolves by numeric id
|
|
4611
|
+
* or slug, the executor runs (the real implementation of the seat: the
|
|
4612
|
+
* spawn plan, the cgroup flags, the queue orchestration, the
|
|
4613
|
+
* in-process engine or the provider call) and the honest result
|
|
4614
|
+
* answers.
|
|
4615
|
+
*
|
|
4616
|
+
* @param id the numeric id (1 to 90) or the slug of the seat.
|
|
4617
|
+
* @param request the run request (code, inputs, deadline, profile, arch).
|
|
4618
|
+
* @returns the honest run result.
|
|
4619
|
+
*/
|
|
4620
|
+
export async function executemodality(id: number | string, request: ModalityRunRequest): Promise<ModalityRunResult> {
|
|
4621
|
+
visitorguard(request);
|
|
4622
|
+
const spec = typeof id === "number" ? modalitybynum(id) : modalitybyslug(id);
|
|
4623
|
+
if (spec === undefined) {
|
|
4624
|
+
throw Object.assign(
|
|
4625
|
+
new Error(`the modality ${typeof id === "number" ? `#${id}` : id} is unknown to the ninety (the ids run 1 to 90)`),
|
|
4626
|
+
{ code: "MODALITY_UNKNOWN" },
|
|
4627
|
+
);
|
|
4628
|
+
}
|
|
4629
|
+
return spec.execute(request);
|
|
4630
|
+
}
|
|
4631
|
+
|
|
4632
|
+
/**
|
|
4633
|
+
* Answers the honest census of the ninety — the report surface the
|
|
4634
|
+
* console, the tests and the plan report read: the count per lane, the
|
|
4635
|
+
* count per state and the plan verdict (never a hardcoded line).
|
|
4636
|
+
*/
|
|
4637
|
+
export function modalitycensus(): {
|
|
4638
|
+
plancount: number;
|
|
4639
|
+
count: number;
|
|
4640
|
+
lanes: Record<ModalityLane, number>;
|
|
4641
|
+
live: number;
|
|
4642
|
+
host: number;
|
|
4643
|
+
planned: number;
|
|
4644
|
+
plananswered: boolean;
|
|
4645
|
+
} {
|
|
4646
|
+
const lanes: Record<ModalityLane, number> = { sandbox: 0, vm: 0, runner: 0, hardware: 0 };
|
|
4647
|
+
let live = 0;
|
|
4648
|
+
let host = 0;
|
|
4649
|
+
let planned = 0;
|
|
4650
|
+
for (const spec of MODALITYENGINE) {
|
|
4651
|
+
lanes[spec.lane] += 1;
|
|
4652
|
+
if (spec.state === "live") live += 1;
|
|
4653
|
+
else if (spec.state === "host") host += 1;
|
|
4654
|
+
else planned += 1;
|
|
4655
|
+
}
|
|
4656
|
+
return {
|
|
4657
|
+
plancount: MODALITYPLANCOUNT,
|
|
4658
|
+
count: MODALITYENGINE.length,
|
|
4659
|
+
lanes,
|
|
4660
|
+
live,
|
|
4661
|
+
host,
|
|
4662
|
+
planned,
|
|
4663
|
+
plananswered: MODALITYENGINE.length >= MODALITYPLANCOUNT,
|
|
4664
|
+
};
|
|
4665
|
+
}
|