@shipfox/api-runners 5.0.0 → 6.0.0

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 (414) hide show
  1. package/.turbo/turbo-build.log +6 -6
  2. package/CHANGELOG.md +61 -0
  3. package/dist/config.d.ts +5 -1
  4. package/dist/config.d.ts.map +1 -1
  5. package/dist/config.js +53 -6
  6. package/dist/config.js.map +1 -1
  7. package/dist/core/capacity-assignments.d.ts +7 -0
  8. package/dist/core/capacity-assignments.d.ts.map +1 -0
  9. package/dist/core/capacity-assignments.js +6 -0
  10. package/dist/core/capacity-assignments.js.map +1 -0
  11. package/dist/core/demand.d.ts +6 -6
  12. package/dist/core/demand.d.ts.map +1 -1
  13. package/dist/core/demand.js +10 -10
  14. package/dist/core/demand.js.map +1 -1
  15. package/dist/core/entities/capacity-assignment.d.ts +9 -0
  16. package/dist/core/entities/capacity-assignment.d.ts.map +1 -0
  17. package/dist/core/entities/capacity-assignment.js +3 -0
  18. package/dist/core/entities/capacity-assignment.js.map +1 -0
  19. package/dist/core/entities/ephemeral-registration-token.d.ts +1 -1
  20. package/dist/core/entities/ephemeral-registration-token.d.ts.map +1 -1
  21. package/dist/core/entities/ephemeral-registration-token.js.map +1 -1
  22. package/dist/core/entities/provisioner-capability-snapshot.d.ts +13 -0
  23. package/dist/core/entities/provisioner-capability-snapshot.d.ts.map +1 -0
  24. package/dist/core/entities/provisioner-capability-snapshot.js +3 -0
  25. package/dist/core/entities/provisioner-capability-snapshot.js.map +1 -0
  26. package/dist/core/entities/provisioner-token.d.ts +10 -2
  27. package/dist/core/entities/provisioner-token.d.ts.map +1 -1
  28. package/dist/core/entities/provisioner-token.js.map +1 -1
  29. package/dist/core/entities/{provisioned-runner.d.ts → runner-instance.d.ts} +6 -6
  30. package/dist/core/entities/runner-instance.d.ts.map +1 -0
  31. package/dist/core/entities/runner-instance.js +3 -0
  32. package/dist/core/entities/runner-instance.js.map +1 -0
  33. package/dist/core/entities/runner-session.d.ts +4 -2
  34. package/dist/core/entities/runner-session.d.ts.map +1 -1
  35. package/dist/core/entities/runner-session.js.map +1 -1
  36. package/dist/core/ephemeral-registration-tokens.d.ts +5 -5
  37. package/dist/core/ephemeral-registration-tokens.d.ts.map +1 -1
  38. package/dist/core/ephemeral-registration-tokens.js +12 -12
  39. package/dist/core/ephemeral-registration-tokens.js.map +1 -1
  40. package/dist/core/errors.d.ts +16 -4
  41. package/dist/core/errors.d.ts.map +1 -1
  42. package/dist/core/errors.js +22 -4
  43. package/dist/core/errors.js.map +1 -1
  44. package/dist/core/index.d.ts +10 -4
  45. package/dist/core/index.d.ts.map +1 -1
  46. package/dist/core/index.js +6 -2
  47. package/dist/core/index.js.map +1 -1
  48. package/dist/core/job-executions.d.ts +2 -0
  49. package/dist/core/job-executions.d.ts.map +1 -1
  50. package/dist/core/job-executions.js +1 -2
  51. package/dist/core/job-executions.js.map +1 -1
  52. package/dist/core/maintenance.d.ts +1 -1
  53. package/dist/core/maintenance.d.ts.map +1 -1
  54. package/dist/core/maintenance.js +5 -5
  55. package/dist/core/maintenance.js.map +1 -1
  56. package/dist/core/provisioner-capability-snapshots.d.ts +5 -0
  57. package/dist/core/provisioner-capability-snapshots.d.ts.map +1 -0
  58. package/dist/core/provisioner-capability-snapshots.js +10 -0
  59. package/dist/core/provisioner-capability-snapshots.js.map +1 -0
  60. package/dist/core/provisioner-tokens.d.ts +10 -0
  61. package/dist/core/provisioner-tokens.d.ts.map +1 -1
  62. package/dist/core/provisioner-tokens.js +23 -1
  63. package/dist/core/provisioner-tokens.js.map +1 -1
  64. package/dist/core/rate-limit.d.ts.map +1 -1
  65. package/dist/core/rate-limit.js +2 -7
  66. package/dist/core/rate-limit.js.map +1 -1
  67. package/dist/core/runner-activation.d.ts +13 -0
  68. package/dist/core/runner-activation.d.ts.map +1 -0
  69. package/dist/core/runner-activation.js +36 -0
  70. package/dist/core/runner-activation.js.map +1 -0
  71. package/dist/core/runner-control-sessions.d.ts +46 -0
  72. package/dist/core/runner-control-sessions.d.ts.map +1 -0
  73. package/dist/core/runner-control-sessions.js +121 -0
  74. package/dist/core/runner-control-sessions.js.map +1 -0
  75. package/dist/core/runner-instances.d.ts +79 -0
  76. package/dist/core/runner-instances.d.ts.map +1 -0
  77. package/dist/core/runner-instances.js +168 -0
  78. package/dist/core/runner-instances.js.map +1 -0
  79. package/dist/core/runner-sessions.d.ts +8 -2
  80. package/dist/core/runner-sessions.d.ts.map +1 -1
  81. package/dist/core/runner-sessions.js +8 -5
  82. package/dist/core/runner-sessions.js.map +1 -1
  83. package/dist/db/capacity-assignments.d.ts +7 -0
  84. package/dist/db/capacity-assignments.d.ts.map +1 -0
  85. package/dist/db/capacity-assignments.js +72 -0
  86. package/dist/db/capacity-assignments.js.map +1 -0
  87. package/dist/db/db.d.ts +2837 -1009
  88. package/dist/db/db.d.ts.map +1 -1
  89. package/dist/db/db.js +11 -2
  90. package/dist/db/db.js.map +1 -1
  91. package/dist/db/ephemeral-registration-tokens.d.ts +2 -2
  92. package/dist/db/ephemeral-registration-tokens.d.ts.map +1 -1
  93. package/dist/db/ephemeral-registration-tokens.js +21 -21
  94. package/dist/db/ephemeral-registration-tokens.js.map +1 -1
  95. package/dist/db/index.d.ts +7 -4
  96. package/dist/db/index.d.ts.map +1 -1
  97. package/dist/db/index.js +5 -2
  98. package/dist/db/index.js.map +1 -1
  99. package/dist/db/job-executions.d.ts +6 -6
  100. package/dist/db/job-executions.d.ts.map +1 -1
  101. package/dist/db/job-executions.js +20 -19
  102. package/dist/db/job-executions.js.map +1 -1
  103. package/dist/db/provisioner-capability-snapshots.d.ts +21 -0
  104. package/dist/db/provisioner-capability-snapshots.d.ts.map +1 -0
  105. package/dist/db/provisioner-capability-snapshots.js +57 -0
  106. package/dist/db/provisioner-capability-snapshots.js.map +1 -0
  107. package/dist/db/provisioner-tokens.d.ts +7 -2
  108. package/dist/db/provisioner-tokens.d.ts.map +1 -1
  109. package/dist/db/provisioner-tokens.js +59 -10
  110. package/dist/db/provisioner-tokens.js.map +1 -1
  111. package/dist/db/reservations.d.ts +18 -0
  112. package/dist/db/reservations.d.ts.map +1 -1
  113. package/dist/db/reservations.js +88 -1
  114. package/dist/db/reservations.js.map +1 -1
  115. package/dist/db/runner-instances.d.ts +91 -0
  116. package/dist/db/runner-instances.d.ts.map +1 -0
  117. package/dist/db/runner-instances.js +511 -0
  118. package/dist/db/runner-instances.js.map +1 -0
  119. package/dist/db/runner-sessions.d.ts +5 -0
  120. package/dist/db/runner-sessions.d.ts.map +1 -1
  121. package/dist/db/runner-sessions.js +48 -2
  122. package/dist/db/runner-sessions.js.map +1 -1
  123. package/dist/db/schema/capacity-assignments.d.ts +113 -0
  124. package/dist/db/schema/capacity-assignments.d.ts.map +1 -0
  125. package/dist/db/schema/capacity-assignments.js +29 -0
  126. package/dist/db/schema/capacity-assignments.js.map +1 -0
  127. package/dist/db/schema/ephemeral-registration-tokens.d.ts +2 -2
  128. package/dist/db/schema/ephemeral-registration-tokens.js +3 -3
  129. package/dist/db/schema/ephemeral-registration-tokens.js.map +1 -1
  130. package/dist/db/schema/provisioner-capability-snapshots.d.ts +207 -0
  131. package/dist/db/schema/provisioner-capability-snapshots.d.ts.map +1 -0
  132. package/dist/db/schema/provisioner-capability-snapshots.js +38 -0
  133. package/dist/db/schema/provisioner-capability-snapshots.js.map +1 -0
  134. package/dist/db/schema/provisioner-tokens.d.ts +19 -1
  135. package/dist/db/schema/provisioner-tokens.d.ts.map +1 -1
  136. package/dist/db/schema/provisioner-tokens.js +29 -3
  137. package/dist/db/schema/provisioner-tokens.js.map +1 -1
  138. package/dist/db/schema/runner-activation-tokens.d.ts +161 -0
  139. package/dist/db/schema/runner-activation-tokens.d.ts.map +1 -0
  140. package/dist/db/schema/runner-activation-tokens.js +27 -0
  141. package/dist/db/schema/runner-activation-tokens.js.map +1 -0
  142. package/dist/db/schema/runner-control-sessions.d.ts +338 -0
  143. package/dist/db/schema/runner-control-sessions.d.ts.map +1 -0
  144. package/dist/db/schema/runner-control-sessions.js +52 -0
  145. package/dist/db/schema/runner-control-sessions.js.map +1 -0
  146. package/dist/db/schema/{provisioned-runners.d.ts → runner-instances.d.ts} +88 -34
  147. package/dist/db/schema/runner-instances.d.ts.map +1 -0
  148. package/dist/db/schema/{provisioned-runners.js → runner-instances.js} +19 -14
  149. package/dist/db/schema/runner-instances.js.map +1 -0
  150. package/dist/db/schema/runner-sessions.d.ts +39 -5
  151. package/dist/db/schema/runner-sessions.d.ts.map +1 -1
  152. package/dist/db/schema/runner-sessions.js +13 -6
  153. package/dist/db/schema/runner-sessions.js.map +1 -1
  154. package/dist/db/schema/running-job-executions.d.ts +2 -2
  155. package/dist/db/schema/running-job-executions.js +4 -4
  156. package/dist/db/schema/running-job-executions.js.map +1 -1
  157. package/dist/index.d.ts +7 -2
  158. package/dist/index.d.ts.map +1 -1
  159. package/dist/index.js +45 -38
  160. package/dist/index.js.map +1 -1
  161. package/dist/installation-provisioning.d.ts +14 -0
  162. package/dist/installation-provisioning.d.ts.map +1 -0
  163. package/dist/installation-provisioning.js +7 -0
  164. package/dist/installation-provisioning.js.map +1 -0
  165. package/dist/metrics/index.d.ts +1 -1
  166. package/dist/metrics/index.d.ts.map +1 -1
  167. package/dist/metrics/index.js +1 -1
  168. package/dist/metrics/index.js.map +1 -1
  169. package/dist/metrics/instance.d.ts +11 -7
  170. package/dist/metrics/instance.d.ts.map +1 -1
  171. package/dist/metrics/instance.js +13 -7
  172. package/dist/metrics/instance.js.map +1 -1
  173. package/dist/presentation/auth/index.d.ts +1 -0
  174. package/dist/presentation/auth/index.d.ts.map +1 -1
  175. package/dist/presentation/auth/index.js +1 -0
  176. package/dist/presentation/auth/index.js.map +1 -1
  177. package/dist/presentation/auth/provisioner-token-auth.d.ts.map +1 -1
  178. package/dist/presentation/auth/provisioner-token-auth.js +5 -1
  179. package/dist/presentation/auth/provisioner-token-auth.js.map +1 -1
  180. package/dist/presentation/auth/runner-control-session-auth.d.ts +4 -0
  181. package/dist/presentation/auth/runner-control-session-auth.d.ts.map +1 -0
  182. package/dist/presentation/auth/runner-control-session-auth.js +29 -0
  183. package/dist/presentation/auth/runner-control-session-auth.js.map +1 -0
  184. package/dist/presentation/auth/runner-registration-token-auth.d.ts +5 -1
  185. package/dist/presentation/auth/runner-registration-token-auth.d.ts.map +1 -1
  186. package/dist/presentation/auth/runner-registration-token-auth.js +21 -1
  187. package/dist/presentation/auth/runner-registration-token-auth.js.map +1 -1
  188. package/dist/presentation/dto/index.d.ts +2 -2
  189. package/dist/presentation/dto/index.d.ts.map +1 -1
  190. package/dist/presentation/dto/index.js +2 -2
  191. package/dist/presentation/dto/index.js.map +1 -1
  192. package/dist/presentation/dto/mint-registration-tokens.d.ts +5 -3
  193. package/dist/presentation/dto/mint-registration-tokens.d.ts.map +1 -1
  194. package/dist/presentation/dto/mint-registration-tokens.js +4 -4
  195. package/dist/presentation/dto/mint-registration-tokens.js.map +1 -1
  196. package/dist/presentation/dto/poll-demand.d.ts.map +1 -1
  197. package/dist/presentation/dto/poll-demand.js +7 -1
  198. package/dist/presentation/dto/poll-demand.js.map +1 -1
  199. package/dist/presentation/dto/provisioner-token.d.ts +1 -0
  200. package/dist/presentation/dto/provisioner-token.d.ts.map +1 -1
  201. package/dist/presentation/dto/provisioner-token.js +5 -1
  202. package/dist/presentation/dto/provisioner-token.js.map +1 -1
  203. package/dist/presentation/dto/{provisioned-runners.d.ts → runner-instances.d.ts} +9 -9
  204. package/dist/presentation/dto/runner-instances.d.ts.map +1 -0
  205. package/dist/presentation/dto/{provisioned-runners.js → runner-instances.js} +6 -6
  206. package/dist/presentation/dto/runner-instances.js.map +1 -0
  207. package/dist/presentation/index.d.ts +2 -2
  208. package/dist/presentation/index.d.ts.map +1 -1
  209. package/dist/presentation/index.js +2 -2
  210. package/dist/presentation/index.js.map +1 -1
  211. package/dist/presentation/inter-module.d.ts +5 -0
  212. package/dist/presentation/inter-module.d.ts.map +1 -0
  213. package/dist/presentation/inter-module.js +43 -0
  214. package/dist/presentation/inter-module.js.map +1 -0
  215. package/dist/presentation/routes/assign-capacity.d.ts +2 -0
  216. package/dist/presentation/routes/assign-capacity.d.ts.map +1 -0
  217. package/dist/presentation/routes/assign-capacity.js +44 -0
  218. package/dist/presentation/routes/assign-capacity.js.map +1 -0
  219. package/dist/presentation/routes/get-provisioner-me.d.ts.map +1 -1
  220. package/dist/presentation/routes/get-provisioner-me.js +5 -1
  221. package/dist/presentation/routes/get-provisioner-me.js.map +1 -1
  222. package/dist/presentation/routes/heartbeat.d.ts +2 -1
  223. package/dist/presentation/routes/heartbeat.d.ts.map +1 -1
  224. package/dist/presentation/routes/heartbeat.js +60 -50
  225. package/dist/presentation/routes/heartbeat.js.map +1 -1
  226. package/dist/presentation/routes/index.d.ts +3 -1
  227. package/dist/presentation/routes/index.d.ts.map +1 -1
  228. package/dist/presentation/routes/index.js +83 -50
  229. package/dist/presentation/routes/index.js.map +1 -1
  230. package/dist/presentation/routes/mint-registration-tokens.d.ts.map +1 -1
  231. package/dist/presentation/routes/mint-registration-tokens.js +5 -5
  232. package/dist/presentation/routes/mint-registration-tokens.js.map +1 -1
  233. package/dist/presentation/routes/planned-capacity.d.ts +3 -0
  234. package/dist/presentation/routes/planned-capacity.d.ts.map +1 -0
  235. package/dist/presentation/routes/planned-capacity.js +54 -0
  236. package/dist/presentation/routes/planned-capacity.js.map +1 -0
  237. package/dist/presentation/routes/poll-demand.d.ts +2 -0
  238. package/dist/presentation/routes/poll-demand.d.ts.map +1 -1
  239. package/dist/presentation/routes/poll-demand.js +86 -45
  240. package/dist/presentation/routes/poll-demand.js.map +1 -1
  241. package/dist/presentation/routes/reconcile-runner-instances.d.ts +2 -0
  242. package/dist/presentation/routes/reconcile-runner-instances.d.ts.map +1 -0
  243. package/dist/presentation/routes/reconcile-runner-instances.js +27 -0
  244. package/dist/presentation/routes/reconcile-runner-instances.js.map +1 -0
  245. package/dist/presentation/routes/register.d.ts +2 -1
  246. package/dist/presentation/routes/register.d.ts.map +1 -1
  247. package/dist/presentation/routes/register.js +55 -52
  248. package/dist/presentation/routes/register.js.map +1 -1
  249. package/dist/presentation/routes/report-runner-instances.d.ts +2 -0
  250. package/dist/presentation/routes/report-runner-instances.d.ts.map +1 -0
  251. package/dist/presentation/routes/report-runner-instances.js +37 -0
  252. package/dist/presentation/routes/report-runner-instances.js.map +1 -0
  253. package/dist/presentation/routes/request-job.d.ts +2 -1
  254. package/dist/presentation/routes/request-job.d.ts.map +1 -1
  255. package/dist/presentation/routes/request-job.js +39 -36
  256. package/dist/presentation/routes/request-job.js.map +1 -1
  257. package/dist/presentation/routes/runner-enrollment.d.ts +7 -0
  258. package/dist/presentation/routes/runner-enrollment.d.ts.map +1 -0
  259. package/dist/presentation/routes/runner-enrollment.js +185 -0
  260. package/dist/presentation/routes/runner-enrollment.js.map +1 -0
  261. package/dist/temporal/activities/index.d.ts +2 -2
  262. package/dist/temporal/activities/index.d.ts.map +1 -1
  263. package/dist/temporal/activities/index.js +2 -2
  264. package/dist/temporal/activities/index.js.map +1 -1
  265. package/dist/temporal/activities/maintenance-activities.d.ts +1 -1
  266. package/dist/temporal/activities/maintenance-activities.d.ts.map +1 -1
  267. package/dist/temporal/activities/maintenance-activities.js +3 -3
  268. package/dist/temporal/activities/maintenance-activities.js.map +1 -1
  269. package/dist/temporal/workflows/index.bundle.js +3 -3
  270. package/dist/temporal/workflows/stuck-job-detector.js +2 -2
  271. package/dist/temporal/workflows/stuck-job-detector.js.map +1 -1
  272. package/dist/tsconfig.test.tsbuildinfo +1 -1
  273. package/drizzle/0000_initial.sql +102 -24
  274. package/drizzle/meta/0000_snapshot.json +483 -63
  275. package/package.json +21 -16
  276. package/src/config.test.ts +0 -26
  277. package/src/config.ts +47 -6
  278. package/src/core/capacity-assignments.ts +10 -0
  279. package/src/core/demand.test.ts +29 -29
  280. package/src/core/demand.ts +22 -22
  281. package/src/core/entities/capacity-assignment.ts +8 -0
  282. package/src/core/entities/ephemeral-registration-token.ts +1 -1
  283. package/src/core/entities/provisioner-capability-snapshot.ts +12 -0
  284. package/src/core/entities/provisioner-token.ts +8 -2
  285. package/src/core/entities/{provisioned-runner.ts → runner-instance.ts} +5 -5
  286. package/src/core/entities/runner-session.ts +4 -2
  287. package/src/core/ephemeral-registration-tokens.test.ts +11 -11
  288. package/src/core/ephemeral-registration-tokens.ts +18 -20
  289. package/src/core/errors.ts +23 -4
  290. package/src/core/index.ts +39 -17
  291. package/src/core/job-executions.ts +3 -2
  292. package/src/core/maintenance.test.ts +13 -17
  293. package/src/core/maintenance.ts +5 -5
  294. package/src/core/provisioner-capability-snapshots.test.ts +35 -0
  295. package/src/core/provisioner-capability-snapshots.ts +12 -0
  296. package/src/core/provisioner-tokens.test.ts +20 -0
  297. package/src/core/provisioner-tokens.ts +33 -0
  298. package/src/core/rate-limit.test.ts +11 -27
  299. package/src/core/rate-limit.ts +3 -11
  300. package/src/core/runner-activation.ts +72 -0
  301. package/src/core/runner-control-sessions.ts +190 -0
  302. package/src/core/{provisioned-runners.test.ts → runner-instances.test.ts} +24 -24
  303. package/src/core/runner-instances.ts +310 -0
  304. package/src/core/runner-sessions.test.ts +112 -22
  305. package/src/core/runner-sessions.ts +25 -14
  306. package/src/db/capacity-assignments.test.ts +159 -0
  307. package/src/db/capacity-assignments.ts +116 -0
  308. package/src/db/db.ts +11 -2
  309. package/src/db/ephemeral-registration-tokens.test.ts +9 -9
  310. package/src/db/ephemeral-registration-tokens.ts +26 -26
  311. package/src/db/index.ts +31 -21
  312. package/src/db/job-executions.test.ts +13 -11
  313. package/src/db/job-executions.ts +33 -27
  314. package/src/db/provisioner-capability-snapshots.test.ts +175 -0
  315. package/src/db/provisioner-capability-snapshots.ts +130 -0
  316. package/src/db/provisioner-tokens.test.ts +139 -2
  317. package/src/db/provisioner-tokens.ts +123 -15
  318. package/src/db/reservations.test.ts +49 -0
  319. package/src/db/reservations.ts +146 -1
  320. package/src/db/{provisioned-runners.test.ts → runner-instances.test.ts} +429 -353
  321. package/src/db/runner-instances.ts +942 -0
  322. package/src/db/runner-sessions.test.ts +19 -6
  323. package/src/db/runner-sessions.ts +78 -2
  324. package/src/db/schema/capacity-assignments.ts +34 -0
  325. package/src/db/schema/ephemeral-registration-tokens.ts +4 -4
  326. package/src/db/schema/provisioner-capability-snapshots.ts +46 -0
  327. package/src/db/schema/provisioner-tokens.ts +33 -2
  328. package/src/db/schema/runner-activation-tokens.ts +22 -0
  329. package/src/db/schema/runner-control-sessions.ts +46 -0
  330. package/src/db/schema/{provisioned-runners.ts → runner-instances.ts} +26 -27
  331. package/src/db/schema/runner-sessions.ts +11 -7
  332. package/src/db/schema/running-job-executions.ts +5 -5
  333. package/src/index.test.ts +26 -0
  334. package/src/index.ts +39 -19
  335. package/src/installation-provisioning.ts +14 -0
  336. package/src/metrics/index.ts +5 -5
  337. package/src/metrics/instance.ts +25 -14
  338. package/src/presentation/auth/index.ts +4 -0
  339. package/src/presentation/auth/provisioner-token-auth.ts +8 -4
  340. package/src/presentation/auth/runner-control-session-auth.ts +31 -0
  341. package/src/presentation/auth/runner-registration-token-auth.ts +39 -3
  342. package/src/presentation/dto/index.ts +5 -5
  343. package/src/presentation/dto/mint-registration-tokens.ts +8 -15
  344. package/src/presentation/dto/poll-demand.ts +3 -1
  345. package/src/presentation/dto/provisioner-token.ts +5 -0
  346. package/src/presentation/dto/{provisioned-runners.ts → runner-instances.ts} +13 -15
  347. package/src/presentation/index.ts +2 -1
  348. package/src/presentation/inter-module.test.ts +15 -0
  349. package/src/presentation/inter-module.ts +53 -0
  350. package/src/presentation/routes/assign-capacity.ts +48 -0
  351. package/src/presentation/routes/get-provisioner-me.ts +3 -4
  352. package/src/presentation/routes/heartbeat.test.ts +4 -3
  353. package/src/presentation/routes/heartbeat.ts +67 -64
  354. package/src/presentation/routes/index.ts +93 -43
  355. package/src/presentation/routes/list-active-runners.test.ts +23 -23
  356. package/src/presentation/routes/manual-registration-tokens.test.ts +4 -3
  357. package/src/presentation/routes/mint-registration-tokens.test.ts +12 -12
  358. package/src/presentation/routes/mint-registration-tokens.ts +5 -7
  359. package/src/presentation/routes/planned-capacity.ts +49 -0
  360. package/src/presentation/routes/poll-demand-release.test.ts +4 -4
  361. package/src/presentation/routes/poll-demand.test.ts +161 -22
  362. package/src/presentation/routes/poll-demand.ts +105 -45
  363. package/src/presentation/routes/provisioner-me.test.ts +37 -3
  364. package/src/presentation/routes/{reconcile-provisioned-runners.test.ts → reconcile-runner-instances.test.ts} +66 -61
  365. package/src/presentation/routes/reconcile-runner-instances.ts +30 -0
  366. package/src/presentation/routes/register.test.ts +9 -5
  367. package/src/presentation/routes/register.ts +61 -57
  368. package/src/presentation/routes/{report-provisioned-runners.test.ts → report-runner-instances.test.ts} +28 -28
  369. package/src/presentation/routes/{report-provisioned-runners.ts → report-runner-instances.ts} +14 -14
  370. package/src/presentation/routes/request-job.test.ts +5 -3
  371. package/src/presentation/routes/request-job.ts +41 -37
  372. package/src/presentation/routes/runner-enrollment.test.ts +259 -0
  373. package/src/presentation/routes/runner-enrollment.ts +174 -0
  374. package/src/temporal/activities/index.ts +2 -2
  375. package/src/temporal/activities/maintenance-activities.test.ts +7 -7
  376. package/src/temporal/activities/maintenance-activities.ts +3 -3
  377. package/src/temporal/workflows/stuck-job-detector.test.ts +3 -3
  378. package/src/temporal/workflows/stuck-job-detector.ts +2 -2
  379. package/test/env.ts +1 -3
  380. package/test/factories/ephemeral-registration-token.ts +2 -2
  381. package/test/factories/provisioner-token.ts +3 -1
  382. package/test/factories/runner-instance.ts +57 -0
  383. package/test/factories/runner-session.ts +3 -1
  384. package/test/fixtures/auth-inter-module.ts +30 -0
  385. package/test/globalSetup.ts +5 -1
  386. package/test/index.ts +2 -1
  387. package/tsconfig.build.tsbuildinfo +1 -1
  388. package/dist/core/entities/provisioned-runner.d.ts.map +0 -1
  389. package/dist/core/entities/provisioned-runner.js +0 -3
  390. package/dist/core/entities/provisioned-runner.js.map +0 -1
  391. package/dist/core/provisioned-runners.d.ts +0 -67
  392. package/dist/core/provisioned-runners.d.ts.map +0 -1
  393. package/dist/core/provisioned-runners.js +0 -162
  394. package/dist/core/provisioned-runners.js.map +0 -1
  395. package/dist/db/provisioned-runners.d.ts +0 -79
  396. package/dist/db/provisioned-runners.d.ts.map +0 -1
  397. package/dist/db/provisioned-runners.js +0 -466
  398. package/dist/db/provisioned-runners.js.map +0 -1
  399. package/dist/db/schema/provisioned-runners.d.ts.map +0 -1
  400. package/dist/db/schema/provisioned-runners.js.map +0 -1
  401. package/dist/presentation/dto/provisioned-runners.d.ts.map +0 -1
  402. package/dist/presentation/dto/provisioned-runners.js.map +0 -1
  403. package/dist/presentation/routes/reconcile-provisioned-runners.d.ts +0 -2
  404. package/dist/presentation/routes/reconcile-provisioned-runners.d.ts.map +0 -1
  405. package/dist/presentation/routes/reconcile-provisioned-runners.js +0 -27
  406. package/dist/presentation/routes/reconcile-provisioned-runners.js.map +0 -1
  407. package/dist/presentation/routes/report-provisioned-runners.d.ts +0 -2
  408. package/dist/presentation/routes/report-provisioned-runners.d.ts.map +0 -1
  409. package/dist/presentation/routes/report-provisioned-runners.js +0 -37
  410. package/dist/presentation/routes/report-provisioned-runners.js.map +0 -1
  411. package/src/core/provisioned-runners.ts +0 -301
  412. package/src/db/provisioned-runners.ts +0 -886
  413. package/src/presentation/routes/reconcile-provisioned-runners.ts +0 -30
  414. package/test/factories/provisioned-runner.ts +0 -78
