@shipfox/api-runners 2.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 (465) hide show
  1. package/.turbo/turbo-build.log +2 -0
  2. package/.turbo/turbo-type$colon$emit.log +1 -0
  3. package/.turbo/turbo-type.log +1 -0
  4. package/CHANGELOG.md +187 -0
  5. package/LICENSE +21 -0
  6. package/dist/config.d.ts +30 -0
  7. package/dist/config.d.ts.map +1 -0
  8. package/dist/config.js +208 -0
  9. package/dist/config.js.map +1 -0
  10. package/dist/core/demand.d.ts +33 -0
  11. package/dist/core/demand.d.ts.map +1 -0
  12. package/dist/core/demand.js +169 -0
  13. package/dist/core/demand.js.map +1 -0
  14. package/dist/core/entities/ephemeral-registration-token.d.ts +14 -0
  15. package/dist/core/entities/ephemeral-registration-token.d.ts.map +1 -0
  16. package/dist/core/entities/ephemeral-registration-token.js +3 -0
  17. package/dist/core/entities/ephemeral-registration-token.js.map +1 -0
  18. package/dist/core/entities/manual-registration-token.d.ts +12 -0
  19. package/dist/core/entities/manual-registration-token.d.ts.map +1 -0
  20. package/dist/core/entities/manual-registration-token.js +3 -0
  21. package/dist/core/entities/manual-registration-token.js.map +1 -0
  22. package/dist/core/entities/provisioned-runner.d.ts +24 -0
  23. package/dist/core/entities/provisioned-runner.d.ts.map +1 -0
  24. package/dist/core/entities/provisioned-runner.js +3 -0
  25. package/dist/core/entities/provisioned-runner.js.map +1 -0
  26. package/dist/core/entities/provisioner-token.d.ts +18 -0
  27. package/dist/core/entities/provisioner-token.d.ts.map +1 -0
  28. package/dist/core/entities/provisioner-token.js +3 -0
  29. package/dist/core/entities/provisioner-token.js.map +1 -0
  30. package/dist/core/entities/reservation.d.ts +10 -0
  31. package/dist/core/entities/reservation.d.ts.map +1 -0
  32. package/dist/core/entities/reservation.js +3 -0
  33. package/dist/core/entities/reservation.js.map +1 -0
  34. package/dist/core/entities/runner-session.d.ts +18 -0
  35. package/dist/core/entities/runner-session.d.ts.map +1 -0
  36. package/dist/core/entities/runner-session.js +3 -0
  37. package/dist/core/entities/runner-session.js.map +1 -0
  38. package/dist/core/ephemeral-registration-tokens.d.ts +31 -0
  39. package/dist/core/ephemeral-registration-tokens.d.ts.map +1 -0
  40. package/dist/core/ephemeral-registration-tokens.js +61 -0
  41. package/dist/core/ephemeral-registration-tokens.js.map +1 -0
  42. package/dist/core/errors.d.ts +62 -0
  43. package/dist/core/errors.d.ts.map +1 -0
  44. package/dist/core/errors.js +92 -0
  45. package/dist/core/errors.js.map +1 -0
  46. package/dist/core/index.d.ts +14 -0
  47. package/dist/core/index.d.ts.map +1 -0
  48. package/dist/core/index.js +10 -0
  49. package/dist/core/index.js.map +1 -0
  50. package/dist/core/job-executions.d.ts +14 -0
  51. package/dist/core/job-executions.d.ts.map +1 -0
  52. package/dist/core/job-executions.js +37 -0
  53. package/dist/core/job-executions.js.map +1 -0
  54. package/dist/core/maintenance-policy.d.ts +2 -0
  55. package/dist/core/maintenance-policy.d.ts.map +1 -0
  56. package/dist/core/maintenance-policy.js +3 -0
  57. package/dist/core/maintenance-policy.js.map +1 -0
  58. package/dist/core/maintenance.d.ts +33 -0
  59. package/dist/core/maintenance.d.ts.map +1 -0
  60. package/dist/core/maintenance.js +55 -0
  61. package/dist/core/maintenance.js.map +1 -0
  62. package/dist/core/manual-registration-tokens.d.ts +17 -0
  63. package/dist/core/manual-registration-tokens.d.ts.map +1 -0
  64. package/dist/core/manual-registration-tokens.js +28 -0
  65. package/dist/core/manual-registration-tokens.js.map +1 -0
  66. package/dist/core/provisioned-runners.d.ts +67 -0
  67. package/dist/core/provisioned-runners.d.ts.map +1 -0
  68. package/dist/core/provisioned-runners.js +162 -0
  69. package/dist/core/provisioned-runners.js.map +1 -0
  70. package/dist/core/provisioner-tokens.d.ts +20 -0
  71. package/dist/core/provisioner-tokens.d.ts.map +1 -0
  72. package/dist/core/provisioner-tokens.js +36 -0
  73. package/dist/core/provisioner-tokens.js.map +1 -0
  74. package/dist/core/rate-limit.d.ts +35 -0
  75. package/dist/core/rate-limit.d.ts.map +1 -0
  76. package/dist/core/rate-limit.js +74 -0
  77. package/dist/core/rate-limit.js.map +1 -0
  78. package/dist/core/runner-sessions.d.ts +26 -0
  79. package/dist/core/runner-sessions.d.ts.map +1 -0
  80. package/dist/core/runner-sessions.js +41 -0
  81. package/dist/core/runner-sessions.js.map +1 -0
  82. package/dist/core/runner-tool-capabilities.d.ts +24 -0
  83. package/dist/core/runner-tool-capabilities.d.ts.map +1 -0
  84. package/dist/core/runner-tool-capabilities.js +43 -0
  85. package/dist/core/runner-tool-capabilities.js.map +1 -0
  86. package/dist/db/db.d.ts +4478 -0
  87. package/dist/db/db.d.ts.map +1 -0
  88. package/dist/db/db.js +36 -0
  89. package/dist/db/db.js.map +1 -0
  90. package/dist/db/ephemeral-registration-tokens.d.ts +47 -0
  91. package/dist/db/ephemeral-registration-tokens.d.ts.map +1 -0
  92. package/dist/db/ephemeral-registration-tokens.js +170 -0
  93. package/dist/db/ephemeral-registration-tokens.js.map +1 -0
  94. package/dist/db/index.d.ts +19 -0
  95. package/dist/db/index.d.ts.map +1 -0
  96. package/dist/db/index.js +15 -0
  97. package/dist/db/index.js.map +1 -0
  98. package/dist/db/job-executions.d.ts +128 -0
  99. package/dist/db/job-executions.d.ts.map +1 -0
  100. package/dist/db/job-executions.js +354 -0
  101. package/dist/db/job-executions.js.map +1 -0
  102. package/dist/db/manual-registration-tokens.d.ts +16 -0
  103. package/dist/db/manual-registration-tokens.d.ts.map +1 -0
  104. package/dist/db/manual-registration-tokens.js +37 -0
  105. package/dist/db/manual-registration-tokens.js.map +1 -0
  106. package/dist/db/provisioned-runners.d.ts +79 -0
  107. package/dist/db/provisioned-runners.d.ts.map +1 -0
  108. package/dist/db/provisioned-runners.js +466 -0
  109. package/dist/db/provisioned-runners.js.map +1 -0
  110. package/dist/db/provisioner-tokens.d.ts +26 -0
  111. package/dist/db/provisioner-tokens.d.ts.map +1 -0
  112. package/dist/db/provisioner-tokens.js +54 -0
  113. package/dist/db/provisioner-tokens.js.map +1 -0
  114. package/dist/db/rate-limits.d.ts +18 -0
  115. package/dist/db/rate-limits.d.ts.map +1 -0
  116. package/dist/db/rate-limits.js +44 -0
  117. package/dist/db/rate-limits.js.map +1 -0
  118. package/dist/db/reservations.d.ts +48 -0
  119. package/dist/db/reservations.d.ts.map +1 -0
  120. package/dist/db/reservations.js +162 -0
  121. package/dist/db/reservations.js.map +1 -0
  122. package/dist/db/runner-sessions.d.ts +18 -0
  123. package/dist/db/runner-sessions.d.ts.map +1 -0
  124. package/dist/db/runner-sessions.js +38 -0
  125. package/dist/db/runner-sessions.js.map +1 -0
  126. package/dist/db/schema/common.d.ts +2 -0
  127. package/dist/db/schema/common.d.ts.map +1 -0
  128. package/dist/db/schema/common.js +4 -0
  129. package/dist/db/schema/common.js.map +1 -0
  130. package/dist/db/schema/ephemeral-registration-tokens.d.ts +199 -0
  131. package/dist/db/schema/ephemeral-registration-tokens.d.ts.map +1 -0
  132. package/dist/db/schema/ephemeral-registration-tokens.js +45 -0
  133. package/dist/db/schema/ephemeral-registration-tokens.js.map +1 -0
  134. package/dist/db/schema/manual-registration-tokens.d.ts +165 -0
  135. package/dist/db/schema/manual-registration-tokens.d.ts.map +1 -0
  136. package/dist/db/schema/manual-registration-tokens.js +40 -0
  137. package/dist/db/schema/manual-registration-tokens.js.map +1 -0
  138. package/dist/db/schema/outbox.d.ts +195 -0
  139. package/dist/db/schema/outbox.d.ts.map +1 -0
  140. package/dist/db/schema/outbox.js +5 -0
  141. package/dist/db/schema/outbox.js.map +1 -0
  142. package/dist/db/schema/pending-job-executions.d.ts +189 -0
  143. package/dist/db/schema/pending-job-executions.d.ts.map +1 -0
  144. package/dist/db/schema/pending-job-executions.js +21 -0
  145. package/dist/db/schema/pending-job-executions.js.map +1 -0
  146. package/dist/db/schema/provisioned-runners.d.ts +379 -0
  147. package/dist/db/schema/provisioned-runners.d.ts.map +1 -0
  148. package/dist/db/schema/provisioned-runners.js +83 -0
  149. package/dist/db/schema/provisioned-runners.js.map +1 -0
  150. package/dist/db/schema/provisioner-tokens.d.ts +216 -0
  151. package/dist/db/schema/provisioner-tokens.d.ts.map +1 -0
  152. package/dist/db/schema/provisioner-tokens.js +48 -0
  153. package/dist/db/schema/provisioner-tokens.js.map +1 -0
  154. package/dist/db/schema/rate-limits.d.ts +145 -0
  155. package/dist/db/schema/rate-limits.d.ts.map +1 -0
  156. package/dist/db/schema/rate-limits.js +25 -0
  157. package/dist/db/schema/rate-limits.js.map +1 -0
  158. package/dist/db/schema/reservations.d.ts +157 -0
  159. package/dist/db/schema/reservations.d.ts.map +1 -0
  160. package/dist/db/schema/reservations.js +34 -0
  161. package/dist/db/schema/reservations.js.map +1 -0
  162. package/dist/db/schema/runner-sessions.d.ts +298 -0
  163. package/dist/db/schema/runner-sessions.d.ts.map +1 -0
  164. package/dist/db/schema/runner-sessions.js +61 -0
  165. package/dist/db/schema/runner-sessions.js.map +1 -0
  166. package/dist/db/schema/running-job-executions.d.ts +334 -0
  167. package/dist/db/schema/running-job-executions.d.ts.map +1 -0
  168. package/dist/db/schema/running-job-executions.js +41 -0
  169. package/dist/db/schema/running-job-executions.js.map +1 -0
  170. package/dist/index.d.ts +6 -0
  171. package/dist/index.d.ts.map +1 -0
  172. package/dist/index.js +56 -0
  173. package/dist/index.js.map +1 -0
  174. package/dist/metrics/index.d.ts +3 -0
  175. package/dist/metrics/index.d.ts.map +1 -0
  176. package/dist/metrics/index.js +4 -0
  177. package/dist/metrics/index.js.map +1 -0
  178. package/dist/metrics/instance.d.ts +34 -0
  179. package/dist/metrics/instance.d.ts.map +1 -0
  180. package/dist/metrics/instance.js +60 -0
  181. package/dist/metrics/instance.js.map +1 -0
  182. package/dist/metrics/service.d.ts +2 -0
  183. package/dist/metrics/service.d.ts.map +1 -0
  184. package/dist/metrics/service.js +21 -0
  185. package/dist/metrics/service.js.map +1 -0
  186. package/dist/presentation/auth/index.d.ts +3 -0
  187. package/dist/presentation/auth/index.d.ts.map +1 -0
  188. package/dist/presentation/auth/index.js +4 -0
  189. package/dist/presentation/auth/index.js.map +1 -0
  190. package/dist/presentation/auth/provisioner-token-auth.d.ts +3 -0
  191. package/dist/presentation/auth/provisioner-token-auth.d.ts.map +1 -0
  192. package/dist/presentation/auth/provisioner-token-auth.js +81 -0
  193. package/dist/presentation/auth/provisioner-token-auth.js.map +1 -0
  194. package/dist/presentation/auth/runner-registration-token-auth.d.ts +17 -0
  195. package/dist/presentation/auth/runner-registration-token-auth.d.ts.map +1 -0
  196. package/dist/presentation/auth/runner-registration-token-auth.js +77 -0
  197. package/dist/presentation/auth/runner-registration-token-auth.js.map +1 -0
  198. package/dist/presentation/dto/active-provisioner.d.ts +8 -0
  199. package/dist/presentation/dto/active-provisioner.d.ts.map +1 -0
  200. package/dist/presentation/dto/active-provisioner.js +10 -0
  201. package/dist/presentation/dto/active-provisioner.js.map +1 -0
  202. package/dist/presentation/dto/index.d.ts +7 -0
  203. package/dist/presentation/dto/index.d.ts.map +1 -0
  204. package/dist/presentation/dto/index.js +8 -0
  205. package/dist/presentation/dto/index.js.map +1 -0
  206. package/dist/presentation/dto/manual-registration-token.d.ts +12 -0
  207. package/dist/presentation/dto/manual-registration-token.d.ts.map +1 -0
  208. package/dist/presentation/dto/manual-registration-token.js +14 -0
  209. package/dist/presentation/dto/manual-registration-token.js.map +1 -0
  210. package/dist/presentation/dto/mint-registration-tokens.d.ts +5 -0
  211. package/dist/presentation/dto/mint-registration-tokens.d.ts.map +1 -0
  212. package/dist/presentation/dto/mint-registration-tokens.js +19 -0
  213. package/dist/presentation/dto/mint-registration-tokens.js.map +1 -0
  214. package/dist/presentation/dto/poll-demand.d.ts +4 -0
  215. package/dist/presentation/dto/poll-demand.d.ts.map +1 -0
  216. package/dist/presentation/dto/poll-demand.js +19 -0
  217. package/dist/presentation/dto/poll-demand.js.map +1 -0
  218. package/dist/presentation/dto/provisioned-runners.d.ts +38 -0
  219. package/dist/presentation/dto/provisioned-runners.d.ts.map +1 -0
  220. package/dist/presentation/dto/provisioned-runners.js +44 -0
  221. package/dist/presentation/dto/provisioned-runners.js.map +1 -0
  222. package/dist/presentation/dto/provisioner-token.d.ts +15 -0
  223. package/dist/presentation/dto/provisioner-token.d.ts.map +1 -0
  224. package/dist/presentation/dto/provisioner-token.js +17 -0
  225. package/dist/presentation/dto/provisioner-token.js.map +1 -0
  226. package/dist/presentation/index.d.ts +4 -0
  227. package/dist/presentation/index.d.ts.map +1 -0
  228. package/dist/presentation/index.js +5 -0
  229. package/dist/presentation/index.js.map +1 -0
  230. package/dist/presentation/routes/create-manual-registration-token.d.ts +2 -0
  231. package/dist/presentation/routes/create-manual-registration-token.d.ts.map +1 -0
  232. package/dist/presentation/routes/create-manual-registration-token.js +44 -0
  233. package/dist/presentation/routes/create-manual-registration-token.js.map +1 -0
  234. package/dist/presentation/routes/create-provisioner-token.d.ts +2 -0
  235. package/dist/presentation/routes/create-provisioner-token.d.ts.map +1 -0
  236. package/dist/presentation/routes/create-provisioner-token.js +42 -0
  237. package/dist/presentation/routes/create-provisioner-token.js.map +1 -0
  238. package/dist/presentation/routes/get-provisioner-me.d.ts +2 -0
  239. package/dist/presentation/routes/get-provisioner-me.d.ts.map +1 -0
  240. package/dist/presentation/routes/get-provisioner-me.js +22 -0
  241. package/dist/presentation/routes/get-provisioner-me.js.map +1 -0
  242. package/dist/presentation/routes/heartbeat.d.ts +2 -0
  243. package/dist/presentation/routes/heartbeat.d.ts.map +1 -0
  244. package/dist/presentation/routes/heartbeat.js +64 -0
  245. package/dist/presentation/routes/heartbeat.js.map +1 -0
  246. package/dist/presentation/routes/index.d.ts +4 -0
  247. package/dist/presentation/routes/index.d.ts.map +1 -0
  248. package/dist/presentation/routes/index.js +97 -0
  249. package/dist/presentation/routes/index.js.map +1 -0
  250. package/dist/presentation/routes/list-active-provisioners.d.ts +2 -0
  251. package/dist/presentation/routes/list-active-provisioners.d.ts.map +1 -0
  252. package/dist/presentation/routes/list-active-provisioners.js +32 -0
  253. package/dist/presentation/routes/list-active-provisioners.js.map +1 -0
  254. package/dist/presentation/routes/list-active-runners.d.ts +2 -0
  255. package/dist/presentation/routes/list-active-runners.d.ts.map +1 -0
  256. package/dist/presentation/routes/list-active-runners.js +32 -0
  257. package/dist/presentation/routes/list-active-runners.js.map +1 -0
  258. package/dist/presentation/routes/list-manual-registration-tokens.d.ts +2 -0
  259. package/dist/presentation/routes/list-manual-registration-tokens.d.ts.map +1 -0
  260. package/dist/presentation/routes/list-manual-registration-tokens.js +32 -0
  261. package/dist/presentation/routes/list-manual-registration-tokens.js.map +1 -0
  262. package/dist/presentation/routes/list-provisioner-tokens.d.ts +2 -0
  263. package/dist/presentation/routes/list-provisioner-tokens.d.ts.map +1 -0
  264. package/dist/presentation/routes/list-provisioner-tokens.js +32 -0
  265. package/dist/presentation/routes/list-provisioner-tokens.js.map +1 -0
  266. package/dist/presentation/routes/mint-registration-tokens.d.ts +2 -0
  267. package/dist/presentation/routes/mint-registration-tokens.d.ts.map +1 -0
  268. package/dist/presentation/routes/mint-registration-tokens.js +74 -0
  269. package/dist/presentation/routes/mint-registration-tokens.js.map +1 -0
  270. package/dist/presentation/routes/poll-demand.d.ts +2 -0
  271. package/dist/presentation/routes/poll-demand.d.ts.map +1 -0
  272. package/dist/presentation/routes/poll-demand.js +53 -0
  273. package/dist/presentation/routes/poll-demand.js.map +1 -0
  274. package/dist/presentation/routes/rate-limit.d.ts +4 -0
  275. package/dist/presentation/routes/rate-limit.d.ts.map +1 -0
  276. package/dist/presentation/routes/rate-limit.js +94 -0
  277. package/dist/presentation/routes/rate-limit.js.map +1 -0
  278. package/dist/presentation/routes/reconcile-provisioned-runners.d.ts +2 -0
  279. package/dist/presentation/routes/reconcile-provisioned-runners.d.ts.map +1 -0
  280. package/dist/presentation/routes/reconcile-provisioned-runners.js +27 -0
  281. package/dist/presentation/routes/reconcile-provisioned-runners.js.map +1 -0
  282. package/dist/presentation/routes/register.d.ts +2 -0
  283. package/dist/presentation/routes/register.d.ts.map +1 -0
  284. package/dist/presentation/routes/register.js +63 -0
  285. package/dist/presentation/routes/register.js.map +1 -0
  286. package/dist/presentation/routes/report-provisioned-runners.d.ts +2 -0
  287. package/dist/presentation/routes/report-provisioned-runners.d.ts.map +1 -0
  288. package/dist/presentation/routes/report-provisioned-runners.js +37 -0
  289. package/dist/presentation/routes/report-provisioned-runners.js.map +1 -0
  290. package/dist/presentation/routes/request-job.d.ts +2 -0
  291. package/dist/presentation/routes/request-job.d.ts.map +1 -0
  292. package/dist/presentation/routes/request-job.js +45 -0
  293. package/dist/presentation/routes/request-job.js.map +1 -0
  294. package/dist/presentation/routes/revoke-manual-registration-token.d.ts +2 -0
  295. package/dist/presentation/routes/revoke-manual-registration-token.d.ts.map +1 -0
  296. package/dist/presentation/routes/revoke-manual-registration-token.js +42 -0
  297. package/dist/presentation/routes/revoke-manual-registration-token.js.map +1 -0
  298. package/dist/presentation/routes/revoke-provisioner-token.d.ts +2 -0
  299. package/dist/presentation/routes/revoke-provisioner-token.d.ts.map +1 -0
  300. package/dist/presentation/routes/revoke-provisioner-token.js +44 -0
  301. package/dist/presentation/routes/revoke-provisioner-token.js.map +1 -0
  302. package/dist/presentation/routes/workspace-membership.d.ts +6 -0
  303. package/dist/presentation/routes/workspace-membership.d.ts.map +1 -0
  304. package/dist/presentation/routes/workspace-membership.js +6 -0
  305. package/dist/presentation/routes/workspace-membership.js.map +1 -0
  306. package/dist/presentation/subscribers/on-workflows-job-execution-timed-out.d.ts +3 -0
  307. package/dist/presentation/subscribers/on-workflows-job-execution-timed-out.d.ts.map +1 -0
  308. package/dist/presentation/subscribers/on-workflows-job-execution-timed-out.js +14 -0
  309. package/dist/presentation/subscribers/on-workflows-job-execution-timed-out.js.map +1 -0
  310. package/dist/temporal/activities/index.d.ts +9 -0
  311. package/dist/temporal/activities/index.d.ts.map +1 -0
  312. package/dist/temporal/activities/index.js +12 -0
  313. package/dist/temporal/activities/index.js.map +1 -0
  314. package/dist/temporal/activities/maintenance-activities.d.ts +31 -0
  315. package/dist/temporal/activities/maintenance-activities.d.ts.map +1 -0
  316. package/dist/temporal/activities/maintenance-activities.js +18 -0
  317. package/dist/temporal/activities/maintenance-activities.js.map +1 -0
  318. package/dist/temporal/constants.d.ts +2 -0
  319. package/dist/temporal/constants.d.ts.map +1 -0
  320. package/dist/temporal/constants.js +3 -0
  321. package/dist/temporal/constants.js.map +1 -0
  322. package/dist/temporal/workflows/index.d.ts +2 -0
  323. package/dist/temporal/workflows/index.d.ts.map +1 -0
  324. package/dist/temporal/workflows/index.js +3 -0
  325. package/dist/temporal/workflows/index.js.map +1 -0
  326. package/dist/temporal/workflows/stuck-job-detector.d.ts +2 -0
  327. package/dist/temporal/workflows/stuck-job-detector.d.ts.map +1 -0
  328. package/dist/temporal/workflows/stuck-job-detector.js +61 -0
  329. package/dist/temporal/workflows/stuck-job-detector.js.map +1 -0
  330. package/dist/tsconfig.test.tsbuildinfo +1 -0
  331. package/drizzle/0000_initial.sql +185 -0
  332. package/drizzle/meta/0000_snapshot.json +1582 -0
  333. package/drizzle/meta/_journal.json +13 -0
  334. package/drizzle.config.ts +7 -0
  335. package/package.json +82 -0
  336. package/src/config.test.ts +374 -0
  337. package/src/config.ts +330 -0
  338. package/src/core/demand.test.ts +301 -0
  339. package/src/core/demand.ts +239 -0
  340. package/src/core/entities/ephemeral-registration-token.ts +13 -0
  341. package/src/core/entities/manual-registration-token.ts +11 -0
  342. package/src/core/entities/provisioned-runner.ts +30 -0
  343. package/src/core/entities/provisioner-token.ts +16 -0
  344. package/src/core/entities/reservation.ts +9 -0
  345. package/src/core/entities/runner-session.ts +18 -0
  346. package/src/core/ephemeral-registration-tokens.test.ts +108 -0
  347. package/src/core/ephemeral-registration-tokens.ts +112 -0
  348. package/src/core/errors.ts +128 -0
  349. package/src/core/index.ts +56 -0
  350. package/src/core/job-executions.ts +47 -0
  351. package/src/core/maintenance-policy.ts +1 -0
  352. package/src/core/maintenance.test.ts +251 -0
  353. package/src/core/maintenance.ts +74 -0
  354. package/src/core/manual-registration-tokens.test.ts +44 -0
  355. package/src/core/manual-registration-tokens.ts +51 -0
  356. package/src/core/provisioned-runners.test.ts +101 -0
  357. package/src/core/provisioned-runners.ts +301 -0
  358. package/src/core/provisioner-tokens.test.ts +61 -0
  359. package/src/core/provisioner-tokens.ts +61 -0
  360. package/src/core/rate-limit.test.ts +158 -0
  361. package/src/core/rate-limit.ts +121 -0
  362. package/src/core/runner-sessions.test.ts +209 -0
  363. package/src/core/runner-sessions.ts +66 -0
  364. package/src/core/runner-tool-capabilities.test.ts +141 -0
  365. package/src/core/runner-tool-capabilities.ts +75 -0
  366. package/src/db/db.ts +38 -0
  367. package/src/db/ephemeral-registration-tokens.test.ts +332 -0
  368. package/src/db/ephemeral-registration-tokens.ts +348 -0
  369. package/src/db/index.ts +90 -0
  370. package/src/db/job-executions.test.ts +1500 -0
  371. package/src/db/job-executions.ts +647 -0
  372. package/src/db/manual-registration-tokens.test.ts +99 -0
  373. package/src/db/manual-registration-tokens.ts +87 -0
  374. package/src/db/provisioned-runners.test.ts +1812 -0
  375. package/src/db/provisioned-runners.ts +886 -0
  376. package/src/db/provisioner-tokens.test.ts +133 -0
  377. package/src/db/provisioner-tokens.ts +142 -0
  378. package/src/db/rate-limits.test.ts +74 -0
  379. package/src/db/rate-limits.ts +69 -0
  380. package/src/db/reservations.test.ts +399 -0
  381. package/src/db/reservations.ts +296 -0
  382. package/src/db/runner-sessions.test.ts +153 -0
  383. package/src/db/runner-sessions.ts +97 -0
  384. package/src/db/schema/common.ts +3 -0
  385. package/src/db/schema/ephemeral-registration-tokens.ts +58 -0
  386. package/src/db/schema/manual-registration-tokens.ts +40 -0
  387. package/src/db/schema/outbox.ts +4 -0
  388. package/src/db/schema/pending-job-executions.ts +25 -0
  389. package/src/db/schema/provisioned-runners.ts +89 -0
  390. package/src/db/schema/provisioner-tokens.ts +50 -0
  391. package/src/db/schema/rate-limits.ts +27 -0
  392. package/src/db/schema/reservations.ts +38 -0
  393. package/src/db/schema/runner-sessions.ts +77 -0
  394. package/src/db/schema/running-job-executions.ts +50 -0
  395. package/src/index.ts +63 -0
  396. package/src/metrics/index.ts +13 -0
  397. package/src/metrics/instance.ts +115 -0
  398. package/src/metrics/service.ts +22 -0
  399. package/src/presentation/auth/index.ts +6 -0
  400. package/src/presentation/auth/provisioner-token-auth.ts +76 -0
  401. package/src/presentation/auth/runner-registration-token-auth.ts +105 -0
  402. package/src/presentation/dto/active-provisioner.ts +15 -0
  403. package/src/presentation/dto/index.ts +13 -0
  404. package/src/presentation/dto/manual-registration-token.ts +23 -0
  405. package/src/presentation/dto/mint-registration-tokens.ts +35 -0
  406. package/src/presentation/dto/poll-demand.ts +20 -0
  407. package/src/presentation/dto/provisioned-runners.ts +88 -0
  408. package/src/presentation/dto/provisioner-token.ts +29 -0
  409. package/src/presentation/index.ts +6 -0
  410. package/src/presentation/routes/create-manual-registration-token.ts +43 -0
  411. package/src/presentation/routes/create-provisioner-token.ts +41 -0
  412. package/src/presentation/routes/get-provisioner-me.ts +22 -0
  413. package/src/presentation/routes/heartbeat.test.ts +325 -0
  414. package/src/presentation/routes/heartbeat.ts +80 -0
  415. package/src/presentation/routes/index.ts +86 -0
  416. package/src/presentation/routes/list-active-provisioners.ts +25 -0
  417. package/src/presentation/routes/list-active-runners.test.ts +366 -0
  418. package/src/presentation/routes/list-active-runners.ts +25 -0
  419. package/src/presentation/routes/list-manual-registration-tokens.ts +25 -0
  420. package/src/presentation/routes/list-provisioner-tokens.ts +25 -0
  421. package/src/presentation/routes/manual-registration-tokens.test.ts +213 -0
  422. package/src/presentation/routes/mint-registration-tokens.test.ts +453 -0
  423. package/src/presentation/routes/mint-registration-tokens.ts +82 -0
  424. package/src/presentation/routes/poll-demand-release.test.ts +80 -0
  425. package/src/presentation/routes/poll-demand.test.ts +323 -0
  426. package/src/presentation/routes/poll-demand.ts +56 -0
  427. package/src/presentation/routes/provisioner-me.test.ts +172 -0
  428. package/src/presentation/routes/provisioner-tokens.test.ts +219 -0
  429. package/src/presentation/routes/rate-limit.ts +115 -0
  430. package/src/presentation/routes/reconcile-provisioned-runners.test.ts +380 -0
  431. package/src/presentation/routes/reconcile-provisioned-runners.ts +30 -0
  432. package/src/presentation/routes/register.test.ts +453 -0
  433. package/src/presentation/routes/register.ts +75 -0
  434. package/src/presentation/routes/report-provisioned-runners.test.ts +249 -0
  435. package/src/presentation/routes/report-provisioned-runners.ts +40 -0
  436. package/src/presentation/routes/request-job.test.ts +222 -0
  437. package/src/presentation/routes/request-job.ts +47 -0
  438. package/src/presentation/routes/revoke-manual-registration-token.ts +35 -0
  439. package/src/presentation/routes/revoke-provisioner-token.ts +37 -0
  440. package/src/presentation/routes/workspace-membership.ts +9 -0
  441. package/src/presentation/subscribers/on-workflows-job-execution-timed-out.test.ts +147 -0
  442. package/src/presentation/subscribers/on-workflows-job-execution-timed-out.ts +17 -0
  443. package/src/temporal/activities/index.ts +17 -0
  444. package/src/temporal/activities/maintenance-activities.test.ts +104 -0
  445. package/src/temporal/activities/maintenance-activities.ts +41 -0
  446. package/src/temporal/constants.ts +1 -0
  447. package/src/temporal/workflows/index.ts +1 -0
  448. package/src/temporal/workflows/stuck-job-detector.test.ts +93 -0
  449. package/src/temporal/workflows/stuck-job-detector.ts +67 -0
  450. package/test/env.ts +11 -0
  451. package/test/factories/ephemeral-registration-token.ts +41 -0
  452. package/test/factories/manual-registration-token.ts +37 -0
  453. package/test/factories/pending-job.ts +44 -0
  454. package/test/factories/provisioned-runner.ts +78 -0
  455. package/test/factories/provisioner-token.ts +41 -0
  456. package/test/factories/reservation.ts +33 -0
  457. package/test/factories/runner-session.ts +32 -0
  458. package/test/globalSetup.ts +23 -0
  459. package/test/index.ts +16 -0
  460. package/test/setup.ts +17 -0
  461. package/tsconfig.build.json +9 -0
  462. package/tsconfig.build.tsbuildinfo +1 -0
  463. package/tsconfig.json +3 -0
  464. package/tsconfig.test.json +8 -0
  465. package/vitest.config.ts +14 -0
