@wenathlan/saddle 2.0.154 → 2.0.159
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 +167 -1334
- package/README.md +59 -60
- package/biome.jsonc +3 -3
- package/browser.ts +482 -0
- package/cli.ts +274 -0
- package/distribution.ts +731 -0
- package/docker.config +29 -8
- package/docs/{architecture.1.8.19.mmd → architecture1819.md} +6 -0
- package/docs/artifactavailability.md +2 -2
- package/docs/browser.md +18 -1
- package/docs/{containerplatforms.2.1.0.md → containerplatforms210.md} +1 -1
- package/docs/{documentmap.1.8.12.md → documentmap1812.md} +1 -1
- package/docs/extension.md +13 -2
- package/docs/features.md +5 -0
- package/docs/libraryapi.md +1 -1
- package/docs/{ninety.modalities.md → ninetymodalities.md} +87 -4
- package/docs/{planning.ninety.md → planningninety.md} +6 -6
- package/docs/{releasenotes.1.8.13.md → releasenotes1813.md} +2 -2
- package/docs/{releasenotes.1.8.14.md → releasenotes1814.md} +1 -1
- package/docs/{repo.synthesis.1.8.16.md → reposynthesis1816.md} +1 -1
- package/docs/todo.md +2 -2
- package/docs/{todo.1.8.15.md → todo1815.md} +1 -1
- package/docs/{todo.1.8.16.md → todo1816.md} +5 -5
- package/execution.ts +677 -0
- package/foundation.ts +314 -0
- package/index.ts +36 -34
- package/isolation.ts +84 -0
- package/modalityregistry.ts +189 -145
- package/modes.ts +170 -0
- package/mttg.config +1 -1
- package/orchestrator.ts +49 -15
- package/package.json +105 -89
- package/qemu.config +2 -2
- package/sandbox.ts +3279 -0
- package/scheduler.ts +1 -1
- package/server.ts +52 -0
- package/tsconfig.json +12 -13
- package/virtual.ts +7528 -0
- package/workloadlaw.ts +293 -0
- package/dist/App.d.ts +0 -5
- package/dist/App.js +0 -52
- package/dist/Sol/Sol.d.ts +0 -3
- package/dist/Sol/Sol.js +0 -46
- package/dist/Sol/agentbrowser/agentbrowser.d.ts +0 -9
- package/dist/Sol/agentbrowser/agentbrowser.js +0 -121
- package/dist/Sol/architecture/architecture.d.ts +0 -9
- package/dist/Sol/architecture/architecture.js +0 -93
- package/dist/Sol/compute/compute.d.ts +0 -9
- package/dist/Sol/compute/compute.js +0 -22
- package/dist/Sol/console/console.d.ts +0 -15
- package/dist/Sol/console/console.js +0 -292
- package/dist/Sol/dashboard/dashboard.d.ts +0 -10
- package/dist/Sol/dashboard/dashboard.js +0 -181
- package/dist/Sol/docs/docs.d.ts +0 -9
- package/dist/Sol/docs/docs.js +0 -28
- package/dist/Sol/home/home.d.ts +0 -35
- package/dist/Sol/home/home.js +0 -239
- package/dist/Sol/integrations/integrations.d.ts +0 -9
- package/dist/Sol/integrations/integrations.js +0 -83
- package/dist/Sol/intro/intro.d.ts +0 -7
- package/dist/Sol/intro/intro.js +0 -102
- package/dist/Sol/login/login.d.ts +0 -16
- package/dist/Sol/login/login.js +0 -49
- package/dist/Sol/notfound/notfound.d.ts +0 -9
- package/dist/Sol/notfound/notfound.js +0 -15
- package/dist/Sol/onboarding/onboarding.d.ts +0 -25
- package/dist/Sol/onboarding/onboarding.js +0 -60
- package/dist/Sol/playground/playground.d.ts +0 -9
- package/dist/Sol/playground/playground.js +0 -127
- package/dist/Sol/register/register.d.ts +0 -16
- package/dist/Sol/register/register.js +0 -193
- package/dist/Sol/shell/Shell.d.ts +0 -124
- package/dist/Sol/shell/Shell.js +0 -335
- package/dist/Sol/wingets/ComputeBootladder.d.ts +0 -6
- package/dist/Sol/wingets/ComputeBootladder.js +0 -71
- package/dist/Sol/wingets/ComputeModalitycatalog.d.ts +0 -7
- package/dist/Sol/wingets/ComputeModalitycatalog.js +0 -71
- package/dist/Sol/wingets/ConsoleApibadge.d.ts +0 -7
- package/dist/Sol/wingets/ConsoleApibadge.js +0 -5
- package/dist/Sol/wingets/ConsoleEdgelaw.d.ts +0 -6
- package/dist/Sol/wingets/ConsoleEdgelaw.js +0 -36
- package/dist/Sol/wingets/ConsoleEventstimeline.d.ts +0 -19
- package/dist/Sol/wingets/ConsoleEventstimeline.js +0 -12
- package/dist/Sol/wingets/ConsoleSnapshotcontrols.d.ts +0 -8
- package/dist/Sol/wingets/ConsoleSnapshotcontrols.js +0 -8
- package/dist/Sol/wingets/ConsoleSpecpanel.d.ts +0 -21
- package/dist/Sol/wingets/ConsoleSpecpanel.js +0 -75
- package/dist/Sol/wingets/ConsoleTerminal.d.ts +0 -22
- package/dist/Sol/wingets/ConsoleTerminal.js +0 -77
- package/dist/Sol/wingets/DashboardAccountpanel.d.ts +0 -18
- package/dist/Sol/wingets/DashboardAccountpanel.js +0 -65
- package/dist/Sol/wingets/DashboardAdminpanel.d.ts +0 -6
- package/dist/Sol/wingets/DashboardAdminpanel.js +0 -90
- package/dist/Sol/wingets/DashboardAuditlog.d.ts +0 -8
- package/dist/Sol/wingets/DashboardAuditlog.js +0 -50
- package/dist/Sol/wingets/DashboardCreatesandboxform.d.ts +0 -10
- package/dist/Sol/wingets/DashboardCreatesandboxform.js +0 -71
- package/dist/Sol/wingets/DashboardEventstimeline.d.ts +0 -8
- package/dist/Sol/wingets/DashboardEventstimeline.js +0 -83
- package/dist/Sol/wingets/DashboardGlobalsandboxestable.d.ts +0 -8
- package/dist/Sol/wingets/DashboardGlobalsandboxestable.js +0 -57
- package/dist/Sol/wingets/DashboardMeshnodestable.d.ts +0 -8
- package/dist/Sol/wingets/DashboardMeshnodestable.js +0 -103
- package/dist/Sol/wingets/DashboardSandboxlist.d.ts +0 -10
- package/dist/Sol/wingets/DashboardSandboxlist.js +0 -163
- package/dist/Sol/wingets/DashboardUserstable.d.ts +0 -8
- package/dist/Sol/wingets/DashboardUserstable.js +0 -78
- package/dist/Sol/wingets/HomeEntrygate.d.ts +0 -23
- package/dist/Sol/wingets/HomeEntrygate.js +0 -73
- package/dist/Sol/wingets/HomeMetricstrip.d.ts +0 -9
- package/dist/Sol/wingets/HomeMetricstrip.js +0 -4
- package/dist/Sol/wingets/HomeRuntimediagram.d.ts +0 -1
- package/dist/Sol/wingets/HomeRuntimediagram.js +0 -22
- package/dist/Sol/wingets/LoginForm.d.ts +0 -19
- package/dist/Sol/wingets/LoginForm.js +0 -141
- package/dist/Sol/wingets/RegisterPasswordconfirm.d.ts +0 -13
- package/dist/Sol/wingets/RegisterPasswordconfirm.js +0 -16
- package/dist/Sol/wingets/RegisterPasswordmeter.d.ts +0 -19
- package/dist/Sol/wingets/RegisterPasswordmeter.js +0 -65
- package/dist/Sol/wingets/RegisterUsernamerules.d.ts +0 -11
- package/dist/Sol/wingets/RegisterUsernamerules.js +0 -16
- package/dist/Sol/wingets/index.d.ts +0 -39
- package/dist/Sol/wingets/index.js +0 -39
- package/dist/alternatives.d.ts +0 -508
- package/dist/alternatives.js +0 -950
- package/dist/api.d.ts +0 -154
- package/dist/api.js +0 -174
- package/dist/auth.d.ts +0 -268
- package/dist/auth.js +0 -476
- package/dist/automation.d.ts +0 -425
- package/dist/automation.js +0 -566
- package/dist/browser.d.ts +0 -561
- package/dist/browser.js +0 -1058
- package/dist/capacitor.config.d.ts +0 -45
- package/dist/capacitor.config.js +0 -48
- package/dist/catalog.d.ts +0 -43
- package/dist/catalog.js +0 -44
- package/dist/cli.d.ts +0 -16
- package/dist/cli.js +0 -168
- package/dist/communication.d.ts +0 -181
- package/dist/communication.js +0 -376
- package/dist/compute.d.ts +0 -1160
- package/dist/compute.js +0 -2272
- package/dist/dashboardlib.d.ts +0 -58
- package/dist/dashboardlib.js +0 -161
- package/dist/db.d.ts +0 -569
- package/dist/db.js +0 -1080
- package/dist/distribution.d.ts +0 -528
- package/dist/distribution.js +0 -810
- package/dist/drizzle.config.d.ts +0 -20
- package/dist/drizzle.config.js +0 -18
- package/dist/edgelandes.d.ts +0 -70
- package/dist/edgelandes.host.d.ts +0 -26
- package/dist/edgelandes.host.js +0 -1089
- package/dist/edgelandes.inproc.d.ts +0 -27
- package/dist/edgelandes.inproc.js +0 -952
- package/dist/edgelandes.js +0 -1133
- package/dist/edgelandes.ninety.d.ts +0 -31
- package/dist/edgelandes.ninety.js +0 -174
- package/dist/edgelandes.virtual.d.ts +0 -25
- package/dist/edgelandes.virtual.js +0 -629
- package/dist/edgeplane.d.ts +0 -121
- package/dist/edgeplane.js +0 -341
- package/dist/examplelocaljob.d.ts +0 -1
- package/dist/examplelocaljob.js +0 -14
- package/dist/execution.d.ts +0 -529
- package/dist/execution.js +0 -760
- package/dist/family.d.ts +0 -16
- package/dist/family.js +0 -42
- package/dist/familyurl.d.ts +0 -21
- package/dist/familyurl.js +0 -24
- package/dist/format.d.ts +0 -18
- package/dist/format.js +0 -59
- package/dist/foundation.d.ts +0 -172
- package/dist/foundation.js +0 -312
- package/dist/index.d.ts +0 -735
- package/dist/index.js +0 -1020
- package/dist/integration.d.ts +0 -235
- package/dist/integration.js +0 -228
- package/dist/intelligence.d.ts +0 -93
- package/dist/intelligence.js +0 -144
- package/dist/isolation.d.ts +0 -128
- package/dist/isolation.js +0 -95
- package/dist/labels.d.ts +0 -665
- package/dist/labels.js +0 -707
- package/dist/localauth.d.ts +0 -127
- package/dist/localauth.js +0 -413
- package/dist/media.d.ts +0 -343
- package/dist/media.js +0 -985
- package/dist/mesh.d.ts +0 -177
- package/dist/mesh.js +0 -468
- package/dist/modalityregistry.d.ts +0 -109
- package/dist/modalityregistry.js +0 -1386
- package/dist/modes.d.ts +0 -358
- package/dist/modes.js +0 -133
- package/dist/onboardingstate.d.ts +0 -79
- package/dist/onboardingstate.js +0 -282
- package/dist/orchestrator.d.ts +0 -1338
- package/dist/orchestrator.js +0 -4310
- package/dist/packager/targetcli.js +0 -4
- package/dist/paths.d.ts +0 -2
- package/dist/paths.js +0 -14
- package/dist/performance.d.ts +0 -415
- package/dist/performance.js +0 -826
- package/dist/quantum.d.ts +0 -809
- package/dist/quantum.js +0 -2072
- package/dist/render.d.ts +0 -380
- package/dist/render.js +0 -1072
- package/dist/runall.d.ts +0 -2
- package/dist/runall.js +0 -39
- package/dist/runtimeprobe.d.ts +0 -4
- package/dist/runtimeprobe.js +0 -8
- package/dist/sandbox.d.ts +0 -355
- package/dist/sandbox.js +0 -1721
- package/dist/sandboxenvprofile.d.ts +0 -29
- package/dist/sandboxenvprofile.js +0 -89
- package/dist/sandboxprofile.d.ts +0 -137
- package/dist/sandboxprofile.js +0 -241
- package/dist/sandboxqueue.d.ts +0 -108
- package/dist/sandboxqueue.js +0 -201
- package/dist/sandboxquota.d.ts +0 -121
- package/dist/sandboxquota.js +0 -236
- package/dist/scheduler.d.ts +0 -595
- package/dist/scheduler.js +0 -1258
- package/dist/scriptlanes.d.ts +0 -182
- package/dist/scriptlanes.js +0 -660
- package/dist/scripts.buildengine.d.ts +0 -1
- package/dist/scripts.buildengine.js +0 -39
- package/dist/scripts.securitygate.d.ts +0 -1
- package/dist/scripts.securitygate.js +0 -68
- package/dist/scripts.validateflatnative.d.ts +0 -1
- package/dist/scripts.validateflatnative.js +0 -72
- package/dist/security.d.ts +0 -500
- package/dist/security.js +0 -1169
- package/dist/seedcatalog.d.ts +0 -13
- package/dist/seedcatalog.js +0 -46
- package/dist/server.d.ts +0 -20
- package/dist/server.js +0 -53
- package/dist/speccatalog.d.ts +0 -31
- package/dist/speccatalog.js +0 -163
- package/dist/tiers.d.ts +0 -1001
- package/dist/tiers.js +0 -2169
- package/dist/utils.d.ts +0 -2
- package/dist/utils.js +0 -5
- package/dist/vaulthandoff.d.ts +0 -141
- package/dist/vaulthandoff.js +0 -444
- package/dist/virtual.d.ts +0 -640
- package/dist/virtual.js +0 -1284
- package/dist/virtualcpu.d.ts +0 -315
- package/dist/virtualcpu.js +0 -1180
- package/dist/virtualgpu.d.ts +0 -344
- package/dist/virtualgpu.js +0 -1000
- package/dist/virtualization.d.ts +0 -606
- package/dist/virtualization.js +0 -1186
- package/dist/virtualmemory.d.ts +0 -817
- package/dist/virtualmemory.js +0 -1795
- package/dist/vite.config.d.ts +0 -3
- package/dist/vite.config.js +0 -48
- package/dist/vitest.config.d.ts +0 -2
- package/dist/vitest.config.js +0 -25
- package/dist/webserver.d.ts +0 -166
- package/dist/webserver.js +0 -2226
- package/dist/workloadlaw.d.ts +0 -133
- package/dist/workloadlaw.js +0 -193
- package/docs/.gitkeep +0 -0
- package/docs/architecture.1.8.19.md +0 -76
- package/docs/logs/examplesession.json +0 -54
- package/docs/repo.analysis.1.8.21.json +0 -2617
- package/docs/repo.candidates.1.8.21.jsonl +0 -666
- package/docs/repo.selected.1.8.21.json +0 -786
- package/manifest.json +0 -20
- package/media.ts +0 -1337
- package/virtualcpu.ts +0 -1274
- package/virtualgpu.ts +0 -1182
- package/virtualization.ts +0 -1628
- package/virtualmemory.ts +0 -2303
- /package/docs/{artifactresearch.1.8.9.md → artifactresearch189.md} +0 -0
- /package/docs/{batches.1816.to.1820.md → batches1816to1820.md} +0 -0
- /package/docs/{brancharchive.2026.08.18.md → brancharchive20260818.md} +0 -0
- /package/docs/{containerplatforms.1.8.17.md → containerplatforms1817.md} +0 -0
- /package/docs/{e2ugh.engine.md → e2ughengine.md} +0 -0
- /package/docs/{flatbuildresearch.1.8.11.md → flatbuildresearch1811.md} +0 -0
- /package/docs/{licenseaudit.1.8.12.md → licenseaudit1812.md} +0 -0
- /package/docs/{mobileconversionresearch.1.8.10.md → mobileconversionresearch1810.md} +0 -0
- /package/docs/{nativeidentityresearch.1.8.11.md → nativeidentityresearch1811.md} +0 -0
- /package/docs/{native.wrappers.md → nativewrappers.md} +0 -0
- /package/docs/{planning.1.8.18.md → planning1818.md} +0 -0
- /package/docs/{planning.1.8.19.md → planning1819.md} +0 -0
- /package/docs/{planning.engine.md → planningengine.md} +0 -0
- /package/docs/{releasenotes.1.8.12.md → releasenotes1812.md} +0 -0
- /package/docs/{releasenotes.1.8.15.md → releasenotes1815.md} +0 -0
- /package/docs/{releasenotes.1.8.16.md → releasenotes1816.md} +0 -0
- /package/docs/{releasenotes.1.8.17.md → releasenotes1817.md} +0 -0
- /package/docs/{releasenotes.1.8.18.md → releasenotes1818.md} +0 -0
- /package/docs/{releasenotes.1.8.19.md → releasenotes1819.md} +0 -0
- /package/docs/{reorganization.1.8.8.md → reorganization188.md} +0 -0
- /package/docs/{reorganization.1.8.9.md → reorganization189.md} +0 -0
- /package/docs/{repo.research.1.8.16.md → reporesearch1816.md} +0 -0
- /package/docs/{repo.research.1.8.21.md → reporesearch1821.md} +0 -0
- /package/docs/{repo.synthesis.1.8.21.md → reposynthesis1821.md} +0 -0
- /package/docs/{research.1.8.18.isolation.md → research1818isolation.md} +0 -0
- /package/docs/{research.1.8.19.virtual.browser.md → research1819virtualbrowser.md} +0 -0
- /package/docs/{scanningresearch.1.8.11.md → scanningresearch1811.md} +0 -0
- /package/docs/{securityaudit.1.8.7.md → securityaudit187.md} +0 -0
- /package/docs/{securityreferences.1.8.11.md → securityreferences1811.md} +0 -0
- /package/docs/{securityresearch.1.8.11.md → securityresearch1811.md} +0 -0
- /package/docs/{signingresearch.1.8.12.md → signingresearch1812.md} +0 -0
- /package/docs/{tauri.glib.risk.1.8.11.md → tauriglibrisk1811.md} +0 -0
- /package/docs/{todo.1.8.18.md → todo1818.md} +0 -0
- /package/docs/{todo.1.8.19.md → todo1819.md} +0 -0
- /package/docs/{toolchainresearch.1.8.9.md → toolchainresearch189.md} +0 -0
- /package/docs/{web.duplicatecleanup.md → webduplicatecleanup.md} +0 -0
- /package/docs/{web.ideas.md → webideas.md} +0 -0
- /package/docs/{workflowimprovements.2026.08.19.md → workflowimprovements20260819.md} +0 -0
package/dist/edgelandes.js
DELETED
|
@@ -1,1133 +0,0 @@
|
|
|
1
|
-
// # edgelandes.ts — the modality lane executors of the edge plane: one
|
|
2
|
-
// executor per live modality, every one running server-side inside the
|
|
3
|
-
// engine process or through a host runtime the engine spawns (the
|
|
4
|
-
// owner's law: the sandbox and the saddle modalities act for the system
|
|
5
|
-
// over the internet, the visitor browser, gpu and cpu are never touched).
|
|
6
|
-
// The executors are the honest lane layer of the edge plane: the in
|
|
7
|
-
// process lanes ride node:vm, the engine's own webassembly and the babel
|
|
8
|
-
// transform surface; the host lanes write the code to a private temp
|
|
9
|
-
// directory and spawn bun, bash, python3 or java under a hard deadline;
|
|
10
|
-
// the engine surface lanes probe docker, exercise the virtual.ts vfs and
|
|
11
|
-
// compile through webpack when the module graph carries it. The ninety
|
|
12
|
-
// law wave (the forty live law) widens the same surfaces: the clr lane
|
|
13
|
-
// spawns dotnet over precompiled assemblies, the container profile lanes
|
|
14
|
-
// compute the memory vector through the engine's own virtualmemory
|
|
15
|
-
// recipe and the ghcr lane pulls only digest-pinned references, the
|
|
16
|
-
// storage lanes register the engine's s3, libsql and mysql adapters when
|
|
17
|
-
// the env configuration carries them, and the ram lanes apply the tiers
|
|
18
|
-
// kernel recipes through the privilege ladder the host answers. Every
|
|
19
|
-
// absence answers an honest coded error — a lane whose runtime is not on
|
|
20
|
-
// the host says so, a lane never fakes a run. Pure node builtins ride
|
|
21
|
-
// the static import surface (the webserver's plain node host strips
|
|
22
|
-
// types and resolves only .ts specifiers); the engine root domain
|
|
23
|
-
// (virtual.ts) and the optional runtimes (@babel/core, webpack) load
|
|
24
|
-
// lazily inside the executors so a missing module degrades that one
|
|
25
|
-
// lane, never the engine boot.
|
|
26
|
-
import { spawn, spawnSync } from "node:child_process";
|
|
27
|
-
import { createHash, createHmac } from "node:crypto";
|
|
28
|
-
import { existsSync } from "node:fs";
|
|
29
|
-
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
30
|
-
import { createRequire } from "node:module";
|
|
31
|
-
import { tmpdir } from "node:os";
|
|
32
|
-
import { dirname, join } from "node:path";
|
|
33
|
-
import process from "node:process";
|
|
34
|
-
import { fileURLToPath } from "node:url";
|
|
35
|
-
import vm from "node:vm";
|
|
36
|
-
/** creates one coded lane error; the edge dispatcher reads the code and
|
|
37
|
-
* never invents a success a lane did not answer. */
|
|
38
|
-
export function edgelaneerror(code, message, details) {
|
|
39
|
-
const error = new Error(message);
|
|
40
|
-
error.name = "edgelaneerror";
|
|
41
|
-
error.code = code;
|
|
42
|
-
if (details?.stderr !== undefined)
|
|
43
|
-
error.stderr = details.stderr;
|
|
44
|
-
return error;
|
|
45
|
-
}
|
|
46
|
-
/** the safe message of an unknown thrown value (no stack ever rides a lane answer). */
|
|
47
|
-
function errormessage(error) {
|
|
48
|
-
return error instanceof Error ? error.message : String(error ?? "unknown lane failure");
|
|
49
|
-
}
|
|
50
|
-
/** encodes one value as one stdout line (json when possible, string otherwise). */
|
|
51
|
-
function encodeline(value) {
|
|
52
|
-
try {
|
|
53
|
-
const encoded = JSON.stringify(value);
|
|
54
|
-
return encoded === undefined ? String(value) : encoded;
|
|
55
|
-
}
|
|
56
|
-
catch {
|
|
57
|
-
return String(value);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
/* ════════════════════════════════════════════════════════════════════ */
|
|
61
|
-
/* Section 2: the host runtime probes — the honest present checks. */
|
|
62
|
-
/* ════════════════════════════════════════════════════════════════════ */
|
|
63
|
-
/** probes one host binary on PATH synchronously (command -v through
|
|
64
|
-
* bash): the honest present check of the lane table. */
|
|
65
|
-
export function probehostbinary(binary) {
|
|
66
|
-
try {
|
|
67
|
-
const probe = spawnSync("bash", ["-c", `command -v ${binary}`], { timeout: 2000, encoding: "utf8" });
|
|
68
|
-
return probe.status === 0;
|
|
69
|
-
}
|
|
70
|
-
catch {
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
/** probes the docker lane surface: the binary on PATH or the daemon socket. */
|
|
75
|
-
export function probedocker() {
|
|
76
|
-
return probehostbinary("docker") || existsSync("/var/run/docker.sock");
|
|
77
|
-
}
|
|
78
|
-
/** probes the engine's own webassembly (the in process wasm lane). */
|
|
79
|
-
export function probewasm() {
|
|
80
|
-
return typeof WebAssembly === "object" && typeof WebAssembly.Module === "function";
|
|
81
|
-
}
|
|
82
|
-
/** probes the node:vm surface (the in process runner lane). */
|
|
83
|
-
export function probenodevm() {
|
|
84
|
-
return typeof vm.runInNewContext === "function";
|
|
85
|
-
}
|
|
86
|
-
/** probes one engine module through the node resolver (createRequire):
|
|
87
|
-
* the honest check for @babel/core and webpack without loading them. */
|
|
88
|
-
export function probemodule(specifier) {
|
|
89
|
-
try {
|
|
90
|
-
createRequire(import.meta.url).resolve(specifier);
|
|
91
|
-
return true;
|
|
92
|
-
}
|
|
93
|
-
catch {
|
|
94
|
-
return false;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
/** probes the engine vfs module file (virtual.ts beside this lane file):
|
|
98
|
-
* the report states the engine ships the vfs; a host whose module graph
|
|
99
|
-
* cannot load it answers the failure per run, never per report. */
|
|
100
|
-
export function probevirtualmodule() {
|
|
101
|
-
try {
|
|
102
|
-
return existsSync(join(dirname(fileURLToPath(import.meta.url)), "virtual.ts"));
|
|
103
|
-
}
|
|
104
|
-
catch {
|
|
105
|
-
return false;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
/** probes the s3 storage configuration the env carries (the runtime
|
|
109
|
-
* surface of the s3 vault lane): the endpoint, the credentials and the
|
|
110
|
-
* bucket — every required key present or the lane answers absent. */
|
|
111
|
-
export function probes3config() {
|
|
112
|
-
const env = process.env;
|
|
113
|
-
return (typeof env.SADDLE_S3_ENDPOINT === "string" &&
|
|
114
|
-
env.SADDLE_S3_ENDPOINT.length > 0 &&
|
|
115
|
-
typeof env.SADDLE_S3_KEY === "string" &&
|
|
116
|
-
env.SADDLE_S3_KEY.length > 0 &&
|
|
117
|
-
typeof env.SADDLE_S3_SECRET === "string" &&
|
|
118
|
-
env.SADDLE_S3_SECRET.length > 0 &&
|
|
119
|
-
typeof env.SADDLE_S3_BUCKET === "string" &&
|
|
120
|
-
env.SADDLE_S3_BUCKET.length > 0);
|
|
121
|
-
}
|
|
122
|
-
/** probes the libsql storage surface: the @libsql/client module the
|
|
123
|
-
* engine graph carries plus the LIBSQL_URL configuration — the honest
|
|
124
|
-
* present check of the libsql vault lane. */
|
|
125
|
-
export function probelibsqlconfig() {
|
|
126
|
-
return (probemodule("@libsql/client") &&
|
|
127
|
-
typeof process.env.LIBSQL_URL === "string" &&
|
|
128
|
-
process.env.LIBSQL_URL.length > 0);
|
|
129
|
-
}
|
|
130
|
-
/** probes the mysql storage surface: the mysql2 promise module plus the
|
|
131
|
-
* MYSQL_URL configuration — the honest present check of the mysql wire
|
|
132
|
-
* vault lane. */
|
|
133
|
-
export function probemysqlconfig() {
|
|
134
|
-
return (probemodule("mysql2/promise") &&
|
|
135
|
-
typeof process.env.MYSQL_URL === "string" &&
|
|
136
|
-
process.env.MYSQL_URL.length > 0);
|
|
137
|
-
}
|
|
138
|
-
/* ════════════════════════════════════════════════════════════════════ */
|
|
139
|
-
/* Section 3: the shared host execution surface — spawn, deadline, temp. */
|
|
140
|
-
/* ════════════════════════════════════════════════════════════════════ */
|
|
141
|
-
/** the per stream capture ceiling: 256 kb of stdout or stderr, the honest
|
|
142
|
-
* bound every host lane reports under. */
|
|
143
|
-
const OUTPUTCAPBYTES = 262144;
|
|
144
|
-
/** the default probe budget of the presence checks. */
|
|
145
|
-
const PROBEBUDGETMS = 2000;
|
|
146
|
-
/** spawns one host runtime under a hard deadline, captures both streams
|
|
147
|
-
* (capped), kills on timeout and answers the coded failures honestly: a
|
|
148
|
-
* missing binary answers HOST_RUNTIME_ABSENT, a nonzero exit answers
|
|
149
|
-
* RUN_EXITED_NONZERO carrying the stderr, a blown deadline answers
|
|
150
|
-
* RUN_DEADLINE_EXCEEDED. */
|
|
151
|
-
function runhostprocess(command, args, options) {
|
|
152
|
-
return new Promise((resolve, reject) => {
|
|
153
|
-
const child = spawn(command, args, {
|
|
154
|
-
cwd: options.cwd,
|
|
155
|
-
env: options.env === undefined ? process.env : { ...process.env, ...options.env },
|
|
156
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
157
|
-
});
|
|
158
|
-
const outchunks = [];
|
|
159
|
-
const errchunks = [];
|
|
160
|
-
let timedout = false;
|
|
161
|
-
const makecollector = (chunks) => {
|
|
162
|
-
let used = 0;
|
|
163
|
-
return (chunk) => {
|
|
164
|
-
if (used < OUTPUTCAPBYTES) {
|
|
165
|
-
chunks.push(chunk);
|
|
166
|
-
used += chunk.byteLength;
|
|
167
|
-
}
|
|
168
|
-
};
|
|
169
|
-
};
|
|
170
|
-
child.stdout?.on("data", makecollector(outchunks));
|
|
171
|
-
child.stderr?.on("data", makecollector(errchunks));
|
|
172
|
-
const timer = setTimeout(() => {
|
|
173
|
-
timedout = true;
|
|
174
|
-
child.kill("SIGKILL");
|
|
175
|
-
}, options.deadline);
|
|
176
|
-
child.on("error", (error) => {
|
|
177
|
-
clearTimeout(timer);
|
|
178
|
-
reject(edgelaneerror("HOST_RUNTIME_ABSENT", `the host runtime ${command} is absent: ${errormessage(error)}`));
|
|
179
|
-
});
|
|
180
|
-
child.on("close", (code, signal) => {
|
|
181
|
-
clearTimeout(timer);
|
|
182
|
-
const stdout = Buffer.concat(outchunks).toString("utf8");
|
|
183
|
-
const stderr = Buffer.concat(errchunks).toString("utf8");
|
|
184
|
-
if (timedout) {
|
|
185
|
-
reject(edgelaneerror("RUN_DEADLINE_EXCEEDED", `the lane exceeded its ${options.deadline} ms deadline and was killed`, { stderr }));
|
|
186
|
-
}
|
|
187
|
-
else if (code !== 0) {
|
|
188
|
-
reject(edgelaneerror("RUN_EXITED_NONZERO", `the lane process ${command} exited with code ${code ?? signal}`, {
|
|
189
|
-
stderr,
|
|
190
|
-
}));
|
|
191
|
-
}
|
|
192
|
-
else {
|
|
193
|
-
resolve({ stdout, stderr });
|
|
194
|
-
}
|
|
195
|
-
});
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
/** probes one host binary asynchronously and answers its resolved path;
|
|
199
|
-
* the probe never throws — absence is an answer, not a failure. */
|
|
200
|
-
async function probelateruntime(binary, deadline) {
|
|
201
|
-
try {
|
|
202
|
-
const run = await runhostprocess("bash", ["-c", `command -v ${binary}`], {
|
|
203
|
-
cwd: tmpdir(),
|
|
204
|
-
deadline: Math.min(deadline, PROBEBUDGETMS),
|
|
205
|
-
});
|
|
206
|
-
return { present: true, path: run.stdout.trim() };
|
|
207
|
-
}
|
|
208
|
-
catch {
|
|
209
|
-
return { present: false, path: "" };
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
/** runs fn inside one private temp directory (mkdtemp under os tmpdir)
|
|
213
|
-
* and removes it no matter how fn answers — the host lanes never leave
|
|
214
|
-
* workspace bytes behind. */
|
|
215
|
-
async function withtempdir(fn) {
|
|
216
|
-
const dir = await mkdtemp(join(tmpdir(), "saddleedge"));
|
|
217
|
-
try {
|
|
218
|
-
return await fn(dir);
|
|
219
|
-
}
|
|
220
|
-
finally {
|
|
221
|
-
await rm(dir, { recursive: true, force: true });
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
/** writes the run inputs as inputs.json beside the program and answers
|
|
225
|
-
* the env overlay pointing at it (SADDLE_EDGE_INPUTS); host lane
|
|
226
|
-
* programs read their inputs from that file when the caller sent any. */
|
|
227
|
-
async function writeinputsfile(dir, inputs) {
|
|
228
|
-
if (inputs === undefined)
|
|
229
|
-
return undefined;
|
|
230
|
-
const path = join(dir, "inputs.json");
|
|
231
|
-
await writeFile(path, inputs === undefined ? "null" : JSON.stringify(inputs ?? null), "utf8");
|
|
232
|
-
return { SADDLE_EDGE_INPUTS: path };
|
|
233
|
-
}
|
|
234
|
-
/* ════════════════════════════════════════════════════════════════════ */
|
|
235
|
-
/* Section 4: the in process lanes — node:vm, webassembly and babel. */
|
|
236
|
-
/* ════════════════════════════════════════════════════════════════════ */
|
|
237
|
-
/** runs one program inside a fresh node:vm context: the sandbox carries
|
|
238
|
-
* only the capturing console surface and the frozen inputs — no require,
|
|
239
|
-
* no process, no host globals leak into the context (a new vm context
|
|
240
|
-
* ships the pure ecmascript builtins only). The completion value of the
|
|
241
|
-
* program rides the last stdout line so a value-returning program
|
|
242
|
-
* answers its value. */
|
|
243
|
-
function runinvm(code, inputs, deadline) {
|
|
244
|
-
const stdout = [];
|
|
245
|
-
const stderr = [];
|
|
246
|
-
const push = (target) => (...values) => {
|
|
247
|
-
target.push(values.map((value) => (typeof value === "string" ? value : encodeline(value))).join(" "));
|
|
248
|
-
};
|
|
249
|
-
const consolesurface = Object.freeze({
|
|
250
|
-
log: push(stdout),
|
|
251
|
-
info: push(stdout),
|
|
252
|
-
debug: push(stdout),
|
|
253
|
-
warn: push(stderr),
|
|
254
|
-
error: push(stderr),
|
|
255
|
-
});
|
|
256
|
-
const sandbox = { console: consolesurface, inputs: inputs === undefined ? null : Object.freeze(inputs) };
|
|
257
|
-
const completion = vm.runInNewContext(code, sandbox, { timeout: deadline, displayErrors: true });
|
|
258
|
-
let out = stdout.join("\n");
|
|
259
|
-
if (completion !== undefined) {
|
|
260
|
-
out += out.length > 0 ? `\n${encodeline(completion)}` : encodeline(completion);
|
|
261
|
-
}
|
|
262
|
-
return { stdout: out, stderr: stderr.join("\n") };
|
|
263
|
-
}
|
|
264
|
-
/** runner-node: the in process javascript lane through node:vm. */
|
|
265
|
-
async function runnernodelane(code, inputs, deadline) {
|
|
266
|
-
return runinvm(code, inputs, deadline);
|
|
267
|
-
}
|
|
268
|
-
/** runner-wasm: the in process webassembly lane. The code field carries
|
|
269
|
-
* the base64 module bytes; the lane instantiates them with a minimal
|
|
270
|
-
* wasi-shaped import shim (every imported function answers 0 — enough
|
|
271
|
-
* for a trivial module) and calls the first exported zero parameter
|
|
272
|
-
* function, answering its return value on stdout. */
|
|
273
|
-
async function runnerwasmlane(code, _inputs, _deadline) {
|
|
274
|
-
const bytes = Buffer.from(String(code), "base64");
|
|
275
|
-
if (bytes.byteLength < 8) {
|
|
276
|
-
throw edgelaneerror("WASM_INSTANTIATION_FAILED", "the wasm lane requires base64 module bytes in the code field");
|
|
277
|
-
}
|
|
278
|
-
let instance;
|
|
279
|
-
try {
|
|
280
|
-
const module = new WebAssembly.Module(new Uint8Array(bytes));
|
|
281
|
-
const importobject = {};
|
|
282
|
-
for (const spec of WebAssembly.Module.imports(module)) {
|
|
283
|
-
importobject[spec.module] ??= {};
|
|
284
|
-
if (spec.kind === "function")
|
|
285
|
-
importobject[spec.module][spec.name] = () => 0;
|
|
286
|
-
}
|
|
287
|
-
instance = new WebAssembly.Instance(module, importobject);
|
|
288
|
-
}
|
|
289
|
-
catch (error) {
|
|
290
|
-
throw edgelaneerror("WASM_INSTANTIATION_FAILED", `the wasm module failed to instantiate: ${errormessage(error)}`);
|
|
291
|
-
}
|
|
292
|
-
const exports = instance.exports;
|
|
293
|
-
const callable = Object.values(exports).find((value) => typeof value === "function" && value.length === 0);
|
|
294
|
-
if (callable === undefined) {
|
|
295
|
-
return { stdout: `instantiated; exports: ${Object.keys(exports).join(", ")}` };
|
|
296
|
-
}
|
|
297
|
-
return { stdout: encodeline(callable()) };
|
|
298
|
-
}
|
|
299
|
-
/** runner-babel: the babel transpile lane. The module transforms the
|
|
300
|
-
* program (parse and regenerate — the honest babel pass, no preset the
|
|
301
|
-
* engine graph does not carry) and the transformed output runs through
|
|
302
|
-
* the same node:vm surface as runner-node. */
|
|
303
|
-
async function runnerbabellane(code, inputs, deadline) {
|
|
304
|
-
let babelmodule;
|
|
305
|
-
try {
|
|
306
|
-
babelmodule = (await import("@babel/core"));
|
|
307
|
-
}
|
|
308
|
-
catch (error) {
|
|
309
|
-
throw edgelaneerror("HOST_RUNTIME_ABSENT", `the host runtime @babel/core is absent from the engine module graph: ${errormessage(error)}`);
|
|
310
|
-
}
|
|
311
|
-
const transform = babelmodule.transformSync ?? babelmodule.default?.transformSync;
|
|
312
|
-
if (typeof transform !== "function") {
|
|
313
|
-
throw edgelaneerror("HOST_RUNTIME_ABSENT", "the host runtime @babel/core did not surface a transform (the lane never fakes one)");
|
|
314
|
-
}
|
|
315
|
-
let transformed;
|
|
316
|
-
try {
|
|
317
|
-
transformed = transform(code, {
|
|
318
|
-
filename: "edgelane.js",
|
|
319
|
-
plugins: [],
|
|
320
|
-
presets: [],
|
|
321
|
-
configFile: false,
|
|
322
|
-
babelrc: false,
|
|
323
|
-
}).code;
|
|
324
|
-
}
|
|
325
|
-
catch (error) {
|
|
326
|
-
throw edgelaneerror("RUN_TRANSFORM_FAILED", `the babel transform failed: ${errormessage(error)}`);
|
|
327
|
-
}
|
|
328
|
-
return runinvm(transformed, inputs, deadline);
|
|
329
|
-
}
|
|
330
|
-
/* ════════════════════════════════════════════════════════════════════ */
|
|
331
|
-
/* Section 5: the host spawned lanes — bun, bash, python3 and java. */
|
|
332
|
-
/* ════════════════════════════════════════════════════════════════════ */
|
|
333
|
-
/** runner-bun: the bun runtime lane — the program rides a temp .ts file
|
|
334
|
-
* executed by the host bun binary under the deadline. */
|
|
335
|
-
async function runnerbunlane(code, inputs, deadline) {
|
|
336
|
-
return withtempdir(async (dir) => {
|
|
337
|
-
const script = join(dir, "edgelane.ts");
|
|
338
|
-
await writeFile(script, code, "utf8");
|
|
339
|
-
const env = await writeinputsfile(dir, inputs);
|
|
340
|
-
const run = await runhostprocess("bun", ["run", script], { cwd: dir, deadline, env });
|
|
341
|
-
return { stdout: run.stdout, stderr: run.stderr };
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
|
-
/** runner-universal: the universal binary lane — the program rides a
|
|
345
|
-
* temp shell script executed by bash under the deadline. */
|
|
346
|
-
async function runneruniversallane(code, inputs, deadline) {
|
|
347
|
-
return withtempdir(async (dir) => {
|
|
348
|
-
const script = join(dir, "edgelane.sh");
|
|
349
|
-
await writeFile(script, code, "utf8");
|
|
350
|
-
const env = await writeinputsfile(dir, inputs);
|
|
351
|
-
const run = await runhostprocess("bash", [script], { cwd: dir, deadline, env });
|
|
352
|
-
return { stdout: run.stdout, stderr: run.stderr };
|
|
353
|
-
});
|
|
354
|
-
}
|
|
355
|
-
/** language-python: the cpython lane — the program rides a temp .py file
|
|
356
|
-
* executed by the host python3 binary under the deadline. */
|
|
357
|
-
async function languagepythonlane(code, inputs, deadline) {
|
|
358
|
-
return withtempdir(async (dir) => {
|
|
359
|
-
const script = join(dir, "edgelane.py");
|
|
360
|
-
await writeFile(script, code, "utf8");
|
|
361
|
-
const env = await writeinputsfile(dir, inputs);
|
|
362
|
-
const run = await runhostprocess("python3", [script], { cwd: dir, deadline, env });
|
|
363
|
-
return { stdout: run.stdout, stderr: run.stderr };
|
|
364
|
-
});
|
|
365
|
-
}
|
|
366
|
-
/** language-java: the jdk lane — the program rides a temp Main.java;
|
|
367
|
-
* when javac is on PATH the lane compiles then runs the class, otherwise
|
|
368
|
-
* the jdk 11+ single-file source mode runs Main.java directly (the
|
|
369
|
-
* honest path this engine host carries). */
|
|
370
|
-
async function languagejavalane(code, inputs, deadline) {
|
|
371
|
-
return withtempdir(async (dir) => {
|
|
372
|
-
const script = join(dir, "Main.java");
|
|
373
|
-
await writeFile(script, code, "utf8");
|
|
374
|
-
const env = await writeinputsfile(dir, inputs);
|
|
375
|
-
if (probehostbinary("javac")) {
|
|
376
|
-
const compile = await runhostprocess("javac", [script], { cwd: dir, deadline, env });
|
|
377
|
-
const run = await runhostprocess("java", ["-cp", dir, "Main"], { cwd: dir, deadline, env });
|
|
378
|
-
return { stdout: compile.stdout + run.stdout, stderr: compile.stderr + run.stderr };
|
|
379
|
-
}
|
|
380
|
-
const run = await runhostprocess("java", [script], { cwd: dir, deadline, env });
|
|
381
|
-
return { stdout: run.stdout, stderr: run.stderr };
|
|
382
|
-
});
|
|
383
|
-
}
|
|
384
|
-
/* ════════════════════════════════════════════════════════════════════ */
|
|
385
|
-
/* Section 6: the engine surface lanes — docker, the vfs and webpack. */
|
|
386
|
-
/* ════════════════════════════════════════════════════════════════════ */
|
|
387
|
-
/** container-docker: the hardened container lane. The lane probes the
|
|
388
|
-
* docker surface first (binary on PATH or the daemon socket) and answers
|
|
389
|
-
* HOST_RUNTIME_ABSENT when the host carries neither — it never fakes a
|
|
390
|
-
* container run; when present the lane runs docker version --format json
|
|
391
|
-
* and answers the probe document as the honest artifact. */
|
|
392
|
-
async function containerdockerlane(_code, _inputs, deadline) {
|
|
393
|
-
const probe = await probelateruntime("docker", deadline);
|
|
394
|
-
if (!probe.present && !existsSync("/var/run/docker.sock")) {
|
|
395
|
-
throw edgelaneerror("HOST_RUNTIME_ABSENT", "the host runtime docker is absent from the engine host (no docker binary and no /var/run/docker.sock)");
|
|
396
|
-
}
|
|
397
|
-
const run = await runhostprocess("docker", ["version", "--format", "json"], { cwd: tmpdir(), deadline });
|
|
398
|
-
return {
|
|
399
|
-
stdout: run.stdout,
|
|
400
|
-
stderr: run.stderr,
|
|
401
|
-
artifact: { mime: "application/json", bytes: new TextEncoder().encode(run.stdout) },
|
|
402
|
-
};
|
|
403
|
-
}
|
|
404
|
-
/** fs-vfs: the virtual filesystem lane. The code field rides the payload
|
|
405
|
-
* bytes: the lane writes them through the engine's own virtual.ts
|
|
406
|
-
* storage surface (localstorage in a temp dir), reads them back and
|
|
407
|
-
* proves the roundtrip, then exercises the localmemory working set
|
|
408
|
-
* bridge (prepare, sync, cleanup) on the same bytes. The module loads
|
|
409
|
-
* lazily: a host whose module graph cannot resolve the root domain
|
|
410
|
-
* answers LANE_MODULE_UNAVAILABLE for this one lane, never a fake
|
|
411
|
-
* roundtrip. */
|
|
412
|
-
async function fsvfslane(code, _inputs, _deadline) {
|
|
413
|
-
let virtual;
|
|
414
|
-
try {
|
|
415
|
-
virtual = (await import("./virtual.ts"));
|
|
416
|
-
}
|
|
417
|
-
catch (error) {
|
|
418
|
-
throw edgelaneerror("LANE_MODULE_UNAVAILABLE", `the engine vfs module is not loadable in this host module graph: ${errormessage(error)}`);
|
|
419
|
-
}
|
|
420
|
-
const bytes = new TextEncoder().encode(String(code));
|
|
421
|
-
return withtempdir(async (dir) => {
|
|
422
|
-
const storage = virtual.localstorage(dir);
|
|
423
|
-
const written = await storage.put({
|
|
424
|
-
key: "edge/vfs-payload.bin",
|
|
425
|
-
data: bytes,
|
|
426
|
-
contenttype: "application/octet-stream",
|
|
427
|
-
});
|
|
428
|
-
const readback = await storage.get("edge/vfs-payload.bin");
|
|
429
|
-
if (readback.byteLength !== bytes.byteLength) {
|
|
430
|
-
throw edgelaneerror("VFS_ROUNDTRIP_FAILED", `the vfs roundtrip answered ${readback.byteLength} bytes for ${bytes.byteLength} written`);
|
|
431
|
-
}
|
|
432
|
-
const memory = virtual.localmemory({ base: dir });
|
|
433
|
-
const set = await memory.prepare({ id: "edgelane-vfs" });
|
|
434
|
-
const sync = await memory.sync(set, bytes);
|
|
435
|
-
await memory.cleanup(set);
|
|
436
|
-
return {
|
|
437
|
-
stdout: `vfs roundtrip: ${bytes.byteLength} bytes, sha256 ${written.sha256.slice(0, 12)}, workingset ${sync.bytes} bytes`,
|
|
438
|
-
artifact: { mime: "application/octet-stream", bytes: readback },
|
|
439
|
-
};
|
|
440
|
-
});
|
|
441
|
-
}
|
|
442
|
-
/** runner-webpack: the bundler lane. The program rides a temp entry.js
|
|
443
|
-
* compiled by the host webpack (mode none, no devtool) into the same
|
|
444
|
-
* temp dir; the bundle answers as the artifact and its size rides
|
|
445
|
-
* stdout. webpack is absent from this engine graph today — the lane
|
|
446
|
-
* answers HOST_RUNTIME_ABSENT and never fakes a bundle; a host that
|
|
447
|
-
* carries webpack gets the real compile. */
|
|
448
|
-
async function runnerwebpacklane(code, _inputs, _deadline) {
|
|
449
|
-
let webpackmodule;
|
|
450
|
-
try {
|
|
451
|
-
webpackmodule = await import("webpack");
|
|
452
|
-
}
|
|
453
|
-
catch (error) {
|
|
454
|
-
throw edgelaneerror("HOST_RUNTIME_ABSENT", `the host runtime webpack is absent from the engine module graph: ${errormessage(error)}`);
|
|
455
|
-
}
|
|
456
|
-
const webpack = webpackmodule.webpack ?? webpackmodule.default;
|
|
457
|
-
if (typeof webpack !== "function") {
|
|
458
|
-
throw edgelaneerror("HOST_RUNTIME_ABSENT", "the host runtime webpack did not surface a compiler (the lane never fakes a bundle)");
|
|
459
|
-
}
|
|
460
|
-
return withtempdir(async (dir) => {
|
|
461
|
-
await writeFile(join(dir, "entry.js"), code, "utf8");
|
|
462
|
-
const compiler = webpack({
|
|
463
|
-
mode: "none",
|
|
464
|
-
devtool: false,
|
|
465
|
-
entry: join(dir, "entry.js"),
|
|
466
|
-
output: { path: dir, filename: "bundle.js" },
|
|
467
|
-
});
|
|
468
|
-
await new Promise((resolve, reject) => {
|
|
469
|
-
compiler.run((error, stats) => {
|
|
470
|
-
if (error) {
|
|
471
|
-
reject(edgelaneerror("BUNDLE_FAILED", `the webpack compile failed: ${errormessage(error)}`));
|
|
472
|
-
}
|
|
473
|
-
else if (stats && typeof stats.hasErrors === "function" && stats.hasErrors()) {
|
|
474
|
-
reject(edgelaneerror("BUNDLE_FAILED", `the webpack compile failed: ${String(stats.toString?.({ errorDetails: false }) ?? "errors")}`));
|
|
475
|
-
}
|
|
476
|
-
else {
|
|
477
|
-
resolve();
|
|
478
|
-
}
|
|
479
|
-
});
|
|
480
|
-
});
|
|
481
|
-
const bundle = await readFile(join(dir, "bundle.js"));
|
|
482
|
-
const bytes = new Uint8Array(bundle);
|
|
483
|
-
return { stdout: `webpack bundle: ${bytes.byteLength} bytes`, artifact: { mime: "application/javascript", bytes } };
|
|
484
|
-
});
|
|
485
|
-
}
|
|
486
|
-
/* ════════════════════════════════════════════════════════════════════ */
|
|
487
|
-
/* Section 7: the host probe lanes — the six runtimes the engine watches. */
|
|
488
|
-
/* ════════════════════════════════════════════════════════════════════ */
|
|
489
|
-
/** builds the probe executor of one host lane (runsc, qemu, firecracker,
|
|
490
|
-
* the jvm, go, ruby): the lane probes the runtime and answers its
|
|
491
|
-
* resolved path on stdout — the execution wiring is the next rung, and
|
|
492
|
-
* an absent runtime answers HOST_RUNTIME_ABSENT naming the runtime. */
|
|
493
|
-
function hostprobelane(runtime) {
|
|
494
|
-
return async (_code, _inputs, deadline) => {
|
|
495
|
-
const probe = await probelateruntime(runtime, deadline);
|
|
496
|
-
if (!probe.present) {
|
|
497
|
-
throw edgelaneerror("HOST_RUNTIME_ABSENT", `the host runtime ${runtime} is absent from the engine host (the ${runtime} lane probes and never fakes)`);
|
|
498
|
-
}
|
|
499
|
-
return { stdout: `${runtime} at ${probe.path}` };
|
|
500
|
-
};
|
|
501
|
-
}
|
|
502
|
-
/* ════════════════════════════════════════════════════════════════════ */
|
|
503
|
-
/* Section 8: the ninety law wave — the ten promotions of the forty */
|
|
504
|
-
/* live law (wave w2 b1): the clr assembly lane, the docker profile */
|
|
505
|
-
/* pair and the ghcr digest pin, the three storage adapter lanes and */
|
|
506
|
-
/* the three kernel recipe lanes of the ram family. */
|
|
507
|
-
/* ════════════════════════════════════════════════════════════════════ */
|
|
508
|
-
/** reads one optional string field of the caller inputs (the pinned
|
|
509
|
-
* image reference of the ghcr lane). */
|
|
510
|
-
function inputstring(inputs, field) {
|
|
511
|
-
const value = inputs?.[field];
|
|
512
|
-
return typeof value === "string" ? value.trim() : "";
|
|
513
|
-
}
|
|
514
|
-
/** reads one optional string array field of the caller inputs (the argv
|
|
515
|
-
* of the clr assembly lane and the ghcr run). */
|
|
516
|
-
function inputstrings(inputs, field) {
|
|
517
|
-
const value = inputs?.[field];
|
|
518
|
-
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
519
|
-
}
|
|
520
|
-
/** reads the optional size the caller inputs carry and clamps it into
|
|
521
|
-
* the recipe window (1 through 512 gb, the tiers recipe law); the
|
|
522
|
-
* fallback answers when the inputs carry no honest number. */
|
|
523
|
-
function clampsizegb(inputs, fallback) {
|
|
524
|
-
const value = inputs?.sizegb;
|
|
525
|
-
if (typeof value !== "number" || !Number.isFinite(value))
|
|
526
|
-
return fallback;
|
|
527
|
-
return Math.min(Math.max(Math.round(value), 1), 512);
|
|
528
|
-
}
|
|
529
|
-
/** parses one host document when it parses, answers the raw text when
|
|
530
|
-
* it does not — the artifact never invents a document the daemon did
|
|
531
|
-
* not answer. */
|
|
532
|
-
function safejson(text) {
|
|
533
|
-
try {
|
|
534
|
-
return JSON.parse(text);
|
|
535
|
-
}
|
|
536
|
-
catch {
|
|
537
|
-
return text;
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
/** races one promise against the lane deadline: a slow wire answers
|
|
541
|
-
* RUN_DEADLINE_EXCEEDED honestly while the underlying work settles in
|
|
542
|
-
* the background (the cleanup surfaces still run through the finally). */
|
|
543
|
-
async function racedeadline(work, deadline) {
|
|
544
|
-
let timer;
|
|
545
|
-
try {
|
|
546
|
-
return await Promise.race([
|
|
547
|
-
work,
|
|
548
|
-
new Promise((_, reject) => {
|
|
549
|
-
timer = setTimeout(() => reject(edgelaneerror("RUN_DEADLINE_EXCEEDED", `the lane exceeded its ${deadline} ms deadline`)), deadline);
|
|
550
|
-
}),
|
|
551
|
-
]);
|
|
552
|
-
}
|
|
553
|
-
finally {
|
|
554
|
-
if (timer !== undefined)
|
|
555
|
-
clearTimeout(timer);
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
/** maps one kernel recipe refusal onto the privilege ladder: a command
|
|
559
|
-
* the host refused for lack of privilege (operation not permitted,
|
|
560
|
-
* permission denied, must be superuser) answers HOST_PRIVILEGE_ABSENT
|
|
561
|
-
* naming the requirement — the lane never hides the kernel verdict
|
|
562
|
-
* behind a generic exit code, and every other failure rides untouched. */
|
|
563
|
-
function recipefailure(requirement, error) {
|
|
564
|
-
const laneerror = error;
|
|
565
|
-
const verdict = `${errormessage(error)}\n${laneerror?.stderr ?? ""}`;
|
|
566
|
-
if (/operation not permitted|permission denied|must be superuser|only root|EPERM|EACCES/i.test(verdict)) {
|
|
567
|
-
throw edgelaneerror("HOST_PRIVILEGE_ABSENT", `the lane requires the ${requirement} and this engine host refused it: ${errormessage(error)}`, { stderr: laneerror?.stderr });
|
|
568
|
-
}
|
|
569
|
-
throw error;
|
|
570
|
-
}
|
|
571
|
-
/** runtime-clr: the common language runtime lane — the code field rides
|
|
572
|
-
* the base64 bytes of one precompiled .net assembly and the lane spawns
|
|
573
|
-
* the host dotnet binary over it (dotnet <assembly.dll> [args], the
|
|
574
|
-
* same host-spawn surface the language lanes ride; the argv rides
|
|
575
|
-
* inputs.args). Bytes that carry no PE signature answer
|
|
576
|
-
* ASSEMBLY_BYTES_INVALID before any spawn — the lane never feeds the
|
|
577
|
-
* runtime bytes it can refuse itself — and an absent dotnet answers
|
|
578
|
-
* HOST_RUNTIME_ABSENT naming the runtime. */
|
|
579
|
-
async function runtimeclrlane(code, inputs, deadline) {
|
|
580
|
-
const bytes = Buffer.from(String(code), "base64");
|
|
581
|
-
if (bytes.byteLength < 8 || bytes[0] !== 0x4d || bytes[1] !== 0x5a) {
|
|
582
|
-
throw edgelaneerror("ASSEMBLY_BYTES_INVALID", "the clr lane requires base64 PE assembly bytes in the code field (every .net assembly opens with the MZ signature)");
|
|
583
|
-
}
|
|
584
|
-
const argv = inputstrings(inputs, "args");
|
|
585
|
-
return withtempdir(async (dir) => {
|
|
586
|
-
const assembly = join(dir, "edgelane.dll");
|
|
587
|
-
await writeFile(assembly, bytes);
|
|
588
|
-
const env = await writeinputsfile(dir, inputs);
|
|
589
|
-
const run = await runhostprocess("dotnet", [assembly, ...argv], { cwd: dir, deadline, env });
|
|
590
|
-
return { stdout: run.stdout, stderr: run.stderr };
|
|
591
|
-
});
|
|
592
|
-
}
|
|
593
|
-
/** builds the docker profile lane of one named profile: the lane probes
|
|
594
|
-
* the docker surface first (binary on PATH or the daemon socket) and
|
|
595
|
-
* answers HOST_RUNTIME_ABSENT when the host carries neither; when
|
|
596
|
-
* present it computes the profile memory vector through the engine's
|
|
597
|
-
* own virtualmemory.ts dockerMemoryFlags recipe — never a hand-pinned
|
|
598
|
-
* flag — drives docker version --format json under the deadline (the
|
|
599
|
-
* pattern of the container-docker lane) and answers the daemon document
|
|
600
|
-
* joined with the profile vector as the honest artifact. */
|
|
601
|
-
function containerprofilelane(profile) {
|
|
602
|
-
return async (_code, _inputs, deadline) => {
|
|
603
|
-
const probe = await probelateruntime("docker", deadline);
|
|
604
|
-
if (!probe.present && !existsSync("/var/run/docker.sock")) {
|
|
605
|
-
throw edgelaneerror("HOST_RUNTIME_ABSENT", "the host runtime docker is absent from the engine host (no docker binary and no /var/run/docker.sock)");
|
|
606
|
-
}
|
|
607
|
-
let memorymodule;
|
|
608
|
-
try {
|
|
609
|
-
memorymodule = (await import("./virtualmemory.ts"));
|
|
610
|
-
}
|
|
611
|
-
catch (error) {
|
|
612
|
-
throw edgelaneerror("LANE_MODULE_UNAVAILABLE", `the engine memory recipe module is not loadable in this host module graph: ${errormessage(error)}`);
|
|
613
|
-
}
|
|
614
|
-
const recipe = memorymodule.dockerMemoryFlags;
|
|
615
|
-
if (typeof recipe !== "function") {
|
|
616
|
-
throw edgelaneerror("LANE_MODULE_UNAVAILABLE", "the engine memory recipe module did not surface dockerMemoryFlags (the lane never pins a hand-made flag vector)");
|
|
617
|
-
}
|
|
618
|
-
const plan = profile === "max"
|
|
619
|
-
? recipe({ ramgb: 32, shmsizegb: 2, unlimitedswap: true })
|
|
620
|
-
: recipe({ ramgb: 2, shmsizegb: 1, unlimitedswap: false });
|
|
621
|
-
const run = await runhostprocess("docker", ["version", "--format", "json"], { cwd: tmpdir(), deadline });
|
|
622
|
-
const document = `${JSON.stringify({ profile, flags: plan.run, rationale: plan.rationale, daemon: safejson(run.stdout) })}\n`;
|
|
623
|
-
return {
|
|
624
|
-
stdout: `docker ${profile} profile: ${plan.run.join(" ")}\n${run.stdout}`,
|
|
625
|
-
stderr: run.stderr,
|
|
626
|
-
artifact: { mime: "application/json", bytes: new TextEncoder().encode(document) },
|
|
627
|
-
};
|
|
628
|
-
};
|
|
629
|
-
}
|
|
630
|
-
/** container-max: the docker max profile lane — the 32g memory ceiling,
|
|
631
|
-
* the unlimited swap, the 2g shm raise and the tmpfs shader cache the
|
|
632
|
-
* engine recipe computes (see containerprofilelane). */
|
|
633
|
-
const containermaxlane = containerprofilelane("max");
|
|
634
|
-
/** container-lite: the docker lite profile lane — the 2g memory ceiling,
|
|
635
|
-
* the capped swap and the 1g shm of the same engine recipe under the
|
|
636
|
-
* light profile (see containerprofilelane). */
|
|
637
|
-
const containerlitelane = containerprofilelane("lite");
|
|
638
|
-
/** the pinned reference grammar of the ghcr lane: ghcr.io/<owner>/
|
|
639
|
-
* <image>@sha256:<64 hex> — the immutability policy never rides a
|
|
640
|
-
* mutable tag. */
|
|
641
|
-
const GHCRPINNEDREF = /^ghcr\.io\/[a-z0-9._-]+(?:\/[a-z0-9._-]+)*@sha256:[a-f0-9]{64}$/i;
|
|
642
|
-
/** container-ghcr: the ghcr immutable lane — the code field (or
|
|
643
|
-
* inputs.image) rides one digest-pinned ghcr.io reference; a tag or a
|
|
644
|
-
* missing reference answers GHCR_REFERENCE_UNPINNED naming the policy
|
|
645
|
-
* before any spawn, and an absent docker answers HOST_RUNTIME_ABSENT.
|
|
646
|
-
* When both hold the lane pulls the pinned digest and runs it under one
|
|
647
|
-
* uuid instance name (--rm — the immutable root is the digest itself),
|
|
648
|
-
* answering the pulled run honestly: a digest the registry never met
|
|
649
|
-
* answers the pull failure with its stderr, never a faked image id. */
|
|
650
|
-
async function containerghcrlane(code, inputs, deadline) {
|
|
651
|
-
const reference = inputstring(inputs, "image").length > 0 ? inputstring(inputs, "image") : code.trim();
|
|
652
|
-
if (!GHCRPINNEDREF.test(reference)) {
|
|
653
|
-
throw edgelaneerror("GHCR_REFERENCE_UNPINNED", `the ghcr lane requires one pinned reference ghcr.io/<owner>/<image>@sha256:<digest> in the code field (the immutability policy never rides a mutable tag), received "${reference}"`);
|
|
654
|
-
}
|
|
655
|
-
const probe = await probelateruntime("docker", deadline);
|
|
656
|
-
if (!probe.present && !existsSync("/var/run/docker.sock")) {
|
|
657
|
-
throw edgelaneerror("HOST_RUNTIME_ABSENT", "the host runtime docker is absent from the engine host (no docker binary and no /var/run/docker.sock)");
|
|
658
|
-
}
|
|
659
|
-
const instance = `saddleedge-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
660
|
-
const pull = await runhostprocess("docker", ["pull", reference], { cwd: tmpdir(), deadline });
|
|
661
|
-
const run = await runhostprocess("docker", ["run", "--rm", "--name", instance, reference, ...inputstrings(inputs, "args")], { cwd: tmpdir(), deadline });
|
|
662
|
-
return {
|
|
663
|
-
stdout: `ghcr pull: ${reference}\ninstance: ${instance}\n${run.stdout}`,
|
|
664
|
-
stderr: `${pull.stderr}${run.stderr}`,
|
|
665
|
-
};
|
|
666
|
-
}
|
|
667
|
-
/** builds the aws signature v4 signer the s3 lane injects into the
|
|
668
|
-
* engine's s3compatible adapter: the canonical request, the string to
|
|
669
|
-
* sign and the hmac chain computed over the env credentials — the
|
|
670
|
-
* adapter stays signer-injected (the engine root domain owns no
|
|
671
|
-
* credentials) and the lane supplies the real one from the config. */
|
|
672
|
-
function s3signer(credentials) {
|
|
673
|
-
const hmac = (key, data) => createHmac("sha256", key).update(data).digest();
|
|
674
|
-
return async (request) => {
|
|
675
|
-
const path = `/${request.bucket}${request.key.length > 0 ? `/${request.key}` : ""}`.replaceAll("//", "/");
|
|
676
|
-
const url = new URL(path, request.endpoint);
|
|
677
|
-
for (const [name, value] of Object.entries(request.query ?? {}))
|
|
678
|
-
url.searchParams.set(name, String(value));
|
|
679
|
-
const payload = request.body instanceof Uint8Array ? Buffer.from(request.body) : Buffer.alloc(0);
|
|
680
|
-
const payloadhash = createHash("sha256").update(payload).digest("hex");
|
|
681
|
-
const amzdate = new Date().toISOString().replace(/[:-]|\.\d{3}/g, "");
|
|
682
|
-
const datestamp = amzdate.slice(0, 8);
|
|
683
|
-
const canonicalquery = [...url.searchParams.entries()]
|
|
684
|
-
.map(([name, value]) => `${encodeURIComponent(name)}=${encodeURIComponent(value)}`)
|
|
685
|
-
.sort()
|
|
686
|
-
.join("&");
|
|
687
|
-
const signedheaders = "host;x-amz-content-sha256;x-amz-date";
|
|
688
|
-
const canonicalheaders = `host:${url.host}\nx-amz-content-sha256:${payloadhash}\nx-amz-date:${amzdate}\n`;
|
|
689
|
-
const canonicalrequest = [
|
|
690
|
-
request.method.toUpperCase(),
|
|
691
|
-
url.pathname,
|
|
692
|
-
canonicalquery,
|
|
693
|
-
canonicalheaders,
|
|
694
|
-
signedheaders,
|
|
695
|
-
payloadhash,
|
|
696
|
-
].join("\n");
|
|
697
|
-
const scope = `${datestamp}/${credentials.region}/s3/aws4_request`;
|
|
698
|
-
const stringtosign = [
|
|
699
|
-
"AWS4-HMAC-SHA256",
|
|
700
|
-
amzdate,
|
|
701
|
-
scope,
|
|
702
|
-
createHash("sha256").update(canonicalrequest).digest("hex"),
|
|
703
|
-
].join("\n");
|
|
704
|
-
const signingkey = hmac(hmac(hmac(hmac(`AWS4${credentials.secret}`, datestamp), credentials.region), "s3"), "aws4_request");
|
|
705
|
-
const signature = createHmac("sha256", signingkey).update(stringtosign).digest("hex");
|
|
706
|
-
return {
|
|
707
|
-
url: `${url.pathname}${url.search}`,
|
|
708
|
-
headers: {
|
|
709
|
-
host: url.host,
|
|
710
|
-
"x-amz-content-sha256": payloadhash,
|
|
711
|
-
"x-amz-date": amzdate,
|
|
712
|
-
authorization: `AWS4-HMAC-SHA256 Credential=${credentials.key}/${scope}, SignedHeaders=${signedheaders}, Signature=${signature}`,
|
|
713
|
-
},
|
|
714
|
-
};
|
|
715
|
-
};
|
|
716
|
-
}
|
|
717
|
-
/** storage-s3: the s3 compatible vault lane — the runtime surface is
|
|
718
|
-
* the env configuration (SADDLE_S3_ENDPOINT, SADDLE_S3_KEY,
|
|
719
|
-
* SADDLE_S3_SECRET, SADDLE_S3_BUCKET and the optional region); without
|
|
720
|
-
* it the lane answers HOST_RUNTIME_ABSENT naming the configuration,
|
|
721
|
-
* never a faked bucket. With it the lane registers the engine's own
|
|
722
|
-
* s3compatible adapter (virtual.ts, the signer-injected surface) with a
|
|
723
|
-
* real aws signature v4 signer built from the credentials and drives a
|
|
724
|
-
* full roundtrip through the endpoint under the deadline — put, get,
|
|
725
|
-
* list and delete of the payload bytes — answering the stored bytes as
|
|
726
|
-
* the artifact. */
|
|
727
|
-
async function storages3lane(code, _inputs, deadline) {
|
|
728
|
-
const env = process.env;
|
|
729
|
-
if (!probes3config()) {
|
|
730
|
-
throw edgelaneerror("HOST_RUNTIME_ABSENT", "the s3 lane requires the storage configuration SADDLE_S3_ENDPOINT, SADDLE_S3_KEY, SADDLE_S3_SECRET and SADDLE_S3_BUCKET on the engine host (the lane never fakes a bucket)");
|
|
731
|
-
}
|
|
732
|
-
let virtual;
|
|
733
|
-
try {
|
|
734
|
-
virtual = (await import("./virtual.ts"));
|
|
735
|
-
}
|
|
736
|
-
catch (error) {
|
|
737
|
-
throw edgelaneerror("LANE_MODULE_UNAVAILABLE", `the engine storage module is not loadable in this host module graph: ${errormessage(error)}`);
|
|
738
|
-
}
|
|
739
|
-
const factory = virtual.s3compatible;
|
|
740
|
-
if (typeof factory !== "function") {
|
|
741
|
-
throw edgelaneerror("LANE_MODULE_UNAVAILABLE", "the engine storage module did not surface s3compatible (the lane never fakes a bucket)");
|
|
742
|
-
}
|
|
743
|
-
const started = Date.now();
|
|
744
|
-
const fetcher = (input, init) => {
|
|
745
|
-
const remaining = deadline - (Date.now() - started);
|
|
746
|
-
if (remaining <= 0) {
|
|
747
|
-
return Promise.reject(edgelaneerror("RUN_DEADLINE_EXCEEDED", `the s3 roundtrip exceeded its ${deadline} ms deadline`));
|
|
748
|
-
}
|
|
749
|
-
return fetch(input, { ...init, signal: AbortSignal.timeout(remaining) });
|
|
750
|
-
};
|
|
751
|
-
const storage = factory({
|
|
752
|
-
endpoint: String(env.SADDLE_S3_ENDPOINT),
|
|
753
|
-
bucket: String(env.SADDLE_S3_BUCKET),
|
|
754
|
-
sign: s3signer({
|
|
755
|
-
key: String(env.SADDLE_S3_KEY),
|
|
756
|
-
secret: String(env.SADDLE_S3_SECRET),
|
|
757
|
-
region: typeof env.SADDLE_S3_REGION === "string" && env.SADDLE_S3_REGION.length > 0 ? env.SADDLE_S3_REGION : "us-east-1",
|
|
758
|
-
}),
|
|
759
|
-
fetcher: fetcher,
|
|
760
|
-
});
|
|
761
|
-
const bytes = new TextEncoder().encode(String(code));
|
|
762
|
-
const key = `edge/s3-lane/${Date.now().toString(36)}.bin`;
|
|
763
|
-
try {
|
|
764
|
-
const written = await storage.put({ key, data: bytes, contenttype: "application/octet-stream" });
|
|
765
|
-
const readback = await storage.get(key);
|
|
766
|
-
if (readback.byteLength !== bytes.byteLength) {
|
|
767
|
-
throw edgelaneerror("S3_ROUNDTRIP_FAILED", `the s3 roundtrip answered ${readback.byteLength} bytes for ${bytes.byteLength} written`);
|
|
768
|
-
}
|
|
769
|
-
const listing = await storage.list("edge/s3-lane/");
|
|
770
|
-
await storage.delete(key);
|
|
771
|
-
return {
|
|
772
|
-
stdout: `s3 roundtrip: ${bytes.byteLength} bytes, sha256 ${written.sha256.slice(0, 12)}, listing ${listing.length} ${listing.length === 1 ? "entry" : "entries"}`,
|
|
773
|
-
artifact: { mime: "application/octet-stream", bytes: readback },
|
|
774
|
-
};
|
|
775
|
-
}
|
|
776
|
-
catch (error) {
|
|
777
|
-
const laneerror = error;
|
|
778
|
-
if (laneerror?.code === "S3_ROUNDTRIP_FAILED" || laneerror?.code === "RUN_DEADLINE_EXCEEDED")
|
|
779
|
-
throw error;
|
|
780
|
-
if (/timeout|timed out|aborted/i.test(errormessage(error))) {
|
|
781
|
-
throw edgelaneerror("RUN_DEADLINE_EXCEEDED", `the s3 roundtrip exceeded its ${deadline} ms deadline`);
|
|
782
|
-
}
|
|
783
|
-
throw edgelaneerror("S3_ROUNDTRIP_FAILED", `the s3 endpoint refused the roundtrip: ${errormessage(error)}`);
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
/** storage-libsql: the libsql vault lane — the runtime surface is the
|
|
787
|
-
* @libsql/client dependency the engine graph carries plus the LIBSQL_URL
|
|
788
|
-
* configuration; without the url the lane answers HOST_RUNTIME_ABSENT
|
|
789
|
-
* naming both, and a module graph that cannot resolve the client
|
|
790
|
-
* answers the same honest refusal. With the url the lane opens the real
|
|
791
|
-
* client (file: and remote libsql: shapes alike), drives one full
|
|
792
|
-
* roundtrip under the deadline — create, insert, select of the payload
|
|
793
|
-
* row — and answers the readback bytes as the artifact, the private
|
|
794
|
-
* table dropping after the run. */
|
|
795
|
-
async function storagelibsqllane(code, _inputs, deadline) {
|
|
796
|
-
const url = process.env.LIBSQL_URL;
|
|
797
|
-
if (typeof url !== "string" || url.length === 0) {
|
|
798
|
-
throw edgelaneerror("HOST_RUNTIME_ABSENT", "the libsql lane requires the LIBSQL_URL configuration and the @libsql/client runtime on the engine host (the lane never fakes a database)");
|
|
799
|
-
}
|
|
800
|
-
let libsql;
|
|
801
|
-
try {
|
|
802
|
-
libsql = (await import("@libsql/client"));
|
|
803
|
-
}
|
|
804
|
-
catch (error) {
|
|
805
|
-
throw edgelaneerror("HOST_RUNTIME_ABSENT", `the libsql lane requires the @libsql/client runtime and this host module graph cannot resolve it: ${errormessage(error)}`);
|
|
806
|
-
}
|
|
807
|
-
const create = libsql.createClient;
|
|
808
|
-
if (typeof create !== "function") {
|
|
809
|
-
throw edgelaneerror("HOST_RUNTIME_ABSENT", "the @libsql/client runtime did not surface createClient (the lane never fakes a database)");
|
|
810
|
-
}
|
|
811
|
-
const client = create({ url });
|
|
812
|
-
const table = `edgelane_libsql_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;
|
|
813
|
-
const payload = String(code);
|
|
814
|
-
let readback = "";
|
|
815
|
-
try {
|
|
816
|
-
readback = await racedeadline((async () => {
|
|
817
|
-
await client.execute({ sql: `create table if not exists ${table} (k text primary key, v text)` });
|
|
818
|
-
await client.execute({ sql: `insert or replace into ${table} (k, v) values (?, ?)`, args: ["edgelane-payload", payload] });
|
|
819
|
-
const selected = await client.execute({ sql: `select v from ${table} where k = ?`, args: ["edgelane-payload"] });
|
|
820
|
-
return String(selected.rows[0]?.v ?? "");
|
|
821
|
-
})(), deadline);
|
|
822
|
-
}
|
|
823
|
-
catch (error) {
|
|
824
|
-
if (error?.code === "RUN_DEADLINE_EXCEEDED")
|
|
825
|
-
throw error;
|
|
826
|
-
throw edgelaneerror("LIBSQL_ROUNDTRIP_FAILED", `the libsql endpoint refused the roundtrip: ${errormessage(error)}`);
|
|
827
|
-
}
|
|
828
|
-
try {
|
|
829
|
-
if (readback !== payload) {
|
|
830
|
-
throw edgelaneerror("LIBSQL_ROUNDTRIP_FAILED", `the libsql roundtrip answered ${readback.length} characters for ${payload.length} written`);
|
|
831
|
-
}
|
|
832
|
-
return {
|
|
833
|
-
stdout: `libsql roundtrip: ${payload.length} characters through the ${url.split(":")[0]} endpoint`,
|
|
834
|
-
artifact: { mime: "text/plain;charset=utf-8", bytes: new TextEncoder().encode(readback) },
|
|
835
|
-
};
|
|
836
|
-
}
|
|
837
|
-
finally {
|
|
838
|
-
try {
|
|
839
|
-
await client.execute({ sql: `drop table if exists ${table}` });
|
|
840
|
-
}
|
|
841
|
-
catch {
|
|
842
|
-
/* the roundtrip already answered; the drop is best effort */
|
|
843
|
-
}
|
|
844
|
-
try {
|
|
845
|
-
await client.close();
|
|
846
|
-
}
|
|
847
|
-
catch {
|
|
848
|
-
/* the close is best effort */
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
/** storage-mysql: the mysql wire vault lane — the runtime surface is
|
|
853
|
-
* the mysql2 dependency plus the MYSQL_URL configuration; without the
|
|
854
|
-
* url the lane answers HOST_RUNTIME_ABSENT naming both. With it the
|
|
855
|
-
* lane opens the real pool, ensures one private jobs table, and drives
|
|
856
|
-
* the engine's own mysql2persistence adapter (virtual.ts — the sql
|
|
857
|
-
* persistence the engine already carries) through a full job roundtrip
|
|
858
|
-
* under the deadline: savejob then getjob, the payload compared — the
|
|
859
|
-
* adapter is the engine's, the wire is real, the table drops after the
|
|
860
|
-
* run. */
|
|
861
|
-
async function storagemysqllane(code, _inputs, deadline) {
|
|
862
|
-
const url = process.env.MYSQL_URL;
|
|
863
|
-
if (typeof url !== "string" || url.length === 0) {
|
|
864
|
-
throw edgelaneerror("HOST_RUNTIME_ABSENT", "the mysql lane requires the MYSQL_URL configuration and the mysql2 runtime on the engine host (the lane never fakes a database)");
|
|
865
|
-
}
|
|
866
|
-
let mysql;
|
|
867
|
-
try {
|
|
868
|
-
mysql = (await import("mysql2/promise"));
|
|
869
|
-
}
|
|
870
|
-
catch (error) {
|
|
871
|
-
throw edgelaneerror("HOST_RUNTIME_ABSENT", `the mysql lane requires the mysql2 runtime and this host module graph cannot resolve it: ${errormessage(error)}`);
|
|
872
|
-
}
|
|
873
|
-
const createPool = mysql.createPool;
|
|
874
|
-
if (typeof createPool !== "function") {
|
|
875
|
-
throw edgelaneerror("HOST_RUNTIME_ABSENT", "the mysql2 runtime did not surface createPool (the lane never fakes a database)");
|
|
876
|
-
}
|
|
877
|
-
let virtual;
|
|
878
|
-
try {
|
|
879
|
-
virtual = (await import("./virtual.ts"));
|
|
880
|
-
}
|
|
881
|
-
catch (error) {
|
|
882
|
-
throw edgelaneerror("LANE_MODULE_UNAVAILABLE", `the engine storage module is not loadable in this host module graph: ${errormessage(error)}`);
|
|
883
|
-
}
|
|
884
|
-
const persistencefactory = virtual.mysql2persistence;
|
|
885
|
-
if (typeof persistencefactory !== "function") {
|
|
886
|
-
throw edgelaneerror("LANE_MODULE_UNAVAILABLE", "the engine storage module did not surface mysql2persistence (the lane never fakes a database)");
|
|
887
|
-
}
|
|
888
|
-
const pool = createPool(url);
|
|
889
|
-
const table = `edgelane_jobs_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;
|
|
890
|
-
const jobid = `edgelane-${Date.now().toString(36)}`;
|
|
891
|
-
const payload = String(code);
|
|
892
|
-
try {
|
|
893
|
-
await racedeadline((async () => {
|
|
894
|
-
await pool.execute(`create table if not exists ${table} (id varchar(64) primary key, name varchar(255), status varchar(32), priority int, input text, outputkey varchar(512), createdat bigint, updatedat bigint)`);
|
|
895
|
-
const persistence = persistencefactory(pool, {
|
|
896
|
-
table: { jobs: table, events: `${table}_events`, sessions: `${table}_sessions`, artifacts: `${table}_artifacts`, chunks: `${table}_chunks` },
|
|
897
|
-
});
|
|
898
|
-
await persistence.savejob({
|
|
899
|
-
id: jobid,
|
|
900
|
-
name: "edgelane mysql roundtrip",
|
|
901
|
-
status: "created",
|
|
902
|
-
priority: 0,
|
|
903
|
-
input: { payload },
|
|
904
|
-
outputkey: null,
|
|
905
|
-
createdat: Date.now(),
|
|
906
|
-
updatedat: null,
|
|
907
|
-
});
|
|
908
|
-
const job = await persistence.getjob(jobid);
|
|
909
|
-
const read = job !== null && typeof job.input === "object" && job.input !== null
|
|
910
|
-
? job.input.payload
|
|
911
|
-
: undefined;
|
|
912
|
-
if (read !== payload) {
|
|
913
|
-
throw edgelaneerror("MYSQL_ROUNDTRIP_FAILED", "the mysql roundtrip did not answer the payload the lane wrote");
|
|
914
|
-
}
|
|
915
|
-
})(), deadline);
|
|
916
|
-
return {
|
|
917
|
-
stdout: `mysql roundtrip: ${payload.length} characters through the mysql wire`,
|
|
918
|
-
artifact: { mime: "text/plain;charset=utf-8", bytes: new TextEncoder().encode(payload) },
|
|
919
|
-
};
|
|
920
|
-
}
|
|
921
|
-
catch (error) {
|
|
922
|
-
const laneerror = error;
|
|
923
|
-
if (laneerror?.code === "MYSQL_ROUNDTRIP_FAILED" || laneerror?.code === "RUN_DEADLINE_EXCEEDED")
|
|
924
|
-
throw error;
|
|
925
|
-
throw edgelaneerror("MYSQL_ROUNDTRIP_FAILED", `the mysql endpoint refused the roundtrip: ${errormessage(error)}`);
|
|
926
|
-
}
|
|
927
|
-
finally {
|
|
928
|
-
try {
|
|
929
|
-
await pool.execute(`drop table if exists ${table}`);
|
|
930
|
-
}
|
|
931
|
-
catch {
|
|
932
|
-
/* the roundtrip already answered; the drop is best effort */
|
|
933
|
-
}
|
|
934
|
-
try {
|
|
935
|
-
await pool.end();
|
|
936
|
-
}
|
|
937
|
-
catch {
|
|
938
|
-
/* the close is best effort */
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
/** loads the engine kernel recipe module (tiers.ts) lazily — the honest
|
|
943
|
-
* LANE_MODULE_UNAVAILABLE refusal answers a host whose module graph
|
|
944
|
-
* cannot resolve the root domain, never a hand-written recipe. */
|
|
945
|
-
async function loadkernelrecipes() {
|
|
946
|
-
try {
|
|
947
|
-
return (await import("./tiers.ts"));
|
|
948
|
-
}
|
|
949
|
-
catch (error) {
|
|
950
|
-
throw edgelaneerror("LANE_MODULE_UNAVAILABLE", `the engine kernel recipe module is not loadable in this host module graph: ${errormessage(error)}`);
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
/** ram-tmpfs: the tmpfs lane — the executor applies the engine's own
|
|
954
|
-
* tiers.ts tmpfsrecipe through the host (mkdir then mount -t tmpfs with
|
|
955
|
-
* the size and mode the recipe prints) on one private mount under the
|
|
956
|
-
* session temp directory, proves the ramdisk with a payload roundtrip
|
|
957
|
-
* (the code field rides the bytes the lane writes into ram and reads
|
|
958
|
-
* back), then releases the mount — no kernel state outlives the run.
|
|
959
|
-
* The size rides inputs.sizegb (the recipe window, the lane default
|
|
960
|
-
* 1); an absent mount binary answers HOST_RUNTIME_ABSENT, a host
|
|
961
|
-
* without the mount privilege answers HOST_PRIVILEGE_ABSENT naming the
|
|
962
|
-
* requirement — the lane never fakes a ramdisk. */
|
|
963
|
-
async function ramtmpfslane(code, inputs, deadline) {
|
|
964
|
-
if (!probehostbinary("mount")) {
|
|
965
|
-
throw edgelaneerror("HOST_RUNTIME_ABSENT", "the host runtime mount is absent from the engine host (the tmpfs lane probes and never fakes)");
|
|
966
|
-
}
|
|
967
|
-
const kernel = await loadkernelrecipes();
|
|
968
|
-
const recipe = kernel.tmpfsrecipe;
|
|
969
|
-
if (typeof recipe !== "function") {
|
|
970
|
-
throw edgelaneerror("LANE_MODULE_UNAVAILABLE", "the engine kernel recipe module did not surface tmpfsrecipe (the lane never hand-writes the kernel law)");
|
|
971
|
-
}
|
|
972
|
-
const sizegb = clampsizegb(inputs, 1);
|
|
973
|
-
const payload = new TextEncoder().encode(String(code));
|
|
974
|
-
return withtempdir(async (dir) => {
|
|
975
|
-
const mountpoint = join(dir, "ramdisk");
|
|
976
|
-
const plan = recipe(mountpoint, sizegb);
|
|
977
|
-
try {
|
|
978
|
-
for (const command of plan.commands) {
|
|
979
|
-
await runhostprocess("bash", ["-c", command], { cwd: dir, deadline });
|
|
980
|
-
}
|
|
981
|
-
const file = join(mountpoint, "edgelane-payload.bin");
|
|
982
|
-
await writeFile(file, payload);
|
|
983
|
-
const readback = new Uint8Array(await readFile(file));
|
|
984
|
-
if (readback.byteLength !== payload.byteLength) {
|
|
985
|
-
throw edgelaneerror("RAM_ROUNDTRIP_FAILED", `the tmpfs roundtrip answered ${readback.byteLength} bytes for ${payload.byteLength} written`);
|
|
986
|
-
}
|
|
987
|
-
return {
|
|
988
|
-
stdout: `tmpfs recipe applied: ${plan.effect} — payload ${payload.byteLength} bytes roundtripped in ram (${plan.source})`,
|
|
989
|
-
artifact: { mime: "application/octet-stream", bytes: readback },
|
|
990
|
-
};
|
|
991
|
-
}
|
|
992
|
-
catch (error) {
|
|
993
|
-
recipefailure("mount privilege (root or CAP_SYS_ADMIN over tmpfs)", error);
|
|
994
|
-
}
|
|
995
|
-
finally {
|
|
996
|
-
try {
|
|
997
|
-
await runhostprocess("bash", ["-c", `umount ${mountpoint}`], { cwd: dir, deadline });
|
|
998
|
-
}
|
|
999
|
-
catch {
|
|
1000
|
-
/* the mount never rode or already left — the release is best effort */
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1003
|
-
});
|
|
1004
|
-
}
|
|
1005
|
-
/** ram-zram: the compressed ram lane — the executor applies the
|
|
1006
|
-
* engine's tiers.ts zramrecipe through the host (modprobe, the zstd or
|
|
1007
|
-
* lz4 algorithm, the disksize, mkswap, swapon at priority 100 and the
|
|
1008
|
-
* zramctl verify), reads the honest verify output, then releases the
|
|
1009
|
-
* device (swapoff and the zramctl reset) — the kernel state never
|
|
1010
|
-
* outlives the run. The size rides inputs.sizegb (the recipe window,
|
|
1011
|
-
* the lane default 8) and the algorithm rides inputs.algo; a missing
|
|
1012
|
-
* binary of the set answers HOST_RUNTIME_ABSENT naming the binaries, a
|
|
1013
|
-
* host without the privilege answers HOST_PRIVILEGE_ABSENT naming the
|
|
1014
|
-
* /sys requirement — never a faked compression ratio. */
|
|
1015
|
-
async function ramzramlane(_code, inputs, deadline) {
|
|
1016
|
-
const missing = ["zramctl", "modprobe", "mkswap", "swapon"].filter((binary) => !probehostbinary(binary));
|
|
1017
|
-
if (missing.length > 0) {
|
|
1018
|
-
throw edgelaneerror("HOST_RUNTIME_ABSENT", `the zram lane requires zramctl, modprobe, mkswap and swapon on the host; ${missing.join(", ")} ${missing.length === 1 ? "is" : "are"} absent (the lane never fakes)`);
|
|
1019
|
-
}
|
|
1020
|
-
const kernel = await loadkernelrecipes();
|
|
1021
|
-
const recipe = kernel.zramrecipe;
|
|
1022
|
-
if (typeof recipe !== "function") {
|
|
1023
|
-
throw edgelaneerror("LANE_MODULE_UNAVAILABLE", "the engine kernel recipe module did not surface zramrecipe (the lane never hand-writes the kernel law)");
|
|
1024
|
-
}
|
|
1025
|
-
const algo = inputstring(inputs, "algo") === "lz4" ? "lz4" : "zstd";
|
|
1026
|
-
const sizegb = clampsizegb(inputs, 8);
|
|
1027
|
-
const plan = recipe(sizegb, algo);
|
|
1028
|
-
let verify = "";
|
|
1029
|
-
try {
|
|
1030
|
-
for (const command of plan.commands) {
|
|
1031
|
-
const run = await runhostprocess("bash", ["-c", command], { cwd: tmpdir(), deadline });
|
|
1032
|
-
verify += run.stdout;
|
|
1033
|
-
}
|
|
1034
|
-
return {
|
|
1035
|
-
stdout: `zram recipe applied: ${plan.effect}\n${verify.trim()} (${plan.source})`,
|
|
1036
|
-
};
|
|
1037
|
-
}
|
|
1038
|
-
catch (error) {
|
|
1039
|
-
recipefailure("zram privilege (root over /sys/block/zram0 and the swap surface)", error);
|
|
1040
|
-
}
|
|
1041
|
-
finally {
|
|
1042
|
-
try {
|
|
1043
|
-
await runhostprocess("bash", ["-c", "swapoff /dev/zram0 2>/dev/null; zramctl --reset /dev/zram0 2>/dev/null || true"], { cwd: tmpdir(), deadline });
|
|
1044
|
-
}
|
|
1045
|
-
catch {
|
|
1046
|
-
/* the device never rode or already reset — the release is best effort */
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
|
-
/** ram-swapfile: the swap lane — the executor applies the engine's
|
|
1051
|
-
* tiers.ts swapfilerecipe through the host (fallocate, chmod 600,
|
|
1052
|
-
* mkswap, swapon) on one private file under the session temp
|
|
1053
|
-
* directory, proves the swap with /proc/swaps, then releases it
|
|
1054
|
-
* (swapoff — the file leaves with the temp directory). The size rides
|
|
1055
|
-
* inputs.sizegb (the recipe window, the lane default 1); missing
|
|
1056
|
-
* binaries answer HOST_RUNTIME_ABSENT, a host without the swap
|
|
1057
|
-
* privilege answers HOST_PRIVILEGE_ABSENT naming swapon — never a
|
|
1058
|
-
* faked swap device. */
|
|
1059
|
-
async function ramswapfilelane(_code, inputs, deadline) {
|
|
1060
|
-
const missing = ["fallocate", "mkswap", "swapon"].filter((binary) => !probehostbinary(binary));
|
|
1061
|
-
if (missing.length > 0) {
|
|
1062
|
-
throw edgelaneerror("HOST_RUNTIME_ABSENT", `the swapfile lane requires fallocate, mkswap and swapon on the host; ${missing.join(", ")} ${missing.length === 1 ? "is" : "are"} absent (the lane never fakes)`);
|
|
1063
|
-
}
|
|
1064
|
-
const kernel = await loadkernelrecipes();
|
|
1065
|
-
const recipe = kernel.swapfilerecipe;
|
|
1066
|
-
if (typeof recipe !== "function") {
|
|
1067
|
-
throw edgelaneerror("LANE_MODULE_UNAVAILABLE", "the engine kernel recipe module did not surface swapfilerecipe (the lane never hand-writes the kernel law)");
|
|
1068
|
-
}
|
|
1069
|
-
const sizegb = clampsizegb(inputs, 1);
|
|
1070
|
-
return withtempdir(async (dir) => {
|
|
1071
|
-
const path = join(dir, "swapfile");
|
|
1072
|
-
const plan = recipe(path, sizegb);
|
|
1073
|
-
try {
|
|
1074
|
-
for (const command of plan.commands) {
|
|
1075
|
-
await runhostprocess("bash", ["-c", command], { cwd: dir, deadline });
|
|
1076
|
-
}
|
|
1077
|
-
const swaps = await readFile("/proc/swaps", "utf8").catch(() => "");
|
|
1078
|
-
if (!swaps.includes(path)) {
|
|
1079
|
-
throw edgelaneerror("SWAP_VERIFY_FAILED", `/proc/swaps does not carry ${path} after the recipe (the lane never claims a swap the kernel did not take)`);
|
|
1080
|
-
}
|
|
1081
|
-
return { stdout: `swapfile recipe applied: ${plan.effect} — /proc/swaps carries the session swap (${plan.source})` };
|
|
1082
|
-
}
|
|
1083
|
-
catch (error) {
|
|
1084
|
-
recipefailure("swapon privilege (root or CAP_SYS_ADMIN over the swap surface)", error);
|
|
1085
|
-
}
|
|
1086
|
-
finally {
|
|
1087
|
-
try {
|
|
1088
|
-
await runhostprocess("bash", ["-c", `swapoff ${path} 2>/dev/null || true`], { cwd: dir, deadline });
|
|
1089
|
-
}
|
|
1090
|
-
catch {
|
|
1091
|
-
/* the swap never rode or already left — the release is best effort */
|
|
1092
|
-
}
|
|
1093
|
-
}
|
|
1094
|
-
});
|
|
1095
|
-
}
|
|
1096
|
-
/* ════════════════════════════════════════════════════════════════════ */
|
|
1097
|
-
/* Section 9: the executor table — one executor per lane id, no gaps. */
|
|
1098
|
-
/* ════════════════════════════════════════════════════════════════════ */
|
|
1099
|
-
/** the lane executor table: every live lane and every host lane of the
|
|
1100
|
-
* edge plane, keyed by modality id; the dispatch plane refuses to boot
|
|
1101
|
-
* over a lane it cannot wire. */
|
|
1102
|
-
export const edgelaneexecutors = Object.freeze({
|
|
1103
|
-
"container-docker": containerdockerlane,
|
|
1104
|
-
"fs-vfs": fsvfslane,
|
|
1105
|
-
"runner-node": runnernodelane,
|
|
1106
|
-
"runner-bun": runnerbunlane,
|
|
1107
|
-
"runner-wasm": runnerwasmlane,
|
|
1108
|
-
"runner-babel": runnerbabellane,
|
|
1109
|
-
"runner-webpack": runnerwebpacklane,
|
|
1110
|
-
"runner-universal": runneruniversallane,
|
|
1111
|
-
"language-python": languagepythonlane,
|
|
1112
|
-
"language-java": languagejavalane,
|
|
1113
|
-
// the ninety law wave (the forty live law, wave w2 b1): the ten
|
|
1114
|
-
// promotions — the clr assembly lane, the docker profile pair and the
|
|
1115
|
-
// ghcr digest pin, the three storage adapter lanes and the three
|
|
1116
|
-
// kernel recipe lanes
|
|
1117
|
-
"runtime-clr": runtimeclrlane,
|
|
1118
|
-
"container-max": containermaxlane,
|
|
1119
|
-
"container-lite": containerlitelane,
|
|
1120
|
-
"container-ghcr": containerghcrlane,
|
|
1121
|
-
"storage-s3": storages3lane,
|
|
1122
|
-
"storage-libsql": storagelibsqllane,
|
|
1123
|
-
"storage-mysql": storagemysqllane,
|
|
1124
|
-
"ram-tmpfs": ramtmpfslane,
|
|
1125
|
-
"ram-zram": ramzramlane,
|
|
1126
|
-
"ram-swapfile": ramswapfilelane,
|
|
1127
|
-
"container-runsc": hostprobelane("runsc"),
|
|
1128
|
-
"emulator-qemu": hostprobelane("qemu-system-x86_64"),
|
|
1129
|
-
"microvm-firecracker": hostprobelane("firecracker"),
|
|
1130
|
-
"runtime-jvm": hostprobelane("java"),
|
|
1131
|
-
"language-go": hostprobelane("go"),
|
|
1132
|
-
"language-ruby": hostprobelane("ruby"),
|
|
1133
|
-
});
|