@@ -8,7 +8,7 @@ import {eq, sql} from 'drizzle-orm';
8
8
  import {EmptyRequiredLabelsError, RunnerSessionExhaustedError} from '#core/errors.js';
9
9
  import {claimJobExecution} from '#core/job-executions.js';
10
10
  import {detectAndExpireStuckJobs} from '#core/maintenance.js';
11
- import {pendingJobFactory, runnerSessionFactory} from '#test/index.js';
11
+ import {pendingJobFactory, runnerSessionFactory, runnersTestAuthClient} from '#test/index.js';
12
12
  import {db} from './db.js';
13
13
  import {
14
14
  cancelRunnerJobs,
@@ -385,10 +385,10 @@ describe('claimPendingJobExecution', () => {
385
385
 
386
386
  it('enforces a non-null session claim cap from the database', async () => {
387
387
  const provisionerId = crypto.randomUUID();
388
- const provisionedRunnerId = `provisioned-runner-${crypto.randomUUID()}`;
388
+ const providerRunnerId = `provisioned-runner-${crypto.randomUUID()}`;
389
389
  await db()
390
390
  .update(runnerSessions)
391
- .set({registrationTokenKind: 'ephemeral', maxClaims: 1, provisionerId, provisionedRunnerId})
391
+ .set({registrationTokenKind: 'ephemeral', maxClaims: 1, provisionerId, providerRunnerId})
392
392
  .where(eq(runnerSessions.id, runnerSessionId));
393
393
  await pendingJobFactory.create({workspaceId});
394
394
  await pendingJobFactory.create({workspaceId});
@@ -403,10 +403,10 @@ describe('claimPendingJobExecution', () => {
403
403
 
404
404
  it('does not spend a claim when a capped session polls an empty queue', async () => {
405
405
  const provisionerId = crypto.randomUUID();
406
- const provisionedRunnerId = `provisioned-runner-${crypto.randomUUID()}`;
406
+ const providerRunnerId = `provisioned-runner-${crypto.randomUUID()}`;
407
407
  await db()
408
408
  .update(runnerSessions)
409
- .set({registrationTokenKind: 'ephemeral', maxClaims: 1, provisionerId, provisionedRunnerId})
409
+ .set({registrationTokenKind: 'ephemeral', maxClaims: 1, provisionerId, providerRunnerId})
410
410
  .where(eq(runnerSessions.id, runnerSessionId));
411
411
 
412
412
  const empty = await claimPendingJobExecution({workspaceId, runnerSessionId, maxClaims: 1});
@@ -494,15 +494,15 @@ describe('claimPendingJobExecution', () => {
494
494
  expect(running[0]?.requiredLabels).toEqual(created.requiredLabels);
495
495
  expect(running[0]?.runnerLabels).toEqual(sessionLabels);
496
496
  expect(running[0]?.provisionerId).toBeNull();
497
- expect(running[0]?.provisionedRunnerId).toBeNull();
497
+ expect(running[0]?.providerRunnerId).toBeNull();
498
498
  });
499
499
 
500
500
  it('copies an ephemeral session provisioned-runner link onto the running job', async () => {
501
501
  const provisionerId = crypto.randomUUID();
502
- const provisionedRunnerId = `provisioned-runner-${crypto.randomUUID()}`;
502
+ const providerRunnerId = `provisioned-runner-${crypto.randomUUID()}`;
503
503
  await db()
504
504
  .update(runnerSessions)
505
- .set({registrationTokenKind: 'ephemeral', maxClaims: 1, provisionerId, provisionedRunnerId})
505
+ .set({registrationTokenKind: 'ephemeral', maxClaims: 1, provisionerId, providerRunnerId})
506
506
  .where(eq(runnerSessions.id, runnerSessionId));
507
507
  const created = await pendingJobFactory.create({workspaceId});
508
508
 
@@ -513,7 +513,7 @@ describe('claimPendingJobExecution', () => {
513
513
  .from(runningJobExecutions)
514
514
  .where(eq(runningJobExecutions.jobId, created.jobId));
515
515
  expect(running?.provisionerId).toBe(provisionerId);
516
- expect(running?.provisionedRunnerId).toBe(provisionedRunnerId);
516
+ expect(running?.providerRunnerId).toBe(providerRunnerId);
517
517
  });
518
518
 
519
519
  it('rejects a running job row with a partial provisioned-runner link', async () => {
@@ -563,7 +563,7 @@ describe('claimPendingJobExecution', () => {
563
563
  labels: sessionLabels,
564
564
  toolCapabilities: {harnesses: {pi: {tools: ['read']}}},
565
565
  });
566
- const underProvisionedRunner = await runnerSessionFactory.create({
566
+ const underRunnerInstance = await runnerSessionFactory.create({
567
567
  workspaceId,
568
568
  labels: sessionLabels,
569
569
  toolCapabilities: {harnesses: {pi: {tools: []}}},
@@ -579,7 +579,7 @@ describe('claimPendingJobExecution', () => {
579
579
 
580
580
  const firstClaim = await claimPendingJobExecution({
581
581
  workspaceId,
582
- runnerSessionId: underProvisionedRunner.id,
582
+ runnerSessionId: underRunnerInstance.id,
583
583
  });
584
584
  const secondClaim = await claimPendingJobExecution({
585
585
  workspaceId,
@@ -758,6 +758,7 @@ describe('claimJobExecution', () => {
758
758
  const created = await pendingJobFactory.create({workspaceId});
759
759
 
760
760
  const claimed = await claimJobExecution({
761
+ auth: runnersTestAuthClient,
761
762
  workspaceId,
762
763
  runnerSessionId,
763
764
  sessionLabels,
@@ -781,6 +782,7 @@ describe('claimJobExecution', () => {
781
782
 
782
783
  it('returns null and mints no token when the queue is empty', async () => {
783
784
  const claimed = await claimJobExecution({
785
+ auth: runnersTestAuthClient,
784
786
  workspaceId,
785
787
  runnerSessionId,
786
788
  sessionLabels,
@@ -110,19 +110,19 @@ export interface ActiveRunningJobExecution {
110
110
  projectId: string;
111
111
  runnerSessionId: string;
112
112
  provisionerId: string | null;
113
- provisionedRunnerId: string | null;
113
+ providerRunnerId: string | null;
114
114
  requiredLabels: string[];
115
115
  runnerLabels: string[];
116
116
  startedAt: Date;
117
117
  lastHeartbeatAt: Date;
118
118
  }
119
119
 
120
- export interface ProvisionedRunnerBoundJobExecution {
120
+ export interface RunnerInstanceBoundJobExecution {
121
121
  workflowRunId: string;
122
122
  workflowRunAttemptId: string;
123
123
  jobId: string;
124
124
  jobExecutionId: string;
125
- provisionedRunnerId: string;
125
+ providerRunnerId: string;
126
126
  startedAt: Date;
127
127
  lastHeartbeatAt: Date;
128
128
  cancellationRequestedAt: Date | null;
@@ -145,29 +145,35 @@ export async function claimPendingJobExecution(params: {
145
145
 
146
146
  return await db().transaction(async (tx) => {
147
147
  let provisionerId: string | null = null;
148
- let provisionedRunnerId: string | null = null;
148
+ let providerRunnerId: string | null = null;
149
149
 
150
150
  if (params.maxClaims !== null) {
151
151
  const [session] = await tx
152
152
  .select({
153
153
  maxClaims: runnerSessions.maxClaims,
154
154
  claimsUsed: runnerSessions.claimsUsed,
155
+ revokedAt: runnerSessions.revokedAt,
155
156
  provisionerId: runnerSessions.provisionerId,
156
- provisionedRunnerId: runnerSessions.provisionedRunnerId,
157
+ providerRunnerId: runnerSessions.providerRunnerId,
157
158
  })
158
159
  .from(runnerSessions)
159
160
  .where(eq(runnerSessions.id, params.runnerSessionId))
160
161
  .limit(1)
161
162
  .for('update');
162
163
 
163
- if (!session || session.maxClaims === null || session.claimsUsed >= session.maxClaims) {
164
+ if (
165
+ !session ||
166
+ session.revokedAt ||
167
+ session.maxClaims === null ||
168
+ session.claimsUsed >= session.maxClaims
169
+ ) {
164
170
  throw new RunnerSessionExhaustedError(params.runnerSessionId);
165
171
  }
166
172
 
167
173
  // Ephemeral sessions are the only capped sessions, and the DB check keeps
168
174
  // their provisioned-runner link present as a pair.
169
175
  provisionerId = session.provisionerId;
170
- provisionedRunnerId = session.provisionedRunnerId;
176
+ providerRunnerId = session.providerRunnerId;
171
177
  }
172
178
 
173
179
  // `id` is a uuidv7 (time-ordered), so it is a deterministic FIFO tiebreaker
@@ -206,7 +212,7 @@ export async function claimPendingJobExecution(params: {
206
212
  projectId: row.projectId,
207
213
  runnerSessionId: params.runnerSessionId,
208
214
  provisionerId,
209
- provisionedRunnerId,
215
+ providerRunnerId,
210
216
  requiredLabels: row.requiredLabels,
211
217
  runnerLabels: params.sessionLabels,
212
218
  })
@@ -409,7 +415,7 @@ export async function listActiveRunningJobExecutions(params: {
409
415
  projectId: runningJobExecutions.projectId,
410
416
  runnerSessionId: runningJobExecutions.runnerSessionId,
411
417
  provisionerId: runningJobExecutions.provisionerId,
412
- provisionedRunnerId: runningJobExecutions.provisionedRunnerId,
418
+ providerRunnerId: runningJobExecutions.providerRunnerId,
413
419
  requiredLabels: runningJobExecutions.requiredLabels,
414
420
  runnerLabels: runningJobExecutions.runnerLabels,
415
421
  startedAt: runningJobExecutions.startedAt,
@@ -426,19 +432,19 @@ export async function listActiveRunningJobExecutions(params: {
426
432
  .limit(params.limit ?? 1000);
427
433
  }
428
434
 
429
- export async function listRunningJobExecutionsByProvisionedRunnerTx(
435
+ export async function listRunningJobExecutionsByRunnerInstanceTx(
430
436
  tx: Tx,
431
437
  params: {
432
438
  workspaceId: string;
433
439
  provisionerId: string;
434
- provisionedRunnerIds: string[];
440
+ providerRunnerIds: string[];
435
441
  },
436
- ): Promise<ProvisionedRunnerBoundJobExecution[]> {
437
- if (params.provisionedRunnerIds.length === 0) return [];
442
+ ): Promise<RunnerInstanceBoundJobExecution[]> {
443
+ if (params.providerRunnerIds.length === 0) return [];
438
444
 
439
445
  const duplicateRows = await tx
440
446
  .select({
441
- provisionedRunnerId: runningJobExecutions.provisionedRunnerId,
447
+ providerRunnerId: runningJobExecutions.providerRunnerId,
442
448
  count: count(),
443
449
  })
444
450
  .from(runningJobExecutions)
@@ -446,21 +452,21 @@ export async function listRunningJobExecutionsByProvisionedRunnerTx(
446
452
  and(
447
453
  eq(runningJobExecutions.workspaceId, params.workspaceId),
448
454
  eq(runningJobExecutions.provisionerId, params.provisionerId),
449
- inArray(runningJobExecutions.provisionedRunnerId, params.provisionedRunnerIds),
455
+ inArray(runningJobExecutions.providerRunnerId, params.providerRunnerIds),
450
456
  ),
451
457
  )
452
- .groupBy(runningJobExecutions.provisionedRunnerId)
458
+ .groupBy(runningJobExecutions.providerRunnerId)
453
459
  .having(sql`count(*) > 1`);
454
460
 
455
- const duplicateProvisionedRunnerIds = duplicateRows.flatMap((row) =>
456
- row.provisionedRunnerId ? [row.provisionedRunnerId] : [],
461
+ const duplicateRunnerInstanceIds = duplicateRows.flatMap((row) =>
462
+ row.providerRunnerId ? [row.providerRunnerId] : [],
457
463
  );
458
- if (duplicateProvisionedRunnerIds.length > 0) {
464
+ if (duplicateRunnerInstanceIds.length > 0) {
459
465
  logger().warn(
460
466
  {
461
467
  workspaceId: params.workspaceId,
462
468
  provisionerId: params.provisionerId,
463
- provisionedRunnerIds: duplicateProvisionedRunnerIds,
469
+ providerRunnerIds: duplicateRunnerInstanceIds,
464
470
  },
465
471
  'multiple running job executions are bound to the same provisioned runner',
466
472
  );
@@ -471,17 +477,17 @@ export async function listRunningJobExecutionsByProvisionedRunnerTx(
471
477
  workflowRunAttemptId: string;
472
478
  jobId: string;
473
479
  jobExecutionId: string;
474
- provisionedRunnerId: string;
480
+ providerRunnerId: string;
475
481
  startedAt: Date | string;
476
482
  lastHeartbeatAt: Date | string;
477
483
  cancellationRequestedAt: Date | string | null;
478
484
  }>(sql`
479
- SELECT DISTINCT ON (${runningJobExecutions.provisionedRunnerId})
485
+ SELECT DISTINCT ON (${runningJobExecutions.providerRunnerId})
480
486
  ${runningJobExecutions.workflowRunId} AS "workflowRunId",
481
487
  ${runningJobExecutions.workflowRunAttemptId} AS "workflowRunAttemptId",
482
488
  ${runningJobExecutions.jobId} AS "jobId",
483
489
  ${runningJobExecutions.jobExecutionId} AS "jobExecutionId",
484
- ${runningJobExecutions.provisionedRunnerId} AS "provisionedRunnerId",
490
+ ${runningJobExecutions.providerRunnerId} AS "providerRunnerId",
485
491
  ${runningJobExecutions.startedAt} AS "startedAt",
486
492
  ${runningJobExecutions.lastHeartbeatAt} AS "lastHeartbeatAt",
487
493
  ${runningJobExecutions.cancellationRequestedAt} AS "cancellationRequestedAt"
@@ -489,11 +495,11 @@ export async function listRunningJobExecutionsByProvisionedRunnerTx(
489
495
  WHERE
490
496
  ${runningJobExecutions.workspaceId} = ${params.workspaceId}
491
497
  AND ${runningJobExecutions.provisionerId} = ${params.provisionerId}
492
- AND ${runningJobExecutions.provisionedRunnerId} IN (${sql.join(
493
- params.provisionedRunnerIds.map((provisionedRunnerId) => sql`${provisionedRunnerId}`),
498
+ AND ${runningJobExecutions.providerRunnerId} IN (${sql.join(
499
+ params.providerRunnerIds.map((providerRunnerId) => sql`${providerRunnerId}`),
494
500
  sql`, `,
495
501
  )})
496
- ORDER BY ${runningJobExecutions.provisionedRunnerId}, ${runningJobExecutions.startedAt} DESC, ${runningJobExecutions.jobExecutionId} DESC
502
+ ORDER BY ${runningJobExecutions.providerRunnerId}, ${runningJobExecutions.startedAt} DESC, ${runningJobExecutions.jobExecutionId} DESC
497
503
  `);
498
504
 
499
505
  return result.rows.map((row) => ({
@@ -501,7 +507,7 @@ export async function listRunningJobExecutionsByProvisionedRunnerTx(
501
507
  workflowRunAttemptId: row.workflowRunAttemptId,
502
508
  jobId: row.jobId,
503
509
  jobExecutionId: row.jobExecutionId,
504
- provisionedRunnerId: row.provisionedRunnerId,
510
+ providerRunnerId: row.providerRunnerId,
505
511
  startedAt: toDate(row.startedAt),
506
512
  lastHeartbeatAt: toDate(row.lastHeartbeatAt),
507
513
  cancellationRequestedAt: row.cancellationRequestedAt
@@ -0,0 +1,175 @@
1
+ import {and, eq} from 'drizzle-orm';
2
+ import {db} from '#db/db.js';
3
+ import {
4
+ hasActiveWorkspaceProvisionerCapability,
5
+ listActiveWorkspaceProvisionerCapabilitySnapshots,
6
+ listStaleWorkspaceProvisionerCapabilitySnapshots,
7
+ publishWorkspaceProvisionerCapabilitySnapshot,
8
+ } from '#db/provisioner-capability-snapshots.js';
9
+ import {provisionerCapabilitySnapshots} from '#db/schema/provisioner-capability-snapshots.js';
10
+ import {provisionerTokens} from '#db/schema/provisioner-tokens.js';
11
+ import {provisionerTokenFactory} from '#test/index.js';
12
+
13
+ describe('workspace provisioner capability snapshots', () => {
14
+ let workspaceId: string;
15
+
16
+ beforeEach(() => {
17
+ workspaceId = crypto.randomUUID();
18
+ });
19
+
20
+ it('atomically replaces one provisioner complete template snapshot', async () => {
21
+ const provisioner = await provisionerTokenFactory.create({workspaceId});
22
+
23
+ await publishWorkspaceProvisionerCapabilitySnapshot({
24
+ workspaceId,
25
+ provisionerId: provisioner.id,
26
+ templates: [template('linux', ['linux'], 4, 1, 3), template('macos', ['macos'], 2, 0, 2)],
27
+ });
28
+ await publishWorkspaceProvisionerCapabilitySnapshot({
29
+ workspaceId,
30
+ provisionerId: provisioner.id,
31
+ templates: [template('linux-arm64', ['linux', 'arm64'], 0, 1, 0)],
32
+ });
33
+
34
+ const rows = await snapshotsFor(provisioner.id);
35
+ expect(rows).toEqual([
36
+ expect.objectContaining({
37
+ templateKey: 'linux-arm64',
38
+ labels: ['arm64', 'linux'],
39
+ availableSlots: 0,
40
+ starting: 1,
41
+ running: 0,
42
+ }),
43
+ ]);
44
+ });
45
+
46
+ it('keeps zero-slot overlapping capability active for every workspace provisioner', async () => {
47
+ const first = await provisionerTokenFactory.create({workspaceId});
48
+ const second = await provisionerTokenFactory.create({workspaceId});
49
+
50
+ await publishWorkspaceProvisionerCapabilitySnapshot({
51
+ workspaceId,
52
+ provisionerId: first.id,
53
+ templates: [template('linux', ['linux', 'x64'], 0, 0, 1)],
54
+ });
55
+ await publishWorkspaceProvisionerCapabilitySnapshot({
56
+ workspaceId,
57
+ provisionerId: second.id,
58
+ templates: [template('linux-gpu', ['linux', 'gpu'], 2, 1, 1)],
59
+ });
60
+
61
+ const active = await listActiveWorkspaceProvisionerCapabilitySnapshots({
62
+ workspaceId,
63
+ windowSeconds: 60,
64
+ });
65
+ const hasLinux = await hasActiveWorkspaceProvisionerCapability({
66
+ workspaceId,
67
+ requiredLabels: ['linux'],
68
+ windowSeconds: 60,
69
+ });
70
+ const hasLinuxX64 = await hasActiveWorkspaceProvisionerCapability({
71
+ workspaceId,
72
+ requiredLabels: ['linux', 'x64'],
73
+ windowSeconds: 60,
74
+ });
75
+
76
+ expect(active).toEqual(
77
+ expect.arrayContaining([
78
+ expect.objectContaining({provisionerId: first.id, availableSlots: 0}),
79
+ expect.objectContaining({provisionerId: second.id, labels: ['gpu', 'linux']}),
80
+ ]),
81
+ );
82
+ expect(hasLinux).toBe(true);
83
+ expect(hasLinuxX64).toBe(true);
84
+ });
85
+
86
+ it('requires every advertised label to be present, not just an overlap', async () => {
87
+ const provisioner = await provisionerTokenFactory.create({workspaceId});
88
+
89
+ await publishWorkspaceProvisionerCapabilitySnapshot({
90
+ workspaceId,
91
+ provisionerId: provisioner.id,
92
+ templates: [template('linux', ['linux'], 1, 0, 0)],
93
+ });
94
+
95
+ const hasLinuxArm64 = await hasActiveWorkspaceProvisionerCapability({
96
+ workspaceId,
97
+ requiredLabels: ['linux', 'arm64'],
98
+ windowSeconds: 60,
99
+ });
100
+
101
+ expect(hasLinuxArm64).toBe(false);
102
+ });
103
+
104
+ it('excludes stale and revoked provisioner capability from active matching', async () => {
105
+ const staleProvisioner = await provisionerTokenFactory.create({workspaceId});
106
+ const revokedProvisioner = await provisionerTokenFactory.create({workspaceId});
107
+
108
+ await publishWorkspaceProvisionerCapabilitySnapshot({
109
+ workspaceId,
110
+ provisionerId: staleProvisioner.id,
111
+ templates: [template('stale-linux', ['linux'], 1, 0, 1)],
112
+ });
113
+ await publishWorkspaceProvisionerCapabilitySnapshot({
114
+ workspaceId,
115
+ provisionerId: revokedProvisioner.id,
116
+ templates: [template('revoked-macos', ['macos'], 1, 0, 1)],
117
+ });
118
+ await db()
119
+ .update(provisionerCapabilitySnapshots)
120
+ .set({advertisedAt: new Date(Date.now() - 61_000)})
121
+ .where(eq(provisionerCapabilitySnapshots.provisionerId, staleProvisioner.id));
122
+ await db()
123
+ .update(provisionerTokens)
124
+ .set({revokedAt: new Date()})
125
+ .where(eq(provisionerTokens.id, revokedProvisioner.id));
126
+
127
+ const active = await listActiveWorkspaceProvisionerCapabilitySnapshots({
128
+ workspaceId,
129
+ windowSeconds: 60,
130
+ });
131
+ const stale = await listStaleWorkspaceProvisionerCapabilitySnapshots({
132
+ workspaceId,
133
+ windowSeconds: 60,
134
+ });
135
+ const hasLinux = await hasActiveWorkspaceProvisionerCapability({
136
+ workspaceId,
137
+ requiredLabels: ['linux'],
138
+ windowSeconds: 60,
139
+ });
140
+ const hasMacos = await hasActiveWorkspaceProvisionerCapability({
141
+ workspaceId,
142
+ requiredLabels: ['macos'],
143
+ windowSeconds: 60,
144
+ });
145
+
146
+ expect(active).toEqual([]);
147
+ expect(stale.map((snapshot) => snapshot.provisionerId).sort()).toEqual(
148
+ [staleProvisioner.id, revokedProvisioner.id].sort(),
149
+ );
150
+ expect(hasLinux).toBe(false);
151
+ expect(hasMacos).toBe(false);
152
+ });
153
+
154
+ function snapshotsFor(provisionerId: string) {
155
+ return db()
156
+ .select()
157
+ .from(provisionerCapabilitySnapshots)
158
+ .where(
159
+ and(
160
+ eq(provisionerCapabilitySnapshots.workspaceId, workspaceId),
161
+ eq(provisionerCapabilitySnapshots.provisionerId, provisionerId),
162
+ ),
163
+ );
164
+ }
165
+
166
+ function template(
167
+ templateKey: string,
168
+ labels: string[],
169
+ availableSlots: number,
170
+ starting: number,
171
+ running: number,
172
+ ) {
173
+ return {templateKey, labels, availableSlots, starting, running};
174
+ }
175
+ });
@@ -0,0 +1,130 @@
1
+ import {canonicalizeLabels} from '@shipfox/runner-labels';
2
+ import {and, arrayContains, eq, gt, isNotNull, isNull, lte, or, sql} from 'drizzle-orm';
3
+ import type {ProvisionerCapabilitySnapshot} from '#core/entities/provisioner-capability-snapshot.js';
4
+ import {db} from './db.js';
5
+ import type {ReservationTemplate} from './reservations.js';
6
+ import {
7
+ provisionerCapabilitySnapshots,
8
+ toProvisionerCapabilitySnapshot,
9
+ } from './schema/provisioner-capability-snapshots.js';
10
+ import {provisionerTokens} from './schema/provisioner-tokens.js';
11
+
12
+ export async function publishWorkspaceProvisionerCapabilitySnapshot(params: {
13
+ workspaceId: string;
14
+ provisionerId: string;
15
+ templates: ReservationTemplate[];
16
+ }): Promise<void> {
17
+ await db().transaction(async (tx) => {
18
+ await tx
19
+ .delete(provisionerCapabilitySnapshots)
20
+ .where(eq(provisionerCapabilitySnapshots.provisionerId, params.provisionerId));
21
+
22
+ if (params.templates.length === 0) return;
23
+
24
+ await tx.insert(provisionerCapabilitySnapshots).values(
25
+ params.templates.map((template) => ({
26
+ workspaceId: params.workspaceId,
27
+ provisionerId: params.provisionerId,
28
+ templateKey: template.templateKey,
29
+ labels: [...canonicalizeLabels(template.labels)],
30
+ availableSlots: template.availableSlots,
31
+ starting: template.starting,
32
+ running: template.running,
33
+ })),
34
+ );
35
+ });
36
+ }
37
+
38
+ export async function listActiveWorkspaceProvisionerCapabilitySnapshots(params: {
39
+ workspaceId: string;
40
+ windowSeconds: number;
41
+ }): Promise<ProvisionerCapabilitySnapshot[]> {
42
+ const rows = await db()
43
+ .select({snapshot: provisionerCapabilitySnapshots})
44
+ .from(provisionerCapabilitySnapshots)
45
+ .innerJoin(
46
+ provisionerTokens,
47
+ eq(provisionerTokens.id, provisionerCapabilitySnapshots.provisionerId),
48
+ )
49
+ .where(and(...activeWorkspaceProvisionerCapabilityConditions(params)));
50
+
51
+ return rows.map(({snapshot}) => toProvisionerCapabilitySnapshot(snapshot));
52
+ }
53
+
54
+ export async function listStaleWorkspaceProvisionerCapabilitySnapshots(params: {
55
+ workspaceId: string;
56
+ windowSeconds: number;
57
+ }): Promise<ProvisionerCapabilitySnapshot[]> {
58
+ const rows = await db()
59
+ .select({snapshot: provisionerCapabilitySnapshots})
60
+ .from(provisionerCapabilitySnapshots)
61
+ .innerJoin(
62
+ provisionerTokens,
63
+ eq(provisionerTokens.id, provisionerCapabilitySnapshots.provisionerId),
64
+ )
65
+ .where(
66
+ and(
67
+ eq(provisionerCapabilitySnapshots.workspaceId, params.workspaceId),
68
+ or(
69
+ lte(
70
+ provisionerCapabilitySnapshots.advertisedAt,
71
+ sql`now() - (${params.windowSeconds} || ' seconds')::interval`,
72
+ ),
73
+ eq(provisionerTokens.scope, 'installation'),
74
+ isNull(provisionerTokens.workspaceId),
75
+ provisionerTokenIsUnavailable(),
76
+ ),
77
+ ),
78
+ );
79
+
80
+ return rows.map(({snapshot}) => toProvisionerCapabilitySnapshot(snapshot));
81
+ }
82
+
83
+ export async function hasActiveWorkspaceProvisionerCapability(params: {
84
+ workspaceId: string;
85
+ requiredLabels: string[];
86
+ windowSeconds: number;
87
+ }): Promise<boolean> {
88
+ const [row] = await db()
89
+ .select({id: provisionerCapabilitySnapshots.id})
90
+ .from(provisionerCapabilitySnapshots)
91
+ .innerJoin(
92
+ provisionerTokens,
93
+ eq(provisionerTokens.id, provisionerCapabilitySnapshots.provisionerId),
94
+ )
95
+ .where(
96
+ and(
97
+ ...activeWorkspaceProvisionerCapabilityConditions(params),
98
+ arrayContains(provisionerCapabilitySnapshots.labels, [
99
+ ...canonicalizeLabels(params.requiredLabels),
100
+ ]),
101
+ ),
102
+ )
103
+ .limit(1);
104
+
105
+ return row !== undefined;
106
+ }
107
+
108
+ function activeWorkspaceProvisionerCapabilityConditions(params: {
109
+ workspaceId: string;
110
+ windowSeconds: number;
111
+ }) {
112
+ return [
113
+ eq(provisionerCapabilitySnapshots.workspaceId, params.workspaceId),
114
+ eq(provisionerTokens.workspaceId, params.workspaceId),
115
+ eq(provisionerTokens.scope, 'workspace' as const),
116
+ isNull(provisionerTokens.revokedAt),
117
+ or(isNull(provisionerTokens.expiresAt), gt(provisionerTokens.expiresAt, sql`now()`)),
118
+ gt(
119
+ provisionerCapabilitySnapshots.advertisedAt,
120
+ sql`now() - (${params.windowSeconds} || ' seconds')::interval`,
121
+ ),
122
+ ];
123
+ }
124
+
125
+ function provisionerTokenIsUnavailable() {
126
+ return or(
127
+ isNotNull(provisionerTokens.revokedAt),
128
+ and(isNotNull(provisionerTokens.expiresAt), lte(provisionerTokens.expiresAt, sql`now()`)),
129
+ );
130
+ }