@@ -0,0 +1,886 @@
1
+ import {logger} from '@shipfox/node-opentelemetry';
2
+ import {canonicalizeLabels} from '@shipfox/runner-labels';
3
+ import {
4
+ and,
5
+ asc,
6
+ desc,
7
+ eq,
8
+ exists,
9
+ gt,
10
+ inArray,
11
+ isNotNull,
12
+ isNull,
13
+ lt,
14
+ notExists,
15
+ notInArray,
16
+ or,
17
+ type SQL,
18
+ sql,
19
+ } from 'drizzle-orm';
20
+ import {alias} from 'drizzle-orm/pg-core';
21
+ import type {ProvisionedRunner, ProvisionedRunnerState} from '#core/entities/provisioned-runner.js';
22
+ import type {Tx} from './db.js';
23
+ import {db} from './db.js';
24
+ import {
25
+ listRunningJobExecutionsByProvisionedRunnerTx,
26
+ type ProvisionedRunnerBoundJobExecution,
27
+ } from './job-executions.js';
28
+ import {releaseReservationUnits} from './reservations.js';
29
+ import {ephemeralRegistrationTokens} from './schema/ephemeral-registration-tokens.js';
30
+ import {provisionedRunners, toProvisionedRunner} from './schema/provisioned-runners.js';
31
+ import {provisionerTokens} from './schema/provisioner-tokens.js';
32
+ import {runnerSessions} from './schema/runner-sessions.js';
33
+ import {runningJobExecutions} from './schema/running-job-executions.js';
34
+
35
+ export const terminalStates = [
36
+ 'stopped',
37
+ 'failed',
38
+ 'terminated',
39
+ ] as const satisfies readonly ProvisionedRunnerState[];
40
+ export const activeStates = [
41
+ 'starting',
42
+ 'running',
43
+ 'stopping',
44
+ ] as const satisfies readonly ProvisionedRunnerState[];
45
+ export const divergenceCountStates = ['starting', 'running'] as const satisfies readonly Extract<
46
+ ProvisionedRunnerState,
47
+ 'starting' | 'running'
48
+ >[];
49
+
50
+ export type ProvisionedRunnerTerminateIntentReason = 'job-cancelled';
51
+
52
+ export interface ProvisionedRunnerTerminateIntent {
53
+ provisionedRunnerId: string;
54
+ reason: ProvisionedRunnerTerminateIntentReason;
55
+ }
56
+
57
+ export interface ActiveProvisionedRunnerTemplateCount {
58
+ templateKey: string;
59
+ state: (typeof divergenceCountStates)[number];
60
+ count: number;
61
+ }
62
+
63
+ export interface ProvisionedRunnerReportEvent {
64
+ provisionedRunnerId: string;
65
+ reservationId: string | null;
66
+ templateKey: string | null;
67
+ labels: string[];
68
+ state: ProvisionedRunnerState;
69
+ reason: string | null;
70
+ runnerSessionId: string | null;
71
+ providerKind: string | null;
72
+ reportedAt: Date;
73
+ }
74
+
75
+ export interface ReportProvisionedRunnersParams {
76
+ workspaceId: string;
77
+ provisionerId: string;
78
+ events: ProvisionedRunnerReportEvent[];
79
+ }
80
+
81
+ export interface ReconcileProvisionedRunnersParams {
82
+ workspaceId: string;
83
+ provisionerId: string;
84
+ observedProvisionedRunnerIds: string[];
85
+ terminateGraceSeconds: number;
86
+ }
87
+
88
+ export interface ReconcileProvisionedRunnersDbResult {
89
+ observedRows: ProvisionedRunner[];
90
+ boundJobExecutionsByProvisionedRunnerId: Map<string, ProvisionedRunnerBoundJobExecution>;
91
+ absentIds: string[];
92
+ reservationsReleased: number;
93
+ }
94
+
95
+ export interface ReapStaleProvisionedRunnersResult {
96
+ reaped: number;
97
+ reservationsReleased: number;
98
+ }
99
+
100
+ interface ProvisionedRunnerReportRow extends ProvisionedRunnerReportEvent {
101
+ startedAt: Date | null;
102
+ stoppingAt: Date | null;
103
+ stoppedAt: Date | null;
104
+ failedAt: Date | null;
105
+ terminatedAt: Date | null;
106
+ }
107
+
108
+ type ProvisionedRunnerMilestoneColumn =
109
+ | typeof provisionedRunners.startedAt
110
+ | typeof provisionedRunners.stoppingAt
111
+ | typeof provisionedRunners.stoppedAt
112
+ | typeof provisionedRunners.failedAt
113
+ | typeof provisionedRunners.terminatedAt;
114
+
115
+ export async function reportProvisionedRunners(params: ReportProvisionedRunnersParams): Promise<{
116
+ accepted: number;
117
+ reservationsReleased: number;
118
+ terminateIntentsHonored: ProvisionedRunnerTerminateIntent[];
119
+ }> {
120
+ if (params.events.length === 0)
121
+ return {accepted: 0, reservationsReleased: 0, terminateIntentsHonored: []};
122
+
123
+ return await db().transaction(async (tx) => {
124
+ const receivedAt = new Date();
125
+ const aggregatedEvents = aggregateEvents(params.events, receivedAt);
126
+ const hasTerminalEvent = aggregatedEvents.some((event) => isTerminalState(event.state));
127
+
128
+ if (hasTerminalEvent) {
129
+ await tx.execute(sql`select pg_advisory_xact_lock(hashtext(${params.workspaceId}))`);
130
+ }
131
+
132
+ const events = await hydrateRunnerSessionIdsFromConsumedTokens(tx, params, aggregatedEvents);
133
+ const terminateIntentsHonored = await listTerminateIntentsHonoredByTerminatedReportsTx(
134
+ tx,
135
+ params,
136
+ events,
137
+ );
138
+
139
+ const values = events.map((event) => ({
140
+ workspaceId: params.workspaceId,
141
+ provisionerId: params.provisionerId,
142
+ provisionedRunnerId: event.provisionedRunnerId,
143
+ reservationId: event.reservationId,
144
+ templateKey: event.templateKey,
145
+ labels: [...canonicalizeLabels(event.labels)],
146
+ state: event.state,
147
+ reason: event.reason,
148
+ runnerSessionId: event.runnerSessionId,
149
+ providerKind: event.providerKind,
150
+ reportedAt: event.reportedAt > receivedAt ? receivedAt : event.reportedAt,
151
+ startedAt: event.startedAt,
152
+ stoppingAt: event.stoppingAt,
153
+ stoppedAt: event.stoppedAt,
154
+ failedAt: event.failedAt,
155
+ terminatedAt: event.terminatedAt,
156
+ }));
157
+
158
+ await tx
159
+ .insert(provisionedRunners)
160
+ .values(values)
161
+ .onConflictDoUpdate({
162
+ target: [
163
+ provisionedRunners.workspaceId,
164
+ provisionedRunners.provisionerId,
165
+ provisionedRunners.provisionedRunnerId,
166
+ ],
167
+ set: {
168
+ reservationId: sql`CASE WHEN ${provisionedRunnerProjectionUpdateCondition()} THEN coalesce(excluded.reservation_id, ${provisionedRunners.reservationId}) ELSE ${provisionedRunners.reservationId} END`,
169
+ templateKey: sql`CASE WHEN ${provisionedRunnerProjectionUpdateCondition()} THEN coalesce(excluded.template_key, ${provisionedRunners.templateKey}) ELSE ${provisionedRunners.templateKey} END`,
170
+ labels: sql`CASE WHEN ${provisionedRunnerProjectionUpdateCondition()} THEN excluded.labels ELSE ${provisionedRunners.labels} END`,
171
+ state: sql`CASE WHEN ${provisionedRunnerProjectionUpdateCondition()} THEN excluded.state ELSE ${provisionedRunners.state} END`,
172
+ reason: sql`CASE WHEN ${provisionedRunnerProjectionUpdateCondition()} THEN excluded.reason ELSE ${provisionedRunners.reason} END`,
173
+ runnerSessionId: sql`CASE WHEN ${provisionedRunnerProjectionUpdateCondition()} THEN coalesce(${provisionedRunners.runnerSessionId}, excluded.runner_session_id) ELSE ${provisionedRunners.runnerSessionId} END`,
174
+ providerKind: sql`CASE WHEN ${provisionedRunnerProjectionUpdateCondition()} THEN coalesce(excluded.provider_kind, ${provisionedRunners.providerKind}) ELSE ${provisionedRunners.providerKind} END`,
175
+ reportedAt: sql`CASE WHEN ${provisionedRunnerProjectionUpdateCondition()} THEN excluded.reported_at ELSE ${provisionedRunners.reportedAt} END`,
176
+ startedAt: firstObservedAt(provisionedRunners.startedAt, sql`excluded.started_at`),
177
+ stoppingAt: firstObservedAt(provisionedRunners.stoppingAt, sql`excluded.stopping_at`),
178
+ stoppedAt: firstObservedAt(provisionedRunners.stoppedAt, sql`excluded.stopped_at`),
179
+ failedAt: firstObservedAt(provisionedRunners.failedAt, sql`excluded.failed_at`),
180
+ terminatedAt: firstObservedAt(
181
+ provisionedRunners.terminatedAt,
182
+ sql`excluded.terminated_at`,
183
+ ),
184
+ updatedAt: sql`now()`,
185
+ },
186
+ setWhere: sql`
187
+ ${provisionedRunnerProjectionUpdateCondition()}
188
+ OR ${provisionedRunnerMilestoneUpdateCondition()}
189
+ `,
190
+ });
191
+
192
+ const reservationsReleased = hasTerminalEvent
193
+ ? await releaseTerminalProvisionedRunnerReservations(tx, params, events)
194
+ : 0;
195
+
196
+ return {accepted: events.length, reservationsReleased, terminateIntentsHonored};
197
+ });
198
+ }
199
+
200
+ export async function listActiveProvisionedRunnerCountsByTemplateTx(
201
+ tx: Tx,
202
+ params: {workspaceId: string; provisionerId: string},
203
+ ): Promise<ActiveProvisionedRunnerTemplateCount[]> {
204
+ const rows = await tx
205
+ .select({
206
+ templateKey: provisionedRunners.templateKey,
207
+ state: provisionedRunners.state,
208
+ count: sql<number>`count(*)::int`,
209
+ })
210
+ .from(provisionedRunners)
211
+ .where(
212
+ and(
213
+ eq(provisionedRunners.workspaceId, params.workspaceId),
214
+ eq(provisionedRunners.provisionerId, params.provisionerId),
215
+ inArray(provisionedRunners.state, divergenceCountStates),
216
+ isNotNull(provisionedRunners.templateKey),
217
+ ),
218
+ )
219
+ .groupBy(provisionedRunners.templateKey, provisionedRunners.state);
220
+
221
+ return rows.flatMap((row) =>
222
+ row.templateKey && isDivergenceCountState(row.state)
223
+ ? [{templateKey: row.templateKey, state: row.state, count: row.count}]
224
+ : [],
225
+ );
226
+ }
227
+
228
+ export async function listActiveProvisionedRunners(params: {
229
+ workspaceId: string;
230
+ windowSeconds: number;
231
+ limit?: number;
232
+ }): Promise<ProvisionedRunner[]> {
233
+ const rows = await db()
234
+ .select()
235
+ .from(provisionedRunners)
236
+ .where(
237
+ and(
238
+ eq(provisionedRunners.workspaceId, params.workspaceId),
239
+ inArray(provisionedRunners.state, activeStates),
240
+ sql`${provisionedRunners.updatedAt} > now() - (${params.windowSeconds} || ' seconds')::interval`,
241
+ ),
242
+ )
243
+ .orderBy(desc(provisionedRunners.updatedAt), desc(provisionedRunners.id))
244
+ .limit(params.limit ?? 1000);
245
+
246
+ return rows.map(toProvisionedRunner);
247
+ }
248
+
249
+ export async function listProvisionerTerminateIntents(params: {
250
+ workspaceId: string;
251
+ provisionerId: string;
252
+ limit: number;
253
+ }): Promise<string[]> {
254
+ return await db().transaction(async (tx) => {
255
+ const rows = await listProvisionerTerminateIntentRowsTx(tx, params);
256
+ return rows.map((row) => row.provisionedRunnerId);
257
+ });
258
+ }
259
+
260
+ export async function listProvisionerTerminateIntentRowsTx(
261
+ tx: Tx,
262
+ params: {
263
+ workspaceId: string;
264
+ provisionerId: string;
265
+ limit: number;
266
+ },
267
+ ): Promise<ProvisionedRunnerTerminateIntent[]> {
268
+ const rows = await provisionerTerminateIntentsQuery(tx, params)
269
+ .orderBy(asc(provisionedRunners.provisionedRunnerId))
270
+ .limit(params.limit + 1);
271
+
272
+ const truncated = rows.length > params.limit;
273
+ const returnedRows = truncated ? rows.slice(0, params.limit) : rows;
274
+ if (truncated) {
275
+ logger().warn(
276
+ {
277
+ workspaceId: params.workspaceId,
278
+ provisionerId: params.provisionerId,
279
+ limit: params.limit,
280
+ returnedCount: returnedRows.length,
281
+ },
282
+ 'provisioner terminate intents truncated by poll-demand limit',
283
+ );
284
+ }
285
+
286
+ return returnedRows;
287
+ }
288
+
289
+ function provisionerTerminateIntentsQuery(
290
+ tx: Tx,
291
+ params: {workspaceId: string; provisionerId: string; provisionedRunnerIds?: string[]},
292
+ ) {
293
+ const newerRunningJobExecutions = alias(runningJobExecutions, 'newer_running_jobs');
294
+
295
+ return tx
296
+ .select({
297
+ provisionedRunnerId: provisionedRunners.provisionedRunnerId,
298
+ reason: sql<ProvisionedRunnerTerminateIntentReason>`'job-cancelled'`,
299
+ })
300
+ .from(runningJobExecutions)
301
+ .innerJoin(
302
+ provisionedRunners,
303
+ and(
304
+ eq(provisionedRunners.workspaceId, runningJobExecutions.workspaceId),
305
+ eq(provisionedRunners.provisionerId, runningJobExecutions.provisionerId),
306
+ eq(provisionedRunners.provisionedRunnerId, runningJobExecutions.provisionedRunnerId),
307
+ ),
308
+ )
309
+ .where(
310
+ and(
311
+ eq(runningJobExecutions.workspaceId, params.workspaceId),
312
+ eq(runningJobExecutions.provisionerId, params.provisionerId),
313
+ isNotNull(runningJobExecutions.cancellationRequestedAt),
314
+ inArray(provisionedRunners.state, activeStates),
315
+ params.provisionedRunnerIds && params.provisionedRunnerIds.length > 0
316
+ ? inArray(provisionedRunners.provisionedRunnerId, params.provisionedRunnerIds)
317
+ : undefined,
318
+ notExists(
319
+ tx
320
+ .select({id: newerRunningJobExecutions.id})
321
+ .from(newerRunningJobExecutions)
322
+ .where(
323
+ and(
324
+ eq(newerRunningJobExecutions.workspaceId, runningJobExecutions.workspaceId),
325
+ eq(newerRunningJobExecutions.provisionerId, runningJobExecutions.provisionerId),
326
+ eq(
327
+ newerRunningJobExecutions.provisionedRunnerId,
328
+ runningJobExecutions.provisionedRunnerId,
329
+ ),
330
+ or(
331
+ gt(newerRunningJobExecutions.startedAt, runningJobExecutions.startedAt),
332
+ and(
333
+ eq(newerRunningJobExecutions.startedAt, runningJobExecutions.startedAt),
334
+ gt(
335
+ newerRunningJobExecutions.jobExecutionId,
336
+ runningJobExecutions.jobExecutionId,
337
+ ),
338
+ ),
339
+ ),
340
+ ),
341
+ ),
342
+ ),
343
+ ),
344
+ )
345
+ .groupBy(provisionedRunners.provisionedRunnerId);
346
+ }
347
+
348
+ async function listTerminateIntentsHonoredByTerminatedReportsTx(
349
+ tx: Tx,
350
+ params: ReportProvisionedRunnersParams,
351
+ events: ProvisionedRunnerReportEvent[],
352
+ ): Promise<ProvisionedRunnerTerminateIntent[]> {
353
+ const terminatedProvisionedRunnerIds = [
354
+ ...new Set(
355
+ events
356
+ .filter((event) => event.state === 'terminated')
357
+ .map((event) => event.provisionedRunnerId),
358
+ ),
359
+ ];
360
+ if (terminatedProvisionedRunnerIds.length === 0) return [];
361
+
362
+ return await provisionerTerminateIntentsQuery(tx, {
363
+ workspaceId: params.workspaceId,
364
+ provisionerId: params.provisionerId,
365
+ provisionedRunnerIds: terminatedProvisionedRunnerIds,
366
+ }).orderBy(asc(provisionedRunners.provisionedRunnerId));
367
+ }
368
+
369
+ export async function reconcileProvisionedRunners(
370
+ params: ReconcileProvisionedRunnersParams,
371
+ ): Promise<ReconcileProvisionedRunnersDbResult> {
372
+ const observedProvisionedRunnerIds = [...new Set(params.observedProvisionedRunnerIds)];
373
+
374
+ return await db().transaction(async (tx) => {
375
+ await tx.execute(sql`select pg_advisory_xact_lock(hashtext(${params.workspaceId}))`);
376
+
377
+ let absentIds: string[] = [];
378
+ let reservationsReleased = 0;
379
+ if (observedProvisionedRunnerIds.length > 0) {
380
+ const staleAbsentRows = await tx
381
+ .select({
382
+ id: provisionedRunners.id,
383
+ provisionedRunnerId: provisionedRunners.provisionedRunnerId,
384
+ })
385
+ .from(provisionedRunners)
386
+ .where(
387
+ and(
388
+ eq(provisionedRunners.workspaceId, params.workspaceId),
389
+ eq(provisionedRunners.provisionerId, params.provisionerId),
390
+ inArray(provisionedRunners.state, activeStates),
391
+ lt(
392
+ provisionedRunners.reportedAt,
393
+ sql`now() - (${params.terminateGraceSeconds} || ' seconds')::interval`,
394
+ ),
395
+ notInArray(provisionedRunners.provisionedRunnerId, observedProvisionedRunnerIds),
396
+ ),
397
+ );
398
+
399
+ if (staleAbsentRows.length > 0) {
400
+ const updated = await tx
401
+ .update(provisionedRunners)
402
+ .set({
403
+ state: 'terminated',
404
+ terminatedAt: sql`coalesce(${provisionedRunners.terminatedAt}, now())`,
405
+ updatedAt: sql`now()`,
406
+ })
407
+ .where(
408
+ and(
409
+ inArray(
410
+ provisionedRunners.id,
411
+ staleAbsentRows.map((row) => row.id),
412
+ ),
413
+ inArray(provisionedRunners.state, activeStates),
414
+ lt(
415
+ provisionedRunners.reportedAt,
416
+ sql`now() - (${params.terminateGraceSeconds} || ' seconds')::interval`,
417
+ ),
418
+ ),
419
+ )
420
+ .returning({provisionedRunnerId: provisionedRunners.provisionedRunnerId});
421
+
422
+ absentIds = updated.map((row) => row.provisionedRunnerId);
423
+ reservationsReleased = await releaseTerminalProvisionedRunnerReservationsByIds(tx, {
424
+ workspaceId: params.workspaceId,
425
+ provisionerId: params.provisionerId,
426
+ provisionedRunnerIds: absentIds,
427
+ });
428
+ }
429
+ }
430
+
431
+ const observedRows =
432
+ observedProvisionedRunnerIds.length === 0
433
+ ? []
434
+ : (
435
+ await tx
436
+ .select()
437
+ .from(provisionedRunners)
438
+ .where(
439
+ and(
440
+ eq(provisionedRunners.workspaceId, params.workspaceId),
441
+ eq(provisionedRunners.provisionerId, params.provisionerId),
442
+ inArray(provisionedRunners.provisionedRunnerId, observedProvisionedRunnerIds),
443
+ ),
444
+ )
445
+ ).map(toProvisionedRunner);
446
+
447
+ const boundJobExecutions = await listRunningJobExecutionsByProvisionedRunnerTx(tx, {
448
+ workspaceId: params.workspaceId,
449
+ provisionerId: params.provisionerId,
450
+ provisionedRunnerIds: observedProvisionedRunnerIds,
451
+ });
452
+
453
+ return {
454
+ observedRows,
455
+ boundJobExecutionsByProvisionedRunnerId: new Map(
456
+ boundJobExecutions.map((jobExecution) => [jobExecution.provisionedRunnerId, jobExecution]),
457
+ ),
458
+ absentIds,
459
+ reservationsReleased,
460
+ };
461
+ });
462
+ }
463
+
464
+ export async function reapStaleProvisionedRunners(params: {
465
+ thresholdSeconds: number;
466
+ limit: number;
467
+ }): Promise<ReapStaleProvisionedRunnersResult> {
468
+ const cutoff = staleProvisionedRunnerCutoff(params.thresholdSeconds);
469
+
470
+ return await db().transaction(async (tx) => {
471
+ const candidateRows = await tx
472
+ .select({
473
+ id: provisionedRunners.id,
474
+ workspaceId: provisionedRunners.workspaceId,
475
+ provisionerId: provisionedRunners.provisionerId,
476
+ provisionedRunnerId: provisionedRunners.provisionedRunnerId,
477
+ })
478
+ .from(provisionedRunners)
479
+ .where(staleProvisionedRunnerWhere(tx, cutoff))
480
+ .orderBy(asc(provisionedRunners.updatedAt), asc(provisionedRunners.id))
481
+ .limit(params.limit);
482
+
483
+ if (candidateRows.length === 0) return {reaped: 0, reservationsReleased: 0};
484
+
485
+ const workspaceIds = [...new Set(candidateRows.map((row) => row.workspaceId))].sort();
486
+ for (const workspaceId of workspaceIds) {
487
+ await tx.execute(sql`select pg_advisory_xact_lock(hashtext(${workspaceId}))`);
488
+ }
489
+
490
+ const updatedRows = await tx
491
+ .update(provisionedRunners)
492
+ .set({
493
+ state: 'failed',
494
+ reason: 'stale-provisioner',
495
+ failedAt: sql`coalesce(${provisionedRunners.failedAt}, now())`,
496
+ updatedAt: sql`now()`,
497
+ })
498
+ .where(
499
+ and(
500
+ inArray(
501
+ provisionedRunners.id,
502
+ candidateRows.map((row) => row.id),
503
+ ),
504
+ staleProvisionedRunnerWhere(tx, cutoff),
505
+ ),
506
+ )
507
+ .returning({
508
+ workspaceId: provisionedRunners.workspaceId,
509
+ provisionerId: provisionedRunners.provisionerId,
510
+ provisionedRunnerId: provisionedRunners.provisionedRunnerId,
511
+ });
512
+
513
+ let reservationsReleased = 0;
514
+ for (const group of groupProvisionedRunnerIds(updatedRows)) {
515
+ reservationsReleased += await releaseTerminalProvisionedRunnerReservationsByIds(tx, {
516
+ workspaceId: group.workspaceId,
517
+ provisionerId: group.provisionerId,
518
+ provisionedRunnerIds: group.provisionedRunnerIds,
519
+ requireUnlinkedSession: false,
520
+ });
521
+ }
522
+
523
+ return {reaped: updatedRows.length, reservationsReleased};
524
+ });
525
+ }
526
+
527
+ async function releaseTerminalProvisionedRunnerReservations(
528
+ tx: Tx,
529
+ params: ReportProvisionedRunnersParams,
530
+ events: ProvisionedRunnerReportEvent[],
531
+ ): Promise<number> {
532
+ const terminalEvents = events.filter((event) => isTerminalState(event.state));
533
+ if (terminalEvents.length === 0) return 0;
534
+
535
+ return await releaseTerminalProvisionedRunnerReservationsByIds(tx, {
536
+ workspaceId: params.workspaceId,
537
+ provisionerId: params.provisionerId,
538
+ provisionedRunnerIds: terminalEvents.map((event) => event.provisionedRunnerId),
539
+ });
540
+ }
541
+
542
+ async function releaseTerminalProvisionedRunnerReservationsByIds(
543
+ tx: Tx,
544
+ params: {
545
+ workspaceId: string;
546
+ provisionerId: string;
547
+ provisionedRunnerIds: string[];
548
+ requireUnlinkedSession?: boolean;
549
+ },
550
+ ): Promise<number> {
551
+ if (params.provisionedRunnerIds.length === 0) return 0;
552
+
553
+ const rows = await tx
554
+ .select({
555
+ id: provisionedRunners.id,
556
+ reservationId: provisionedRunners.reservationId,
557
+ })
558
+ .from(provisionedRunners)
559
+ .where(
560
+ and(
561
+ eq(provisionedRunners.workspaceId, params.workspaceId),
562
+ eq(provisionedRunners.provisionerId, params.provisionerId),
563
+ inArray(provisionedRunners.provisionedRunnerId, params.provisionedRunnerIds),
564
+ inArray(provisionedRunners.state, terminalStates),
565
+ isNotNull(provisionedRunners.reservationId),
566
+ params.requireUnlinkedSession === false
567
+ ? undefined
568
+ : isNull(provisionedRunners.runnerSessionId),
569
+ isNull(provisionedRunners.reservationReleasedAt),
570
+ ),
571
+ );
572
+
573
+ if (rows.length === 0) return 0;
574
+
575
+ const updated = await tx
576
+ .update(provisionedRunners)
577
+ .set({reservationReleasedAt: sql`now()`, updatedAt: sql`now()`})
578
+ .where(
579
+ and(
580
+ inArray(
581
+ provisionedRunners.id,
582
+ rows.map((row) => row.id),
583
+ ),
584
+ params.requireUnlinkedSession === false
585
+ ? undefined
586
+ : isNull(provisionedRunners.runnerSessionId),
587
+ isNull(provisionedRunners.reservationReleasedAt),
588
+ ),
589
+ )
590
+ .returning({reservationId: provisionedRunners.reservationId});
591
+
592
+ const releasesByReservationId = new Map<string, number>();
593
+ for (const row of updated) {
594
+ if (!row.reservationId) continue;
595
+ releasesByReservationId.set(
596
+ row.reservationId,
597
+ (releasesByReservationId.get(row.reservationId) ?? 0) + 1,
598
+ );
599
+ }
600
+
601
+ if (releasesByReservationId.size === 0) return 0;
602
+
603
+ return await releaseReservationUnits(tx, {
604
+ workspaceId: params.workspaceId,
605
+ provisionerId: params.provisionerId,
606
+ releases: [...releasesByReservationId].map(([reservationId, count]) => ({
607
+ reservationId,
608
+ count,
609
+ })),
610
+ });
611
+ }
612
+
613
+ function staleProvisionedRunnerCutoff(thresholdSeconds: number): SQL {
614
+ return sql`now() - (${thresholdSeconds} || ' seconds')::interval`;
615
+ }
616
+
617
+ function staleProvisionedRunnerWhere(tx: Tx, cutoff: SQL): SQL<boolean> {
618
+ return and(
619
+ inArray(provisionedRunners.state, activeStates),
620
+ lt(provisionedRunners.reportedAt, cutoff),
621
+ lt(provisionedRunners.updatedAt, cutoff),
622
+ exists(
623
+ tx
624
+ .select({id: provisionerTokens.id})
625
+ .from(provisionerTokens)
626
+ .where(
627
+ and(
628
+ eq(provisionerTokens.id, provisionedRunners.provisionerId),
629
+ eq(provisionerTokens.workspaceId, provisionedRunners.workspaceId),
630
+ or(isNull(provisionerTokens.lastSeenAt), lt(provisionerTokens.lastSeenAt, cutoff)),
631
+ ),
632
+ ),
633
+ ),
634
+ notExists(
635
+ tx
636
+ .select({id: runningJobExecutions.id})
637
+ .from(runningJobExecutions)
638
+ .where(
639
+ and(
640
+ eq(runningJobExecutions.workspaceId, provisionedRunners.workspaceId),
641
+ eq(runningJobExecutions.provisionerId, provisionedRunners.provisionerId),
642
+ eq(runningJobExecutions.provisionedRunnerId, provisionedRunners.provisionedRunnerId),
643
+ ),
644
+ ),
645
+ ),
646
+ notExists(
647
+ tx
648
+ .select({id: runnerSessions.id})
649
+ .from(runnerSessions)
650
+ .where(
651
+ and(
652
+ eq(runnerSessions.workspaceId, provisionedRunners.workspaceId),
653
+ eq(runnerSessions.provisionerId, provisionedRunners.provisionerId),
654
+ eq(runnerSessions.provisionedRunnerId, provisionedRunners.provisionedRunnerId),
655
+ sql`${runnerSessions.updatedAt} >= ${cutoff}`,
656
+ ),
657
+ ),
658
+ ),
659
+ ) as SQL<boolean>;
660
+ }
661
+
662
+ function groupProvisionedRunnerIds(
663
+ rows: Array<{workspaceId: string; provisionerId: string; provisionedRunnerId: string}>,
664
+ ): Array<{workspaceId: string; provisionerId: string; provisionedRunnerIds: string[]}> {
665
+ const groups = new Map<
666
+ string,
667
+ {workspaceId: string; provisionerId: string; provisionedRunnerIds: string[]}
668
+ >();
669
+ for (const row of rows) {
670
+ const key = `${row.workspaceId}:${row.provisionerId}`;
671
+ const group = groups.get(key) ?? {
672
+ workspaceId: row.workspaceId,
673
+ provisionerId: row.provisionerId,
674
+ provisionedRunnerIds: [],
675
+ };
676
+ group.provisionedRunnerIds.push(row.provisionedRunnerId);
677
+ groups.set(key, group);
678
+ }
679
+ return [...groups.values()];
680
+ }
681
+
682
+ async function hydrateRunnerSessionIdsFromConsumedTokens(
683
+ tx: Tx,
684
+ params: ReportProvisionedRunnersParams,
685
+ events: ProvisionedRunnerReportRow[],
686
+ ): Promise<ProvisionedRunnerReportRow[]> {
687
+ const provisionedRunnerIds = [...new Set(events.map((event) => event.provisionedRunnerId))];
688
+ if (provisionedRunnerIds.length === 0) return events;
689
+
690
+ const tokenRows = await tx
691
+ .select({
692
+ provisionedRunnerId: ephemeralRegistrationTokens.provisionedRunnerId,
693
+ consumedSessionId: ephemeralRegistrationTokens.consumedSessionId,
694
+ })
695
+ .from(ephemeralRegistrationTokens)
696
+ .where(
697
+ and(
698
+ eq(ephemeralRegistrationTokens.workspaceId, params.workspaceId),
699
+ eq(ephemeralRegistrationTokens.provisionerId, params.provisionerId),
700
+ inArray(ephemeralRegistrationTokens.provisionedRunnerId, provisionedRunnerIds),
701
+ isNotNull(ephemeralRegistrationTokens.consumedSessionId),
702
+ ),
703
+ )
704
+ .orderBy(
705
+ desc(ephemeralRegistrationTokens.consumedAt),
706
+ desc(ephemeralRegistrationTokens.createdAt),
707
+ );
708
+
709
+ const consumedSessionIdsByProvisionedRunnerId = new Map<string, string>();
710
+ for (const row of tokenRows) {
711
+ if (!row.consumedSessionId) continue;
712
+ if (consumedSessionIdsByProvisionedRunnerId.has(row.provisionedRunnerId)) continue;
713
+ consumedSessionIdsByProvisionedRunnerId.set(row.provisionedRunnerId, row.consumedSessionId);
714
+ }
715
+
716
+ if (consumedSessionIdsByProvisionedRunnerId.size === 0) return events;
717
+
718
+ return events.map((event) => {
719
+ const consumedSessionId = consumedSessionIdsByProvisionedRunnerId.get(
720
+ event.provisionedRunnerId,
721
+ );
722
+ if (!consumedSessionId || event.runnerSessionId === consumedSessionId) return event;
723
+ return {...event, runnerSessionId: consumedSessionId};
724
+ });
725
+ }
726
+
727
+ function aggregateEvents(
728
+ events: ProvisionedRunnerReportEvent[],
729
+ receivedAt: Date,
730
+ ): ProvisionedRunnerReportRow[] {
731
+ const byProvisionedRunnerId = new Map<string, ProvisionedRunnerReportRow>();
732
+ for (const rawEvent of events) {
733
+ const event = toProvisionedRunnerReportRow(rawEvent, receivedAt);
734
+ const existing = byProvisionedRunnerId.get(event.provisionedRunnerId);
735
+ if (existing) mergeMilestones(existing, event);
736
+ if (!existing || compareProvisionedRunnerReportEvents(event, existing) > 0) {
737
+ byProvisionedRunnerId.set(
738
+ event.provisionedRunnerId,
739
+ existing ? mergeProjectionMetadata(event, existing) : event,
740
+ );
741
+ }
742
+ }
743
+ return [...byProvisionedRunnerId.values()];
744
+ }
745
+
746
+ function toProvisionedRunnerReportRow(
747
+ event: ProvisionedRunnerReportEvent,
748
+ receivedAt: Date,
749
+ ): ProvisionedRunnerReportRow {
750
+ const reportedAt = event.reportedAt > receivedAt ? receivedAt : event.reportedAt;
751
+ return {
752
+ ...event,
753
+ reportedAt,
754
+ startedAt: event.state === 'running' ? reportedAt : null,
755
+ stoppingAt: event.state === 'stopping' ? reportedAt : null,
756
+ stoppedAt: event.state === 'stopped' ? reportedAt : null,
757
+ failedAt: event.state === 'failed' ? reportedAt : null,
758
+ terminatedAt: event.state === 'terminated' ? reportedAt : null,
759
+ };
760
+ }
761
+
762
+ function mergeMilestones(target: ProvisionedRunnerReportRow, source: ProvisionedRunnerReportRow) {
763
+ target.startedAt = earliestDate(target.startedAt, source.startedAt);
764
+ target.stoppingAt = earliestDate(target.stoppingAt, source.stoppingAt);
765
+ target.stoppedAt = earliestDate(target.stoppedAt, source.stoppedAt);
766
+ target.failedAt = earliestDate(target.failedAt, source.failedAt);
767
+ target.terminatedAt = earliestDate(target.terminatedAt, source.terminatedAt);
768
+ }
769
+
770
+ function mergeProjectionMetadata(
771
+ event: ProvisionedRunnerReportRow,
772
+ existing: ProvisionedRunnerReportRow,
773
+ ): ProvisionedRunnerReportRow {
774
+ return {
775
+ ...event,
776
+ reservationId: event.reservationId ?? existing.reservationId,
777
+ templateKey: event.templateKey ?? existing.templateKey,
778
+ runnerSessionId: event.runnerSessionId ?? existing.runnerSessionId,
779
+ providerKind: event.providerKind ?? existing.providerKind,
780
+ ...pickMilestones(existing),
781
+ };
782
+ }
783
+
784
+ function pickMilestones(event: ProvisionedRunnerReportRow) {
785
+ return {
786
+ startedAt: event.startedAt,
787
+ stoppingAt: event.stoppingAt,
788
+ stoppedAt: event.stoppedAt,
789
+ failedAt: event.failedAt,
790
+ terminatedAt: event.terminatedAt,
791
+ };
792
+ }
793
+
794
+ function earliestDate(a: Date | null, b: Date | null): Date | null {
795
+ if (!a) return b;
796
+ if (!b) return a;
797
+ return a < b ? a : b;
798
+ }
799
+
800
+ function compareProvisionedRunnerReportEvents(
801
+ a: ProvisionedRunnerReportEvent,
802
+ b: ProvisionedRunnerReportEvent,
803
+ ): number {
804
+ const timeDelta = a.reportedAt.getTime() - b.reportedAt.getTime();
805
+ const rankDelta = getProvisionedRunnerStateRank(a.state) - getProvisionedRunnerStateRank(b.state);
806
+ if (rankDelta !== 0) return rankDelta;
807
+ return timeDelta;
808
+ }
809
+
810
+ function getProvisionedRunnerStateRank(state: ProvisionedRunnerState): number {
811
+ switch (state) {
812
+ case 'starting':
813
+ return 1;
814
+ case 'running':
815
+ return 2;
816
+ case 'stopping':
817
+ return 3;
818
+ case 'stopped':
819
+ return 4;
820
+ case 'failed':
821
+ return 5;
822
+ case 'terminated':
823
+ return 6;
824
+ }
825
+ }
826
+
827
+ function provisionedRunnerStateRank(state: SQL | typeof provisionedRunners.state): SQL<number> {
828
+ return sql<number>`
829
+ CASE ${state}
830
+ WHEN 'starting' THEN 1
831
+ WHEN 'running' THEN 2
832
+ WHEN 'stopping' THEN 3
833
+ WHEN 'stopped' THEN 4
834
+ WHEN 'failed' THEN 5
835
+ WHEN 'terminated' THEN 6
836
+ ELSE 0
837
+ END
838
+ `;
839
+ }
840
+
841
+ function provisionedRunnerProjectionUpdateCondition(): SQL<boolean> {
842
+ return sql<boolean>`
843
+ ${provisionedRunnerStateRank(sql`excluded.state`)} > ${provisionedRunnerStateRank(provisionedRunners.state)}
844
+ OR (
845
+ ${provisionedRunnerStateRank(sql`excluded.state`)} = ${provisionedRunnerStateRank(provisionedRunners.state)}
846
+ AND excluded.reported_at >= ${provisionedRunners.reportedAt}
847
+ )
848
+ `;
849
+ }
850
+
851
+ function provisionedRunnerMilestoneUpdateCondition(): SQL<boolean> {
852
+ return sql<boolean>`
853
+ ${milestoneNeedsUpdate(provisionedRunners.startedAt, sql`excluded.started_at`)}
854
+ OR ${milestoneNeedsUpdate(provisionedRunners.stoppingAt, sql`excluded.stopping_at`)}
855
+ OR ${milestoneNeedsUpdate(provisionedRunners.stoppedAt, sql`excluded.stopped_at`)}
856
+ OR ${milestoneNeedsUpdate(provisionedRunners.failedAt, sql`excluded.failed_at`)}
857
+ OR ${milestoneNeedsUpdate(provisionedRunners.terminatedAt, sql`excluded.terminated_at`)}
858
+ `;
859
+ }
860
+
861
+ function milestoneNeedsUpdate(
862
+ current: SQL | ProvisionedRunnerMilestoneColumn,
863
+ incoming: SQL,
864
+ ): SQL<boolean> {
865
+ return sql<boolean>`${incoming} IS NOT NULL AND (${current} IS NULL OR ${incoming} < ${current})`;
866
+ }
867
+
868
+ function firstObservedAt(current: SQL | ProvisionedRunnerMilestoneColumn, incoming: SQL) {
869
+ return sql`
870
+ CASE
871
+ WHEN ${incoming} IS NULL THEN ${current}
872
+ WHEN ${current} IS NULL THEN ${incoming}
873
+ ELSE least(${current}, ${incoming})
874
+ END
875
+ `;
876
+ }
877
+
878
+ export function isTerminalState(state: ProvisionedRunnerState): boolean {
879
+ return terminalStates.includes(state as (typeof terminalStates)[number]);
880
+ }
881
+
882
+ function isDivergenceCountState(
883
+ state: ProvisionedRunnerState,
884
+ ): state is (typeof divergenceCountStates)[number] {
885
+ return divergenceCountStates.includes(state as (typeof divergenceCountStates)[number]);
886
+ }