@relayflows/sdk 2.0.29 → 2.0.31

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 (393) hide show
  1. package/dist/agent-artifacts.d.ts.map +1 -1
  2. package/dist/agent-artifacts.js +2 -9
  3. package/dist/agent-artifacts.js.map +1 -1
  4. package/dist/agent-cwd.d.ts +69 -0
  5. package/dist/agent-cwd.d.ts.map +1 -0
  6. package/dist/agent-cwd.js +140 -0
  7. package/dist/agent-cwd.js.map +1 -0
  8. package/dist/artifact-scan-policy.d.ts +35 -0
  9. package/dist/artifact-scan-policy.d.ts.map +1 -0
  10. package/dist/artifact-scan-policy.js +49 -0
  11. package/dist/artifact-scan-policy.js.map +1 -0
  12. package/dist/authored-completion-record.d.ts +35 -0
  13. package/dist/authored-completion-record.d.ts.map +1 -0
  14. package/dist/authored-completion-record.js +92 -0
  15. package/dist/authored-completion-record.js.map +1 -0
  16. package/dist/authored-completion.d.ts +99 -0
  17. package/dist/authored-completion.d.ts.map +1 -0
  18. package/dist/authored-completion.js +184 -0
  19. package/dist/authored-completion.js.map +1 -0
  20. package/dist/authored-flow-error.d.ts +13 -1
  21. package/dist/authored-flow-error.d.ts.map +1 -1
  22. package/dist/authored-flow-error.js +12 -0
  23. package/dist/authored-flow-error.js.map +1 -1
  24. package/dist/authored-flow-executor.d.ts +7 -30
  25. package/dist/authored-flow-executor.d.ts.map +1 -1
  26. package/dist/authored-flow-executor.js +64 -48
  27. package/dist/authored-flow-executor.js.map +1 -1
  28. package/dist/authored-human.d.ts.map +1 -1
  29. package/dist/authored-human.js +1 -3
  30. package/dist/authored-human.js.map +1 -1
  31. package/dist/authored-node-entry.js +4 -1
  32. package/dist/authored-node-entry.js.map +1 -1
  33. package/dist/authored-node-runner.d.ts +9 -1
  34. package/dist/authored-node-runner.d.ts.map +1 -1
  35. package/dist/authored-node-runner.js +92 -17
  36. package/dist/authored-node-runner.js.map +1 -1
  37. package/dist/authored-root.d.ts +5 -0
  38. package/dist/authored-root.d.ts.map +1 -1
  39. package/dist/authored-root.js +20 -2
  40. package/dist/authored-root.js.map +1 -1
  41. package/dist/authored-step-output.d.ts +35 -10
  42. package/dist/authored-step-output.d.ts.map +1 -1
  43. package/dist/authored-step-output.js +108 -40
  44. package/dist/authored-step-output.js.map +1 -1
  45. package/dist/authored-verdict.d.ts +28 -0
  46. package/dist/authored-verdict.d.ts.map +1 -0
  47. package/dist/authored-verdict.js +79 -0
  48. package/dist/authored-verdict.js.map +1 -0
  49. package/dist/authored-worker-step.d.ts +5 -2
  50. package/dist/authored-worker-step.d.ts.map +1 -1
  51. package/dist/authored-worker-step.js +48 -11
  52. package/dist/authored-worker-step.js.map +1 -1
  53. package/dist/bundle-extensions.d.ts.map +1 -1
  54. package/dist/bundle-extensions.js +4 -7
  55. package/dist/bundle-extensions.js.map +1 -1
  56. package/dist/bundle.d.ts.map +1 -1
  57. package/dist/bundle.js +41 -7
  58. package/dist/bundle.js.map +1 -1
  59. package/dist/canonical.d.ts.map +1 -1
  60. package/dist/canonical.js +18 -6
  61. package/dist/canonical.js.map +1 -1
  62. package/dist/cli/check-worker-surface.d.ts +27 -0
  63. package/dist/cli/check-worker-surface.d.ts.map +1 -0
  64. package/dist/cli/check-worker-surface.js +63 -0
  65. package/dist/cli/check-worker-surface.js.map +1 -0
  66. package/dist/cli/check.d.ts +19 -2
  67. package/dist/cli/check.d.ts.map +1 -1
  68. package/dist/cli/check.js +12 -4
  69. package/dist/cli/check.js.map +1 -1
  70. package/dist/cli/cloud-format.d.ts +9 -0
  71. package/dist/cli/cloud-format.d.ts.map +1 -0
  72. package/dist/cli/cloud-format.js +77 -0
  73. package/dist/cli/cloud-format.js.map +1 -0
  74. package/dist/cli/cloud-live.d.ts +39 -0
  75. package/dist/cli/cloud-live.d.ts.map +1 -0
  76. package/dist/cli/cloud-live.js +325 -0
  77. package/dist/cli/cloud-live.js.map +1 -0
  78. package/dist/cli/cloud-read.d.ts +3 -7
  79. package/dist/cli/cloud-read.d.ts.map +1 -1
  80. package/dist/cli/cloud-read.js +14 -266
  81. package/dist/cli/cloud-read.js.map +1 -1
  82. package/dist/cli/cloud-refusal.d.ts +39 -0
  83. package/dist/cli/cloud-refusal.d.ts.map +1 -0
  84. package/dist/cli/cloud-refusal.js +95 -0
  85. package/dist/cli/cloud-refusal.js.map +1 -0
  86. package/dist/cli/cloud-status-view.d.ts +7 -0
  87. package/dist/cli/cloud-status-view.d.ts.map +1 -0
  88. package/dist/cli/cloud-status-view.js +214 -0
  89. package/dist/cli/cloud-status-view.js.map +1 -0
  90. package/dist/cli/direct-run.d.ts.map +1 -1
  91. package/dist/cli/direct-run.js +16 -2
  92. package/dist/cli/direct-run.js.map +1 -1
  93. package/dist/cli/hn-monitor.d.ts +0 -13
  94. package/dist/cli/hn-monitor.d.ts.map +1 -1
  95. package/dist/cli/hn-monitor.js +2 -1
  96. package/dist/cli/hn-monitor.js.map +1 -1
  97. package/dist/cli/local-agent-remedy.d.ts +80 -0
  98. package/dist/cli/local-agent-remedy.d.ts.map +1 -0
  99. package/dist/cli/local-agent-remedy.js +116 -0
  100. package/dist/cli/local-agent-remedy.js.map +1 -0
  101. package/dist/cli/observer-session.d.ts +41 -0
  102. package/dist/cli/observer-session.d.ts.map +1 -0
  103. package/dist/cli/observer-session.js +78 -0
  104. package/dist/cli/observer-session.js.map +1 -0
  105. package/dist/cli/run.d.ts +30 -2
  106. package/dist/cli/run.d.ts.map +1 -1
  107. package/dist/cli/run.js +186 -24
  108. package/dist/cli/run.js.map +1 -1
  109. package/dist/cli/status.d.ts +9 -0
  110. package/dist/cli/status.d.ts.map +1 -1
  111. package/dist/cli/status.js +31 -2
  112. package/dist/cli/status.js.map +1 -1
  113. package/dist/cli/step-evidence.d.ts +90 -0
  114. package/dist/cli/step-evidence.d.ts.map +1 -0
  115. package/dist/cli/step-evidence.js +259 -0
  116. package/dist/cli/step-evidence.js.map +1 -0
  117. package/dist/cli/step-failure.d.ts +14 -4
  118. package/dist/cli/step-failure.d.ts.map +1 -1
  119. package/dist/cli/step-failure.js +41 -76
  120. package/dist/cli/step-failure.js.map +1 -1
  121. package/dist/cli-commands.d.ts +6 -0
  122. package/dist/cli-commands.d.ts.map +1 -1
  123. package/dist/cli-commands.js +2 -0
  124. package/dist/cli-commands.js.map +1 -1
  125. package/dist/cli.d.ts +10 -0
  126. package/dist/cli.d.ts.map +1 -1
  127. package/dist/cli.js +53 -42
  128. package/dist/cli.js.map +1 -1
  129. package/dist/cloud-read.d.ts +15 -0
  130. package/dist/cloud-read.d.ts.map +1 -1
  131. package/dist/cloud-read.js +20 -1
  132. package/dist/cloud-read.js.map +1 -1
  133. package/dist/cloud-run-record.d.ts +27 -0
  134. package/dist/cloud-run-record.d.ts.map +1 -0
  135. package/dist/cloud-run-record.js +56 -0
  136. package/dist/cloud-run-record.js.map +1 -0
  137. package/dist/cloud-run.d.ts +2 -9
  138. package/dist/cloud-run.d.ts.map +1 -1
  139. package/dist/cloud-run.js +3 -20
  140. package/dist/cloud-run.js.map +1 -1
  141. package/dist/communication/local.d.ts.map +1 -1
  142. package/dist/communication/local.js +2 -1
  143. package/dist/communication/local.js.map +1 -1
  144. package/dist/communication/worker.d.ts +1 -1
  145. package/dist/communication/worker.d.ts.map +1 -1
  146. package/dist/communication/worker.js +13 -5
  147. package/dist/communication/worker.js.map +1 -1
  148. package/dist/compile.d.ts.map +1 -1
  149. package/dist/compile.js +28 -6
  150. package/dist/compile.js.map +1 -1
  151. package/dist/direct-input.d.ts.map +1 -1
  152. package/dist/direct-input.js +21 -1
  153. package/dist/direct-input.js.map +1 -1
  154. package/dist/failure-kinds.d.ts +96 -6
  155. package/dist/failure-kinds.d.ts.map +1 -1
  156. package/dist/failure-kinds.js +29 -2
  157. package/dist/failure-kinds.js.map +1 -1
  158. package/dist/flow-extension-compat.d.ts.map +1 -1
  159. package/dist/flow-extension-compat.js +26 -8
  160. package/dist/flow-extension-compat.js.map +1 -1
  161. package/dist/flow-extension-loader.d.ts +7 -0
  162. package/dist/flow-extension-loader.d.ts.map +1 -1
  163. package/dist/flow-extension-loader.js +48 -22
  164. package/dist/flow-extension-loader.js.map +1 -1
  165. package/dist/flow-requirements.d.ts.map +1 -1
  166. package/dist/flow-requirements.js +24 -9
  167. package/dist/flow-requirements.js.map +1 -1
  168. package/dist/fs-descriptor.d.ts +17 -0
  169. package/dist/fs-descriptor.d.ts.map +1 -0
  170. package/dist/fs-descriptor.js +96 -0
  171. package/dist/fs-descriptor.js.map +1 -0
  172. package/dist/gate-contract.d.ts +7 -0
  173. package/dist/gate-contract.d.ts.map +1 -1
  174. package/dist/gate-contract.js +6 -1
  175. package/dist/gate-contract.js.map +1 -1
  176. package/dist/helper-preflight.d.ts.map +1 -1
  177. package/dist/helper-preflight.js +10 -3
  178. package/dist/helper-preflight.js.map +1 -1
  179. package/dist/helper-reference.d.ts +25 -0
  180. package/dist/helper-reference.d.ts.map +1 -0
  181. package/dist/helper-reference.js +129 -0
  182. package/dist/helper-reference.js.map +1 -0
  183. package/dist/hosted-base-snapshot.d.ts +25 -0
  184. package/dist/hosted-base-snapshot.d.ts.map +1 -0
  185. package/dist/hosted-base-snapshot.js +278 -0
  186. package/dist/hosted-base-snapshot.js.map +1 -0
  187. package/dist/hosted-extension-declarations.d.ts +10 -0
  188. package/dist/hosted-extension-declarations.d.ts.map +1 -0
  189. package/dist/hosted-extension-declarations.js +272 -0
  190. package/dist/hosted-extension-declarations.js.map +1 -0
  191. package/dist/hosted-extension-isolation.d.ts +82 -0
  192. package/dist/hosted-extension-isolation.d.ts.map +1 -0
  193. package/dist/hosted-extension-isolation.js +322 -0
  194. package/dist/hosted-extension-isolation.js.map +1 -0
  195. package/dist/hosted-extension-manifest.d.ts +9 -0
  196. package/dist/hosted-extension-manifest.d.ts.map +1 -0
  197. package/dist/hosted-extension-manifest.js +235 -0
  198. package/dist/hosted-extension-manifest.js.map +1 -0
  199. package/dist/hosted-extension-protocol.d.ts +16 -0
  200. package/dist/hosted-extension-protocol.d.ts.map +1 -0
  201. package/dist/hosted-extension-protocol.js +224 -0
  202. package/dist/hosted-extension-protocol.js.map +1 -0
  203. package/dist/hosted-extension-runtime.d.ts +30 -0
  204. package/dist/hosted-extension-runtime.d.ts.map +1 -0
  205. package/dist/hosted-extension-runtime.js +167 -0
  206. package/dist/hosted-extension-runtime.js.map +1 -0
  207. package/dist/hosted-extension-sandbox-source.d.ts +9 -0
  208. package/dist/hosted-extension-sandbox-source.d.ts.map +1 -0
  209. package/dist/hosted-extension-sandbox-source.js +95 -0
  210. package/dist/hosted-extension-sandbox-source.js.map +1 -0
  211. package/dist/hosted-extension-sandbox.d.ts +39 -0
  212. package/dist/hosted-extension-sandbox.d.ts.map +1 -0
  213. package/dist/hosted-extension-sandbox.js +418 -0
  214. package/dist/hosted-extension-sandbox.js.map +1 -0
  215. package/dist/hosted-project.d.ts +3 -0
  216. package/dist/hosted-project.d.ts.map +1 -0
  217. package/dist/hosted-project.js +19 -0
  218. package/dist/hosted-project.js.map +1 -0
  219. package/dist/hosted-promise-safety.d.ts +12 -0
  220. package/dist/hosted-promise-safety.d.ts.map +1 -0
  221. package/dist/hosted-promise-safety.js +63 -0
  222. package/dist/hosted-promise-safety.js.map +1 -0
  223. package/dist/index.d.ts +3 -1
  224. package/dist/index.d.ts.map +1 -1
  225. package/dist/index.js +2 -0
  226. package/dist/index.js.map +1 -1
  227. package/dist/intrinsic-array.d.ts +3 -0
  228. package/dist/intrinsic-array.d.ts.map +1 -0
  229. package/dist/intrinsic-array.js +14 -0
  230. package/dist/intrinsic-array.js.map +1 -0
  231. package/dist/journal-client.d.ts +3 -1
  232. package/dist/journal-client.d.ts.map +1 -1
  233. package/dist/journal-client.js +17 -2
  234. package/dist/journal-client.js.map +1 -1
  235. package/dist/journal-projection.d.ts +16 -0
  236. package/dist/journal-projection.d.ts.map +1 -0
  237. package/dist/journal-projection.js +108 -0
  238. package/dist/journal-projection.js.map +1 -0
  239. package/dist/json-value.d.ts +6 -1
  240. package/dist/json-value.d.ts.map +1 -1
  241. package/dist/json-value.js +155 -36
  242. package/dist/json-value.js.map +1 -1
  243. package/dist/llm-worker.d.ts +9 -0
  244. package/dist/llm-worker.d.ts.map +1 -1
  245. package/dist/llm-worker.js +16 -2
  246. package/dist/llm-worker.js.map +1 -1
  247. package/dist/local-agent.d.ts +1 -1
  248. package/dist/local-agent.d.ts.map +1 -1
  249. package/dist/local-agent.js +4 -3
  250. package/dist/local-agent.js.map +1 -1
  251. package/dist/named-gate-lowering.js +35 -5
  252. package/dist/named-gate-lowering.js.map +1 -1
  253. package/dist/named-gate-preflight.d.ts +27 -0
  254. package/dist/named-gate-preflight.d.ts.map +1 -0
  255. package/dist/named-gate-preflight.js +47 -0
  256. package/dist/named-gate-preflight.js.map +1 -0
  257. package/dist/named-gates.d.ts +1 -1
  258. package/dist/named-gates.d.ts.map +1 -1
  259. package/dist/named-gates.js +3 -0
  260. package/dist/named-gates.js.map +1 -1
  261. package/dist/observer-link.d.ts +7 -0
  262. package/dist/observer-link.d.ts.map +1 -1
  263. package/dist/observer-link.js +3 -1
  264. package/dist/observer-link.js.map +1 -1
  265. package/dist/plugin-store.d.ts +8 -2
  266. package/dist/plugin-store.d.ts.map +1 -1
  267. package/dist/plugin-store.js +233 -56
  268. package/dist/plugin-store.js.map +1 -1
  269. package/dist/preflight.d.ts.map +1 -1
  270. package/dist/preflight.js +7 -0
  271. package/dist/preflight.js.map +1 -1
  272. package/dist/protocol.d.ts +2 -0
  273. package/dist/protocol.d.ts.map +1 -1
  274. package/dist/redact.d.ts +42 -0
  275. package/dist/redact.d.ts.map +1 -1
  276. package/dist/redact.js +128 -0
  277. package/dist/redact.js.map +1 -1
  278. package/dist/run-projection.d.ts +83 -0
  279. package/dist/run-projection.d.ts.map +1 -0
  280. package/dist/run-projection.js +199 -0
  281. package/dist/run-projection.js.map +1 -0
  282. package/dist/semver-range.d.ts.map +1 -1
  283. package/dist/semver-range.js +27 -15
  284. package/dist/semver-range.js.map +1 -1
  285. package/dist/shell-word.d.ts +19 -0
  286. package/dist/shell-word.d.ts.map +1 -0
  287. package/dist/shell-word.js +23 -0
  288. package/dist/shell-word.js.map +1 -0
  289. package/dist/spec.d.ts +52 -3
  290. package/dist/spec.d.ts.map +1 -1
  291. package/dist/spec.js.map +1 -1
  292. package/dist/step-fields.d.ts +1 -1
  293. package/dist/step-fields.js +1 -1
  294. package/dist/step-fields.js.map +1 -1
  295. package/dist/steps-green.d.ts +27 -0
  296. package/dist/steps-green.d.ts.map +1 -0
  297. package/dist/steps-green.js +164 -0
  298. package/dist/steps-green.js.map +1 -0
  299. package/dist/validate.d.ts.map +1 -1
  300. package/dist/validate.js +39 -8
  301. package/dist/validate.js.map +1 -1
  302. package/dist/worker-lease.d.ts +8 -1
  303. package/dist/worker-lease.d.ts.map +1 -1
  304. package/dist/worker-lease.js +34 -4
  305. package/dist/worker-lease.js.map +1 -1
  306. package/dist/worker.d.ts +8 -0
  307. package/dist/worker.d.ts.map +1 -1
  308. package/dist/worker.js +17 -9
  309. package/dist/worker.js.map +1 -1
  310. package/package.json +3 -7
  311. package/src/agent-artifacts.ts +2 -10
  312. package/src/agent-cwd.ts +139 -0
  313. package/src/artifact-scan-policy.ts +51 -0
  314. package/src/authored-completion-record.ts +119 -0
  315. package/src/authored-completion.ts +203 -0
  316. package/src/authored-flow-error.ts +13 -1
  317. package/src/authored-flow-executor.ts +101 -59
  318. package/src/authored-human.ts +1 -4
  319. package/src/authored-node-entry.ts +4 -1
  320. package/src/authored-node-runner.ts +96 -22
  321. package/src/authored-root.ts +20 -2
  322. package/src/authored-step-output.ts +139 -42
  323. package/src/authored-verdict.ts +87 -0
  324. package/src/authored-worker-step.ts +60 -12
  325. package/src/bundle-extensions.ts +6 -5
  326. package/src/bundle.ts +52 -7
  327. package/src/canonical.ts +20 -6
  328. package/src/cli/check-worker-surface.ts +66 -0
  329. package/src/cli/check.ts +35 -4
  330. package/src/cli/cloud-format.ts +82 -0
  331. package/src/cli/cloud-live.ts +366 -0
  332. package/src/cli/cloud-read.ts +13 -282
  333. package/src/cli/cloud-refusal.ts +113 -0
  334. package/src/cli/cloud-status-view.ts +213 -0
  335. package/src/cli/direct-run.ts +19 -2
  336. package/src/cli/hn-monitor.ts +2 -1
  337. package/src/cli/local-agent-remedy.ts +160 -0
  338. package/src/cli/observer-session.ts +107 -0
  339. package/src/cli/run.ts +233 -28
  340. package/src/cli/status.ts +59 -3
  341. package/src/cli/step-evidence.ts +310 -0
  342. package/src/cli/step-failure.ts +57 -81
  343. package/src/cli-commands.ts +2 -0
  344. package/src/cli.ts +64 -44
  345. package/src/cloud-read.ts +25 -1
  346. package/src/cloud-run-record.ts +64 -0
  347. package/src/cloud-run.ts +5 -24
  348. package/src/communication/local.ts +2 -1
  349. package/src/communication/worker.ts +13 -5
  350. package/src/compile.ts +28 -6
  351. package/src/direct-input.ts +22 -1
  352. package/src/failure-kinds.ts +104 -2
  353. package/src/flow-extension-compat.ts +32 -7
  354. package/src/flow-extension-loader.ts +56 -21
  355. package/src/flow-requirements.ts +24 -9
  356. package/src/fs-descriptor.ts +127 -0
  357. package/src/gate-contract.ts +13 -1
  358. package/src/helper-preflight.ts +10 -3
  359. package/src/helper-reference.ts +130 -0
  360. package/src/hosted-base-snapshot.ts +351 -0
  361. package/src/hosted-extension-declarations.ts +387 -0
  362. package/src/hosted-extension-isolation.ts +457 -0
  363. package/src/hosted-extension-manifest.ts +249 -0
  364. package/src/hosted-extension-protocol.ts +262 -0
  365. package/src/hosted-extension-runtime.ts +266 -0
  366. package/src/hosted-extension-sandbox-source.ts +94 -0
  367. package/src/hosted-extension-sandbox.ts +489 -0
  368. package/src/hosted-project.ts +18 -0
  369. package/src/hosted-promise-safety.ts +68 -0
  370. package/src/index.ts +20 -0
  371. package/src/intrinsic-array.ts +14 -0
  372. package/src/journal-client.ts +19 -2
  373. package/src/journal-projection.ts +106 -0
  374. package/src/json-value.ts +184 -32
  375. package/src/llm-worker.ts +16 -2
  376. package/src/local-agent.ts +4 -2
  377. package/src/named-gate-lowering.ts +35 -5
  378. package/src/named-gate-preflight.ts +47 -0
  379. package/src/named-gates.ts +3 -0
  380. package/src/observer-link.ts +10 -1
  381. package/src/plugin-store.ts +323 -56
  382. package/src/preflight.ts +7 -0
  383. package/src/protocol.ts +2 -0
  384. package/src/redact.ts +111 -0
  385. package/src/run-projection.ts +247 -0
  386. package/src/semver-range.ts +39 -14
  387. package/src/shell-word.ts +24 -0
  388. package/src/spec.ts +54 -3
  389. package/src/step-fields.ts +1 -1
  390. package/src/steps-green.ts +168 -0
  391. package/src/validate.ts +35 -8
  392. package/src/worker-lease.ts +35 -5
  393. package/src/worker.ts +27 -11
