@shipfox/api-runners 5.0.0 → 7.0.1

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 (400) hide show
  1. package/.turbo/turbo-build.log +6 -6
  2. package/CHANGELOG.md +80 -0
  3. package/dist/config.d.ts +5 -1
  4. package/dist/config.d.ts.map +1 -1
  5. package/dist/config.js +54 -7
  6. package/dist/config.js.map +1 -1
  7. package/dist/core/demand.d.ts +6 -6
  8. package/dist/core/demand.d.ts.map +1 -1
  9. package/dist/core/demand.js +10 -10
  10. package/dist/core/demand.js.map +1 -1
  11. package/dist/core/entities/ephemeral-registration-token.d.ts +1 -1
  12. package/dist/core/entities/ephemeral-registration-token.d.ts.map +1 -1
  13. package/dist/core/entities/ephemeral-registration-token.js.map +1 -1
  14. package/dist/core/entities/provisioner-capability-snapshot.d.ts +13 -0
  15. package/dist/core/entities/provisioner-capability-snapshot.d.ts.map +1 -0
  16. package/dist/core/entities/provisioner-capability-snapshot.js +3 -0
  17. package/dist/core/entities/provisioner-capability-snapshot.js.map +1 -0
  18. package/dist/core/entities/provisioner-token.d.ts +10 -2
  19. package/dist/core/entities/provisioner-token.d.ts.map +1 -1
  20. package/dist/core/entities/provisioner-token.js.map +1 -1
  21. package/dist/core/entities/{provisioned-runner.d.ts → runner-instance.d.ts} +7 -6
  22. package/dist/core/entities/runner-instance.d.ts.map +1 -0
  23. package/dist/core/entities/runner-instance.js +3 -0
  24. package/dist/core/entities/runner-instance.js.map +1 -0
  25. package/dist/core/entities/runner-session.d.ts +4 -2
  26. package/dist/core/entities/runner-session.d.ts.map +1 -1
  27. package/dist/core/entities/runner-session.js.map +1 -1
  28. package/dist/core/ephemeral-registration-tokens.d.ts +5 -5
  29. package/dist/core/ephemeral-registration-tokens.d.ts.map +1 -1
  30. package/dist/core/ephemeral-registration-tokens.js +12 -12
  31. package/dist/core/ephemeral-registration-tokens.js.map +1 -1
  32. package/dist/core/errors.d.ts +16 -4
  33. package/dist/core/errors.d.ts.map +1 -1
  34. package/dist/core/errors.js +22 -4
  35. package/dist/core/errors.js.map +1 -1
  36. package/dist/core/index.d.ts +9 -5
  37. package/dist/core/index.d.ts.map +1 -1
  38. package/dist/core/index.js +6 -3
  39. package/dist/core/index.js.map +1 -1
  40. package/dist/core/job-executions.d.ts +2 -0
  41. package/dist/core/job-executions.d.ts.map +1 -1
  42. package/dist/core/job-executions.js +1 -2
  43. package/dist/core/job-executions.js.map +1 -1
  44. package/dist/core/maintenance.d.ts +1 -1
  45. package/dist/core/maintenance.d.ts.map +1 -1
  46. package/dist/core/maintenance.js +5 -5
  47. package/dist/core/maintenance.js.map +1 -1
  48. package/dist/core/provisioner-capability-snapshots.d.ts +5 -0
  49. package/dist/core/provisioner-capability-snapshots.d.ts.map +1 -0
  50. package/dist/core/provisioner-capability-snapshots.js +10 -0
  51. package/dist/core/provisioner-capability-snapshots.js.map +1 -0
  52. package/dist/core/provisioner-tokens.d.ts +10 -0
  53. package/dist/core/provisioner-tokens.d.ts.map +1 -1
  54. package/dist/core/provisioner-tokens.js +23 -1
  55. package/dist/core/provisioner-tokens.js.map +1 -1
  56. package/dist/core/rate-limit.d.ts.map +1 -1
  57. package/dist/core/rate-limit.js +2 -7
  58. package/dist/core/rate-limit.js.map +1 -1
  59. package/dist/core/runner-activation.d.ts +13 -0
  60. package/dist/core/runner-activation.d.ts.map +1 -0
  61. package/dist/core/runner-activation.js +36 -0
  62. package/dist/core/runner-activation.js.map +1 -0
  63. package/dist/core/runner-assignments.d.ts +6 -0
  64. package/dist/core/runner-assignments.d.ts.map +1 -0
  65. package/dist/core/runner-assignments.js +6 -0
  66. package/dist/core/runner-assignments.js.map +1 -0
  67. package/dist/core/runner-control-sessions.d.ts +46 -0
  68. package/dist/core/runner-control-sessions.d.ts.map +1 -0
  69. package/dist/core/runner-control-sessions.js +121 -0
  70. package/dist/core/runner-control-sessions.js.map +1 -0
  71. package/dist/core/runner-instances.d.ts +72 -0
  72. package/dist/core/runner-instances.d.ts.map +1 -0
  73. package/dist/core/runner-instances.js +165 -0
  74. package/dist/core/runner-instances.js.map +1 -0
  75. package/dist/core/runner-sessions.d.ts +8 -2
  76. package/dist/core/runner-sessions.d.ts.map +1 -1
  77. package/dist/core/runner-sessions.js +8 -5
  78. package/dist/core/runner-sessions.js.map +1 -1
  79. package/dist/db/db.d.ts +2698 -1054
  80. package/dist/db/db.d.ts.map +1 -1
  81. package/dist/db/db.js +9 -2
  82. package/dist/db/db.js.map +1 -1
  83. package/dist/db/ephemeral-registration-tokens.d.ts +2 -2
  84. package/dist/db/ephemeral-registration-tokens.d.ts.map +1 -1
  85. package/dist/db/ephemeral-registration-tokens.js +21 -21
  86. package/dist/db/ephemeral-registration-tokens.js.map +1 -1
  87. package/dist/db/index.d.ts +7 -4
  88. package/dist/db/index.d.ts.map +1 -1
  89. package/dist/db/index.js +5 -2
  90. package/dist/db/index.js.map +1 -1
  91. package/dist/db/job-executions.d.ts +6 -6
  92. package/dist/db/job-executions.d.ts.map +1 -1
  93. package/dist/db/job-executions.js +20 -19
  94. package/dist/db/job-executions.js.map +1 -1
  95. package/dist/db/provisioner-capability-snapshots.d.ts +21 -0
  96. package/dist/db/provisioner-capability-snapshots.d.ts.map +1 -0
  97. package/dist/db/provisioner-capability-snapshots.js +57 -0
  98. package/dist/db/provisioner-capability-snapshots.js.map +1 -0
  99. package/dist/db/provisioner-tokens.d.ts +7 -2
  100. package/dist/db/provisioner-tokens.d.ts.map +1 -1
  101. package/dist/db/provisioner-tokens.js +59 -10
  102. package/dist/db/provisioner-tokens.js.map +1 -1
  103. package/dist/db/reservations.d.ts +18 -0
  104. package/dist/db/reservations.d.ts.map +1 -1
  105. package/dist/db/reservations.js +88 -1
  106. package/dist/db/reservations.js.map +1 -1
  107. package/dist/db/runner-assignments.d.ts +7 -0
  108. package/dist/db/runner-assignments.d.ts.map +1 -0
  109. package/dist/db/runner-assignments.js +55 -0
  110. package/dist/db/runner-assignments.js.map +1 -0
  111. package/dist/db/runner-instances.d.ts +84 -0
  112. package/dist/db/runner-instances.d.ts.map +1 -0
  113. package/dist/db/runner-instances.js +497 -0
  114. package/dist/db/runner-instances.js.map +1 -0
  115. package/dist/db/runner-sessions.d.ts +5 -0
  116. package/dist/db/runner-sessions.d.ts.map +1 -1
  117. package/dist/db/runner-sessions.js +48 -2
  118. package/dist/db/runner-sessions.js.map +1 -1
  119. package/dist/db/schema/ephemeral-registration-tokens.d.ts +2 -2
  120. package/dist/db/schema/ephemeral-registration-tokens.js +3 -3
  121. package/dist/db/schema/ephemeral-registration-tokens.js.map +1 -1
  122. package/dist/db/schema/provisioner-capability-snapshots.d.ts +207 -0
  123. package/dist/db/schema/provisioner-capability-snapshots.d.ts.map +1 -0
  124. package/dist/db/schema/provisioner-capability-snapshots.js +38 -0
  125. package/dist/db/schema/provisioner-capability-snapshots.js.map +1 -0
  126. package/dist/db/schema/provisioner-tokens.d.ts +19 -1
  127. package/dist/db/schema/provisioner-tokens.d.ts.map +1 -1
  128. package/dist/db/schema/provisioner-tokens.js +29 -3
  129. package/dist/db/schema/provisioner-tokens.js.map +1 -1
  130. package/dist/db/schema/runner-activation-tokens.d.ts +161 -0
  131. package/dist/db/schema/runner-activation-tokens.d.ts.map +1 -0
  132. package/dist/db/schema/runner-activation-tokens.js +27 -0
  133. package/dist/db/schema/runner-activation-tokens.js.map +1 -0
  134. package/dist/db/schema/runner-control-sessions.d.ts +338 -0
  135. package/dist/db/schema/runner-control-sessions.d.ts.map +1 -0
  136. package/dist/db/schema/runner-control-sessions.js +52 -0
  137. package/dist/db/schema/runner-control-sessions.js.map +1 -0
  138. package/dist/db/schema/{provisioned-runners.d.ts → runner-instances.d.ts} +105 -34
  139. package/dist/db/schema/runner-instances.d.ts.map +1 -0
  140. package/dist/db/schema/{provisioned-runners.js → runner-instances.js} +23 -14
  141. package/dist/db/schema/runner-instances.js.map +1 -0
  142. package/dist/db/schema/runner-sessions.d.ts +39 -5
  143. package/dist/db/schema/runner-sessions.d.ts.map +1 -1
  144. package/dist/db/schema/runner-sessions.js +13 -6
  145. package/dist/db/schema/runner-sessions.js.map +1 -1
  146. package/dist/db/schema/running-job-executions.d.ts +2 -2
  147. package/dist/db/schema/running-job-executions.js +4 -4
  148. package/dist/db/schema/running-job-executions.js.map +1 -1
  149. package/dist/index.d.ts +6 -2
  150. package/dist/index.d.ts.map +1 -1
  151. package/dist/index.js +45 -39
  152. package/dist/index.js.map +1 -1
  153. package/dist/installation-provisioning.d.ts +14 -0
  154. package/dist/installation-provisioning.d.ts.map +1 -0
  155. package/dist/installation-provisioning.js +7 -0
  156. package/dist/installation-provisioning.js.map +1 -0
  157. package/dist/metrics/index.d.ts +1 -1
  158. package/dist/metrics/index.d.ts.map +1 -1
  159. package/dist/metrics/index.js +1 -1
  160. package/dist/metrics/index.js.map +1 -1
  161. package/dist/metrics/instance.d.ts +11 -7
  162. package/dist/metrics/instance.d.ts.map +1 -1
  163. package/dist/metrics/instance.js +13 -7
  164. package/dist/metrics/instance.js.map +1 -1
  165. package/dist/presentation/auth/index.d.ts +1 -0
  166. package/dist/presentation/auth/index.d.ts.map +1 -1
  167. package/dist/presentation/auth/index.js +1 -0
  168. package/dist/presentation/auth/index.js.map +1 -1
  169. package/dist/presentation/auth/provisioner-token-auth.d.ts.map +1 -1
  170. package/dist/presentation/auth/provisioner-token-auth.js +5 -1
  171. package/dist/presentation/auth/provisioner-token-auth.js.map +1 -1
  172. package/dist/presentation/auth/runner-control-session-auth.d.ts +4 -0
  173. package/dist/presentation/auth/runner-control-session-auth.d.ts.map +1 -0
  174. package/dist/presentation/auth/runner-control-session-auth.js +29 -0
  175. package/dist/presentation/auth/runner-control-session-auth.js.map +1 -0
  176. package/dist/presentation/auth/runner-registration-token-auth.d.ts +5 -1
  177. package/dist/presentation/auth/runner-registration-token-auth.d.ts.map +1 -1
  178. package/dist/presentation/auth/runner-registration-token-auth.js +21 -1
  179. package/dist/presentation/auth/runner-registration-token-auth.js.map +1 -1
  180. package/dist/presentation/dto/index.d.ts +1 -2
  181. package/dist/presentation/dto/index.d.ts.map +1 -1
  182. package/dist/presentation/dto/index.js +1 -2
  183. package/dist/presentation/dto/index.js.map +1 -1
  184. package/dist/presentation/dto/poll-demand.d.ts.map +1 -1
  185. package/dist/presentation/dto/poll-demand.js +7 -1
  186. package/dist/presentation/dto/poll-demand.js.map +1 -1
  187. package/dist/presentation/dto/provisioner-token.d.ts +1 -0
  188. package/dist/presentation/dto/provisioner-token.d.ts.map +1 -1
  189. package/dist/presentation/dto/provisioner-token.js +5 -1
  190. package/dist/presentation/dto/provisioner-token.js.map +1 -1
  191. package/dist/presentation/dto/{provisioned-runners.d.ts → runner-instances.d.ts} +9 -9
  192. package/dist/presentation/dto/runner-instances.d.ts.map +1 -0
  193. package/dist/presentation/dto/{provisioned-runners.js → runner-instances.js} +6 -6
  194. package/dist/presentation/dto/runner-instances.js.map +1 -0
  195. package/dist/presentation/index.d.ts +2 -2
  196. package/dist/presentation/index.d.ts.map +1 -1
  197. package/dist/presentation/index.js +2 -2
  198. package/dist/presentation/index.js.map +1 -1
  199. package/dist/presentation/inter-module.d.ts +5 -0
  200. package/dist/presentation/inter-module.d.ts.map +1 -0
  201. package/dist/presentation/inter-module.js +43 -0
  202. package/dist/presentation/inter-module.js.map +1 -0
  203. package/dist/presentation/routes/assign-runner-instances.d.ts +2 -0
  204. package/dist/presentation/routes/assign-runner-instances.d.ts.map +1 -0
  205. package/dist/presentation/routes/assign-runner-instances.js +40 -0
  206. package/dist/presentation/routes/assign-runner-instances.js.map +1 -0
  207. package/dist/presentation/routes/get-provisioner-me.d.ts.map +1 -1
  208. package/dist/presentation/routes/get-provisioner-me.js +5 -1
  209. package/dist/presentation/routes/get-provisioner-me.js.map +1 -1
  210. package/dist/presentation/routes/heartbeat.d.ts +2 -1
  211. package/dist/presentation/routes/heartbeat.d.ts.map +1 -1
  212. package/dist/presentation/routes/heartbeat.js +60 -50
  213. package/dist/presentation/routes/heartbeat.js.map +1 -1
  214. package/dist/presentation/routes/index.d.ts +3 -1
  215. package/dist/presentation/routes/index.d.ts.map +1 -1
  216. package/dist/presentation/routes/index.js +80 -51
  217. package/dist/presentation/routes/index.js.map +1 -1
  218. package/dist/presentation/routes/poll-demand.d.ts +2 -0
  219. package/dist/presentation/routes/poll-demand.d.ts.map +1 -1
  220. package/dist/presentation/routes/poll-demand.js +86 -45
  221. package/dist/presentation/routes/poll-demand.js.map +1 -1
  222. package/dist/presentation/routes/reconcile-runner-instances.d.ts +2 -0
  223. package/dist/presentation/routes/reconcile-runner-instances.d.ts.map +1 -0
  224. package/dist/presentation/routes/reconcile-runner-instances.js +27 -0
  225. package/dist/presentation/routes/reconcile-runner-instances.js.map +1 -0
  226. package/dist/presentation/routes/register.d.ts +2 -1
  227. package/dist/presentation/routes/register.d.ts.map +1 -1
  228. package/dist/presentation/routes/register.js +55 -52
  229. package/dist/presentation/routes/register.js.map +1 -1
  230. package/dist/presentation/routes/report-runner-instances.d.ts +2 -0
  231. package/dist/presentation/routes/report-runner-instances.d.ts.map +1 -0
  232. package/dist/presentation/routes/report-runner-instances.js +37 -0
  233. package/dist/presentation/routes/report-runner-instances.js.map +1 -0
  234. package/dist/presentation/routes/request-job.d.ts +2 -1
  235. package/dist/presentation/routes/request-job.d.ts.map +1 -1
  236. package/dist/presentation/routes/request-job.js +39 -36
  237. package/dist/presentation/routes/request-job.js.map +1 -1
  238. package/dist/presentation/routes/runner-enrollment.d.ts +8 -0
  239. package/dist/presentation/routes/runner-enrollment.d.ts.map +1 -0
  240. package/dist/presentation/routes/runner-enrollment.js +212 -0
  241. package/dist/presentation/routes/runner-enrollment.js.map +1 -0
  242. package/dist/temporal/activities/index.d.ts +2 -2
  243. package/dist/temporal/activities/index.d.ts.map +1 -1
  244. package/dist/temporal/activities/index.js +2 -2
  245. package/dist/temporal/activities/index.js.map +1 -1
  246. package/dist/temporal/activities/maintenance-activities.d.ts +1 -1
  247. package/dist/temporal/activities/maintenance-activities.d.ts.map +1 -1
  248. package/dist/temporal/activities/maintenance-activities.js +3 -3
  249. package/dist/temporal/activities/maintenance-activities.js.map +1 -1
  250. package/dist/temporal/workflows/index.bundle.js +3 -3
  251. package/dist/temporal/workflows/stuck-job-detector.js +2 -2
  252. package/dist/temporal/workflows/stuck-job-detector.js.map +1 -1
  253. package/dist/tsconfig.test.tsbuildinfo +1 -1
  254. package/drizzle/0000_initial.sql +91 -24
  255. package/drizzle/meta/0000_snapshot.json +392 -63
  256. package/package.json +21 -16
  257. package/src/config.test.ts +0 -26
  258. package/src/config.ts +48 -7
  259. package/src/core/demand.test.ts +29 -29
  260. package/src/core/demand.ts +22 -22
  261. package/src/core/entities/ephemeral-registration-token.ts +1 -1
  262. package/src/core/entities/provisioner-capability-snapshot.ts +12 -0
  263. package/src/core/entities/provisioner-token.ts +8 -2
  264. package/src/core/entities/{provisioned-runner.ts → runner-instance.ts} +6 -5
  265. package/src/core/entities/runner-session.ts +4 -2
  266. package/src/core/ephemeral-registration-tokens.test.ts +11 -11
  267. package/src/core/ephemeral-registration-tokens.ts +18 -20
  268. package/src/core/errors.ts +23 -4
  269. package/src/core/index.ts +37 -22
  270. package/src/core/job-executions.ts +3 -2
  271. package/src/core/maintenance.test.ts +13 -17
  272. package/src/core/maintenance.ts +5 -5
  273. package/src/core/provisioner-capability-snapshots.test.ts +35 -0
  274. package/src/core/provisioner-capability-snapshots.ts +12 -0
  275. package/src/core/provisioner-tokens.test.ts +20 -0
  276. package/src/core/provisioner-tokens.ts +33 -0
  277. package/src/core/rate-limit.test.ts +11 -27
  278. package/src/core/rate-limit.ts +3 -11
  279. package/src/core/runner-activation.ts +72 -0
  280. package/src/core/runner-assignments.ts +9 -0
  281. package/src/core/runner-control-sessions.ts +190 -0
  282. package/src/core/{provisioned-runners.test.ts → runner-instances.test.ts} +24 -24
  283. package/src/core/runner-instances.ts +301 -0
  284. package/src/core/runner-sessions.test.ts +112 -22
  285. package/src/core/runner-sessions.ts +25 -14
  286. package/src/db/db.ts +9 -2
  287. package/src/db/ephemeral-registration-tokens.test.ts +9 -9
  288. package/src/db/ephemeral-registration-tokens.ts +26 -26
  289. package/src/db/index.ts +30 -21
  290. package/src/db/job-executions.test.ts +13 -11
  291. package/src/db/job-executions.ts +33 -27
  292. package/src/db/provisioner-capability-snapshots.test.ts +175 -0
  293. package/src/db/provisioner-capability-snapshots.ts +130 -0
  294. package/src/db/provisioner-tokens.test.ts +139 -2
  295. package/src/db/provisioner-tokens.ts +123 -15
  296. package/src/db/reservations.test.ts +49 -0
  297. package/src/db/reservations.ts +146 -1
  298. package/src/db/runner-assignments.test.ts +163 -0
  299. package/src/db/runner-assignments.ts +115 -0
  300. package/src/db/{provisioned-runners.test.ts → runner-instances.test.ts} +442 -353
  301. package/src/db/runner-instances.ts +922 -0
  302. package/src/db/runner-sessions.test.ts +19 -6
  303. package/src/db/runner-sessions.ts +78 -2
  304. package/src/db/schema/ephemeral-registration-tokens.ts +4 -4
  305. package/src/db/schema/provisioner-capability-snapshots.ts +46 -0
  306. package/src/db/schema/provisioner-tokens.ts +33 -2
  307. package/src/db/schema/runner-activation-tokens.ts +22 -0
  308. package/src/db/schema/runner-control-sessions.ts +46 -0
  309. package/src/db/schema/{provisioned-runners.ts → runner-instances.ts} +28 -27
  310. package/src/db/schema/runner-sessions.ts +11 -7
  311. package/src/db/schema/running-job-executions.ts +5 -5
  312. package/src/index.test.ts +26 -0
  313. package/src/index.ts +38 -27
  314. package/src/installation-provisioning.ts +14 -0
  315. package/src/metrics/index.ts +5 -5
  316. package/src/metrics/instance.ts +25 -14
  317. package/src/presentation/auth/index.ts +4 -0
  318. package/src/presentation/auth/provisioner-token-auth.ts +8 -4
  319. package/src/presentation/auth/runner-control-session-auth.ts +31 -0
  320. package/src/presentation/auth/runner-registration-token-auth.ts +39 -3
  321. package/src/presentation/dto/index.ts +4 -8
  322. package/src/presentation/dto/poll-demand.ts +3 -1
  323. package/src/presentation/dto/provisioner-token.ts +5 -0
  324. package/src/presentation/dto/{provisioned-runners.ts → runner-instances.ts} +13 -15
  325. package/src/presentation/index.ts +2 -1
  326. package/src/presentation/inter-module.test.ts +15 -0
  327. package/src/presentation/inter-module.ts +53 -0
  328. package/src/presentation/routes/assign-runner-instances.ts +49 -0
  329. package/src/presentation/routes/get-provisioner-me.ts +3 -4
  330. package/src/presentation/routes/heartbeat.test.ts +4 -3
  331. package/src/presentation/routes/heartbeat.ts +67 -64
  332. package/src/presentation/routes/index.ts +91 -44
  333. package/src/presentation/routes/list-active-runners.test.ts +23 -23
  334. package/src/presentation/routes/manual-registration-tokens.test.ts +4 -3
  335. package/src/presentation/routes/poll-demand-release.test.ts +4 -4
  336. package/src/presentation/routes/poll-demand.test.ts +161 -22
  337. package/src/presentation/routes/poll-demand.ts +105 -45
  338. package/src/presentation/routes/provisioner-me.test.ts +37 -3
  339. package/src/presentation/routes/{reconcile-provisioned-runners.test.ts → reconcile-runner-instances.test.ts} +66 -61
  340. package/src/presentation/routes/reconcile-runner-instances.ts +30 -0
  341. package/src/presentation/routes/register.test.ts +9 -5
  342. package/src/presentation/routes/register.ts +61 -57
  343. package/src/presentation/routes/{report-provisioned-runners.test.ts → report-runner-instances.test.ts} +28 -28
  344. package/src/presentation/routes/{report-provisioned-runners.ts → report-runner-instances.ts} +14 -14
  345. package/src/presentation/routes/request-job.test.ts +5 -3
  346. package/src/presentation/routes/request-job.ts +41 -37
  347. package/src/presentation/routes/runner-enrollment.test.ts +319 -0
  348. package/src/presentation/routes/runner-enrollment.ts +196 -0
  349. package/src/temporal/activities/index.ts +2 -2
  350. package/src/temporal/activities/maintenance-activities.test.ts +7 -7
  351. package/src/temporal/activities/maintenance-activities.ts +3 -3
  352. package/src/temporal/workflows/stuck-job-detector.test.ts +3 -3
  353. package/src/temporal/workflows/stuck-job-detector.ts +2 -2
  354. package/test/env.ts +1 -3
  355. package/test/factories/ephemeral-registration-token.ts +2 -2
  356. package/test/factories/provisioner-token.ts +3 -1
  357. package/test/factories/runner-instance.ts +57 -0
  358. package/test/factories/runner-session.ts +3 -1
  359. package/test/fixtures/auth-inter-module.ts +30 -0
  360. package/test/globalSetup.ts +4 -1
  361. package/test/index.ts +2 -1
  362. package/tsconfig.build.tsbuildinfo +1 -1
  363. package/dist/core/entities/provisioned-runner.d.ts.map +0 -1
  364. package/dist/core/entities/provisioned-runner.js +0 -3
  365. package/dist/core/entities/provisioned-runner.js.map +0 -1
  366. package/dist/core/provisioned-runners.d.ts +0 -67
  367. package/dist/core/provisioned-runners.d.ts.map +0 -1
  368. package/dist/core/provisioned-runners.js +0 -162
  369. package/dist/core/provisioned-runners.js.map +0 -1
  370. package/dist/db/provisioned-runners.d.ts +0 -79
  371. package/dist/db/provisioned-runners.d.ts.map +0 -1
  372. package/dist/db/provisioned-runners.js +0 -466
  373. package/dist/db/provisioned-runners.js.map +0 -1
  374. package/dist/db/schema/provisioned-runners.d.ts.map +0 -1
  375. package/dist/db/schema/provisioned-runners.js.map +0 -1
  376. package/dist/presentation/dto/mint-registration-tokens.d.ts +0 -5
  377. package/dist/presentation/dto/mint-registration-tokens.d.ts.map +0 -1
  378. package/dist/presentation/dto/mint-registration-tokens.js +0 -19
  379. package/dist/presentation/dto/mint-registration-tokens.js.map +0 -1
  380. package/dist/presentation/dto/provisioned-runners.d.ts.map +0 -1
  381. package/dist/presentation/dto/provisioned-runners.js.map +0 -1
  382. package/dist/presentation/routes/mint-registration-tokens.d.ts +0 -2
  383. package/dist/presentation/routes/mint-registration-tokens.d.ts.map +0 -1
  384. package/dist/presentation/routes/mint-registration-tokens.js +0 -74
  385. package/dist/presentation/routes/mint-registration-tokens.js.map +0 -1
  386. package/dist/presentation/routes/reconcile-provisioned-runners.d.ts +0 -2
  387. package/dist/presentation/routes/reconcile-provisioned-runners.d.ts.map +0 -1
  388. package/dist/presentation/routes/reconcile-provisioned-runners.js +0 -27
  389. package/dist/presentation/routes/reconcile-provisioned-runners.js.map +0 -1
  390. package/dist/presentation/routes/report-provisioned-runners.d.ts +0 -2
  391. package/dist/presentation/routes/report-provisioned-runners.d.ts.map +0 -1
  392. package/dist/presentation/routes/report-provisioned-runners.js +0 -37
  393. package/dist/presentation/routes/report-provisioned-runners.js.map +0 -1
  394. package/src/core/provisioned-runners.ts +0 -301
  395. package/src/db/provisioned-runners.ts +0 -886
  396. package/src/presentation/dto/mint-registration-tokens.ts +0 -35
  397. package/src/presentation/routes/mint-registration-tokens.test.ts +0 -453
  398. package/src/presentation/routes/mint-registration-tokens.ts +0 -82
  399. package/src/presentation/routes/reconcile-provisioned-runners.ts +0 -30
  400. package/test/factories/provisioned-runner.ts +0 -78
