@wenathlan/saddle 2.0.153 → 2.0.155

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 (307) hide show
  1. package/Dockerfile +139 -55
  2. package/README.md +58 -59
  3. package/browser.ts +482 -0
  4. package/cli.ts +274 -0
  5. package/distribution.ts +731 -0
  6. package/docker.config +29 -8
  7. package/docs/artifactavailability.md +2 -2
  8. package/docs/browser.md +18 -1
  9. package/docs/{containerplatforms.2.1.0.md → containerplatforms210.md} +1 -1
  10. package/docs/{documentmap.1.8.12.md → documentmap1812.md} +1 -1
  11. package/docs/extension.md +13 -2
  12. package/docs/libraryapi.md +1 -1
  13. package/docs/{ninety.modalities.md → ninetymodalities.md} +87 -4
  14. package/docs/{planning.ninety.md → planningninety.md} +6 -6
  15. package/docs/{releasenotes.1.8.13.md → releasenotes1813.md} +2 -2
  16. package/docs/{releasenotes.1.8.14.md → releasenotes1814.md} +1 -1
  17. package/docs/{repo.synthesis.1.8.16.md → reposynthesis1816.md} +1 -1
  18. package/docs/todo.md +2 -2
  19. package/docs/{todo.1.8.15.md → todo1815.md} +1 -1
  20. package/docs/{todo.1.8.16.md → todo1816.md} +5 -5
  21. package/execution.ts +677 -0
  22. package/foundation.ts +314 -0
  23. package/index.ts +35 -27
  24. package/isolation.ts +84 -0
  25. package/modes.ts +170 -0
  26. package/mttg.config +1 -1
  27. package/orchestrator.ts +49 -15
  28. package/package.json +90 -75
  29. package/qemu.config +2 -2
  30. package/sandbox.ts +2094 -0
  31. package/scheduler.ts +1 -1
  32. package/server.ts +52 -0
  33. package/tsconfig.json +11 -12
  34. package/virtual.ts +1105 -0
  35. package/virtualgpu.ts +1 -1
  36. package/virtualization.ts +1 -1
  37. package/workloadlaw.ts +293 -0
  38. package/dist/App.d.ts +0 -5
  39. package/dist/App.js +0 -52
  40. package/dist/Sol/Sol.d.ts +0 -3
  41. package/dist/Sol/Sol.js +0 -46
  42. package/dist/Sol/agentbrowser/agentbrowser.d.ts +0 -9
  43. package/dist/Sol/agentbrowser/agentbrowser.js +0 -121
  44. package/dist/Sol/architecture/architecture.d.ts +0 -9
  45. package/dist/Sol/architecture/architecture.js +0 -93
  46. package/dist/Sol/compute/compute.d.ts +0 -9
  47. package/dist/Sol/compute/compute.js +0 -22
  48. package/dist/Sol/console/console.d.ts +0 -15
  49. package/dist/Sol/console/console.js +0 -292
  50. package/dist/Sol/dashboard/dashboard.d.ts +0 -10
  51. package/dist/Sol/dashboard/dashboard.js +0 -181
  52. package/dist/Sol/docs/docs.d.ts +0 -9
  53. package/dist/Sol/docs/docs.js +0 -28
  54. package/dist/Sol/home/home.d.ts +0 -35
  55. package/dist/Sol/home/home.js +0 -239
  56. package/dist/Sol/integrations/integrations.d.ts +0 -9
  57. package/dist/Sol/integrations/integrations.js +0 -83
  58. package/dist/Sol/intro/intro.d.ts +0 -7
  59. package/dist/Sol/intro/intro.js +0 -102
  60. package/dist/Sol/login/login.d.ts +0 -16
  61. package/dist/Sol/login/login.js +0 -49
  62. package/dist/Sol/notfound/notfound.d.ts +0 -9
  63. package/dist/Sol/notfound/notfound.js +0 -15
  64. package/dist/Sol/onboarding/onboarding.d.ts +0 -25
  65. package/dist/Sol/onboarding/onboarding.js +0 -60
  66. package/dist/Sol/playground/playground.d.ts +0 -9
  67. package/dist/Sol/playground/playground.js +0 -127
  68. package/dist/Sol/register/register.d.ts +0 -16
  69. package/dist/Sol/register/register.js +0 -193
  70. package/dist/Sol/shell/Shell.d.ts +0 -124
  71. package/dist/Sol/shell/Shell.js +0 -335
  72. package/dist/Sol/wingets/ComputeBootladder.d.ts +0 -6
  73. package/dist/Sol/wingets/ComputeBootladder.js +0 -71
  74. package/dist/Sol/wingets/ComputeModalitycatalog.d.ts +0 -7
  75. package/dist/Sol/wingets/ComputeModalitycatalog.js +0 -71
  76. package/dist/Sol/wingets/ConsoleApibadge.d.ts +0 -7
  77. package/dist/Sol/wingets/ConsoleApibadge.js +0 -5
  78. package/dist/Sol/wingets/ConsoleEdgelaw.d.ts +0 -6
  79. package/dist/Sol/wingets/ConsoleEdgelaw.js +0 -36
  80. package/dist/Sol/wingets/ConsoleEventstimeline.d.ts +0 -19
  81. package/dist/Sol/wingets/ConsoleEventstimeline.js +0 -12
  82. package/dist/Sol/wingets/ConsoleSnapshotcontrols.d.ts +0 -8
  83. package/dist/Sol/wingets/ConsoleSnapshotcontrols.js +0 -8
  84. package/dist/Sol/wingets/ConsoleSpecpanel.d.ts +0 -21
  85. package/dist/Sol/wingets/ConsoleSpecpanel.js +0 -75
  86. package/dist/Sol/wingets/ConsoleTerminal.d.ts +0 -22
  87. package/dist/Sol/wingets/ConsoleTerminal.js +0 -77
  88. package/dist/Sol/wingets/DashboardAccountpanel.d.ts +0 -18
  89. package/dist/Sol/wingets/DashboardAccountpanel.js +0 -65
  90. package/dist/Sol/wingets/DashboardAdminpanel.d.ts +0 -6
  91. package/dist/Sol/wingets/DashboardAdminpanel.js +0 -90
  92. package/dist/Sol/wingets/DashboardAuditlog.d.ts +0 -8
  93. package/dist/Sol/wingets/DashboardAuditlog.js +0 -50
  94. package/dist/Sol/wingets/DashboardCreatesandboxform.d.ts +0 -10
  95. package/dist/Sol/wingets/DashboardCreatesandboxform.js +0 -71
  96. package/dist/Sol/wingets/DashboardEventstimeline.d.ts +0 -8
  97. package/dist/Sol/wingets/DashboardEventstimeline.js +0 -83
  98. package/dist/Sol/wingets/DashboardGlobalsandboxestable.d.ts +0 -8
  99. package/dist/Sol/wingets/DashboardGlobalsandboxestable.js +0 -57
  100. package/dist/Sol/wingets/DashboardMeshnodestable.d.ts +0 -8
  101. package/dist/Sol/wingets/DashboardMeshnodestable.js +0 -103
  102. package/dist/Sol/wingets/DashboardSandboxlist.d.ts +0 -10
  103. package/dist/Sol/wingets/DashboardSandboxlist.js +0 -163
  104. package/dist/Sol/wingets/DashboardUserstable.d.ts +0 -8
  105. package/dist/Sol/wingets/DashboardUserstable.js +0 -78
  106. package/dist/Sol/wingets/HomeEntrygate.d.ts +0 -23
  107. package/dist/Sol/wingets/HomeEntrygate.js +0 -73
  108. package/dist/Sol/wingets/HomeMetricstrip.d.ts +0 -9
  109. package/dist/Sol/wingets/HomeMetricstrip.js +0 -4
  110. package/dist/Sol/wingets/HomeRuntimediagram.d.ts +0 -1
  111. package/dist/Sol/wingets/HomeRuntimediagram.js +0 -22
  112. package/dist/Sol/wingets/LoginForm.d.ts +0 -19
  113. package/dist/Sol/wingets/LoginForm.js +0 -141
  114. package/dist/Sol/wingets/RegisterPasswordconfirm.d.ts +0 -13
  115. package/dist/Sol/wingets/RegisterPasswordconfirm.js +0 -16
  116. package/dist/Sol/wingets/RegisterPasswordmeter.d.ts +0 -19
  117. package/dist/Sol/wingets/RegisterPasswordmeter.js +0 -65
  118. package/dist/Sol/wingets/RegisterUsernamerules.d.ts +0 -11
  119. package/dist/Sol/wingets/RegisterUsernamerules.js +0 -16
  120. package/dist/Sol/wingets/index.d.ts +0 -39
  121. package/dist/Sol/wingets/index.js +0 -39
  122. package/dist/alternatives.d.ts +0 -508
  123. package/dist/alternatives.js +0 -950
  124. package/dist/api.d.ts +0 -154
  125. package/dist/api.js +0 -174
  126. package/dist/auth.d.ts +0 -268
  127. package/dist/auth.js +0 -476
  128. package/dist/automation.d.ts +0 -425
  129. package/dist/automation.js +0 -566
  130. package/dist/browser.d.ts +0 -561
  131. package/dist/browser.js +0 -1058
  132. package/dist/capacitor.config.d.ts +0 -45
  133. package/dist/capacitor.config.js +0 -48
  134. package/dist/catalog.d.ts +0 -43
  135. package/dist/catalog.js +0 -44
  136. package/dist/cli.d.ts +0 -16
  137. package/dist/cli.js +0 -168
  138. package/dist/communication.d.ts +0 -181
  139. package/dist/communication.js +0 -376
  140. package/dist/compute.d.ts +0 -1160
  141. package/dist/compute.js +0 -2272
  142. package/dist/dashboardlib.d.ts +0 -58
  143. package/dist/dashboardlib.js +0 -161
  144. package/dist/db.d.ts +0 -569
  145. package/dist/db.js +0 -1080
  146. package/dist/distribution.d.ts +0 -528
  147. package/dist/distribution.js +0 -810
  148. package/dist/drizzle.config.d.ts +0 -20
  149. package/dist/drizzle.config.js +0 -18
  150. package/dist/edgelandes.d.ts +0 -70
  151. package/dist/edgelandes.host.d.ts +0 -26
  152. package/dist/edgelandes.host.js +0 -1089
  153. package/dist/edgelandes.inproc.d.ts +0 -27
  154. package/dist/edgelandes.inproc.js +0 -952
  155. package/dist/edgelandes.js +0 -1133
  156. package/dist/edgelandes.ninety.d.ts +0 -31
  157. package/dist/edgelandes.ninety.js +0 -174
  158. package/dist/edgelandes.virtual.d.ts +0 -25
  159. package/dist/edgelandes.virtual.js +0 -629
  160. package/dist/edgeplane.d.ts +0 -114
  161. package/dist/edgeplane.js +0 -305
  162. package/dist/examplelocaljob.d.ts +0 -1
  163. package/dist/examplelocaljob.js +0 -14
  164. package/dist/execution.d.ts +0 -529
  165. package/dist/execution.js +0 -760
  166. package/dist/family.d.ts +0 -16
  167. package/dist/family.js +0 -42
  168. package/dist/familyurl.d.ts +0 -21
  169. package/dist/familyurl.js +0 -24
  170. package/dist/format.d.ts +0 -18
  171. package/dist/format.js +0 -59
  172. package/dist/foundation.d.ts +0 -172
  173. package/dist/foundation.js +0 -312
  174. package/dist/index.d.ts +0 -735
  175. package/dist/index.js +0 -1020
  176. package/dist/integration.d.ts +0 -235
  177. package/dist/integration.js +0 -228
  178. package/dist/intelligence.d.ts +0 -93
  179. package/dist/intelligence.js +0 -144
  180. package/dist/isolation.d.ts +0 -128
  181. package/dist/isolation.js +0 -95
  182. package/dist/labels.d.ts +0 -665
  183. package/dist/labels.js +0 -751
  184. package/dist/localauth.d.ts +0 -127
  185. package/dist/localauth.js +0 -413
  186. package/dist/media.d.ts +0 -343
  187. package/dist/media.js +0 -985
  188. package/dist/mesh.d.ts +0 -177
  189. package/dist/mesh.js +0 -468
  190. package/dist/modalityregistry.d.ts +0 -109
  191. package/dist/modalityregistry.js +0 -1386
  192. package/dist/modes.d.ts +0 -358
  193. package/dist/modes.js +0 -133
  194. package/dist/onboardingstate.d.ts +0 -79
  195. package/dist/onboardingstate.js +0 -282
  196. package/dist/orchestrator.d.ts +0 -1338
  197. package/dist/orchestrator.js +0 -4310
  198. package/dist/packager/targetcli.js +0 -4
  199. package/dist/paths.d.ts +0 -2
  200. package/dist/paths.js +0 -14
  201. package/dist/performance.d.ts +0 -415
  202. package/dist/performance.js +0 -826
  203. package/dist/quantum.d.ts +0 -809
  204. package/dist/quantum.js +0 -2072
  205. package/dist/render.d.ts +0 -380
  206. package/dist/render.js +0 -1072
  207. package/dist/runall.d.ts +0 -2
  208. package/dist/runall.js +0 -39
  209. package/dist/runtimeprobe.d.ts +0 -4
  210. package/dist/runtimeprobe.js +0 -8
  211. package/dist/sandbox.d.ts +0 -355
  212. package/dist/sandbox.js +0 -1721
  213. package/dist/sandboxenvprofile.d.ts +0 -29
  214. package/dist/sandboxenvprofile.js +0 -89
  215. package/dist/sandboxprofile.d.ts +0 -137
  216. package/dist/sandboxprofile.js +0 -241
  217. package/dist/sandboxqueue.d.ts +0 -108
  218. package/dist/sandboxqueue.js +0 -201
  219. package/dist/sandboxquota.d.ts +0 -121
  220. package/dist/sandboxquota.js +0 -236
  221. package/dist/scheduler.d.ts +0 -595
  222. package/dist/scheduler.js +0 -1258
  223. package/dist/scriptlanes.d.ts +0 -182
  224. package/dist/scriptlanes.js +0 -660
  225. package/dist/scripts.buildengine.d.ts +0 -1
  226. package/dist/scripts.buildengine.js +0 -39
  227. package/dist/scripts.securitygate.d.ts +0 -1
  228. package/dist/scripts.securitygate.js +0 -68
  229. package/dist/scripts.validateflatnative.d.ts +0 -1
  230. package/dist/scripts.validateflatnative.js +0 -72
  231. package/dist/security.d.ts +0 -500
  232. package/dist/security.js +0 -1169
  233. package/dist/seedcatalog.d.ts +0 -13
  234. package/dist/seedcatalog.js +0 -46
  235. package/dist/server.d.ts +0 -20
  236. package/dist/server.js +0 -53
  237. package/dist/speccatalog.d.ts +0 -31
  238. package/dist/speccatalog.js +0 -163
  239. package/dist/tiers.d.ts +0 -1001
  240. package/dist/tiers.js +0 -2169
  241. package/dist/utils.d.ts +0 -2
  242. package/dist/utils.js +0 -5
  243. package/dist/vaulthandoff.d.ts +0 -141
  244. package/dist/vaulthandoff.js +0 -444
  245. package/dist/virtual.d.ts +0 -640
  246. package/dist/virtual.js +0 -1284
  247. package/dist/virtualcpu.d.ts +0 -315
  248. package/dist/virtualcpu.js +0 -1180
  249. package/dist/virtualgpu.d.ts +0 -344
  250. package/dist/virtualgpu.js +0 -1000
  251. package/dist/virtualization.d.ts +0 -606
  252. package/dist/virtualization.js +0 -1186
  253. package/dist/virtualmemory.d.ts +0 -817
  254. package/dist/virtualmemory.js +0 -1795
  255. package/dist/vite.config.d.ts +0 -3
  256. package/dist/vite.config.js +0 -43
  257. package/dist/vitest.config.d.ts +0 -2
  258. package/dist/vitest.config.js +0 -25
  259. package/dist/webserver.d.ts +0 -166
  260. package/dist/webserver.js +0 -2226
  261. package/dist/workloadlaw.d.ts +0 -133
  262. package/dist/workloadlaw.js +0 -193
  263. package/manifest.json +0 -20
  264. package/media.ts +0 -1337
  265. /package/docs/{architecture.1.8.19.md → architecture1819.md} +0 -0
  266. /package/docs/{architecture.1.8.19.mmd → architecture1819.mmd} +0 -0
  267. /package/docs/{artifactresearch.1.8.9.md → artifactresearch189.md} +0 -0
  268. /package/docs/{batches.1816.to.1820.md → batches1816to1820.md} +0 -0
  269. /package/docs/{brancharchive.2026.08.18.md → brancharchive20260818.md} +0 -0
  270. /package/docs/{containerplatforms.1.8.17.md → containerplatforms1817.md} +0 -0
  271. /package/docs/{e2ugh.engine.md → e2ughengine.md} +0 -0
  272. /package/docs/{flatbuildresearch.1.8.11.md → flatbuildresearch1811.md} +0 -0
  273. /package/docs/{licenseaudit.1.8.12.md → licenseaudit1812.md} +0 -0
  274. /package/docs/{mobileconversionresearch.1.8.10.md → mobileconversionresearch1810.md} +0 -0
  275. /package/docs/{nativeidentityresearch.1.8.11.md → nativeidentityresearch1811.md} +0 -0
  276. /package/docs/{native.wrappers.md → nativewrappers.md} +0 -0
  277. /package/docs/{planning.1.8.18.md → planning1818.md} +0 -0
  278. /package/docs/{planning.1.8.19.md → planning1819.md} +0 -0
  279. /package/docs/{planning.engine.md → planningengine.md} +0 -0
  280. /package/docs/{releasenotes.1.8.12.md → releasenotes1812.md} +0 -0
  281. /package/docs/{releasenotes.1.8.15.md → releasenotes1815.md} +0 -0
  282. /package/docs/{releasenotes.1.8.16.md → releasenotes1816.md} +0 -0
  283. /package/docs/{releasenotes.1.8.17.md → releasenotes1817.md} +0 -0
  284. /package/docs/{releasenotes.1.8.18.md → releasenotes1818.md} +0 -0
  285. /package/docs/{releasenotes.1.8.19.md → releasenotes1819.md} +0 -0
  286. /package/docs/{reorganization.1.8.8.md → reorganization188.md} +0 -0
  287. /package/docs/{reorganization.1.8.9.md → reorganization189.md} +0 -0
  288. /package/docs/{repo.analysis.1.8.21.json → repoanalysis1821.json} +0 -0
  289. /package/docs/{repo.candidates.1.8.21.jsonl → repocandidates1821.jsonl} +0 -0
  290. /package/docs/{repo.research.1.8.16.md → reporesearch1816.md} +0 -0
  291. /package/docs/{repo.research.1.8.21.md → reporesearch1821.md} +0 -0
  292. /package/docs/{repo.selected.1.8.21.json → reposelected1821.json} +0 -0
  293. /package/docs/{repo.synthesis.1.8.21.md → reposynthesis1821.md} +0 -0
  294. /package/docs/{research.1.8.18.isolation.md → research1818isolation.md} +0 -0
  295. /package/docs/{research.1.8.19.virtual.browser.md → research1819virtualbrowser.md} +0 -0
  296. /package/docs/{scanningresearch.1.8.11.md → scanningresearch1811.md} +0 -0
  297. /package/docs/{securityaudit.1.8.7.md → securityaudit187.md} +0 -0
  298. /package/docs/{securityreferences.1.8.11.md → securityreferences1811.md} +0 -0
  299. /package/docs/{securityresearch.1.8.11.md → securityresearch1811.md} +0 -0
  300. /package/docs/{signingresearch.1.8.12.md → signingresearch1812.md} +0 -0
  301. /package/docs/{tauri.glib.risk.1.8.11.md → tauriglibrisk1811.md} +0 -0
  302. /package/docs/{todo.1.8.18.md → todo1818.md} +0 -0
  303. /package/docs/{todo.1.8.19.md → todo1819.md} +0 -0
  304. /package/docs/{toolchainresearch.1.8.9.md → toolchainresearch189.md} +0 -0
  305. /package/docs/{web.duplicatecleanup.md → webduplicatecleanup.md} +0 -0
  306. /package/docs/{web.ideas.md → webideas.md} +0 -0
  307. /package/docs/{workflowimprovements.2026.08.19.md → workflowimprovements20260819.md} +0 -0