@@ -0,0 +1,130 @@
1
+ import { parse, parseExpressionAt } from 'acorn';
2
+
3
+ /**
4
+ * Which `f.<namespace>` helpers a flow body actually USES.
5
+ *
6
+ * Read from a parse, not from the text. A helper merely NAMED in a string, a
7
+ * comment, a template quasi or a regex is not used, and declaring a
8
+ * requirement from one refuses a flow for a mount it never touches:
9
+ * `f.run("echo f.gitlab")`, an agent prompt naming `f.slack`, or a PR title in
10
+ * a commit message were each enough to demand a GitLab mount.
11
+ *
12
+ * This was first written as a lexer that blanked comments, strings and
13
+ * regexes. That is the wrong tool: `/` is a regex or a division depending on
14
+ * whether the preceding token ends an expression, which is not decidable
15
+ * without parse context, and the two shapes that ambiguity broke —
16
+ * interpolations (`${await f.gitlab…}`) and a quote inside a regex (`/'/`) —
17
+ * both HID real helper use, deploying a flow without its mount to fail at
18
+ * runtime. A parser settles every one of those by construction.
19
+ *
20
+ * What a parser still cannot see is indirection: `const p = 'gitlab';
21
+ * f[p].issues…` is invisible to any static reading, because it needs value
22
+ * tracking. This scan is therefore a convenience for the literal case, never
23
+ * the authority — `header.tools[namespace] === true` is the declaration that
24
+ * is, and it short-circuits this entirely.
25
+ */
26
+ export function helperNamespacesUsed(body: string, root: string): ReadonlySet<string> {
27
+ const program = parseFlowBody(body);
28
+ if (program === null) return textFallback(body, root);
29
+ const used = new Set<string>();
30
+ walk(program, (node) => {
31
+ if (node.type !== 'MemberExpression') return;
32
+ const object = node.object as AstNode | undefined;
33
+ if (object?.type !== 'Identifier' || object.name !== root) return;
34
+ const namespace = memberName(node);
35
+ if (namespace !== undefined) used.add(namespace);
36
+ });
37
+ return used;
38
+ }
39
+
40
+ /** `f.slack`, `f["slack"]`, `f?.slack` — but not `f[variable]`, which is unknowable. */
41
+ function memberName(node: AstNode): string | undefined {
42
+ const property = node.property as AstNode | undefined;
43
+ if (property === undefined) return undefined;
44
+ if (node.computed !== true) return property.type === 'Identifier' ? property.name : undefined;
45
+ return property.type === 'Literal' && typeof property.value === 'string' ? property.value : undefined;
46
+ }
47
+
48
+ /**
49
+ * Parse whatever `Function.prototype.toString()` produced.
50
+ *
51
+ * It can be an arrow, a function expression, an async method shorthand from an
52
+ * object literal, or a declaration — only some of which are expressions, so
53
+ * each shape gets a try. Types are already stripped by the time a body reaches
54
+ * here (Node strips before evaluating, and bundlers transpile), so this is
55
+ * plain JavaScript.
56
+ */
57
+ function parseFlowBody(body: string): AstNode | null {
58
+ const options = { ecmaVersion: 'latest' as const, allowAwaitOutsideFunction: true, allowReturnOutsideFunction: true };
59
+ // `parseExpressionAt` stops at the end of the first expression and does not
60
+ // object to what follows, so `async post(f) { … }` parses as the identifier
61
+ // `async` and reports success having read three characters. Every attempt
62
+ // must therefore consume the whole source, or a method body would be walked
63
+ // as its own name and declare no helpers at all.
64
+ const whole = (source: string, node: AstNode): AstNode => {
65
+ const end = typeof node.end === 'number' ? node.end : -1;
66
+ if (end < 0 || source.slice(end).trim() !== '') throw new SyntaxError('unconsumed input');
67
+ return node;
68
+ };
69
+ for (const attempt of [
70
+ () => whole(body, parseExpressionAt(body, 0, options) as unknown as AstNode),
71
+ () => whole(`(${body})`, parseExpressionAt(`(${body})`, 0, options) as unknown as AstNode),
72
+ () => parse(body, options) as unknown as AstNode,
73
+ // An object-literal method (`async post(f) { … }`) is neither expression
74
+ // nor statement on its own; it only parses inside an object.
75
+ () => whole(`({${body}})`, parseExpressionAt(`({${body}})`, 0, options) as unknown as AstNode),
76
+ ]) {
77
+ try {
78
+ return attempt();
79
+ } catch {
80
+ continue;
81
+ }
82
+ }
83
+ return null;
84
+ }
85
+
86
+ /**
87
+ * Last resort when nothing parses: match the text.
88
+ *
89
+ * Deliberately the permissive direction. An unparseable body is a shape this
90
+ * code does not understand, and under-reporting would deploy a flow without a
91
+ * mount it needs and fail at the call; over-reporting only asks for a mount
92
+ * that may go unused. Reaching here at all is a bug worth hearing about.
93
+ */
94
+ function textFallback(body: string, root: string): ReadonlySet<string> {
95
+ const used = new Set<string>();
96
+ const escaped = root.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
97
+ const pattern = new RegExp(`(?:^|[^\\w$.])${escaped}\\s*(?:\\.\\s*([\\w$]+)|\\[\\s*['"]([^'"]+)['"]\\s*\\])`, 'gu');
98
+ for (const match of body.matchAll(pattern)) {
99
+ const namespace = match[1] ?? match[2];
100
+ if (namespace !== undefined) used.add(namespace);
101
+ }
102
+ return used;
103
+ }
104
+
105
+ type AstNode = {
106
+ type: string;
107
+ end?: number;
108
+ name?: string;
109
+ value?: unknown;
110
+ computed?: boolean;
111
+ [key: string]: unknown;
112
+ };
113
+
114
+ /** Depth-first over every child node, without pulling in a second package. */
115
+ function walk(node: AstNode, visit: (node: AstNode) => void): void {
116
+ visit(node);
117
+ for (const key of Object.keys(node)) {
118
+ if (key === 'type' || key === 'start' || key === 'end' || key === 'loc') continue;
119
+ const child = node[key];
120
+ if (Array.isArray(child)) {
121
+ for (const entry of child) if (isNode(entry)) walk(entry, visit);
122
+ } else if (isNode(child)) {
123
+ walk(child, visit);
124
+ }
125
+ }
126
+ }
127
+
128
+ function isNode(value: unknown): value is AstNode {
129
+ return typeof value === 'object' && value !== null && typeof (value as { type?: unknown }).type === 'string';
130
+ }
@@ -0,0 +1,351 @@
1
+ import { constants } from 'node:fs';
2
+ import { chmod, mkdir, mkdtemp, realpath, rm, writeFile } from 'node:fs/promises';
3
+ import { tmpdir } from 'node:os';
4
+ import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
5
+ import { canonicalize } from './canonical.js';
6
+ import { sha256 } from './bundle.js';
7
+ import {
8
+ closeDescriptor,
9
+ closeDirectory,
10
+ descriptorIsDirectory,
11
+ descriptorIsFile,
12
+ directoryEntryIsDirectory,
13
+ directoryEntryIsFile,
14
+ openDirectory,
15
+ openDescriptor,
16
+ readDescriptor,
17
+ readDirectory,
18
+ readDirectoryEntry,
19
+ statDescriptor,
20
+ } from './fs-descriptor.js';
21
+ import { findHostedProject } from './hosted-project.js';
22
+ import { appendIntrinsicArray } from './intrinsic-array.js';
23
+ import {
24
+ assertHostedPromiseSafety,
25
+ frozenHostedPromiseValue,
26
+ hostedPromiseValue,
27
+ } from './hosted-promise-safety.js';
28
+ import { PluginError } from './plugin-manifest.js';
29
+
30
+ const EXCLUDED_DIRECTORIES = new Set(['.flows', '.git', 'node_modules']);
31
+ const CHMOD = chmod;
32
+ const MKDIR = mkdir;
33
+ const MKDTEMP = mkdtemp;
34
+ const REALPATH = realpath;
35
+ const RM = rm;
36
+ const WRITE_FILE = writeFile;
37
+ const TMPDIR = tmpdir;
38
+ const PATH_DIRNAME = dirname;
39
+ const PATH_IS_ABSOLUTE = isAbsolute;
40
+ const PATH_JOIN = join;
41
+ const PATH_RELATIVE = relative;
42
+ const PATH_RESOLVE = resolve;
43
+ const PATH_SEPARATOR = sep;
44
+ const PROCESS_PLATFORM = process.platform;
45
+ const ARRAY_SORT = Function.prototype.call.bind(Array.prototype.sort) as <T>(
46
+ array: T[],
47
+ compare?: (left: T, right: T) => number,
48
+ ) => T[];
49
+ const OBJECT_CREATE = Object.create;
50
+ const OBJECT_FREEZE = Object.freeze;
51
+ const OBJECT_GET_OWN_PROPERTY_DESCRIPTOR = Object.getOwnPropertyDescriptor;
52
+ const BIG_INT = BigInt;
53
+ const BUFFER_ALLOC_UNSAFE = Buffer.allocUnsafe;
54
+ const NUMBER = Number;
55
+ const SET_HAS = Function.prototype.call.bind(Set.prototype.has) as <T>(set: Set<T>, value: T) => boolean;
56
+ const STRING_LOCALE_COMPARE = Function.prototype.call.bind(String.prototype.localeCompare) as (
57
+ value: string,
58
+ other: string,
59
+ ) => number;
60
+ const STRING_STARTS_WITH = Function.prototype.call.bind(String.prototype.startsWith) as (
61
+ value: string,
62
+ search: string,
63
+ ) => boolean;
64
+ const MAX_ENTRIES = 10_000;
65
+ const MAX_BYTES = 64 * 1024 * 1024;
66
+ const MAX_DEPTH = 64;
67
+ const READ_FLAGS = constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK;
68
+
69
+ interface SourceFile {
70
+ readonly path: string;
71
+ readonly bytes: Uint8Array;
72
+ readonly sha256: string;
73
+ }
74
+
75
+ interface SourceBudget {
76
+ entries: number;
77
+ bytes: number;
78
+ }
79
+
80
+ /** @internal Deterministic race seams for this security boundary's tests. */
81
+ export interface HostedBaseSnapshotTestHooks {
82
+ readonly beforeOpen?: (path: string) => Promise<void>;
83
+ readonly afterStat?: (path: string) => Promise<void>;
84
+ }
85
+
86
+ const EMPTY_HOSTED_BASE_HOOKS = OBJECT_FREEZE(
87
+ OBJECT_CREATE(null),
88
+ ) as HostedBaseSnapshotTestHooks;
89
+
90
+ export interface HostedBaseSourceRoot {
91
+ readonly root: string;
92
+ readonly prefix: string;
93
+ }
94
+
95
+ export interface HostedBaseSnapshot {
96
+ readonly liveSources: readonly HostedBaseSourceRoot[];
97
+ readonly liveDigest: string;
98
+ readonly snapshotRoot: string;
99
+ readonly snapshotFlowPath: string;
100
+ readonly snapshotFlowSha256: string;
101
+ }
102
+
103
+ /**
104
+ * Materialize project source without importing tenant code. Project
105
+ * node_modules is never linked or read. Every admitted source byte is
106
+ * represented in liveDigest and copied to the private identity directory.
107
+ */
108
+ export async function createHostedBaseSnapshot(
109
+ flowPath: string,
110
+ projectRootOverride?: string,
111
+ ): Promise<HostedBaseSnapshot> {
112
+ assertHostedPromiseSafety('plugin_source_invalid');
113
+ const origin = await REALPATH(PATH_RESOLVE(flowPath));
114
+ const discovered = projectRootOverride ?? findHostedProject(PATH_DIRNAME(origin)) ?? PATH_DIRNAME(origin);
115
+ const projectRoot = await REALPATH(discovered);
116
+ const flowRelative = PATH_RELATIVE(projectRoot, origin);
117
+ if (
118
+ flowRelative === '' ||
119
+ PATH_IS_ABSOLUTE(flowRelative) ||
120
+ flowRelative === '..' ||
121
+ STRING_STARTS_WITH(flowRelative, `..${PATH_SEPARATOR}`)
122
+ ) {
123
+ throw invalid('Hosted base flow must be a file inside its project root.');
124
+ }
125
+ const liveSources = frozenHostedPromiseValue([OBJECT_FREEZE({ root: projectRoot, prefix: '' })]);
126
+ const files = await readAuthorityFiles(liveSources);
127
+ const liveDigest = sourceDigest(files);
128
+ const liveFlow = sourceFileAt(files, flowRelative);
129
+ if (liveFlow === undefined) throw invalid('Hosted base flow is missing from its admitted source.');
130
+ const snapshotRoot = await MKDTEMP(PATH_JOIN(TMPDIR(), 'flows-hosted-base-'));
131
+ await CHMOD(snapshotRoot, 0o700);
132
+ try {
133
+ for (let index = 0; index < files.length; index += 1) {
134
+ const file = files[index]!;
135
+ const target = PATH_JOIN(snapshotRoot, file.path);
136
+ await MKDIR(PATH_DIRNAME(target), { recursive: true, mode: 0o700 });
137
+ await WRITE_FILE(target, file.bytes, { flag: 'wx', mode: 0o400 });
138
+ }
139
+ const snapshotFiles = await readSnapshotTree(snapshotRoot);
140
+ const snapshotDigest = sourceDigest(snapshotFiles);
141
+ if (snapshotDigest !== liveDigest) {
142
+ throw invalid('Hosted base private snapshot does not match its buffered source.');
143
+ }
144
+ const snapshotFlow = sourceFileAt(snapshotFiles, flowRelative);
145
+ if (snapshotFlow === undefined || snapshotFlow.sha256 !== liveFlow.sha256) {
146
+ throw invalid('Hosted base private snapshot does not contain its buffered flow source.');
147
+ }
148
+ return frozenHostedPromiseValue({
149
+ liveSources,
150
+ liveDigest,
151
+ snapshotRoot,
152
+ snapshotFlowPath: PATH_JOIN(snapshotRoot, flowRelative),
153
+ snapshotFlowSha256: snapshotFlow.sha256,
154
+ });
155
+ } catch (error) {
156
+ await RM(snapshotRoot, { recursive: true, force: true });
157
+ throw error;
158
+ }
159
+ }
160
+
161
+ export async function hostedBaseSourceDigest(
162
+ sources: readonly HostedBaseSourceRoot[],
163
+ hooks: HostedBaseSnapshotTestHooks = EMPTY_HOSTED_BASE_HOOKS,
164
+ ): Promise<string> {
165
+ assertHostedPromiseSafety('plugin_source_invalid');
166
+ return sourceDigest(await readAuthorityFiles(sources, hooks));
167
+ }
168
+
169
+ export async function removeHostedBaseSnapshot(snapshot: HostedBaseSnapshot): Promise<void> {
170
+ await RM(snapshot.snapshotRoot, { recursive: true, force: true });
171
+ }
172
+
173
+ async function readAuthorityFiles(
174
+ sources: readonly HostedBaseSourceRoot[],
175
+ hooks: HostedBaseSnapshotTestHooks = EMPTY_HOSTED_BASE_HOOKS,
176
+ ): Promise<readonly SourceFile[]> {
177
+ const budget: SourceBudget = { entries: 0, bytes: 0 };
178
+ const files: SourceFile[] = [];
179
+ for (let sourceIndex = 0; sourceIndex < sources.length; sourceIndex += 1) {
180
+ const source = sources[sourceIndex]!;
181
+ const sourceFiles = await readTree(source.root, source.prefix, budget, hooks);
182
+ for (let fileIndex = 0; fileIndex < sourceFiles.length; fileIndex += 1) {
183
+ appendIntrinsicArray(files, sourceFiles[fileIndex]!);
184
+ }
185
+ }
186
+ ARRAY_SORT(files, (left, right) => STRING_LOCALE_COMPARE(left.path, right.path));
187
+ return frozenHostedPromiseValue(files);
188
+ }
189
+
190
+ async function readSnapshotTree(root: string): Promise<readonly SourceFile[]> {
191
+ const files: SourceFile[] = [];
192
+ const budget: SourceBudget = { entries: 0, bytes: 0 };
193
+ async function visit(directory: string, prefix: string): Promise<void> {
194
+ const entries = await readDirectory(directory);
195
+ ARRAY_SORT(entries, (left, right) => STRING_LOCALE_COMPARE(left.name, right.name));
196
+ for (let index = 0; index < entries.length; index += 1) {
197
+ const entry = entries[index]!;
198
+ budget.entries += 1;
199
+ if (budget.entries > MAX_ENTRIES) throw tooLarge();
200
+ const path = prefix === '' ? entry.name : PATH_JOIN(prefix, entry.name);
201
+ const absolute = PATH_JOIN(directory, entry.name);
202
+ if (directoryEntryIsDirectory(entry)) await visit(absolute, path);
203
+ else if (directoryEntryIsFile(entry)) {
204
+ const descriptor = await openDescriptor(absolute, READ_FLAGS);
205
+ try {
206
+ const before = await statDescriptor(descriptor, { bigint: true });
207
+ if (!descriptorIsFile(before) || before.size < 0n || before.size > BIG_INT(MAX_BYTES - budget.bytes))
208
+ throw tooLarge();
209
+ const expectedBytes = NUMBER(before.size);
210
+ const bytes = await readBounded(descriptor, expectedBytes, path);
211
+ const after = await statDescriptor(descriptor, { bigint: true });
212
+ if (after.size !== before.size || after.mtimeNs !== before.mtimeNs || after.ctimeNs !== before.ctimeNs) {
213
+ throw invalid(`Hosted base snapshot changed while reading "${path}".`);
214
+ }
215
+ budget.bytes += expectedBytes;
216
+ appendIntrinsicArray(files, OBJECT_FREEZE({ path, bytes, sha256: sha256(bytes) }));
217
+ } finally {
218
+ await closeDescriptor(descriptor);
219
+ }
220
+ } else throw invalid(`Hosted base snapshot contains unsupported entry "${path}".`);
221
+ }
222
+ }
223
+ await visit(root, '');
224
+ ARRAY_SORT(files, (left, right) => STRING_LOCALE_COMPARE(left.path, right.path));
225
+ return frozenHostedPromiseValue(files);
226
+ }
227
+
228
+ async function readTree(
229
+ root: string,
230
+ prefix: string,
231
+ budget: SourceBudget,
232
+ hooks: HostedBaseSnapshotTestHooks,
233
+ ): Promise<readonly SourceFile[]> {
234
+ if (PROCESS_PLATFORM !== 'linux') {
235
+ throw invalid('Hosted base source snapshotting requires Linux.');
236
+ }
237
+ const files: SourceFile[] = [];
238
+ async function visit(directory: number, relativeDirectory: string, depth: number): Promise<void> {
239
+ if (depth > MAX_DEPTH) throw tooLarge();
240
+ const entries = await openDirectory(`/proc/self/fd/${directory}`);
241
+ try {
242
+ for (;;) {
243
+ const entry = await readDirectoryEntry(entries);
244
+ if (entry === null) break;
245
+ if (SET_HAS(EXCLUDED_DIRECTORIES, entry.name)) continue;
246
+ budget.entries += 1;
247
+ if (budget.entries > MAX_ENTRIES) throw tooLarge();
248
+ const relativePath = relativeDirectory === '' ? entry.name : PATH_JOIN(relativeDirectory, entry.name);
249
+ const absolutePath = PATH_JOIN(root, relativePath);
250
+ await ownHook(hooks, 'beforeOpen')?.(absolutePath);
251
+ const descriptor = await openDescriptor(`/proc/self/fd/${directory}/${entry.name}`, READ_FLAGS);
252
+ try {
253
+ const before = await statDescriptor(descriptor, { bigint: true });
254
+ if (descriptorIsDirectory(before)) {
255
+ await visit(descriptor, relativePath, depth + 1);
256
+ } else if (descriptorIsFile(before)) {
257
+ if (before.size < 0n || before.size > BIG_INT(MAX_BYTES - budget.bytes)) throw tooLarge();
258
+ const expectedBytes = NUMBER(before.size);
259
+ await ownHook(hooks, 'afterStat')?.(absolutePath);
260
+ const bytes = await readBounded(descriptor, expectedBytes, relativePath);
261
+ const after = await statDescriptor(descriptor, { bigint: true });
262
+ if (after.size !== before.size || after.mtimeNs !== before.mtimeNs || after.ctimeNs !== before.ctimeNs) {
263
+ throw invalid(`Hosted base source changed while reading "${relativePath}".`);
264
+ }
265
+ budget.bytes += expectedBytes;
266
+ appendIntrinsicArray(
267
+ files,
268
+ OBJECT_FREEZE({
269
+ path: prefix === '' ? relativePath : PATH_JOIN(prefix, relativePath),
270
+ bytes,
271
+ sha256: sha256(bytes),
272
+ }),
273
+ );
274
+ } else {
275
+ throw invalid(`Hosted base authority contains unsupported entry "${relativePath}".`);
276
+ }
277
+ } finally {
278
+ await closeDescriptor(descriptor);
279
+ }
280
+ }
281
+ } finally {
282
+ await closeDirectory(entries);
283
+ }
284
+ }
285
+ try {
286
+ const rootDescriptor = await openDescriptor(root, READ_FLAGS);
287
+ try {
288
+ if (!descriptorIsDirectory(await statDescriptor(rootDescriptor, { bigint: true }))) {
289
+ throw invalid('Hosted base source root is not a directory.');
290
+ }
291
+ await visit(rootDescriptor, '', 0);
292
+ } finally {
293
+ await closeDescriptor(rootDescriptor);
294
+ }
295
+ } catch (error) {
296
+ if (error instanceof PluginError) throw error;
297
+ throw invalid('Hosted base source or trusted dependency is unreadable.');
298
+ }
299
+ return frozenHostedPromiseValue(files);
300
+ }
301
+
302
+ function ownHook(
303
+ hooks: HostedBaseSnapshotTestHooks,
304
+ name: keyof HostedBaseSnapshotTestHooks,
305
+ ): ((path: string) => Promise<void>) | undefined {
306
+ const descriptor = OBJECT_GET_OWN_PROPERTY_DESCRIPTOR(hooks, name);
307
+ return descriptor !== undefined && 'value' in descriptor && typeof descriptor.value === 'function'
308
+ ? descriptor.value as (path: string) => Promise<void>
309
+ : undefined;
310
+ }
311
+
312
+ async function readBounded(descriptor: number, expectedBytes: number, relativePath: string): Promise<Buffer> {
313
+ const bytes = BUFFER_ALLOC_UNSAFE(expectedBytes);
314
+ let offset = 0;
315
+ while (offset < expectedBytes) {
316
+ const bytesRead = await readDescriptor(descriptor, bytes, offset, expectedBytes - offset, offset);
317
+ if (bytesRead === 0) {
318
+ throw invalid(`Hosted base source changed while reading "${relativePath}".`);
319
+ }
320
+ offset += bytesRead;
321
+ }
322
+ const extra = BUFFER_ALLOC_UNSAFE(1);
323
+ if ((await readDescriptor(descriptor, extra, 0, 1, expectedBytes)) !== 0) {
324
+ throw invalid(`Hosted base source changed while reading "${relativePath}".`);
325
+ }
326
+ return hostedPromiseValue(bytes);
327
+ }
328
+
329
+ function tooLarge(): PluginError {
330
+ return invalid('Hosted base source exceeds the snapshot entry or byte limit.');
331
+ }
332
+
333
+ function sourceDigest(files: readonly SourceFile[]): string {
334
+ const records: Array<{ path: string; sha256: string }> = [];
335
+ for (let index = 0; index < files.length; index += 1) {
336
+ const file = files[index]!;
337
+ appendIntrinsicArray(records, { path: file.path, sha256: file.sha256 });
338
+ }
339
+ return sha256(canonicalize(records));
340
+ }
341
+
342
+ function sourceFileAt(files: readonly SourceFile[], path: string): SourceFile | undefined {
343
+ for (let index = 0; index < files.length; index += 1) {
344
+ if (files[index]!.path === path) return files[index];
345
+ }
346
+ return undefined;
347
+ }
348
+
349
+ function invalid(message: string): PluginError {
350
+ return new PluginError('plugin_source_invalid', message);
351
+ }