@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.
Files changed (314) hide show
  1. package/Dockerfile +167 -1334
  2. package/README.md +59 -60
  3. package/biome.jsonc +3 -3
  4. package/browser.ts +482 -0
  5. package/cli.ts +274 -0
  6. package/distribution.ts +731 -0
  7. package/docker.config +29 -8
  8. package/docs/{architecture.1.8.19.mmd → architecture1819.md} +6 -0
  9. package/docs/artifactavailability.md +2 -2
  10. package/docs/browser.md +18 -1
  11. package/docs/{containerplatforms.2.1.0.md → containerplatforms210.md} +1 -1
  12. package/docs/{documentmap.1.8.12.md → documentmap1812.md} +1 -1
  13. package/docs/extension.md +13 -2
  14. package/docs/features.md +5 -0
  15. package/docs/libraryapi.md +1 -1
  16. package/docs/{ninety.modalities.md → ninetymodalities.md} +87 -4
  17. package/docs/{planning.ninety.md → planningninety.md} +6 -6
  18. package/docs/{releasenotes.1.8.13.md → releasenotes1813.md} +2 -2
  19. package/docs/{releasenotes.1.8.14.md → releasenotes1814.md} +1 -1
  20. package/docs/{repo.synthesis.1.8.16.md → reposynthesis1816.md} +1 -1
  21. package/docs/todo.md +2 -2
  22. package/docs/{todo.1.8.15.md → todo1815.md} +1 -1
  23. package/docs/{todo.1.8.16.md → todo1816.md} +5 -5
  24. package/execution.ts +677 -0
  25. package/foundation.ts +314 -0
  26. package/index.ts +36 -34
  27. package/isolation.ts +84 -0
  28. package/modalityregistry.ts +189 -145
  29. package/modes.ts +170 -0
  30. package/mttg.config +1 -1
  31. package/orchestrator.ts +49 -15
  32. package/package.json +105 -89
  33. package/qemu.config +2 -2
  34. package/sandbox.ts +3279 -0
  35. package/scheduler.ts +1 -1
  36. package/server.ts +52 -0
  37. package/tsconfig.json +12 -13
  38. package/virtual.ts +7528 -0
  39. package/workloadlaw.ts +293 -0
  40. package/dist/App.d.ts +0 -5
  41. package/dist/App.js +0 -52
  42. package/dist/Sol/Sol.d.ts +0 -3
  43. package/dist/Sol/Sol.js +0 -46
  44. package/dist/Sol/agentbrowser/agentbrowser.d.ts +0 -9
  45. package/dist/Sol/agentbrowser/agentbrowser.js +0 -121
  46. package/dist/Sol/architecture/architecture.d.ts +0 -9
  47. package/dist/Sol/architecture/architecture.js +0 -93
  48. package/dist/Sol/compute/compute.d.ts +0 -9
  49. package/dist/Sol/compute/compute.js +0 -22
  50. package/dist/Sol/console/console.d.ts +0 -15
  51. package/dist/Sol/console/console.js +0 -292
  52. package/dist/Sol/dashboard/dashboard.d.ts +0 -10
  53. package/dist/Sol/dashboard/dashboard.js +0 -181
  54. package/dist/Sol/docs/docs.d.ts +0 -9
  55. package/dist/Sol/docs/docs.js +0 -28
  56. package/dist/Sol/home/home.d.ts +0 -35
  57. package/dist/Sol/home/home.js +0 -239
  58. package/dist/Sol/integrations/integrations.d.ts +0 -9
  59. package/dist/Sol/integrations/integrations.js +0 -83
  60. package/dist/Sol/intro/intro.d.ts +0 -7
  61. package/dist/Sol/intro/intro.js +0 -102
  62. package/dist/Sol/login/login.d.ts +0 -16
  63. package/dist/Sol/login/login.js +0 -49
  64. package/dist/Sol/notfound/notfound.d.ts +0 -9
  65. package/dist/Sol/notfound/notfound.js +0 -15
  66. package/dist/Sol/onboarding/onboarding.d.ts +0 -25
  67. package/dist/Sol/onboarding/onboarding.js +0 -60
  68. package/dist/Sol/playground/playground.d.ts +0 -9
  69. package/dist/Sol/playground/playground.js +0 -127
  70. package/dist/Sol/register/register.d.ts +0 -16
  71. package/dist/Sol/register/register.js +0 -193
  72. package/dist/Sol/shell/Shell.d.ts +0 -124
  73. package/dist/Sol/shell/Shell.js +0 -335
  74. package/dist/Sol/wingets/ComputeBootladder.d.ts +0 -6
  75. package/dist/Sol/wingets/ComputeBootladder.js +0 -71
  76. package/dist/Sol/wingets/ComputeModalitycatalog.d.ts +0 -7
  77. package/dist/Sol/wingets/ComputeModalitycatalog.js +0 -71
  78. package/dist/Sol/wingets/ConsoleApibadge.d.ts +0 -7
  79. package/dist/Sol/wingets/ConsoleApibadge.js +0 -5
  80. package/dist/Sol/wingets/ConsoleEdgelaw.d.ts +0 -6
  81. package/dist/Sol/wingets/ConsoleEdgelaw.js +0 -36
  82. package/dist/Sol/wingets/ConsoleEventstimeline.d.ts +0 -19
  83. package/dist/Sol/wingets/ConsoleEventstimeline.js +0 -12
  84. package/dist/Sol/wingets/ConsoleSnapshotcontrols.d.ts +0 -8
  85. package/dist/Sol/wingets/ConsoleSnapshotcontrols.js +0 -8
  86. package/dist/Sol/wingets/ConsoleSpecpanel.d.ts +0 -21
  87. package/dist/Sol/wingets/ConsoleSpecpanel.js +0 -75
  88. package/dist/Sol/wingets/ConsoleTerminal.d.ts +0 -22
  89. package/dist/Sol/wingets/ConsoleTerminal.js +0 -77
  90. package/dist/Sol/wingets/DashboardAccountpanel.d.ts +0 -18
  91. package/dist/Sol/wingets/DashboardAccountpanel.js +0 -65
  92. package/dist/Sol/wingets/DashboardAdminpanel.d.ts +0 -6
  93. package/dist/Sol/wingets/DashboardAdminpanel.js +0 -90
  94. package/dist/Sol/wingets/DashboardAuditlog.d.ts +0 -8
  95. package/dist/Sol/wingets/DashboardAuditlog.js +0 -50
  96. package/dist/Sol/wingets/DashboardCreatesandboxform.d.ts +0 -10
  97. package/dist/Sol/wingets/DashboardCreatesandboxform.js +0 -71
  98. package/dist/Sol/wingets/DashboardEventstimeline.d.ts +0 -8
  99. package/dist/Sol/wingets/DashboardEventstimeline.js +0 -83
  100. package/dist/Sol/wingets/DashboardGlobalsandboxestable.d.ts +0 -8
  101. package/dist/Sol/wingets/DashboardGlobalsandboxestable.js +0 -57
  102. package/dist/Sol/wingets/DashboardMeshnodestable.d.ts +0 -8
  103. package/dist/Sol/wingets/DashboardMeshnodestable.js +0 -103
  104. package/dist/Sol/wingets/DashboardSandboxlist.d.ts +0 -10
  105. package/dist/Sol/wingets/DashboardSandboxlist.js +0 -163
  106. package/dist/Sol/wingets/DashboardUserstable.d.ts +0 -8
  107. package/dist/Sol/wingets/DashboardUserstable.js +0 -78
  108. package/dist/Sol/wingets/HomeEntrygate.d.ts +0 -23
  109. package/dist/Sol/wingets/HomeEntrygate.js +0 -73
  110. package/dist/Sol/wingets/HomeMetricstrip.d.ts +0 -9
  111. package/dist/Sol/wingets/HomeMetricstrip.js +0 -4
  112. package/dist/Sol/wingets/HomeRuntimediagram.d.ts +0 -1
  113. package/dist/Sol/wingets/HomeRuntimediagram.js +0 -22
  114. package/dist/Sol/wingets/LoginForm.d.ts +0 -19
  115. package/dist/Sol/wingets/LoginForm.js +0 -141
  116. package/dist/Sol/wingets/RegisterPasswordconfirm.d.ts +0 -13
  117. package/dist/Sol/wingets/RegisterPasswordconfirm.js +0 -16
  118. package/dist/Sol/wingets/RegisterPasswordmeter.d.ts +0 -19
  119. package/dist/Sol/wingets/RegisterPasswordmeter.js +0 -65
  120. package/dist/Sol/wingets/RegisterUsernamerules.d.ts +0 -11
  121. package/dist/Sol/wingets/RegisterUsernamerules.js +0 -16
  122. package/dist/Sol/wingets/index.d.ts +0 -39
  123. package/dist/Sol/wingets/index.js +0 -39
  124. package/dist/alternatives.d.ts +0 -508
  125. package/dist/alternatives.js +0 -950
  126. package/dist/api.d.ts +0 -154
  127. package/dist/api.js +0 -174
  128. package/dist/auth.d.ts +0 -268
  129. package/dist/auth.js +0 -476
  130. package/dist/automation.d.ts +0 -425
  131. package/dist/automation.js +0 -566
  132. package/dist/browser.d.ts +0 -561
  133. package/dist/browser.js +0 -1058
  134. package/dist/capacitor.config.d.ts +0 -45
  135. package/dist/capacitor.config.js +0 -48
  136. package/dist/catalog.d.ts +0 -43
  137. package/dist/catalog.js +0 -44
  138. package/dist/cli.d.ts +0 -16
  139. package/dist/cli.js +0 -168
  140. package/dist/communication.d.ts +0 -181
  141. package/dist/communication.js +0 -376
  142. package/dist/compute.d.ts +0 -1160
  143. package/dist/compute.js +0 -2272
  144. package/dist/dashboardlib.d.ts +0 -58
  145. package/dist/dashboardlib.js +0 -161
  146. package/dist/db.d.ts +0 -569
  147. package/dist/db.js +0 -1080
  148. package/dist/distribution.d.ts +0 -528
  149. package/dist/distribution.js +0 -810
  150. package/dist/drizzle.config.d.ts +0 -20
  151. package/dist/drizzle.config.js +0 -18
  152. package/dist/edgelandes.d.ts +0 -70
  153. package/dist/edgelandes.host.d.ts +0 -26
  154. package/dist/edgelandes.host.js +0 -1089
  155. package/dist/edgelandes.inproc.d.ts +0 -27
  156. package/dist/edgelandes.inproc.js +0 -952
  157. package/dist/edgelandes.js +0 -1133
  158. package/dist/edgelandes.ninety.d.ts +0 -31
  159. package/dist/edgelandes.ninety.js +0 -174
  160. package/dist/edgelandes.virtual.d.ts +0 -25
  161. package/dist/edgelandes.virtual.js +0 -629
  162. package/dist/edgeplane.d.ts +0 -121
  163. package/dist/edgeplane.js +0 -341
  164. package/dist/examplelocaljob.d.ts +0 -1
  165. package/dist/examplelocaljob.js +0 -14
  166. package/dist/execution.d.ts +0 -529
  167. package/dist/execution.js +0 -760
  168. package/dist/family.d.ts +0 -16
  169. package/dist/family.js +0 -42
  170. package/dist/familyurl.d.ts +0 -21
  171. package/dist/familyurl.js +0 -24
  172. package/dist/format.d.ts +0 -18
  173. package/dist/format.js +0 -59
  174. package/dist/foundation.d.ts +0 -172
  175. package/dist/foundation.js +0 -312
  176. package/dist/index.d.ts +0 -735
  177. package/dist/index.js +0 -1020
  178. package/dist/integration.d.ts +0 -235
  179. package/dist/integration.js +0 -228
  180. package/dist/intelligence.d.ts +0 -93
  181. package/dist/intelligence.js +0 -144
  182. package/dist/isolation.d.ts +0 -128
  183. package/dist/isolation.js +0 -95
  184. package/dist/labels.d.ts +0 -665
  185. package/dist/labels.js +0 -707
  186. package/dist/localauth.d.ts +0 -127
  187. package/dist/localauth.js +0 -413
  188. package/dist/media.d.ts +0 -343
  189. package/dist/media.js +0 -985
  190. package/dist/mesh.d.ts +0 -177
  191. package/dist/mesh.js +0 -468
  192. package/dist/modalityregistry.d.ts +0 -109
  193. package/dist/modalityregistry.js +0 -1386
  194. package/dist/modes.d.ts +0 -358
  195. package/dist/modes.js +0 -133
  196. package/dist/onboardingstate.d.ts +0 -79
  197. package/dist/onboardingstate.js +0 -282
  198. package/dist/orchestrator.d.ts +0 -1338
  199. package/dist/orchestrator.js +0 -4310
  200. package/dist/packager/targetcli.js +0 -4
  201. package/dist/paths.d.ts +0 -2
  202. package/dist/paths.js +0 -14
  203. package/dist/performance.d.ts +0 -415
  204. package/dist/performance.js +0 -826
  205. package/dist/quantum.d.ts +0 -809
  206. package/dist/quantum.js +0 -2072
  207. package/dist/render.d.ts +0 -380
  208. package/dist/render.js +0 -1072
  209. package/dist/runall.d.ts +0 -2
  210. package/dist/runall.js +0 -39
  211. package/dist/runtimeprobe.d.ts +0 -4
  212. package/dist/runtimeprobe.js +0 -8
  213. package/dist/sandbox.d.ts +0 -355
  214. package/dist/sandbox.js +0 -1721
  215. package/dist/sandboxenvprofile.d.ts +0 -29
  216. package/dist/sandboxenvprofile.js +0 -89
  217. package/dist/sandboxprofile.d.ts +0 -137
  218. package/dist/sandboxprofile.js +0 -241
  219. package/dist/sandboxqueue.d.ts +0 -108
  220. package/dist/sandboxqueue.js +0 -201
  221. package/dist/sandboxquota.d.ts +0 -121
  222. package/dist/sandboxquota.js +0 -236
  223. package/dist/scheduler.d.ts +0 -595
  224. package/dist/scheduler.js +0 -1258
  225. package/dist/scriptlanes.d.ts +0 -182
  226. package/dist/scriptlanes.js +0 -660
  227. package/dist/scripts.buildengine.d.ts +0 -1
  228. package/dist/scripts.buildengine.js +0 -39
  229. package/dist/scripts.securitygate.d.ts +0 -1
  230. package/dist/scripts.securitygate.js +0 -68
  231. package/dist/scripts.validateflatnative.d.ts +0 -1
  232. package/dist/scripts.validateflatnative.js +0 -72
  233. package/dist/security.d.ts +0 -500
  234. package/dist/security.js +0 -1169
  235. package/dist/seedcatalog.d.ts +0 -13
  236. package/dist/seedcatalog.js +0 -46
  237. package/dist/server.d.ts +0 -20
  238. package/dist/server.js +0 -53
  239. package/dist/speccatalog.d.ts +0 -31
  240. package/dist/speccatalog.js +0 -163
  241. package/dist/tiers.d.ts +0 -1001
  242. package/dist/tiers.js +0 -2169
  243. package/dist/utils.d.ts +0 -2
  244. package/dist/utils.js +0 -5
  245. package/dist/vaulthandoff.d.ts +0 -141
  246. package/dist/vaulthandoff.js +0 -444
  247. package/dist/virtual.d.ts +0 -640
  248. package/dist/virtual.js +0 -1284
  249. package/dist/virtualcpu.d.ts +0 -315
  250. package/dist/virtualcpu.js +0 -1180
  251. package/dist/virtualgpu.d.ts +0 -344
  252. package/dist/virtualgpu.js +0 -1000
  253. package/dist/virtualization.d.ts +0 -606
  254. package/dist/virtualization.js +0 -1186
  255. package/dist/virtualmemory.d.ts +0 -817
  256. package/dist/virtualmemory.js +0 -1795
  257. package/dist/vite.config.d.ts +0 -3
  258. package/dist/vite.config.js +0 -48
  259. package/dist/vitest.config.d.ts +0 -2
  260. package/dist/vitest.config.js +0 -25
  261. package/dist/webserver.d.ts +0 -166
  262. package/dist/webserver.js +0 -2226
  263. package/dist/workloadlaw.d.ts +0 -133
  264. package/dist/workloadlaw.js +0 -193
  265. package/docs/.gitkeep +0 -0
  266. package/docs/architecture.1.8.19.md +0 -76
  267. package/docs/logs/examplesession.json +0 -54
  268. package/docs/repo.analysis.1.8.21.json +0 -2617
  269. package/docs/repo.candidates.1.8.21.jsonl +0 -666
  270. package/docs/repo.selected.1.8.21.json +0 -786
  271. package/manifest.json +0 -20
  272. package/media.ts +0 -1337
  273. package/virtualcpu.ts +0 -1274
  274. package/virtualgpu.ts +0 -1182
  275. package/virtualization.ts +0 -1628
  276. package/virtualmemory.ts +0 -2303
  277. /package/docs/{artifactresearch.1.8.9.md → artifactresearch189.md} +0 -0
  278. /package/docs/{batches.1816.to.1820.md → batches1816to1820.md} +0 -0
  279. /package/docs/{brancharchive.2026.08.18.md → brancharchive20260818.md} +0 -0
  280. /package/docs/{containerplatforms.1.8.17.md → containerplatforms1817.md} +0 -0
  281. /package/docs/{e2ugh.engine.md → e2ughengine.md} +0 -0
  282. /package/docs/{flatbuildresearch.1.8.11.md → flatbuildresearch1811.md} +0 -0
  283. /package/docs/{licenseaudit.1.8.12.md → licenseaudit1812.md} +0 -0
  284. /package/docs/{mobileconversionresearch.1.8.10.md → mobileconversionresearch1810.md} +0 -0
  285. /package/docs/{nativeidentityresearch.1.8.11.md → nativeidentityresearch1811.md} +0 -0
  286. /package/docs/{native.wrappers.md → nativewrappers.md} +0 -0
  287. /package/docs/{planning.1.8.18.md → planning1818.md} +0 -0
  288. /package/docs/{planning.1.8.19.md → planning1819.md} +0 -0
  289. /package/docs/{planning.engine.md → planningengine.md} +0 -0
  290. /package/docs/{releasenotes.1.8.12.md → releasenotes1812.md} +0 -0
  291. /package/docs/{releasenotes.1.8.15.md → releasenotes1815.md} +0 -0
  292. /package/docs/{releasenotes.1.8.16.md → releasenotes1816.md} +0 -0
  293. /package/docs/{releasenotes.1.8.17.md → releasenotes1817.md} +0 -0
  294. /package/docs/{releasenotes.1.8.18.md → releasenotes1818.md} +0 -0
  295. /package/docs/{releasenotes.1.8.19.md → releasenotes1819.md} +0 -0
  296. /package/docs/{reorganization.1.8.8.md → reorganization188.md} +0 -0
  297. /package/docs/{reorganization.1.8.9.md → reorganization189.md} +0 -0
  298. /package/docs/{repo.research.1.8.16.md → reporesearch1816.md} +0 -0
  299. /package/docs/{repo.research.1.8.21.md → reporesearch1821.md} +0 -0
  300. /package/docs/{repo.synthesis.1.8.21.md → reposynthesis1821.md} +0 -0
  301. /package/docs/{research.1.8.18.isolation.md → research1818isolation.md} +0 -0
  302. /package/docs/{research.1.8.19.virtual.browser.md → research1819virtualbrowser.md} +0 -0
  303. /package/docs/{scanningresearch.1.8.11.md → scanningresearch1811.md} +0 -0
  304. /package/docs/{securityaudit.1.8.7.md → securityaudit187.md} +0 -0
  305. /package/docs/{securityreferences.1.8.11.md → securityreferences1811.md} +0 -0
  306. /package/docs/{securityresearch.1.8.11.md → securityresearch1811.md} +0 -0
  307. /package/docs/{signingresearch.1.8.12.md → signingresearch1812.md} +0 -0
  308. /package/docs/{tauri.glib.risk.1.8.11.md → tauriglibrisk1811.md} +0 -0
  309. /package/docs/{todo.1.8.18.md → todo1818.md} +0 -0
  310. /package/docs/{todo.1.8.19.md → todo1819.md} +0 -0
  311. /package/docs/{toolchainresearch.1.8.9.md → toolchainresearch189.md} +0 -0
  312. /package/docs/{web.duplicatecleanup.md → webduplicatecleanup.md} +0 -0
  313. /package/docs/{web.ideas.md → webideas.md} +0 -0
  314. /package/docs/{workflowimprovements.2026.08.19.md → workflowimprovements20260819.md} +0 -0
@@ -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
- });