@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
package/dist/mesh.d.ts DELETED
@@ -1,177 +0,0 @@
1
- /**
2
- * mesh.ts — signed node-to-node communication for the saddle web mesh
3
- * (v7-BACK).
4
- *
5
- * the mesh connects clone nodes to the main authority: requests are
6
- * signed with HMAC-SHA256 over `${timestamp}.${method}.${path}.${bodyhash}`,
7
- * protected by a 60-second anti-replay window with a 1024-entry nonce
8
- * cache, and may optionally encrypt payloads with AES-256-GCM using the
9
- * SADDLE_MESH_KEY hex secret. the module also carries the node role
10
- * (SADDLE_ROLE=main|clone|standalone, default standalone), the signed
11
- * fetch client towards SADDLE_MAIN_URL and the 60-second heartbeat
12
- * loop clones run against the main registry.
13
- *
14
- * contexts (9): types, noderole, requestsigning, antireplay,
15
- * payloadcrypto, meshmessages, mainclient, heartbeat, sessionforwarding.
16
- *
17
- * rules: lowercase identifiers, english jsdoc in third person, no emoji,
18
- * try/catch on every fallible path, node:* modules plus the global
19
- * fetch only, zero dependencies, no hardcoded localhost (the main url
20
- * always comes from the environment).
21
- */
22
- import type { IncomingMessage } from 'node:http';
23
- /** the node role resolved from saddle_role. */
24
- export type NodeRole = 'main' | 'clone' | 'standalone';
25
- /** the mesh verification outcome: the ok arm or the rejection payload
26
- * consumed by writeerror. */
27
- export type MeshVerdict = {
28
- ok: true;
29
- } | {
30
- ok: false;
31
- status: number;
32
- code: string;
33
- message: string;
34
- };
35
- /** the mesh wire envelope {type, from, data, ts}; when the mesh key is
36
- * configured the data field is replaced by {enc: '<hex>'}. */
37
- export type MeshEnvelope = {
38
- type: string;
39
- from: string;
40
- data: unknown;
41
- ts: string;
42
- };
43
- /** the opened mesh message produced by openmeshmessage. */
44
- export type MeshMessage = {
45
- type: string;
46
- from: string;
47
- data: unknown;
48
- ts: string;
49
- };
50
- /** the main node response relayed by the signed client: status, the
51
- * parsed json body (null when not json) and the set-cookie relay. */
52
- export type MainResponse = {
53
- status: number;
54
- body: unknown;
55
- setcookie: string[];
56
- };
57
- /** the announcing node description of the clone heartbeat loop. */
58
- export type HeartbeatSelf = {
59
- url: string;
60
- region?: string;
61
- rolename?: string;
62
- };
63
- /** the node role: main, clone or standalone. */
64
- export declare const role: NodeRole;
65
- /**
66
- * reads the shared mesh secret from the environment.
67
- *
68
- * @returns the secret or an empty string when unconfigured.
69
- */
70
- export declare function meshsecret(): string;
71
- /**
72
- * reads the main node base url (no trailing slash) from the
73
- * environment; never a hardcoded address.
74
- *
75
- * @returns the trimmed base url or an empty string.
76
- */
77
- export declare function mainurl(): string;
78
- /**
79
- * computes the sha256 hex digest of one request body.
80
- *
81
- * @param body the exact raw body string ('' for GET).
82
- * @returns the hex body hash.
83
- */
84
- export declare function bodyhash(body: string): string;
85
- /**
86
- * signs one mesh request: HMAC-SHA256 hex over the string
87
- * `${timestamp}.${method}.${path}.${bodyhash}`.
88
- *
89
- * @param method the upper-case http method.
90
- * @param path the full request path (e.g. /api/v1/mesh/register).
91
- * @param body the exact raw body string ('' for GET).
92
- * @param secret the shared mesh secret.
93
- * @param timestamp the millisecond timestamp string
94
- * (defaults to now).
95
- * @returns the hex signature.
96
- */
97
- export declare function sign(method: string, path: string, body: string, secret: string, timestamp?: string): string;
98
- /**
99
- * verifies one incoming mesh request: the x-saddle-timestamp header must
100
- * sit inside the 60-second window, the x-saddle-signature header must
101
- * match the recomputed HMAC over the raw body, and the signature must
102
- * not have been seen before (replay protection).
103
- *
104
- * @param req the incoming request.
105
- * @param rawbody the exact raw request body ('' for GET).
106
- * @param secret the shared mesh secret.
107
- * @returns the verification outcome.
108
- */
109
- export declare function verifymesh(req: IncomingMessage, rawbody: string, secret: string): MeshVerdict;
110
- /**
111
- * encrypts one utf-8 payload with AES-256-GCM; the output is hex
112
- * `iv(12) || ciphertext || tag(16)`.
113
- *
114
- * @param plaintext the payload to encrypt.
115
- * @returns the hex envelope or null when the key is
116
- * not configured.
117
- */
118
- export declare function encrypt(plaintext: string): string | null;
119
- /**
120
- * decrypts one AES-256-GCM hex envelope produced by encrypt.
121
- *
122
- * @param envelope the hex iv || ciphertext || tag payload.
123
- * @returns the utf-8 plaintext or null on any failure.
124
- */
125
- export declare function decrypt(envelope: string): string | null;
126
- /**
127
- * builds one mesh message envelope {type, from, data, ts}; when the
128
- * mesh key is configured the data field is replaced by the GCM
129
- * envelope {enc: '<hex>'}.
130
- *
131
- * @param type the message type (e.g. 'heartbeat').
132
- * @param from the sender node id or url.
133
- * @param data the message payload.
134
- * @returns the wire envelope.
135
- */
136
- export declare function meshmessage(type: string, from: string, data: unknown): MeshEnvelope;
137
- /**
138
- * opens one mesh message envelope produced by meshmessage, decrypting
139
- * the data field when needed.
140
- *
141
- * @param envelope the wire envelope.
142
- * @returns the opened message or null on tampering.
143
- */
144
- export declare function openmeshmessage(envelope: unknown): MeshMessage | null;
145
- /**
146
- * performs one signed POST towards the main node described by
147
- * SADDLE_MAIN_URL using the global fetch; the signature headers are
148
- * computed with the shared SADDLE_MESH_SECRET.
149
- *
150
- * @param path the full api path (e.g. /api/v1/auth/login).
151
- * @param body the json-serializable payload.
152
- * @returns the response status, the parsed json body and the
153
- * set-cookie relay.
154
- * @throws when the mesh is unconfigured, the
155
- * request fails or the body is not json.
156
- */
157
- export declare function postmain(path: string, body: unknown): Promise<MainResponse>;
158
- /**
159
- * runs the clone registry loop: one immediate registration followed by
160
- * heartbeats every 60 seconds, all signed towards the main node. the
161
- * interval is unref'd so the process can still exit cleanly.
162
- *
163
- * @param self the announcing node description.
164
- * @returns the heartbeat timer or null when the
165
- * node is not a clone or the mesh is unconfigured.
166
- */
167
- export declare function startheartbeat(self: HeartbeatSelf): NodeJS.Timeout | null;
168
- /**
169
- * forwards one auth request (register or login) from a clone to the
170
- * main authority; the caller caches the returned session locally when
171
- * the main node accepts it.
172
- *
173
- * @param action the auth action.
174
- * @param body the credentials payload.
175
- * @returns the main response verbatim.
176
- */
177
- export declare function forwardauth(action: 'register' | 'login', body: unknown): Promise<MainResponse>;
package/dist/mesh.js DELETED
@@ -1,468 +0,0 @@
1
- /**
2
- * mesh.ts — signed node-to-node communication for the saddle web mesh
3
- * (v7-BACK).
4
- *
5
- * the mesh connects clone nodes to the main authority: requests are
6
- * signed with HMAC-SHA256 over `${timestamp}.${method}.${path}.${bodyhash}`,
7
- * protected by a 60-second anti-replay window with a 1024-entry nonce
8
- * cache, and may optionally encrypt payloads with AES-256-GCM using the
9
- * SADDLE_MESH_KEY hex secret. the module also carries the node role
10
- * (SADDLE_ROLE=main|clone|standalone, default standalone), the signed
11
- * fetch client towards SADDLE_MAIN_URL and the 60-second heartbeat
12
- * loop clones run against the main registry.
13
- *
14
- * contexts (9): types, noderole, requestsigning, antireplay,
15
- * payloadcrypto, meshmessages, mainclient, heartbeat, sessionforwarding.
16
- *
17
- * rules: lowercase identifiers, english jsdoc in third person, no emoji,
18
- * try/catch on every fallible path, node:* modules plus the global
19
- * fetch only, zero dependencies, no hardcoded localhost (the main url
20
- * always comes from the environment).
21
- */
22
- import { createCipheriv, createDecipheriv, createHash, createHmac, randomBytes, timingSafeEqual, } from 'node:crypto';
23
- import process from 'node:process';
24
- /* ------------------------------------------------------------------ */
25
- /* context: noderole */
26
- /* ------------------------------------------------------------------ */
27
- /**
28
- * resolves the node role once at import time: saddle_role accepts the
29
- * values main, clone and standalone; anything else (including unset)
30
- * falls back to standalone so a bare `node web/server.ts` boot behaves
31
- * as a self-contained authority.
32
- *
33
- * @returns the resolved role.
34
- */
35
- function resolverole() {
36
- const value = String(process.env.SADDLE_ROLE ?? 'standalone').toLowerCase();
37
- if (value === 'main' || value === 'clone' || value === 'standalone') {
38
- return value;
39
- }
40
- return 'standalone';
41
- }
42
- /** the node role: main, clone or standalone. */
43
- export const role = resolverole();
44
- /**
45
- * reads the shared mesh secret from the environment.
46
- *
47
- * @returns the secret or an empty string when unconfigured.
48
- */
49
- export function meshsecret() {
50
- return String(process.env.SADDLE_MESH_SECRET ?? '');
51
- }
52
- /**
53
- * reads the main node base url (no trailing slash) from the
54
- * environment; never a hardcoded address.
55
- *
56
- * @returns the trimmed base url or an empty string.
57
- */
58
- export function mainurl() {
59
- return String(process.env.SADDLE_MAIN_URL ?? '').replace(/\/+$/, '');
60
- }
61
- /* ------------------------------------------------------------------ */
62
- /* context: requestsigning */
63
- /* ------------------------------------------------------------------ */
64
- /** the anti-replay window in milliseconds. */
65
- const replaywindowms = 60 * 1000;
66
- /** the nonce cache ceiling (simple LRU eviction past this size). */
67
- const noncelimit = 1024;
68
- /**
69
- * computes the sha256 hex digest of one request body.
70
- *
71
- * @param body the exact raw body string ('' for GET).
72
- * @returns the hex body hash.
73
- */
74
- export function bodyhash(body) {
75
- return createHash('sha256').update(String(body)).digest('hex');
76
- }
77
- /**
78
- * signs one mesh request: HMAC-SHA256 hex over the string
79
- * `${timestamp}.${method}.${path}.${bodyhash}`.
80
- *
81
- * @param method the upper-case http method.
82
- * @param path the full request path (e.g. /api/v1/mesh/register).
83
- * @param body the exact raw body string ('' for GET).
84
- * @param secret the shared mesh secret.
85
- * @param timestamp the millisecond timestamp string
86
- * (defaults to now).
87
- * @returns the hex signature.
88
- */
89
- export function sign(method, path, body, secret, timestamp = String(Date.now())) {
90
- return createHmac('sha256', String(secret))
91
- .update(`${timestamp}.${String(method).toUpperCase()}.${path}.${bodyhash(body)}`)
92
- .digest('hex');
93
- }
94
- /* ------------------------------------------------------------------ */
95
- /* context: antireplay */
96
- /* ------------------------------------------------------------------ */
97
- /** seen signature nonces with insertion order for LRU eviction. */
98
- const nonces = new Map();
99
- /**
100
- * records one signature in the nonce cache, evicting the oldest entry
101
- * past the 1024-entry ceiling.
102
- *
103
- * @param signature the verified signature hex.
104
- * @returns void.
105
- */
106
- function recordnonce(signature) {
107
- if (nonces.has(signature)) {
108
- return;
109
- }
110
- nonces.set(signature, Date.now());
111
- while (nonces.size > noncelimit) {
112
- const oldest = nonces.keys().next().value;
113
- if (oldest === undefined) {
114
- break;
115
- }
116
- nonces.delete(oldest);
117
- }
118
- }
119
- /**
120
- * verifies one incoming mesh request: the x-saddle-timestamp header must
121
- * sit inside the 60-second window, the x-saddle-signature header must
122
- * match the recomputed HMAC over the raw body, and the signature must
123
- * not have been seen before (replay protection).
124
- *
125
- * @param req the incoming request.
126
- * @param rawbody the exact raw request body ('' for GET).
127
- * @param secret the shared mesh secret.
128
- * @returns the verification outcome.
129
- */
130
- export function verifymesh(req, rawbody, secret) {
131
- try {
132
- const timestamp = req.headers?.['x-saddle-timestamp'];
133
- const signature = req.headers?.['x-saddle-signature'];
134
- if (typeof timestamp !== 'string' || typeof signature !== 'string') {
135
- return {
136
- ok: false,
137
- status: 401,
138
- code: 'mesh-unauthenticated',
139
- message: 'mesh requests require x-saddle-timestamp and x-saddle-signature headers',
140
- };
141
- }
142
- const skew = Math.abs(Date.now() - Number.parseInt(timestamp, 10));
143
- if (!Number.isFinite(skew) || skew > replaywindowms) {
144
- return {
145
- ok: false,
146
- status: 401,
147
- code: 'mesh-stale-timestamp',
148
- message: 'mesh timestamp outside the 60 second window',
149
- };
150
- }
151
- const path = String(req.url ?? '/').split('?')[0];
152
- const expected = sign(req.method ?? 'GET', path, String(rawbody), secret, timestamp);
153
- const left = Buffer.from(expected, 'hex');
154
- const right = Buffer.from(signature, 'hex');
155
- if (left.length !== right.length || !timingSafeEqual(left, right)) {
156
- return {
157
- ok: false,
158
- status: 401,
159
- code: 'mesh-bad-signature',
160
- message: 'mesh signature mismatch',
161
- };
162
- }
163
- if (nonces.has(signature)) {
164
- return {
165
- ok: false,
166
- status: 401,
167
- code: 'mesh-replay',
168
- message: 'mesh signature was already used',
169
- };
170
- }
171
- recordnonce(signature);
172
- return { ok: true };
173
- }
174
- catch (error) {
175
- return {
176
- ok: false,
177
- status: 401,
178
- code: 'mesh-verify-failed',
179
- message: error instanceof Error ? error.message : String(error),
180
- };
181
- }
182
- }
183
- /* ------------------------------------------------------------------ */
184
- /* context: payloadcrypto (optional AES-256-GCM) */
185
- /* ------------------------------------------------------------------ */
186
- /** the authenticated payload key length in bytes. */
187
- const meshkeylen = 32;
188
- /** the GCM initialization vector length in bytes. */
189
- const ivlen = 12;
190
- /** the GCM authentication tag length in bytes. */
191
- const taglen = 16;
192
- /**
193
- * resolves the optional SADDLE_MESH_KEY (32 bytes hex) used for payload
194
- * encryption between nodes.
195
- *
196
- * @returns the key or null when unset/invalid.
197
- */
198
- function meshkey() {
199
- try {
200
- const raw = String(process.env.SADDLE_MESH_KEY ?? '');
201
- if (raw.length === 0) {
202
- return null;
203
- }
204
- const key = Buffer.from(raw, 'hex');
205
- return key.length === meshkeylen ? key : null;
206
- }
207
- catch {
208
- return null;
209
- }
210
- }
211
- /**
212
- * encrypts one utf-8 payload with AES-256-GCM; the output is hex
213
- * `iv(12) || ciphertext || tag(16)`.
214
- *
215
- * @param plaintext the payload to encrypt.
216
- * @returns the hex envelope or null when the key is
217
- * not configured.
218
- */
219
- export function encrypt(plaintext) {
220
- try {
221
- const key = meshkey();
222
- if (key === null) {
223
- return null;
224
- }
225
- const iv = randomBytes(ivlen);
226
- const cipher = createCipheriv('aes-256-gcm', key, iv);
227
- const ciphertext = Buffer.concat([
228
- cipher.update(String(plaintext), 'utf8'),
229
- cipher.final(),
230
- ]);
231
- const tag = cipher.getAuthTag();
232
- return Buffer.concat([iv, ciphertext, tag]).toString('hex');
233
- }
234
- catch {
235
- return null;
236
- }
237
- }
238
- /**
239
- * decrypts one AES-256-GCM hex envelope produced by encrypt.
240
- *
241
- * @param envelope the hex iv || ciphertext || tag payload.
242
- * @returns the utf-8 plaintext or null on any failure.
243
- */
244
- export function decrypt(envelope) {
245
- try {
246
- const key = meshkey();
247
- if (key === null) {
248
- return null;
249
- }
250
- const raw = Buffer.from(String(envelope), 'hex');
251
- if (raw.length <= ivlen + taglen) {
252
- return null;
253
- }
254
- const iv = raw.subarray(0, ivlen);
255
- const tag = raw.subarray(raw.length - taglen);
256
- const ciphertext = raw.subarray(ivlen, raw.length - taglen);
257
- const decipher = createDecipheriv('aes-256-gcm', key, iv);
258
- decipher.setAuthTag(tag);
259
- return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString('utf8');
260
- }
261
- catch {
262
- return null;
263
- }
264
- }
265
- /* ------------------------------------------------------------------ */
266
- /* context: meshmessages */
267
- /* ------------------------------------------------------------------ */
268
- /**
269
- * builds one mesh message envelope {type, from, data, ts}; when the
270
- * mesh key is configured the data field is replaced by the GCM
271
- * envelope {enc: '<hex>'}.
272
- *
273
- * @param type the message type (e.g. 'heartbeat').
274
- * @param from the sender node id or url.
275
- * @param data the message payload.
276
- * @returns the wire envelope.
277
- */
278
- export function meshmessage(type, from, data) {
279
- const envelope = { type, from, data, ts: new Date().toISOString() };
280
- const key = meshkey();
281
- if (key !== null) {
282
- const sealed = encrypt(JSON.stringify(envelope.data));
283
- if (sealed !== null) {
284
- return { ...envelope, data: { enc: sealed } };
285
- }
286
- }
287
- return envelope;
288
- }
289
- /**
290
- * opens one mesh message envelope produced by meshmessage, decrypting
291
- * the data field when needed.
292
- *
293
- * @param envelope the wire envelope.
294
- * @returns the opened message or null on tampering.
295
- */
296
- export function openmeshmessage(envelope) {
297
- try {
298
- if (envelope === null || typeof envelope !== 'object') {
299
- return null;
300
- }
301
- const message = envelope;
302
- let data = message.data;
303
- if (data !== null &&
304
- typeof data === 'object' &&
305
- typeof data.enc === 'string') {
306
- const opened = decrypt(data.enc);
307
- if (opened === null) {
308
- return null;
309
- }
310
- data = JSON.parse(opened);
311
- }
312
- return {
313
- type: String(message.type ?? ''),
314
- from: String(message.from ?? ''),
315
- data,
316
- ts: String(message.ts ?? ''),
317
- };
318
- }
319
- catch {
320
- return null;
321
- }
322
- }
323
- /* ------------------------------------------------------------------ */
324
- /* context: mainclient */
325
- /* ------------------------------------------------------------------ */
326
- /** the signed request timeout in milliseconds. */
327
- const requesttimeoutms = 10 * 1000;
328
- /**
329
- * performs one signed POST towards the main node described by
330
- * SADDLE_MAIN_URL using the global fetch; the signature headers are
331
- * computed with the shared SADDLE_MESH_SECRET.
332
- *
333
- * @param path the full api path (e.g. /api/v1/auth/login).
334
- * @param body the json-serializable payload.
335
- * @returns the response status, the parsed json body and the
336
- * set-cookie relay.
337
- * @throws when the mesh is unconfigured, the
338
- * request fails or the body is not json.
339
- */
340
- export async function postmain(path, body) {
341
- const base = mainurl();
342
- const secret = meshsecret();
343
- if (base.length === 0 || secret.length === 0) {
344
- throw Object.assign(new Error('SADDLE_MAIN_URL and SADDLE_MESH_SECRET are required for mesh forwarding'), { code: 'mesh-unconfigured' });
345
- }
346
- const payload = JSON.stringify(body ?? {});
347
- const timestamp = String(Date.now());
348
- const signature = sign('POST', path, payload, secret, timestamp);
349
- let response;
350
- try {
351
- response = await fetch(`${base}${path}`, {
352
- method: 'POST',
353
- headers: {
354
- 'content-type': 'application/json',
355
- 'x-saddle-timestamp': timestamp,
356
- 'x-saddle-signature': signature,
357
- },
358
- body: payload,
359
- signal: AbortSignal.timeout(requesttimeoutms),
360
- });
361
- }
362
- catch (error) {
363
- throw Object.assign(new Error(`mesh request to ${path} failed: ${error instanceof Error ? error.message : String(error)}`), { code: 'mesh-unreachable' });
364
- }
365
- let parsed = null;
366
- try {
367
- parsed = await response.json();
368
- }
369
- catch {
370
- parsed = null;
371
- }
372
- let setcookie = [];
373
- try {
374
- setcookie =
375
- typeof response.headers.getSetCookie === 'function'
376
- ? response.headers.getSetCookie()
377
- : [];
378
- }
379
- catch {
380
- setcookie = [];
381
- }
382
- return { status: response.status, body: parsed, setcookie };
383
- }
384
- /* ------------------------------------------------------------------ */
385
- /* context: heartbeat */
386
- /* ------------------------------------------------------------------ */
387
- /** the clone heartbeat period in milliseconds. */
388
- const heartbeatperiodms = 60 * 1000;
389
- /**
390
- * runs the clone registry loop: one immediate registration followed by
391
- * heartbeats every 60 seconds, all signed towards the main node. the
392
- * interval is unref'd so the process can still exit cleanly.
393
- *
394
- * @param self the announcing node description.
395
- * @returns the heartbeat timer or null when the
396
- * node is not a clone or the mesh is unconfigured.
397
- */
398
- export function startheartbeat(self) {
399
- if (role !== 'clone' ||
400
- mainurl().length === 0 ||
401
- meshsecret().length === 0 ||
402
- String(self?.url ?? '').length === 0) {
403
- return null;
404
- }
405
- const beat = async () => {
406
- try {
407
- const registered = await postmain('/api/v1/mesh/register', self);
408
- if (registered.status !== 200 && registered.status !== 201) {
409
- return false;
410
- }
411
- const nodeid = registered.body !== null && typeof registered.body === 'object'
412
- ? String(registered.body?.nodeid ??
413
- registered.body?.id ??
414
- '')
415
- : '';
416
- if (nodeid.length === 0) {
417
- return false;
418
- }
419
- const pulsed = await postmain('/api/v1/mesh/heartbeat', { nodeid });
420
- return pulsed.status === 200;
421
- }
422
- catch {
423
- /* the next tick retries; the clone keeps serving locally */
424
- return false;
425
- }
426
- };
427
- /* boot ladder: the immediate beat may race the main node's own boot,
428
- * so short retries run until the first success; afterwards the steady
429
- * 60 second interval keeps the registry fresh. */
430
- let established = false;
431
- const runbeat = async () => {
432
- const ok = await beat();
433
- if (ok) {
434
- established = true;
435
- }
436
- return ok;
437
- };
438
- runbeat();
439
- for (const delay of [2000, 5000, 10000, 20000]) {
440
- const retry = setTimeout(() => {
441
- if (!established) {
442
- runbeat();
443
- }
444
- }, delay);
445
- retry.unref?.();
446
- }
447
- const timer = setInterval(() => {
448
- runbeat();
449
- }, heartbeatperiodms);
450
- timer.unref?.();
451
- return timer;
452
- }
453
- /* ------------------------------------------------------------------ */
454
- /* context: sessionforwarding */
455
- /* ------------------------------------------------------------------ */
456
- /**
457
- * forwards one auth request (register or login) from a clone to the
458
- * main authority; the caller caches the returned session locally when
459
- * the main node accepts it.
460
- *
461
- * @param action the auth action.
462
- * @param body the credentials payload.
463
- * @returns the main response verbatim.
464
- */
465
- export function forwardauth(action, body) {
466
- const path = action === 'register' ? '/api/v1/auth/register' : '/api/v1/auth/login';
467
- return postmain(path, body);
468
- }