@@ -1,660 +0,0 @@
1
- /**
2
- * scriptlanes.ts — the script lanes of the saddle engine: sh execution
3
- * through the universal runner, yml and yaml workflow descriptors
4
- * interpreted and compiled through babel into executable job plans,
5
- * background execution through the house job queue, and docker-ready
6
- * descriptors for the container lane.
7
- *
8
- * The lanes this file opens answer the modalities the registry declares
9
- * (runner-universal for the bash lane, runner-babel for the compile lane,
10
- * container-docker for the descriptor lane) and keep the owner's PR15
11
- * mandate verbatim: "Implemente execução de SHs / interpretação de YML /
12
- * YAML com compilação via Babel para permitir execução em segundo plano /
13
- * Docker".
14
- *
15
- * The laws every lane below keeps:
16
- * - sh scripts execute server-side through the engine (the bash lane),
17
- * never the visitor machine: every public run entry refuses a scope
18
- * that carries window or document (assertservercontext);
19
- * - yml and yaml descriptors interpret into executable job plans through
20
- * the house yaml subset (documented at the parser), and the babel lane
21
- * compiles the descriptor through @babel/core into a regenerated
22
- * literal the engine parses back through node:vm — the roundtrip is
23
- * validated, never presumed;
24
- * - background execution rides the house jobqueue and the runstatuses
25
- * vocabulary (created, submitted, running, succeeded, failed,
26
- * cancelled): the lane kicks the run detached from the caller and the
27
- * status stays observable through scriptRunStatus;
28
- * - honest execution only: no faked results, a {code, message} shape on
29
- * every fallible path, and deadlines enforced with a timeout kill on
30
- * the child process.
31
- */
32
- import { spawn } from "node:child_process";
33
- import { runInNewContext } from "node:vm";
34
- import { jobqueue, runrecord, runstatuses, transitionrun } from "./execution.js";
35
- import { aserror, idfactory, saddleerror, validationerror } from "./foundation.js";
36
- /** refuses a scope that carries window or document: the script lanes
37
- * execute server-side only, and the visitor machine is never touched
38
- * (the never-the-visitor-browser law). every public run entry calls
39
- * this before any work. */
40
- export function assertservercontext(scope = globalThis) {
41
- if (scope.window !== undefined || scope.document !== undefined) {
42
- throw saddleerror("the script lanes execute server-side only — the visitor browser context is refused", {
43
- code: "VISITOR_CONTEXT_REFUSED",
44
- details: { window: scope.window !== undefined, document: scope.document !== undefined },
45
- });
46
- }
47
- }
48
- /** validates a script plan honestly: a non-empty steps array where every
49
- * step carries a non-empty string name, a string run and an optional
50
- * positive finite timeoutms, with a known source and compile lane. */
51
- function validateplan(plan) {
52
- if (!plan || typeof plan !== "object")
53
- throw validationerror("the script plan must be an object");
54
- const candidate = plan;
55
- if (!Array.isArray(candidate.steps) || candidate.steps.length === 0) {
56
- throw validationerror("the script plan must carry a non-empty steps array");
57
- }
58
- for (const step of candidate.steps) {
59
- if (!step || typeof step !== "object")
60
- throw validationerror("every script step must be an object");
61
- const record = step;
62
- if (typeof record.name !== "string" || record.name.trim().length === 0) {
63
- throw validationerror("every script step requires a non-empty string name");
64
- }
65
- if (typeof record.run !== "string")
66
- throw validationerror(`the step "${record.name}" requires a string run command`);
67
- if (record.timeoutms !== undefined &&
68
- (typeof record.timeoutms !== "number" || !Number.isFinite(record.timeoutms) || record.timeoutms < 1)) {
69
- throw validationerror(`the step "${record.name}" timeoutms must be a positive finite number`);
70
- }
71
- }
72
- if (!["sh", "yml", "yaml"].includes(candidate.source ?? "")) {
73
- throw validationerror("the script plan source must be sh, yml or yaml");
74
- }
75
- if (!["babel", "direct"].includes(candidate.compiledvia ?? "")) {
76
- throw validationerror("the script plan compile lane must be babel or direct");
77
- }
78
- return candidate;
79
- }
80
- /** validates the shared deadline option: absent passes as undefined, a
81
- * present value must be a positive finite number of milliseconds. */
82
- function validatedeadline(value) {
83
- if (value === undefined)
84
- return undefined;
85
- if (typeof value !== "number" || !Number.isFinite(value) || value < 1) {
86
- throw validationerror("the script run deadlinems must be a positive finite number of milliseconds");
87
- }
88
- return value;
89
- }
90
- /* ════════════════════════════════════════════════════════════════════ */
91
- /* Section 2: the interpretation lanes — the sh wrap and the house yaml subset parser. */
92
- /* ════════════════════════════════════════════════════════════════════ */
93
- /**
94
- * the interpretation lanes: interpretsh wraps a raw sh script as one
95
- * direct step, and the house yaml subset parser reads the workflow
96
- * descriptors (yml and yaml spellings) into step lists.
97
- */
98
- /** wraps a raw sh script as a one-step plan: the script rides the bash
99
- * lane directly, no compile step, so the plan declares the direct lane. */
100
- export function interpretsh(script) {
101
- if (typeof script !== "string" || script.trim().length === 0) {
102
- throw validationerror("the sh script must be a non-empty string");
103
- }
104
- return { steps: [{ name: "script", run: script }], source: "sh", compiledvia: "direct" };
105
- }
106
- /** the yaml error factory: every interpret failure throws the honest
107
- * house shape with the YAML_INTERPRET_FAILED code. */
108
- function yamlerror(message) {
109
- return saddleerror(`the yml interpretation failed: ${message}`, { code: "YAML_INTERPRET_FAILED" });
110
- }
111
- /** strips a `#` comment the way yaml does: outside quotes, and only at
112
- * line start or after whitespace (a `#` glued to a word is content). */
113
- function striphyamlcomment(line) {
114
- let quote = null;
115
- for (let index = 0; index < line.length; index += 1) {
116
- const char = line[index];
117
- if (quote) {
118
- if (char === quote)
119
- quote = null;
120
- continue;
121
- }
122
- if (char === '"' || char === "'") {
123
- quote = char;
124
- continue;
125
- }
126
- if (char === "#" && (index === 0 || line[index - 1] === " " || line[index - 1] === "\t")) {
127
- return line.slice(0, index);
128
- }
129
- }
130
- return line;
131
- }
132
- /** parses one scalar of the house subset: a plain string, a double- or
133
- * single-quoted string, an integer or decimal, or one of the two
134
- * booleans. the subset never invents types beyond these (nulls, dates
135
- * and exponents stay plain strings — documented, not silently coerced). */
136
- function parsehousescalar(raw, lineno) {
137
- const text = raw.trim();
138
- if (text.length === 0) {
139
- throw yamlerror(`line ${lineno}: the value after the colon is empty (block scalars are outside the house subset)`);
140
- }
141
- if (text.length >= 2 && text.startsWith('"') && text.endsWith('"')) {
142
- const inner = text.slice(1, -1);
143
- if (inner.includes('"'))
144
- throw yamlerror(`line ${lineno}: an unescaped double quote rides inside a quoted scalar`);
145
- return inner;
146
- }
147
- if (text.length >= 2 && text.startsWith("'") && text.endsWith("'")) {
148
- return text.slice(1, -1).replace(/''/g, "'");
149
- }
150
- if (text === "true")
151
- return true;
152
- if (text === "false")
153
- return false;
154
- if (/^-?\d+$/.test(text) || /^-?\d+\.\d+$/.test(text))
155
- return Number(text);
156
- return text;
157
- }
158
- /** the house yaml subset the lane interprets, documented honestly:
159
- * exactly one top-level mapping key — `steps:` — whose value is an
160
- * indented list of flat mappings; every list item carries `name:` (a
161
- * string), `run:` (a string) and the optional `timeoutms:` (a positive
162
- * number); values are single-line scalars (plain or quoted strings,
163
- * integers, decimals, true and false); comments start at a `#` outside
164
- * quotes after whitespace; every list line shares one indentation and
165
- * every key of one item shares a deeper one — a dedent or a mismatch is
166
- * an honest refusal, never a guess. unknown keys, duplicate keys,
167
- * document markers, nested mappings and block scalars are all outside
168
- * the subset and refuse with YAML_INTERPRET_FAILED. */
169
- function parsehousesubset(text) {
170
- if (typeof text !== "string")
171
- throw validationerror("the yml descriptor must be a string");
172
- const steps = [];
173
- const lines = text.split(/\r?\n/);
174
- let sawsteps = false;
175
- let itemindent = -1;
176
- let fieldindent = -1;
177
- let current = null;
178
- function closecurrent(lineno) {
179
- if (!current)
180
- return;
181
- const name = current.get("name");
182
- const run = current.get("run");
183
- const timeoutms = current.get("timeoutms");
184
- if (typeof name !== "string" || name.trim().length === 0) {
185
- throw yamlerror(`line ${lineno}: every step requires a non-empty string name`);
186
- }
187
- if (typeof run !== "string")
188
- throw yamlerror(`line ${lineno}: the step "${name}" requires a string run command`);
189
- if (timeoutms !== undefined && (typeof timeoutms !== "number" || !Number.isFinite(timeoutms) || timeoutms < 1)) {
190
- throw yamlerror(`line ${lineno}: the step "${name}" timeoutms must be a positive number`);
191
- }
192
- steps.push(timeoutms === undefined ? { name, run } : { name, run, timeoutms });
193
- current = null;
194
- }
195
- function parsefield(content, lineno) {
196
- const match = /^([A-Za-z][A-Za-z0-9_-]*):(?:[ \t]+(.*))?$/.exec(content);
197
- if (!match)
198
- throw yamlerror(`line ${lineno}: the house subset speaks "key: value" lines — malformed: ${content}`);
199
- const key = match[1];
200
- if (!["name", "run", "timeoutms"].includes(key)) {
201
- throw yamlerror(`line ${lineno}: unknown key "${key}" — the house subset carries name, run and timeoutms only`);
202
- }
203
- if (current?.has(key))
204
- throw yamlerror(`line ${lineno}: the key "${key}" appears twice in one step`);
205
- if (match[2] === undefined || match[2].trim().length === 0) {
206
- throw yamlerror(`line ${lineno}: the value for "${key}" is missing (block scalars are outside the house subset)`);
207
- }
208
- current?.set(key, parsehousescalar(match[2], lineno));
209
- }
210
- for (let index = 0; index < lines.length; index += 1) {
211
- const lineno = index + 1;
212
- const stripped = striphyamlcomment(lines[index]).replace(/\s+$/, "");
213
- if (stripped.trim().length === 0)
214
- continue;
215
- const indent = stripped.length - stripped.replace(/^ +/, "").length;
216
- const content = stripped.trim();
217
- if (!sawsteps) {
218
- if (indent !== 0)
219
- throw yamlerror(`line ${lineno}: the descriptor must open with the top-level steps key at column zero`);
220
- if (content !== "steps:") {
221
- throw yamlerror(`line ${lineno}: the house subset carries exactly one top-level key (steps) — found: ${content}`);
222
- }
223
- sawsteps = true;
224
- continue;
225
- }
226
- if (content === "-" || content.startsWith("- ")) {
227
- closecurrent(lineno);
228
- if (itemindent === -1) {
229
- if (indent === 0)
230
- throw yamlerror(`line ${lineno}: the steps list must be indented under the steps key`);
231
- itemindent = indent;
232
- }
233
- else if (indent !== itemindent) {
234
- throw yamlerror(`line ${lineno}: the steps list items must share one indentation (expected column ${itemindent}, found ${indent})`);
235
- }
236
- current = new Map();
237
- fieldindent = -1;
238
- const inline = content === "-" ? "" : content.slice(2).trim();
239
- if (inline.length > 0) {
240
- fieldindent = stripped.indexOf(inline);
241
- parsefield(inline, lineno);
242
- }
243
- continue;
244
- }
245
- if (!current)
246
- throw yamlerror(`line ${lineno}: a steps entry must start with a list dash (found: ${content})`);
247
- if (indent === 0) {
248
- throw yamlerror(`line ${lineno}: a second top-level key arrived after the steps list: ${content}`);
249
- }
250
- if (fieldindent === -1) {
251
- if (indent <= itemindent)
252
- throw yamlerror(`line ${lineno}: a bare list dash must be followed by deeper-indented keys`);
253
- fieldindent = indent;
254
- }
255
- if (indent !== fieldindent) {
256
- throw yamlerror(`line ${lineno}: bad indentation — the keys of one step must share column ${fieldindent} (found column ${indent})`);
257
- }
258
- parsefield(content, lineno);
259
- }
260
- closecurrent(lines.length);
261
- if (!sawsteps)
262
- throw yamlerror("the descriptor never carries the top-level steps key");
263
- if (steps.length === 0)
264
- throw yamlerror("the steps list is empty — the descriptor must carry at least one step");
265
- return steps;
266
- }
267
- /** interprets a yml workflow descriptor into an executable job plan
268
- * through the house yaml subset; the plan declares the babel compile
269
- * lane (compilewithbabel performs it). a parse failure throws the honest
270
- * YAML_INTERPRET_FAILED code. */
271
- export function interpretyml(text) {
272
- return { steps: parsehousesubset(text), source: "yml", compiledvia: "babel" };
273
- }
274
- /** interprets a yaml workflow descriptor — the same house subset under
275
- * the yaml spelling, so both file extensions ride one lane. */
276
- export function interpretyaml(text) {
277
- return { steps: parsehousesubset(text), source: "yaml", compiledvia: "babel" };
278
- }
279
- /* ════════════════════════════════════════════════════════════════════ */
280
- /* Section 3: the babel compile lane — the descriptor roundtrips through @babel/core and node:vm. */
281
- /* ════════════════════════════════════════════════════════════════════ */
282
- /**
283
- * the babel compile lane: the plan descriptor is serialized to a
284
- * javascript literal, regenerated through @babel/core transformSync (no
285
- * presets — the transform parses and re-emits the source), and parsed
286
- * back through node:vm runInNewContext in a fresh context that carries
287
- * no globals, so the compiled text is data, never executable logic. the
288
- * roundtrip is validated against the input plan — a loss refuses with
289
- * BABEL_COMPILE_FAILED instead of degrading, and a missing @babel/core
290
- * refuses with BABEL_RUNTIME_ABSENT.
291
- */
292
- /** the honest refusal when @babel/core is not resolvable at runtime. */
293
- function babelabsenterror(cause) {
294
- return saddleerror("the babel compile lane requires @babel/core at runtime and it is absent in this install — the compile refuses instead of degrading", { code: "BABEL_RUNTIME_ABSENT", cause, details: { lane: "runner-babel" } });
295
- }
296
- /** compiles a script plan through the babel lane: serialize the plan to
297
- * a javascript literal, transform it through @babel/core (no presets,
298
- * no config files — the minimal options the runtime carries), parse the
299
- * regenerated source back through node:vm, validate the roundtrip kept
300
- * every step and the source, and return the plan stamped
301
- * compiledvia "babel". */
302
- export async function compilewithbabel(plan) {
303
- assertservercontext();
304
- validateplan(plan);
305
- let transform = null;
306
- try {
307
- const mod = (await import("@babel/core"));
308
- const candidate = mod.transformSync ?? mod.default?.transformSync;
309
- if (typeof candidate === "function") {
310
- transform = candidate;
311
- }
312
- }
313
- catch (error) {
314
- throw babelabsenterror(error);
315
- }
316
- if (!transform)
317
- throw babelabsenterror();
318
- const serialized = `(${JSON.stringify({ steps: plan.steps, source: plan.source }, null, 2)});`;
319
- let compiled;
320
- try {
321
- const result = transform(serialized, { configFile: false, babelrc: false, filename: "scriptplan.js" });
322
- if (result === null || typeof result.code !== "string")
323
- throw new Error("the babel transform returned no code");
324
- compiled = result.code;
325
- }
326
- catch (error) {
327
- const failure = error;
328
- throw saddleerror(`the babel compile of the ${plan.source} descriptor failed: ${String(failure?.message ?? failure)}`, {
329
- code: "BABEL_COMPILE_FAILED",
330
- cause: error,
331
- });
332
- }
333
- let parsed;
334
- try {
335
- parsed = runInNewContext(compiled, {}, { timeout: 5000 });
336
- }
337
- catch (error) {
338
- throw saddleerror("the compiled descriptor did not parse back through the vm lane", {
339
- code: "BABEL_COMPILE_FAILED",
340
- cause: error,
341
- });
342
- }
343
- const roundtripped = parsed;
344
- if (JSON.stringify(roundtripped?.steps) !== JSON.stringify(plan.steps) || roundtripped?.source !== plan.source) {
345
- throw saddleerror("the babel roundtrip lost plan content — the compile is refused, never faked", {
346
- code: "BABEL_COMPILE_FAILED",
347
- });
348
- }
349
- /* the validated plan steps ride onward (the vm objects live in the
350
- fresh context and are the proof of the roundtrip, not the payload) */
351
- return { steps: plan.steps, source: plan.source, compiledvia: "babel" };
352
- }
353
- /* ════════════════════════════════════════════════════════════════════ */
354
- /* Section 4: the execution lane — sequential server-side bash with enforced deadlines. */
355
- /* ════════════════════════════════════════════════════════════════════ */
356
- /**
357
- * the execution lane: every step runs server-side through `bash -c` on
358
- * the engine machine (the runner-universal lane), with the exit code,
359
- * both streams and the duration captured honestly. a failing step fails
360
- * the plan and the later steps are skipped — reported as skipped, never
361
- * silently dropped.
362
- */
363
- /** the default per-step deadline when neither the step nor the caller
364
- * carries one: ten seconds, the house default the mandate names. */
365
- const DEFAULTSTEPTIMEOUTMS = 10000;
366
- /** the stream capture cap (about one megabyte per stream): the lane
367
- * keeps draining past the cap but stops accumulating, and flags the
368
- * truncation on the step record so the report never lies about size. */
369
- const STREAMCAP = 1_000_000;
370
- /** builds the honest failure entry for a step that could not spawn. */
371
- function spawnfailure(step, error) {
372
- const failure = error;
373
- return {
374
- code: "SCRIPT_SPAWN_FAILED",
375
- message: `the bash lane could not spawn the step "${step.name}": ${String(failure?.message ?? failure)}`,
376
- };
377
- }
378
- /** runs one step through `bash -c` with a timeout kill: the child is
379
- * spawned with piped streams, the deadline timer sends SIGKILL when it
380
- * fires, and the record carries the exit code, the signal, both captured
381
- * streams (capped honestly), the duration and the failure entry. */
382
- function runstep(step, timeoutms) {
383
- return new Promise((resolve) => {
384
- const startedat = Date.now();
385
- let stdout = "";
386
- let stderr = "";
387
- let stdouttruncated = false;
388
- let stderrtruncated = false;
389
- let timedout = false;
390
- let settled = false;
391
- const finish = (record) => {
392
- if (settled)
393
- return;
394
- settled = true;
395
- resolve(record);
396
- };
397
- let child;
398
- try {
399
- child = spawn("bash", ["-c", step.run], { stdio: ["ignore", "pipe", "pipe"] });
400
- }
401
- catch (error) {
402
- finish({
403
- name: step.name,
404
- exitcode: null,
405
- signal: null,
406
- stdout: "",
407
- stderr: "",
408
- durationms: Date.now() - startedat,
409
- error: spawnfailure(step, error),
410
- });
411
- return;
412
- }
413
- const timer = setTimeout(() => {
414
- timedout = true;
415
- child.kill("SIGKILL");
416
- }, timeoutms);
417
- const capture = (chunk, target) => {
418
- const text = chunk.toString("utf8");
419
- if (target === "stdout") {
420
- if (stdout.length < STREAMCAP)
421
- stdout += text;
422
- else
423
- stdouttruncated = true;
424
- }
425
- else if (stderr.length < STREAMCAP)
426
- stderr += text;
427
- else
428
- stderrtruncated = true;
429
- };
430
- child.stdout?.on("data", (chunk) => capture(chunk, "stdout"));
431
- child.stderr?.on("data", (chunk) => capture(chunk, "stderr"));
432
- child.on("error", (error) => {
433
- clearTimeout(timer);
434
- finish({
435
- name: step.name,
436
- exitcode: null,
437
- signal: null,
438
- stdout,
439
- stderr,
440
- durationms: Date.now() - startedat,
441
- error: spawnfailure(step, error),
442
- });
443
- });
444
- child.on("close", (exitcode, signal) => {
445
- clearTimeout(timer);
446
- const ok = exitcode === 0 && !timedout;
447
- finish({
448
- name: step.name,
449
- exitcode,
450
- signal,
451
- stdout,
452
- stderr,
453
- durationms: Date.now() - startedat,
454
- timedout: timedout || undefined,
455
- truncated: stdouttruncated || stderrtruncated || undefined,
456
- error: ok
457
- ? undefined
458
- : timedout
459
- ? {
460
- code: "STEP_DEADLINE_EXCEEDED",
461
- message: `the step "${step.name}" exceeded its ${timeoutms}ms deadline and was killed`,
462
- }
463
- : {
464
- code: "SCRIPT_STEP_FAILED",
465
- message: `the step "${step.name}" exited with code ${exitcode}`,
466
- },
467
- });
468
- });
469
- });
470
- }
471
- /** executes a script plan sequentially server-side: each step runs
472
- * through `bash -c` with the deadline `step.timeoutms ?? the caller
473
- * deadlinems ?? ten seconds`, the exit code and both streams are
474
- * captured, a failing step fails the plan honestly and every later step
475
- * is skipped with a null exit code and the skipped flag. the storage
476
- * option is carried for the engine commit lane — this lane executes and
477
- * answers, it never pretends a commit. */
478
- export async function runscript(plan, options = {}) {
479
- assertservercontext();
480
- validateplan(plan);
481
- const deadlinems = validatedeadline(options.deadlinems);
482
- const steps = [];
483
- let failure;
484
- for (const step of plan.steps) {
485
- if (failure) {
486
- steps.push({
487
- name: step.name,
488
- exitcode: null,
489
- signal: null,
490
- stdout: "",
491
- stderr: "",
492
- durationms: 0,
493
- skipped: true,
494
- });
495
- continue;
496
- }
497
- const timeoutms = step.timeoutms ?? deadlinems ?? DEFAULTSTEPTIMEOUTMS;
498
- const record = await runstep(step, timeoutms);
499
- steps.push(record);
500
- if (record.exitcode !== 0) {
501
- failure = record.error ?? { code: "SCRIPT_STEP_FAILED", message: `the step "${step.name}" failed` };
502
- }
503
- }
504
- return failure === undefined ? { status: "succeeded", steps } : { status: "failed", steps, error: failure };
505
- }
506
- /* ════════════════════════════════════════════════════════════════════ */
507
- /* Section 5: the background lane — detached runs in the house job queue with observable statuses. */
508
- /* ════════════════════════════════════════════════════════════════════ */
509
- /**
510
- * the background lane: runscriptbackground registers the run as a house
511
- * run record (the runstatuses vocabulary), queues the execution through
512
- * the execution.ts jobqueue (one attempt — script side effects never
513
- * replay), kicks it without awaiting and returns immediately; the
514
- * statuses stay observable through scriptRunStatus until the detached
515
- * run settles with its result.
516
- */
517
- /** the id factory the background lane stamps run ids with. */
518
- const scriptrunids = idfactory();
519
- /** the house job queue the detached script runs ride: concurrency two
520
- * (background lanes may overlap), one attempt per run (a failed script
521
- * is reported, never silently retried — honest execution only). */
522
- const scriptlanequeue = jobqueue({ concurrency: 2, maxattempts: 1, retryall: false });
523
- /** the registry of background runs this process carries: runid to the
524
- * mutable record holder with the result once the run settles. */
525
- const scriptruns = new Map();
526
- /** settles one background run: walks the legal transition and stores the
527
- * result. the transition from running to succeeded or failed is the one
528
- * legal close of the lane, so a refusal here keeps the record as it was
529
- * and never invents a status. */
530
- function finalizescriptrun(runid, status, result) {
531
- const holder = scriptruns.get(runid);
532
- if (!holder)
533
- return;
534
- try {
535
- holder.record = transitionrun(holder.record, status, {
536
- reason: `the detached run settled as ${result.status}`,
537
- });
538
- }
539
- catch {
540
- /* the transition refused — the record stays where it was and the
541
- result below still tells the truth about the execution */
542
- }
543
- holder.result = result;
544
- }
545
- /** kicks a script plan in the background: the run is registered as a
546
- * house run record, queued through the house job queue and executed
547
- * without awaiting — the call returns {runid, status} immediately and
548
- * the statuses stay observable through scriptRunStatus (created,
549
- * running, succeeded, failed — the runstatuses vocabulary). */
550
- export function runscriptbackground(plan, options = {}) {
551
- assertservercontext();
552
- validateplan(plan);
553
- validatedeadline(options.deadlinems);
554
- const runid = scriptrunids.next("scriptrun");
555
- let record = runrecord({
556
- requestid: runid,
557
- name: `scriptlane-${plan.source}`,
558
- status: "created",
559
- runid,
560
- metadata: { source: plan.source, compiledvia: plan.compiledvia, stepcount: plan.steps.length },
561
- });
562
- const holder = { record };
563
- scriptruns.set(runid, holder);
564
- record = transitionrun(record, "submitted", {
565
- runid,
566
- reason: "the script lane queued the run in the house job queue",
567
- });
568
- record = transitionrun(record, "running", {
569
- reason: "the script lane kicked the run detached from the caller",
570
- });
571
- holder.record = record;
572
- void scriptlanequeue
573
- .add(plan, async () => runscript(plan, options), { id: runid })
574
- .then((result) => {
575
- finalizescriptrun(runid, result.status === "succeeded" ? "succeeded" : "failed", result);
576
- }, (error) => {
577
- const failure = aserror(error, runid);
578
- finalizescriptrun(runid, "failed", {
579
- status: "failed",
580
- steps: [],
581
- error: { code: failure.code, message: failure.message },
582
- });
583
- });
584
- return { runid, status: record.status };
585
- }
586
- /** reads the observable record of one background run: the runstatuses
587
- * vocabulary status, the auditable transition history, and the result
588
- * once the detached run settled. an unknown runid refuses with the
589
- * honest SCRIPT_RUN_NOT_FOUND code. */
590
- export function scriptRunStatus(runid) {
591
- assertservercontext();
592
- const key = String(runid ?? "");
593
- const holder = scriptruns.get(key);
594
- if (!holder) {
595
- throw saddleerror(`the script run is unknown: ${key}`, { code: "SCRIPT_RUN_NOT_FOUND", details: { runid: key } });
596
- }
597
- if (!runstatuses.includes(holder.record.status)) {
598
- throw saddleerror(`the script run record carries an out-of-vocabulary status: ${holder.record.status}`, {
599
- code: "SCRIPT_RUN_CORRUPT",
600
- details: { runid: key },
601
- });
602
- }
603
- return { ...holder.record, history: [...holder.record.history], result: holder.result };
604
- }
605
- /* ════════════════════════════════════════════════════════════════════ */
606
- /* Section 6: the docker lane — docker-ready descriptors for the container modality. */
607
- /* ════════════════════════════════════════════════════════════════════ */
608
- /**
609
- * the docker lane: dockerdescriptor emits the docker-ready descriptor
610
- * the container modality (container-docker) consumes — a Dockerfile on
611
- * the house node base with every step as an escaped RUN line, plus the
612
- * verbatim plan script and the sh entrypoint.
613
- */
614
- /** the house node base image the descriptor pins: the Dockerfile
615
- * saddle-runtime stage rides FROM node:26.8.1-slim (the node-engine
616
- * service image), so the descriptor asks for the base the house
617
- * actually pins rather than a floating tag. */
618
- export const SCRIPTLANES_NODE_BASE = "node:26.8.1-slim";
619
- /** the heredoc sentinel of the plan script block: a quoted delimiter
620
- * keeps the content verbatim (no expansion, quotes survive), and a
621
- * descriptor whose script carries the sentinel itself refuses honestly
622
- * instead of corrupting the block. */
623
- const PLANHEREDOCSENTINEL = "SCRIPTLANES-PLAN-EOF";
624
- /** escapes a step command for one dockerfile RUN line: the backslash
625
- * first (so the other escapes cannot be forged), then the double quote
626
- * and the dollar sign (the container shell never expands the plan), and
627
- * a newline folds to an escaped \n so every step rides exactly one RUN
628
- * line — the multi-line safety of the lane. */
629
- function dockerescape(text) {
630
- return text.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\$/g, "\\$").replace(/\r?\n/g, "\\n");
631
- }
632
- /** emits the docker-ready descriptor for the container lane: a
633
- * Dockerfile on the house node base carrying every step as an escaped
634
- * RUN line (bash -c with the quotes and dollars escaped), the verbatim
635
- * plan script written through a quoted heredoc COPY, and the sh
636
- * entrypoint that runs it. the returned entrypoint matches the
637
- * ENTRYPOINT line of the emitted Dockerfile. */
638
- export function dockerdescriptor(plan) {
639
- assertservercontext();
640
- validateplan(plan);
641
- const runlines = plan.steps.map((step) => `RUN bash -c "${dockerescape(step.run)}"`);
642
- const planscript = plan.steps.map((step) => `# ${step.name}\n${step.run}`).join("\n");
643
- if (planscript.split(/\r?\n/).some((line) => line.trim() === PLANHEREDOCSENTINEL)) {
644
- throw saddleerror("the plan script carries the heredoc sentinel — the docker descriptor refuses instead of corrupting", {
645
- code: "SCRIPTLANES_DESCRIPTOR_UNSAFE",
646
- });
647
- }
648
- const dockerfile = [
649
- `# docker-ready descriptor generated by the saddle script lanes (source: ${plan.source}, compile lane: ${plan.compiledvia}).`,
650
- `FROM ${SCRIPTLANES_NODE_BASE}`,
651
- "WORKDIR /app",
652
- ...runlines,
653
- `COPY <<'${PLANHEREDOCSENTINEL}' /app/plan.sh`,
654
- planscript,
655
- PLANHEREDOCSENTINEL,
656
- 'ENTRYPOINT ["bash", "/app/plan.sh"]',
657
- "",
658
- ].join("\n");
659
- return { dockerfile, entrypoint: "bash /app/plan.sh" };
660
- }
@@ -1 +0,0 @@
1
- export {};