@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,647 @@
1
+ import {
2
+ RUNNER_JOB_CLAIMED,
3
+ RUNNER_JOB_LEASE_EXPIRED,
4
+ RUNNER_JOB_QUEUED,
5
+ type RunnersEventMap,
6
+ type RunnerToolCapabilitiesDto,
7
+ } from '@shipfox/api-runners-dto';
8
+ import {logger} from '@shipfox/node-opentelemetry';
9
+ import {writeOutboxEvent, writeOutboxEvents} from '@shipfox/node-outbox';
10
+ import {canonicalizeLabels} from '@shipfox/runner-labels';
11
+ import {
12
+ and,
13
+ arrayContained,
14
+ asc,
15
+ count,
16
+ desc,
17
+ eq,
18
+ gt,
19
+ inArray,
20
+ isNotNull,
21
+ isNull,
22
+ lt,
23
+ lte,
24
+ or,
25
+ sql,
26
+ } from 'drizzle-orm';
27
+ import {
28
+ EmptyRequiredLabelsError,
29
+ RunnerSessionExhaustedError,
30
+ RunningJobExecutionNotFoundError,
31
+ } from '#core/errors.js';
32
+ import {jobExecutionEnqueuedCount, jobExecutionLeaseExpiredCount} from '#metrics/instance.js';
33
+ import type {Tx} from './db.js';
34
+ import {db} from './db.js';
35
+ import {runnersOutbox} from './schema/outbox.js';
36
+ import {pendingJobExecutions} from './schema/pending-job-executions.js';
37
+ import {runnerSessions} from './schema/runner-sessions.js';
38
+ import {runningJobExecutions} from './schema/running-job-executions.js';
39
+
40
+ export interface EnqueueJobExecutionParams {
41
+ workspaceId: string;
42
+ workflowRunId: string;
43
+ workflowRunAttemptId: string;
44
+ jobId: string;
45
+ jobExecutionId: string;
46
+ projectId: string;
47
+ requiredLabels: string[];
48
+ }
49
+
50
+ // Idempotent while the job execution is still pending: a duplicate jobExecutionId already in
51
+ // `runners_pending_jobs` is a no-op. Temporal retries the enqueue activity
52
+ // at-least-once, so a unique-violation throw on a retry-after-lost-result
53
+ // would permanently fail a healthy job execution's workflow. The guard does not extend
54
+ // past the claim: once the job execution has moved to `runners_running_jobs`, a retry
55
+ // can reinsert an orphan pending row, which a later `claimPendingJobExecution` drops
56
+ // via the running-execution unique constraint (onConflictDoNothing) instead of failing.
57
+ export async function enqueueJobExecution(params: EnqueueJobExecutionParams): Promise<void> {
58
+ const requiredLabels = [...canonicalizeLabels(params.requiredLabels)];
59
+ if (requiredLabels.length === 0) throw new EmptyRequiredLabelsError();
60
+
61
+ const enqueued = await db().transaction(async (tx) => {
62
+ const [inserted] = await tx
63
+ .insert(pendingJobExecutions)
64
+ .values({
65
+ workspaceId: params.workspaceId,
66
+ workflowRunId: params.workflowRunId,
67
+ workflowRunAttemptId: params.workflowRunAttemptId,
68
+ jobId: params.jobId,
69
+ jobExecutionId: params.jobExecutionId,
70
+ projectId: params.projectId,
71
+ requiredLabels,
72
+ })
73
+ .onConflictDoNothing({target: pendingJobExecutions.jobExecutionId})
74
+ .returning({createdAt: pendingJobExecutions.createdAt});
75
+
76
+ // A retry that hits the conflict inserts nothing: the first enqueue already
77
+ // emitted the queued event (durably, in the outbox), so re-emitting would
78
+ // only add a redundant row the subscriber coalesces away. Skip it.
79
+ if (!inserted) return false;
80
+
81
+ await writeOutboxEvent<RunnersEventMap>(tx, runnersOutbox, {
82
+ type: RUNNER_JOB_QUEUED,
83
+ payload: {
84
+ workflowRunId: params.workflowRunId,
85
+ workflowRunAttemptId: params.workflowRunAttemptId,
86
+ jobId: params.jobId,
87
+ jobExecutionId: params.jobExecutionId,
88
+ queuedAt: inserted.createdAt.toISOString(),
89
+ },
90
+ });
91
+ return true;
92
+ });
93
+
94
+ if (enqueued) jobExecutionEnqueuedCount.add(1);
95
+ }
96
+
97
+ export interface ClaimedJobExecution {
98
+ workflowRunId: string;
99
+ workflowRunAttemptId: string;
100
+ jobId: string;
101
+ jobExecutionId: string;
102
+ projectId: string;
103
+ }
104
+
105
+ export interface ActiveRunningJobExecution {
106
+ workflowRunId: string;
107
+ workflowRunAttemptId: string;
108
+ jobId: string;
109
+ jobExecutionId: string;
110
+ projectId: string;
111
+ runnerSessionId: string;
112
+ provisionerId: string | null;
113
+ provisionedRunnerId: string | null;
114
+ requiredLabels: string[];
115
+ runnerLabels: string[];
116
+ startedAt: Date;
117
+ lastHeartbeatAt: Date;
118
+ }
119
+
120
+ export interface ProvisionedRunnerBoundJobExecution {
121
+ workflowRunId: string;
122
+ workflowRunAttemptId: string;
123
+ jobId: string;
124
+ jobExecutionId: string;
125
+ provisionedRunnerId: string;
126
+ startedAt: Date;
127
+ lastHeartbeatAt: Date;
128
+ cancellationRequestedAt: Date | null;
129
+ }
130
+
131
+ export async function claimPendingJobExecution(params: {
132
+ workspaceId: string;
133
+ runnerSessionId: string;
134
+ sessionLabels: string[];
135
+ maxClaims: number | null;
136
+ runnerSessionLivenessThrottleSeconds: number;
137
+ }): Promise<ClaimedJobExecution | null> {
138
+ await touchRunnerSessionLiveness({
139
+ workspaceId: params.workspaceId,
140
+ runnerSessionId: params.runnerSessionId,
141
+ throttleSeconds: params.runnerSessionLivenessThrottleSeconds,
142
+ });
143
+
144
+ if (params.sessionLabels.length === 0) return null;
145
+
146
+ return await db().transaction(async (tx) => {
147
+ let provisionerId: string | null = null;
148
+ let provisionedRunnerId: string | null = null;
149
+
150
+ if (params.maxClaims !== null) {
151
+ const [session] = await tx
152
+ .select({
153
+ maxClaims: runnerSessions.maxClaims,
154
+ claimsUsed: runnerSessions.claimsUsed,
155
+ provisionerId: runnerSessions.provisionerId,
156
+ provisionedRunnerId: runnerSessions.provisionedRunnerId,
157
+ })
158
+ .from(runnerSessions)
159
+ .where(eq(runnerSessions.id, params.runnerSessionId))
160
+ .limit(1)
161
+ .for('update');
162
+
163
+ if (!session || session.maxClaims === null || session.claimsUsed >= session.maxClaims) {
164
+ throw new RunnerSessionExhaustedError(params.runnerSessionId);
165
+ }
166
+
167
+ // Ephemeral sessions are the only capped sessions, and the DB check keeps
168
+ // their provisioned-runner link present as a pair.
169
+ provisionerId = session.provisionerId;
170
+ provisionedRunnerId = session.provisionedRunnerId;
171
+ }
172
+
173
+ // `id` is a uuidv7 (time-ordered), so it is a deterministic FIFO tiebreaker
174
+ // for rows sharing a created_at within a batch.
175
+ const [row] = await tx
176
+ .select()
177
+ .from(pendingJobExecutions)
178
+ .where(
179
+ and(
180
+ eq(pendingJobExecutions.workspaceId, params.workspaceId),
181
+ arrayContained(pendingJobExecutions.requiredLabels, params.sessionLabels),
182
+ ),
183
+ )
184
+ .orderBy(asc(pendingJobExecutions.createdAt), asc(pendingJobExecutions.id))
185
+ .limit(1)
186
+ .for('update', {skipLocked: true});
187
+
188
+ if (!row) return null;
189
+
190
+ await tx.delete(pendingJobExecutions).where(eq(pendingJobExecutions.id, row.id));
191
+
192
+ // An enqueue retry that lands after a prior claim can leave an orphan pending
193
+ // row whose jobExecutionId is already in `runners_running_jobs`. Insert-or-skip
194
+ // on the jobExecutionId unique constraint: when the job execution is already running
195
+ // the insert touches no row, so we commit the orphan's deletion and return
196
+ // null rather than let the unique violation roll the claim back into a poison
197
+ // loop. The runner just re-polls for a real job execution.
198
+ const inserted = await tx
199
+ .insert(runningJobExecutions)
200
+ .values({
201
+ workspaceId: row.workspaceId,
202
+ workflowRunId: row.workflowRunId,
203
+ workflowRunAttemptId: row.workflowRunAttemptId,
204
+ jobId: row.jobId,
205
+ jobExecutionId: row.jobExecutionId,
206
+ projectId: row.projectId,
207
+ runnerSessionId: params.runnerSessionId,
208
+ provisionerId,
209
+ provisionedRunnerId,
210
+ requiredLabels: row.requiredLabels,
211
+ runnerLabels: params.sessionLabels,
212
+ })
213
+ .onConflictDoNothing({target: runningJobExecutions.jobExecutionId})
214
+ .returning({claimedAt: runningJobExecutions.startedAt});
215
+
216
+ const claimed = inserted[0];
217
+ if (!claimed) return null;
218
+
219
+ if (params.maxClaims !== null) {
220
+ await tx
221
+ .update(runnerSessions)
222
+ .set({claimsUsed: sql`${runnerSessions.claimsUsed} + 1`, updatedAt: sql`now()`})
223
+ .where(eq(runnerSessions.id, params.runnerSessionId));
224
+ }
225
+
226
+ // The running-row insert is the runner claiming the job execution. Emit in the same tx; the
227
+ // payload carries the row's own claim instant so a consumer records the true time,
228
+ // not the outbox drain time.
229
+ await writeOutboxEvent<RunnersEventMap>(tx, runnersOutbox, {
230
+ type: RUNNER_JOB_CLAIMED,
231
+ payload: {
232
+ workflowRunId: row.workflowRunId,
233
+ workflowRunAttemptId: row.workflowRunAttemptId,
234
+ jobId: row.jobId,
235
+ jobExecutionId: row.jobExecutionId,
236
+ claimedAt: claimed.claimedAt.toISOString(),
237
+ },
238
+ });
239
+
240
+ return {
241
+ workflowRunId: row.workflowRunId,
242
+ workflowRunAttemptId: row.workflowRunAttemptId,
243
+ jobId: row.jobId,
244
+ jobExecutionId: row.jobExecutionId,
245
+ projectId: row.projectId,
246
+ };
247
+ });
248
+ }
249
+
250
+ async function touchRunnerSessionLiveness(params: {
251
+ workspaceId: string;
252
+ runnerSessionId: string;
253
+ throttleSeconds: number;
254
+ }): Promise<void> {
255
+ await db()
256
+ .update(runnerSessions)
257
+ .set({updatedAt: sql`now()`})
258
+ .where(
259
+ and(
260
+ eq(runnerSessions.id, params.runnerSessionId),
261
+ eq(runnerSessions.workspaceId, params.workspaceId),
262
+ sql`${runnerSessions.updatedAt} < now() - (${params.throttleSeconds} || ' seconds')::interval`,
263
+ ),
264
+ );
265
+ }
266
+
267
+ /**
268
+ * Releases a job execution's lease when the orchestration workflow finalizes it: deletes the
269
+ * running-job-execution row AND any lingering pending row for the same execution, in one tx.
270
+ * Idempotent (0-row no-op), no token scope (the workflow is authoritative), and
271
+ * emits no event — the workflow already owns the outcome. Sweeping the pending row
272
+ * too closes the at-least-once window where an enqueue retry left an orphan that a
273
+ * later claim would otherwise pick up for an already-finished job execution.
274
+ */
275
+ export async function releaseJobExecution(params: {jobExecutionId: string}): Promise<void> {
276
+ await db().transaction(async (tx) => {
277
+ // Delete pending before running to match `claimPendingJobExecution`'s lock-acquisition
278
+ // order (it locks the pending row first, then the running row). A concurrent
279
+ // claim picking up an orphan pending row for this same job execution would otherwise
280
+ // deadlock against the reverse order here.
281
+ await tx
282
+ .delete(pendingJobExecutions)
283
+ .where(eq(pendingJobExecutions.jobExecutionId, params.jobExecutionId));
284
+ await tx
285
+ .delete(runningJobExecutions)
286
+ .where(eq(runningJobExecutions.jobExecutionId, params.jobExecutionId));
287
+ });
288
+ }
289
+
290
+ /**
291
+ * Reaps stale leases (bounded by `limit`), emitting one
292
+ * `runners.job.lease_expired` event per reaped job execution.
293
+ *
294
+ * The cutoff is re-checked in the DELETE, not just the locking subquery, so a
295
+ * heartbeat landing mid-call spares the live row. Each reaped execution also sweeps its
296
+ * pending row: a failed best-effort `releaseJobExecution` would otherwise leave an orphan
297
+ * that a later claim re-runs as an already-finished job execution.
298
+ *
299
+ * Locks running-then-pending, the inverse of `claimPendingJobExecution` / `releaseJobExecution`.
300
+ * That pre-existing asymmetry opens a narrow deadlock window against a concurrent
301
+ * claim of the same orphan-pending job execution; Postgres breaks it and the cron retries.
302
+ */
303
+ export async function expireStuckJobExecutions(params: {
304
+ thresholdSeconds: number;
305
+ noFirstHeartbeatGraceSeconds: number;
306
+ limit?: number;
307
+ }): Promise<
308
+ Array<{
309
+ workflowRunId: string;
310
+ workflowRunAttemptId: string;
311
+ jobId: string;
312
+ jobExecutionId: string;
313
+ }>
314
+ > {
315
+ const reaped = await db().transaction(async (tx) => {
316
+ const heartbeatCutoff = sql`now() - (${params.thresholdSeconds} || ' seconds')::interval`;
317
+ const firstHeartbeatCutoff = sql`now() - (${params.noFirstHeartbeatGraceSeconds} || ' seconds')::interval`;
318
+ const stalePredicate = or(
319
+ and(
320
+ isNull(runningJobExecutions.firstHeartbeatAt),
321
+ lte(runningJobExecutions.lastHeartbeatAt, runningJobExecutions.startedAt),
322
+ lt(runningJobExecutions.startedAt, firstHeartbeatCutoff),
323
+ ),
324
+ and(
325
+ or(
326
+ isNotNull(runningJobExecutions.firstHeartbeatAt),
327
+ gt(runningJobExecutions.lastHeartbeatAt, runningJobExecutions.startedAt),
328
+ ),
329
+ lt(runningJobExecutions.lastHeartbeatAt, heartbeatCutoff),
330
+ ),
331
+ );
332
+
333
+ const staleIds = tx
334
+ .select({id: runningJobExecutions.id})
335
+ .from(runningJobExecutions)
336
+ .where(stalePredicate)
337
+ .orderBy(
338
+ asc(
339
+ sql`CASE WHEN ${runningJobExecutions.firstHeartbeatAt} IS NULL AND ${runningJobExecutions.lastHeartbeatAt} <= ${runningJobExecutions.startedAt} THEN ${runningJobExecutions.startedAt} ELSE ${runningJobExecutions.lastHeartbeatAt} END`,
340
+ ),
341
+ )
342
+ .limit(params.limit ?? 100)
343
+ .for('update', {skipLocked: true});
344
+
345
+ const deleted = await tx
346
+ .delete(runningJobExecutions)
347
+ .where(and(inArray(runningJobExecutions.id, staleIds), stalePredicate))
348
+ .returning({
349
+ workflowRunId: runningJobExecutions.workflowRunId,
350
+ workflowRunAttemptId: runningJobExecutions.workflowRunAttemptId,
351
+ jobId: runningJobExecutions.jobId,
352
+ jobExecutionId: runningJobExecutions.jobExecutionId,
353
+ });
354
+
355
+ if (deleted.length === 0) return [];
356
+
357
+ await tx.delete(pendingJobExecutions).where(
358
+ inArray(
359
+ pendingJobExecutions.jobExecutionId,
360
+ deleted.map((row) => row.jobExecutionId),
361
+ ),
362
+ );
363
+
364
+ await writeOutboxEvents<RunnersEventMap>(
365
+ tx,
366
+ runnersOutbox,
367
+ deleted.map((row) => ({
368
+ type: RUNNER_JOB_LEASE_EXPIRED,
369
+ payload: {
370
+ workflowRunId: row.workflowRunId,
371
+ workflowRunAttemptId: row.workflowRunAttemptId,
372
+ jobId: row.jobId,
373
+ jobExecutionId: row.jobExecutionId,
374
+ },
375
+ })),
376
+ );
377
+
378
+ return deleted;
379
+ });
380
+
381
+ if (reaped.length > 0) jobExecutionLeaseExpiredCount.add(reaped.length);
382
+
383
+ return reaped;
384
+ }
385
+
386
+ export async function getJobExecutionQueueDepth(): Promise<{
387
+ pendingJobExecutions: number;
388
+ runningJobExecutions: number;
389
+ }> {
390
+ const [pending] = await db().select({value: count()}).from(pendingJobExecutions);
391
+ const [running] = await db().select({value: count()}).from(runningJobExecutions);
392
+ return {
393
+ pendingJobExecutions: pending?.value ?? 0,
394
+ runningJobExecutions: running?.value ?? 0,
395
+ };
396
+ }
397
+
398
+ export async function listActiveRunningJobExecutions(params: {
399
+ workspaceId: string;
400
+ windowSeconds: number;
401
+ limit?: number;
402
+ }): Promise<ActiveRunningJobExecution[]> {
403
+ return await db()
404
+ .select({
405
+ jobId: runningJobExecutions.jobId,
406
+ jobExecutionId: runningJobExecutions.jobExecutionId,
407
+ workflowRunId: runningJobExecutions.workflowRunId,
408
+ workflowRunAttemptId: runningJobExecutions.workflowRunAttemptId,
409
+ projectId: runningJobExecutions.projectId,
410
+ runnerSessionId: runningJobExecutions.runnerSessionId,
411
+ provisionerId: runningJobExecutions.provisionerId,
412
+ provisionedRunnerId: runningJobExecutions.provisionedRunnerId,
413
+ requiredLabels: runningJobExecutions.requiredLabels,
414
+ runnerLabels: runningJobExecutions.runnerLabels,
415
+ startedAt: runningJobExecutions.startedAt,
416
+ lastHeartbeatAt: runningJobExecutions.lastHeartbeatAt,
417
+ })
418
+ .from(runningJobExecutions)
419
+ .where(
420
+ and(
421
+ eq(runningJobExecutions.workspaceId, params.workspaceId),
422
+ sql`${runningJobExecutions.lastHeartbeatAt} > now() - (${params.windowSeconds} || ' seconds')::interval`,
423
+ ),
424
+ )
425
+ .orderBy(desc(runningJobExecutions.lastHeartbeatAt), desc(runningJobExecutions.id))
426
+ .limit(params.limit ?? 1000);
427
+ }
428
+
429
+ export async function listRunningJobExecutionsByProvisionedRunnerTx(
430
+ tx: Tx,
431
+ params: {
432
+ workspaceId: string;
433
+ provisionerId: string;
434
+ provisionedRunnerIds: string[];
435
+ },
436
+ ): Promise<ProvisionedRunnerBoundJobExecution[]> {
437
+ if (params.provisionedRunnerIds.length === 0) return [];
438
+
439
+ const duplicateRows = await tx
440
+ .select({
441
+ provisionedRunnerId: runningJobExecutions.provisionedRunnerId,
442
+ count: count(),
443
+ })
444
+ .from(runningJobExecutions)
445
+ .where(
446
+ and(
447
+ eq(runningJobExecutions.workspaceId, params.workspaceId),
448
+ eq(runningJobExecutions.provisionerId, params.provisionerId),
449
+ inArray(runningJobExecutions.provisionedRunnerId, params.provisionedRunnerIds),
450
+ ),
451
+ )
452
+ .groupBy(runningJobExecutions.provisionedRunnerId)
453
+ .having(sql`count(*) > 1`);
454
+
455
+ const duplicateProvisionedRunnerIds = duplicateRows.flatMap((row) =>
456
+ row.provisionedRunnerId ? [row.provisionedRunnerId] : [],
457
+ );
458
+ if (duplicateProvisionedRunnerIds.length > 0) {
459
+ logger().warn(
460
+ {
461
+ workspaceId: params.workspaceId,
462
+ provisionerId: params.provisionerId,
463
+ provisionedRunnerIds: duplicateProvisionedRunnerIds,
464
+ },
465
+ 'multiple running job executions are bound to the same provisioned runner',
466
+ );
467
+ }
468
+
469
+ const result = await tx.execute<{
470
+ workflowRunId: string;
471
+ workflowRunAttemptId: string;
472
+ jobId: string;
473
+ jobExecutionId: string;
474
+ provisionedRunnerId: string;
475
+ startedAt: Date | string;
476
+ lastHeartbeatAt: Date | string;
477
+ cancellationRequestedAt: Date | string | null;
478
+ }>(sql`
479
+ SELECT DISTINCT ON (${runningJobExecutions.provisionedRunnerId})
480
+ ${runningJobExecutions.workflowRunId} AS "workflowRunId",
481
+ ${runningJobExecutions.workflowRunAttemptId} AS "workflowRunAttemptId",
482
+ ${runningJobExecutions.jobId} AS "jobId",
483
+ ${runningJobExecutions.jobExecutionId} AS "jobExecutionId",
484
+ ${runningJobExecutions.provisionedRunnerId} AS "provisionedRunnerId",
485
+ ${runningJobExecutions.startedAt} AS "startedAt",
486
+ ${runningJobExecutions.lastHeartbeatAt} AS "lastHeartbeatAt",
487
+ ${runningJobExecutions.cancellationRequestedAt} AS "cancellationRequestedAt"
488
+ FROM ${runningJobExecutions}
489
+ WHERE
490
+ ${runningJobExecutions.workspaceId} = ${params.workspaceId}
491
+ AND ${runningJobExecutions.provisionerId} = ${params.provisionerId}
492
+ AND ${runningJobExecutions.provisionedRunnerId} IN (${sql.join(
493
+ params.provisionedRunnerIds.map((provisionedRunnerId) => sql`${provisionedRunnerId}`),
494
+ sql`, `,
495
+ )})
496
+ ORDER BY ${runningJobExecutions.provisionedRunnerId}, ${runningJobExecutions.startedAt} DESC, ${runningJobExecutions.jobExecutionId} DESC
497
+ `);
498
+
499
+ return result.rows.map((row) => ({
500
+ workflowRunId: row.workflowRunId,
501
+ workflowRunAttemptId: row.workflowRunAttemptId,
502
+ jobId: row.jobId,
503
+ jobExecutionId: row.jobExecutionId,
504
+ provisionedRunnerId: row.provisionedRunnerId,
505
+ startedAt: toDate(row.startedAt),
506
+ lastHeartbeatAt: toDate(row.lastHeartbeatAt),
507
+ cancellationRequestedAt: row.cancellationRequestedAt
508
+ ? toDate(row.cancellationRequestedAt)
509
+ : null,
510
+ }));
511
+ }
512
+
513
+ function toDate(value: Date | string): Date {
514
+ return value instanceof Date ? value : new Date(value);
515
+ }
516
+
517
+ export async function isJobLeaseActive(params: {
518
+ jobId?: string;
519
+ jobExecutionId: string;
520
+ runnerSessionId: string;
521
+ }): Promise<boolean> {
522
+ const [row] = await db()
523
+ .select({id: runningJobExecutions.id})
524
+ .from(runningJobExecutions)
525
+ .where(
526
+ and(
527
+ eq(runningJobExecutions.jobExecutionId, params.jobExecutionId),
528
+ eq(runningJobExecutions.runnerSessionId, params.runnerSessionId),
529
+ params.jobId === undefined ? undefined : eq(runningJobExecutions.jobId, params.jobId),
530
+ ),
531
+ )
532
+ .limit(1);
533
+
534
+ return row !== undefined;
535
+ }
536
+
537
+ export async function recordHeartbeat(params: {
538
+ jobExecutionId: string;
539
+ runnerSessionId: string;
540
+ toolCapabilities?: RunnerToolCapabilitiesDto | null;
541
+ }): Promise<{
542
+ cancellationRequested: boolean;
543
+ previousToolCapabilities: RunnerToolCapabilitiesDto | null;
544
+ currentToolCapabilities: RunnerToolCapabilitiesDto | null;
545
+ runningJobExecution: {
546
+ workflowRunId: string;
547
+ workflowRunAttemptId: string;
548
+ jobId: string;
549
+ jobExecutionId: string;
550
+ projectId: string;
551
+ workspaceId: string;
552
+ runnerSessionId: string;
553
+ };
554
+ }> {
555
+ const result = await db().transaction(async (tx) => {
556
+ const updated = await tx
557
+ .update(runningJobExecutions)
558
+ .set({
559
+ firstHeartbeatAt: sql`COALESCE(${runningJobExecutions.firstHeartbeatAt}, now())`,
560
+ lastHeartbeatAt: sql`now()`,
561
+ })
562
+ .where(
563
+ and(
564
+ eq(runningJobExecutions.jobExecutionId, params.jobExecutionId),
565
+ eq(runningJobExecutions.runnerSessionId, params.runnerSessionId),
566
+ ),
567
+ )
568
+ .returning({
569
+ cancellationRequestedAt: runningJobExecutions.cancellationRequestedAt,
570
+ workflowRunId: runningJobExecutions.workflowRunId,
571
+ workflowRunAttemptId: runningJobExecutions.workflowRunAttemptId,
572
+ jobId: runningJobExecutions.jobId,
573
+ jobExecutionId: runningJobExecutions.jobExecutionId,
574
+ projectId: runningJobExecutions.projectId,
575
+ workspaceId: runningJobExecutions.workspaceId,
576
+ runnerSessionId: runningJobExecutions.runnerSessionId,
577
+ });
578
+
579
+ const row = updated[0];
580
+ if (!row) throw new RunningJobExecutionNotFoundError(params.jobExecutionId);
581
+
582
+ const [previous] = await tx
583
+ .select({toolCapabilities: runnerSessions.toolCapabilities})
584
+ .from(runnerSessions)
585
+ .where(eq(runnerSessions.id, params.runnerSessionId))
586
+ .limit(1)
587
+ .for('update');
588
+
589
+ const [session] = await tx
590
+ .update(runnerSessions)
591
+ .set({
592
+ toolCapabilities: params.toolCapabilities ?? null,
593
+ toolCapabilitiesReportedAt: params.toolCapabilities ? sql`now()` : null,
594
+ updatedAt: sql`now()`,
595
+ })
596
+ .where(eq(runnerSessions.id, params.runnerSessionId))
597
+ .returning({toolCapabilities: runnerSessions.toolCapabilities});
598
+
599
+ return {
600
+ row,
601
+ previousToolCapabilities: previous?.toolCapabilities ?? null,
602
+ currentToolCapabilities: session?.toolCapabilities ?? null,
603
+ };
604
+ });
605
+
606
+ const row = result.row;
607
+ if (!row) throw new RunningJobExecutionNotFoundError(params.jobExecutionId);
608
+ return {
609
+ cancellationRequested: row.cancellationRequestedAt !== null,
610
+ previousToolCapabilities: result.previousToolCapabilities,
611
+ currentToolCapabilities: result.currentToolCapabilities,
612
+ runningJobExecution: {
613
+ workflowRunId: row.workflowRunId,
614
+ workflowRunAttemptId: row.workflowRunAttemptId,
615
+ jobId: row.jobId,
616
+ jobExecutionId: row.jobExecutionId,
617
+ projectId: row.projectId,
618
+ workspaceId: row.workspaceId,
619
+ runnerSessionId: row.runnerSessionId,
620
+ },
621
+ };
622
+ }
623
+
624
+ export async function requestJobExecutionCancellation(params: {
625
+ jobExecutionId: string;
626
+ }): Promise<void> {
627
+ await db()
628
+ .update(runningJobExecutions)
629
+ .set({
630
+ cancellationRequestedAt: sql`COALESCE(${runningJobExecutions.cancellationRequestedAt}, now())`,
631
+ })
632
+ .where(eq(runningJobExecutions.jobExecutionId, params.jobExecutionId));
633
+ }
634
+
635
+ export async function cancelRunnerJobs(params: {jobIds: string[]}): Promise<void> {
636
+ if (params.jobIds.length === 0) return;
637
+
638
+ await db().transaction(async (tx) => {
639
+ await tx.delete(pendingJobExecutions).where(inArray(pendingJobExecutions.jobId, params.jobIds));
640
+ await tx
641
+ .update(runningJobExecutions)
642
+ .set({
643
+ cancellationRequestedAt: sql`COALESCE(${runningJobExecutions.cancellationRequestedAt}, now())`,
644
+ })
645
+ .where(inArray(runningJobExecutions.jobId, params.jobIds));
646
+ });
647
+ }