@@ -3,6 +3,7 @@ import {db} from '#db/db.js';
3
3
  import {
4
4
  deleteReservationsByIds,
5
5
  pollDemandAndReserve,
6
+ pollInstallationDemandAndReserve,
6
7
  releaseReservationUnits,
7
8
  } from '#db/reservations.js';
8
9
  import {reservations} from '#db/schema/reservations.js';
@@ -75,6 +76,54 @@ describe('pollDemandAndReserve', () => {
75
76
  expect(reserved).toBeLessThanOrEqual(5);
76
77
  });
77
78
 
79
+ it('allocates eligible workspace heads in oldest-demand order without overselling templates', async () => {
80
+ const olderWorkspaceId = crypto.randomUUID();
81
+ const newerWorkspaceId = crypto.randomUUID();
82
+ await pendingJobFactory.create({workspaceId: olderWorkspaceId, requiredLabels: ['linux']});
83
+ await pendingJobFactory.create({workspaceId: newerWorkspaceId, requiredLabels: ['linux']});
84
+
85
+ const result = await pollInstallationDemandAndReserve({
86
+ provisionerId,
87
+ maxReservations: 5,
88
+ ttlSeconds: 60,
89
+ templates: [template('linux', ['linux'], 1)],
90
+ capabilityWindowSeconds: 60,
91
+ eligibleWorkspaceIds: new Set([olderWorkspaceId, newerWorkspaceId]),
92
+ });
93
+
94
+ expect(result.reservations).toEqual([
95
+ expect.objectContaining({workspaceId: olderWorkspaceId, labels: ['linux'], count: 1}),
96
+ ]);
97
+ });
98
+
99
+ it('reports committed installation grants before an aborted poll stops allocating', async () => {
100
+ const firstWorkspaceId = crypto.randomUUID();
101
+ const secondWorkspaceId = crypto.randomUUID();
102
+ const abortController = new AbortController();
103
+ const reportedReservations: string[] = [];
104
+ await pendingJobFactory.create({workspaceId: firstWorkspaceId, requiredLabels: ['linux']});
105
+ await pendingJobFactory.create({workspaceId: secondWorkspaceId, requiredLabels: ['linux']});
106
+
107
+ const result = await pollInstallationDemandAndReserve({
108
+ provisionerId,
109
+ maxReservations: 2,
110
+ ttlSeconds: 60,
111
+ templates: [template('linux', ['linux'], 2)],
112
+ capabilityWindowSeconds: 60,
113
+ eligibleWorkspaceIds: new Set([firstWorkspaceId, secondWorkspaceId]),
114
+ signal: abortController.signal,
115
+ onReservations: (reservations) => {
116
+ reportedReservations.push(...reservations.map((reservation) => reservation.reservationId));
117
+ abortController.abort();
118
+ },
119
+ });
120
+
121
+ expect(result.reservations).toHaveLength(1);
122
+ expect(reportedReservations).toEqual(
123
+ result.reservations.map((reservation) => reservation.reservationId),
124
+ );
125
+ });
126
+
78
127
  it('does not count expired reservations against demand', async () => {
79
128
  await createPendingJobs(1, ['linux']);
80
129
  await reservationFactory.create({
@@ -3,6 +3,8 @@ import {and, asc, eq, gt, inArray, lt, sql} from 'drizzle-orm';
3
3
  import type {Tx} from './db.js';
4
4
  import {db} from './db.js';
5
5
  import {pendingJobExecutions} from './schema/pending-job-executions.js';
6
+ import {provisionerCapabilitySnapshots} from './schema/provisioner-capability-snapshots.js';
7
+ import {provisionerTokens} from './schema/provisioner-tokens.js';
6
8
  import {reservations} from './schema/reservations.js';
7
9
 
8
10
  export interface ReservationTemplate {
@@ -14,6 +16,7 @@ export interface ReservationTemplate {
14
16
  }
15
17
 
16
18
  export interface DemandStat {
19
+ workspaceId?: string;
17
20
  labels: string[];
18
21
  queued: number;
19
22
  reserved: number;
@@ -22,6 +25,7 @@ export interface DemandStat {
22
25
 
23
26
  export interface ReservationGrant {
24
27
  reservationId: string;
28
+ workspaceId?: string;
25
29
  labels: string[];
26
30
  count: number;
27
31
  expiresAt: Date;
@@ -33,6 +37,18 @@ export interface PollDemandAndReserveParams {
33
37
  maxReservations: number;
34
38
  ttlSeconds: number;
35
39
  templates: ReservationTemplate[];
40
+ capabilityWindowSeconds?: number;
41
+ }
42
+
43
+ export interface InstallationPollDemandAndReserveParams {
44
+ provisionerId: string;
45
+ maxReservations: number;
46
+ ttlSeconds: number;
47
+ templates: ReservationTemplate[];
48
+ capabilityWindowSeconds: number;
49
+ eligibleWorkspaceIds: ReadonlySet<string>;
50
+ signal?: AbortSignal;
51
+ onReservations?: (reservations: ReservationGrant[]) => void;
36
52
  }
37
53
 
38
54
  interface NormalizedTemplate {
@@ -60,8 +76,75 @@ export async function pollDemandAndReserveTx(
60
76
  params: PollDemandAndReserveParams,
61
77
  ): Promise<{stats: DemandStat[]; reservations: ReservationGrant[]}> {
62
78
  await tx.execute(sql`select pg_advisory_xact_lock(hashtext(${params.workspaceId}))`);
79
+ return await pollDemandAndReserveLockedTx(tx, params);
80
+ }
81
+
82
+ export async function pollInstallationDemandAndReserve(
83
+ params: InstallationPollDemandAndReserveParams,
84
+ ): Promise<{stats: DemandStat[]; reservations: ReservationGrant[]}> {
85
+ const candidateWorkspaceIds = await listInstallationDemandWorkspaceIds(
86
+ params.eligibleWorkspaceIds,
87
+ );
88
+ const results: Array<{stats: DemandStat[]; reservations: ReservationGrant[]}> = [];
89
+ let remainingMaxReservations = params.maxReservations;
90
+ const remainingTemplates = params.templates.map((template) => ({
91
+ ...template,
92
+ labels: [...canonicalizeLabels(template.labels)],
93
+ remainingSlots: template.availableSlots,
94
+ }));
95
+ for (const workspaceId of candidateWorkspaceIds) {
96
+ if (params.signal?.aborted || remainingMaxReservations === 0) break;
97
+ const result = await db().transaction(async (tx) => {
98
+ const lockResult = await tx.execute<{locked: boolean}>(
99
+ sql`select pg_try_advisory_xact_lock(hashtext(${workspaceId})) as locked`,
100
+ );
101
+ const locked = lockResult.rows[0];
102
+ if (!locked?.locked) return {stats: [], reservations: []};
103
+ return await pollDemandAndReserveLockedTx(tx, {
104
+ workspaceId,
105
+ provisionerId: params.provisionerId,
106
+ maxReservations: remainingMaxReservations,
107
+ ttlSeconds: params.ttlSeconds,
108
+ templates: remainingTemplates.map((template) => ({
109
+ ...template,
110
+ availableSlots: template.remainingSlots,
111
+ })),
112
+ capabilityWindowSeconds: params.capabilityWindowSeconds,
113
+ });
114
+ });
115
+ results.push(result);
116
+ consumeInstallationTemplateSlots(remainingTemplates, result.reservations);
117
+ params.onReservations?.(result.reservations);
118
+ remainingMaxReservations -= result.reservations.reduce(
119
+ (total, reservation) => total + reservation.count,
120
+ 0,
121
+ );
122
+ }
123
+ return {
124
+ stats: results.flatMap((result) => result.stats),
125
+ reservations: results.flatMap((result) => result.reservations),
126
+ };
127
+ }
63
128
 
64
- const demandRows = (
129
+ function consumeInstallationTemplateSlots(
130
+ templates: NormalizedTemplate[],
131
+ reservations: ReservationGrant[],
132
+ ): void {
133
+ for (const reservation of reservations) {
134
+ const satisfyingTemplates = templates
135
+ .filter((template) => isSubset(reservation.labels, template.labels))
136
+ .sort(
137
+ (a, b) => a.labels.length - b.labels.length || a.templateKey.localeCompare(b.templateKey),
138
+ );
139
+ drawSlots(satisfyingTemplates, reservation.count);
140
+ }
141
+ }
142
+
143
+ async function pollDemandAndReserveLockedTx(
144
+ tx: Tx,
145
+ params: PollDemandAndReserveParams,
146
+ ): Promise<{stats: DemandStat[]; reservations: ReservationGrant[]}> {
147
+ let demandRows = (
65
148
  await tx
66
149
  .select({
67
150
  requiredLabels: pendingJobExecutions.requiredLabels,
@@ -75,6 +158,15 @@ export async function pollDemandAndReserveTx(
75
158
  ...row,
76
159
  oldestQueuedAt: new Date(row.oldestQueuedAt),
77
160
  }));
161
+ if (params.capabilityWindowSeconds !== undefined) {
162
+ const capabilityLabels = await listActiveWorkspaceCapabilityLabelsTx(tx, {
163
+ workspaceId: params.workspaceId,
164
+ windowSeconds: params.capabilityWindowSeconds,
165
+ });
166
+ demandRows = demandRows.filter(
167
+ (demand) => !capabilityLabels.some((labels) => isSubset(demand.requiredLabels, labels)),
168
+ );
169
+ }
78
170
 
79
171
  const activeReservationRows = await tx
80
172
  .select({
@@ -114,6 +206,8 @@ export async function pollDemandAndReserveTx(
114
206
  const stats: DemandStat[] = [];
115
207
  const grants: ReservationGrant[] = [];
116
208
  let remainingMaxReservations = params.maxReservations;
209
+ const workspaceIdField =
210
+ params.capabilityWindowSeconds === undefined ? {} : {workspaceId: params.workspaceId};
117
211
 
118
212
  for (const demand of sortDemandRows(demandRows)) {
119
213
  const satisfyingTemplates = templates
@@ -152,6 +246,7 @@ export async function pollDemandAndReserveTx(
152
246
  reservedAfterGrant += grant;
153
247
  grants.push({
154
248
  reservationId: inserted.id,
249
+ ...workspaceIdField,
155
250
  labels: demand.requiredLabels,
156
251
  count: grant,
157
252
  expiresAt: inserted.expiresAt,
@@ -159,6 +254,7 @@ export async function pollDemandAndReserveTx(
159
254
  }
160
255
 
161
256
  stats.push({
257
+ ...workspaceIdField,
162
258
  labels: demand.requiredLabels,
163
259
  queued: demand.queued,
164
260
  reserved: reservedAfterGrant,
@@ -169,6 +265,55 @@ export async function pollDemandAndReserveTx(
169
265
  return {stats, reservations: grants};
170
266
  }
171
267
 
268
+ async function listInstallationDemandWorkspaceIds(eligibleWorkspaceIds: ReadonlySet<string>) {
269
+ if (eligibleWorkspaceIds.size === 0) return [];
270
+ const rows = await db()
271
+ .select({
272
+ workspaceId: pendingJobExecutions.workspaceId,
273
+ oldestQueuedAt: sql<Date>`min(${pendingJobExecutions.createdAt})`,
274
+ })
275
+ .from(pendingJobExecutions)
276
+ .where(inArray(pendingJobExecutions.workspaceId, [...eligibleWorkspaceIds]))
277
+ .groupBy(pendingJobExecutions.workspaceId)
278
+ .orderBy(
279
+ asc(sql`min(${pendingJobExecutions.createdAt})`),
280
+ asc(pendingJobExecutions.workspaceId),
281
+ );
282
+ return rows.map((row) => row.workspaceId);
283
+ }
284
+
285
+ export async function listQueuedDemandWorkspaceIds(): Promise<string[]> {
286
+ const rows = await db()
287
+ .select({workspaceId: pendingJobExecutions.workspaceId})
288
+ .from(pendingJobExecutions)
289
+ .groupBy(pendingJobExecutions.workspaceId);
290
+ return rows.map((row) => row.workspaceId);
291
+ }
292
+
293
+ async function listActiveWorkspaceCapabilityLabelsTx(
294
+ tx: Tx,
295
+ params: {workspaceId: string; windowSeconds: number},
296
+ ): Promise<string[][]> {
297
+ const rows = await tx
298
+ .select({labels: provisionerCapabilitySnapshots.labels})
299
+ .from(provisionerCapabilitySnapshots)
300
+ .innerJoin(
301
+ provisionerTokens,
302
+ eq(provisionerTokens.id, provisionerCapabilitySnapshots.provisionerId),
303
+ )
304
+ .where(
305
+ and(
306
+ eq(provisionerCapabilitySnapshots.workspaceId, params.workspaceId),
307
+ eq(provisionerTokens.workspaceId, params.workspaceId),
308
+ eq(provisionerTokens.scope, 'workspace'),
309
+ sql`${provisionerCapabilitySnapshots.advertisedAt} > now() - (${params.windowSeconds} || ' seconds')::interval`,
310
+ sql`${provisionerTokens.revokedAt} is null`,
311
+ sql`(${provisionerTokens.expiresAt} is null or ${provisionerTokens.expiresAt} > now())`,
312
+ ),
313
+ );
314
+ return rows.map((row) => row.labels);
315
+ }
316
+
172
317
  export async function deleteExpiredReservations(params?: {limit?: number}): Promise<number> {
173
318
  const expiredIds = db()
174
319
  .select({id: reservations.id})
@@ -0,0 +1,163 @@
1
+ import {eq} from 'drizzle-orm';
2
+ import {
3
+ ReservationExpiredError,
4
+ RunnerInstanceAlreadyAssignedError,
5
+ RunnerInstanceNotAssignableError,
6
+ } from '#core/errors.js';
7
+ import {db} from '#db/db.js';
8
+ import {assignRunnerInstances} from '#db/runner-assignments.js';
9
+ import {reservations} from '#db/schema/reservations.js';
10
+ import {runnerControlSessions} from '#db/schema/runner-control-sessions.js';
11
+ import {providerRunners} from '#db/schema/runner-instances.js';
12
+
13
+ describe('assignRunnerInstances', () => {
14
+ let workspaceId: string;
15
+ let provisionerId: string;
16
+
17
+ beforeEach(() => {
18
+ workspaceId = crypto.randomUUID();
19
+ provisionerId = crypto.randomUUID();
20
+ });
21
+
22
+ it('writes the reservation and workspace after enrollment', async () => {
23
+ const reservation = await createReservation();
24
+ const runner = await createEnrolledRunner();
25
+
26
+ const assigned = await assignRunnerInstances({
27
+ provisionerId,
28
+ reservationId: reservation.id,
29
+ runnerInstanceIds: [runner.id],
30
+ });
31
+
32
+ expect(assigned).toEqual([runner.id]);
33
+ const [stored] = await db()
34
+ .select()
35
+ .from(providerRunners)
36
+ .where(eq(providerRunners.id, runner.id));
37
+ expect(stored).toMatchObject({
38
+ workspaceId,
39
+ reservationId: reservation.id,
40
+ assignedAt: expect.any(Date),
41
+ });
42
+ });
43
+
44
+ it('is idempotent for concurrent retries of the same assignment', async () => {
45
+ const reservation = await createReservation();
46
+ const runner = await createEnrolledRunner();
47
+
48
+ const results = await Promise.all([
49
+ assignRunnerInstances({
50
+ provisionerId,
51
+ reservationId: reservation.id,
52
+ runnerInstanceIds: [runner.id],
53
+ }),
54
+ assignRunnerInstances({
55
+ provisionerId,
56
+ reservationId: reservation.id,
57
+ runnerInstanceIds: [runner.id],
58
+ }),
59
+ ]);
60
+
61
+ expect(results).toEqual([[runner.id], [runner.id]]);
62
+ });
63
+
64
+ it('rejects expired reservations', async () => {
65
+ const reservation = await createReservation({expiresAt: new Date(Date.now() - 1_000)});
66
+ const runner = await createEnrolledRunner();
67
+
68
+ const assignment = assignRunnerInstances({
69
+ provisionerId,
70
+ reservationId: reservation.id,
71
+ runnerInstanceIds: [runner.id],
72
+ });
73
+
74
+ await expect(assignment).rejects.toThrow(ReservationExpiredError);
75
+ });
76
+
77
+ it('rejects unenrolled or incompatible runners', async () => {
78
+ const reservation = await createReservation({requiredLabels: ['linux', 'gpu']});
79
+ const runner = await createEnrolledRunner({labels: ['linux']});
80
+
81
+ const assignment = assignRunnerInstances({
82
+ provisionerId,
83
+ reservationId: reservation.id,
84
+ runnerInstanceIds: [runner.id],
85
+ });
86
+
87
+ await expect(assignment).rejects.toThrow(RunnerInstanceNotAssignableError);
88
+ });
89
+
90
+ it('rejects a runner assigned to a different reservation', async () => {
91
+ const firstReservation = await createReservation();
92
+ const secondReservation = await createReservation();
93
+ const runner = await createEnrolledRunner();
94
+ await assignRunnerInstances({
95
+ provisionerId,
96
+ reservationId: firstReservation.id,
97
+ runnerInstanceIds: [runner.id],
98
+ });
99
+
100
+ const assignment = assignRunnerInstances({
101
+ provisionerId,
102
+ reservationId: secondReservation.id,
103
+ runnerInstanceIds: [runner.id],
104
+ });
105
+
106
+ await expect(assignment).rejects.toThrow(RunnerInstanceAlreadyAssignedError);
107
+ });
108
+
109
+ it('rejects assignments that exceed reservation capacity', async () => {
110
+ const reservation = await createReservation();
111
+ const firstRunner = await createEnrolledRunner();
112
+ const secondRunner = await createEnrolledRunner();
113
+
114
+ const assignment = assignRunnerInstances({
115
+ provisionerId,
116
+ reservationId: reservation.id,
117
+ runnerInstanceIds: [firstRunner.id, secondRunner.id],
118
+ });
119
+
120
+ await expect(assignment).rejects.toThrow(RunnerInstanceNotAssignableError);
121
+ });
122
+
123
+ async function createReservation(
124
+ overrides: Partial<{expiresAt: Date; requiredLabels: string[]}> = {},
125
+ ) {
126
+ const [reservation] = await db()
127
+ .insert(reservations)
128
+ .values({
129
+ workspaceId,
130
+ provisionerId,
131
+ requiredLabels: overrides.requiredLabels ?? ['linux'],
132
+ count: 1,
133
+ expiresAt: overrides.expiresAt ?? new Date(Date.now() + 60_000),
134
+ })
135
+ .returning();
136
+ if (!reservation) throw new Error('Reservation insert returned no row');
137
+ return reservation;
138
+ }
139
+
140
+ async function createEnrolledRunner(overrides: Partial<{labels: string[]}> = {}) {
141
+ const [runner] = await db()
142
+ .insert(providerRunners)
143
+ .values({
144
+ provisionerId,
145
+ providerRunnerId: crypto.randomUUID(),
146
+ labels: overrides.labels ?? ['linux'],
147
+ state: 'running',
148
+ reportedAt: new Date(),
149
+ })
150
+ .returning();
151
+ if (!runner) throw new Error('Runner instance insert returned no row');
152
+ await db()
153
+ .insert(runnerControlSessions)
154
+ .values({
155
+ runnerInstanceId: runner.id,
156
+ provisionerId,
157
+ hashedToken: crypto.randomUUID(),
158
+ prefix: 'test',
159
+ expiresAt: new Date(Date.now() + 60_000),
160
+ });
161
+ return runner;
162
+ }
163
+ });
@@ -0,0 +1,115 @@
1
+ import {and, eq, inArray, isNull, sql} from 'drizzle-orm';
2
+ import {
3
+ ReservationExpiredError,
4
+ ReservationNotFoundError,
5
+ RunnerInstanceAlreadyAssignedError,
6
+ RunnerInstanceNotAssignableError,
7
+ } from '#core/errors.js';
8
+ import {db} from './db.js';
9
+ import {reservations} from './schema/reservations.js';
10
+ import {runnerControlSessions} from './schema/runner-control-sessions.js';
11
+ import {providerRunners} from './schema/runner-instances.js';
12
+
13
+ /** Atomically consumes reservation units by writing the immutable assignment on each runner. */
14
+ export async function assignRunnerInstances(params: {
15
+ provisionerId: string;
16
+ reservationId: string;
17
+ runnerInstanceIds: string[];
18
+ }): Promise<string[]> {
19
+ const runnerInstanceIds = [...params.runnerInstanceIds].sort();
20
+ return await db().transaction(async (tx) => {
21
+ await tx.execute(
22
+ sql`select pg_advisory_xact_lock(hashtext(${`runners_assignment:${params.provisionerId}:${params.reservationId}`}))`,
23
+ );
24
+ const [reservation] = await tx
25
+ .select()
26
+ .from(reservations)
27
+ .where(
28
+ and(
29
+ eq(reservations.id, params.reservationId),
30
+ eq(reservations.provisionerId, params.provisionerId),
31
+ ),
32
+ )
33
+ .limit(1)
34
+ .for('update');
35
+ if (!reservation) throw new ReservationNotFoundError(params.reservationId);
36
+ if (reservation.expiresAt <= new Date())
37
+ throw new ReservationExpiredError(params.reservationId);
38
+
39
+ const runnerRows = await tx
40
+ .select({
41
+ id: providerRunners.id,
42
+ reservationId: providerRunners.reservationId,
43
+ workspaceId: providerRunners.workspaceId,
44
+ providerRunnerId: providerRunners.providerRunnerId,
45
+ labels: providerRunners.labels,
46
+ state: providerRunners.state,
47
+ })
48
+ .from(providerRunners)
49
+ .where(
50
+ and(
51
+ eq(providerRunners.provisionerId, params.provisionerId),
52
+ inArray(providerRunners.id, runnerInstanceIds),
53
+ ),
54
+ )
55
+ .for('update');
56
+ const activeControlSessions = await tx
57
+ .select({runnerInstanceId: runnerControlSessions.runnerInstanceId})
58
+ .from(runnerControlSessions)
59
+ .where(
60
+ and(
61
+ inArray(
62
+ runnerControlSessions.runnerInstanceId,
63
+ runnerRows.map((runner) => runner.id),
64
+ ),
65
+ isNull(runnerControlSessions.closedAt),
66
+ ),
67
+ );
68
+ const runnerInstanceIdsWithControlSession = new Set(
69
+ activeControlSessions.map((session) => session.runnerInstanceId),
70
+ );
71
+ const runners = runnerRows.map((runner) => ({
72
+ ...runner,
73
+ controlSessionId: runnerInstanceIdsWithControlSession.has(runner.id) ? runner.id : null,
74
+ }));
75
+ if (runners.length !== runnerInstanceIds.length)
76
+ throw new RunnerInstanceNotAssignableError(runnerInstanceIds[0] ?? '');
77
+
78
+ const alreadyAssigned = runners.filter((runner) => runner.reservationId !== null);
79
+ if (alreadyAssigned.some((runner) => runner.reservationId !== reservation.id))
80
+ throw new RunnerInstanceAlreadyAssignedError(alreadyAssigned[0]?.id ?? '');
81
+ const newRunners = runners.filter((runner) => runner.reservationId === null);
82
+ const assignedCount = await tx
83
+ .select({count: sql<number>`count(*)::int`})
84
+ .from(providerRunners)
85
+ .where(eq(providerRunners.reservationId, reservation.id));
86
+ if ((assignedCount[0]?.count ?? 0) + newRunners.length > reservation.count)
87
+ throw new RunnerInstanceNotAssignableError(newRunners[0]?.id ?? '');
88
+ for (const runner of newRunners) {
89
+ if (
90
+ runner.state !== 'running' ||
91
+ !runner.providerRunnerId ||
92
+ !runner.controlSessionId ||
93
+ !reservation.requiredLabels.every((label) => runner.labels.includes(label))
94
+ )
95
+ throw new RunnerInstanceNotAssignableError(runner.id);
96
+ }
97
+ if (newRunners.length > 0) {
98
+ await tx
99
+ .update(providerRunners)
100
+ .set({
101
+ workspaceId: reservation.workspaceId,
102
+ reservationId: reservation.id,
103
+ assignedAt: sql`now()`,
104
+ updatedAt: sql`now()`,
105
+ })
106
+ .where(
107
+ inArray(
108
+ providerRunners.id,
109
+ newRunners.map((runner) => runner.id),
110
+ ),
111
+ );
112
+ }
113
+ return params.runnerInstanceIds;
114
+ });
115
+ }