@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,147 @@
1
+ import type {WorkflowsJobExecutionTimedOutEventDto} from '@shipfox/api-workflows-dto';
2
+ import {eq} from 'drizzle-orm';
3
+ import {db} from '#db/db.js';
4
+ import {claimPendingJobExecution} from '#db/job-executions.js';
5
+ import {runningJobExecutions} from '#db/schema/running-job-executions.js';
6
+ import {pendingJobFactory, runnerSessionFactory} from '#test/index.js';
7
+ import {onWorkflowsJobExecutionTimedOut} from './on-workflows-job-execution-timed-out.js';
8
+
9
+ function buildPayload(
10
+ jobId: string,
11
+ jobExecutionId: string,
12
+ workflowRunAttemptId: string,
13
+ ): WorkflowsJobExecutionTimedOutEventDto {
14
+ return {jobId, jobExecutionId, workflowRunAttemptId};
15
+ }
16
+
17
+ describe('onWorkflowsJobExecutionTimedOut', () => {
18
+ let workspaceId: string;
19
+ let runnerSessionId: string;
20
+
21
+ beforeEach(async () => {
22
+ workspaceId = crypto.randomUUID();
23
+ const runnerSession = await runnerSessionFactory.create({workspaceId});
24
+ runnerSessionId = runnerSession.id;
25
+ });
26
+
27
+ it('sets cancellation_requested_at on the matching running_jobs row', async () => {
28
+ await pendingJobFactory.create({workspaceId});
29
+ const claimed = await claimPendingJobExecution({
30
+ workspaceId,
31
+ runnerSessionId,
32
+ sessionLabels: ['linux', 'x64'],
33
+ maxClaims: null,
34
+ runnerSessionLivenessThrottleSeconds: 10,
35
+ });
36
+ expect(claimed).not.toBeNull();
37
+
38
+ await onWorkflowsJobExecutionTimedOut(
39
+ buildPayload(
40
+ claimed?.jobId as string,
41
+ claimed?.jobExecutionId as string,
42
+ claimed?.workflowRunAttemptId as string,
43
+ ),
44
+ );
45
+
46
+ const rows = await db()
47
+ .select()
48
+ .from(runningJobExecutions)
49
+ .where(eq(runningJobExecutions.jobId, claimed?.jobId as string));
50
+ expect(rows[0]?.cancellationRequestedAt).not.toBeNull();
51
+ });
52
+
53
+ it('does not cancel another running execution for the same job', async () => {
54
+ await pendingJobFactory.create({workspaceId});
55
+ const claimed = await claimPendingJobExecution({
56
+ workspaceId,
57
+ runnerSessionId,
58
+ sessionLabels: ['linux', 'x64'],
59
+ maxClaims: null,
60
+ runnerSessionLivenessThrottleSeconds: 10,
61
+ });
62
+ expect(claimed).not.toBeNull();
63
+ const siblingJobExecutionId = crypto.randomUUID();
64
+ const siblingRunnerSession = await runnerSessionFactory.create({workspaceId});
65
+ await db()
66
+ .insert(runningJobExecutions)
67
+ .values({
68
+ workspaceId,
69
+ workflowRunId: claimed?.workflowRunId as string,
70
+ jobId: claimed?.jobId as string,
71
+ jobExecutionId: siblingJobExecutionId,
72
+ workflowRunAttemptId: claimed?.workflowRunAttemptId as string,
73
+ projectId: claimed?.projectId as string,
74
+ runnerSessionId: siblingRunnerSession.id,
75
+ requiredLabels: ['linux'],
76
+ runnerLabels: ['linux', 'x64'],
77
+ });
78
+
79
+ await onWorkflowsJobExecutionTimedOut(
80
+ buildPayload(
81
+ claimed?.jobId as string,
82
+ claimed?.jobExecutionId as string,
83
+ claimed?.workflowRunAttemptId as string,
84
+ ),
85
+ );
86
+
87
+ const rows = await db()
88
+ .select({
89
+ jobExecutionId: runningJobExecutions.jobExecutionId,
90
+ cancellationRequestedAt: runningJobExecutions.cancellationRequestedAt,
91
+ })
92
+ .from(runningJobExecutions)
93
+ .where(eq(runningJobExecutions.jobId, claimed?.jobId as string));
94
+ const byJobExecutionId = new Map(
95
+ rows.map((row) => [row.jobExecutionId, row.cancellationRequestedAt]),
96
+ );
97
+ expect(byJobExecutionId.get(claimed?.jobExecutionId as string)).not.toBeNull();
98
+ expect(byJobExecutionId.get(siblingJobExecutionId)).toBeNull();
99
+ });
100
+
101
+ it('idempotent under double delivery: second call preserves the first timestamp', async () => {
102
+ await pendingJobFactory.create({workspaceId});
103
+ const claimed = await claimPendingJobExecution({
104
+ workspaceId,
105
+ runnerSessionId,
106
+ sessionLabels: ['linux', 'x64'],
107
+ maxClaims: null,
108
+ runnerSessionLivenessThrottleSeconds: 10,
109
+ });
110
+
111
+ await onWorkflowsJobExecutionTimedOut(
112
+ buildPayload(
113
+ claimed?.jobId as string,
114
+ claimed?.jobExecutionId as string,
115
+ claimed?.workflowRunAttemptId as string,
116
+ ),
117
+ );
118
+ const after1 = await db()
119
+ .select()
120
+ .from(runningJobExecutions)
121
+ .where(eq(runningJobExecutions.jobId, claimed?.jobId as string));
122
+ const firstTs = after1[0]?.cancellationRequestedAt;
123
+
124
+ await new Promise((r) => setTimeout(r, 10));
125
+ await onWorkflowsJobExecutionTimedOut(
126
+ buildPayload(
127
+ claimed?.jobId as string,
128
+ claimed?.jobExecutionId as string,
129
+ claimed?.workflowRunAttemptId as string,
130
+ ),
131
+ );
132
+
133
+ const after2 = await db()
134
+ .select()
135
+ .from(runningJobExecutions)
136
+ .where(eq(runningJobExecutions.jobId, claimed?.jobId as string));
137
+ expect(after2[0]?.cancellationRequestedAt?.getTime()).toBe(firstTs?.getTime());
138
+ });
139
+
140
+ it('no-op when the running_jobs row is gone (already finalized)', async () => {
141
+ await expect(
142
+ onWorkflowsJobExecutionTimedOut(
143
+ buildPayload(crypto.randomUUID(), crypto.randomUUID(), crypto.randomUUID()),
144
+ ),
145
+ ).resolves.toBeUndefined();
146
+ });
147
+ });
@@ -0,0 +1,17 @@
1
+ import type {WorkflowsJobExecutionTimedOutEventDto} from '@shipfox/api-workflows-dto';
2
+ import {logger} from '@shipfox/node-opentelemetry';
3
+ import {requestJobExecutionCancellation} from '#db/job-executions.js';
4
+
5
+ export async function onWorkflowsJobExecutionTimedOut(
6
+ payload: WorkflowsJobExecutionTimedOutEventDto,
7
+ ): Promise<void> {
8
+ logger().info(
9
+ {
10
+ jobId: payload.jobId,
11
+ jobExecutionId: payload.jobExecutionId,
12
+ workflowRunAttemptId: payload.workflowRunAttemptId,
13
+ },
14
+ 'Requesting runner cancellation for timed-out job execution',
15
+ );
16
+ await requestJobExecutionCancellation({jobExecutionId: payload.jobExecutionId});
17
+ }
@@ -0,0 +1,17 @@
1
+ import {
2
+ deleteExpiredEphemeralRegistrationTokensActivity,
3
+ deleteExpiredReservationsActivity,
4
+ deleteExpiredRunnerSessionsActivity,
5
+ detectAndExpireStuckJobsActivity,
6
+ reapStaleProvisionedRunnersActivity,
7
+ } from './maintenance-activities.js';
8
+
9
+ export function createRunnersMaintenanceActivities() {
10
+ return {
11
+ deleteExpiredEphemeralRegistrationTokensActivity,
12
+ deleteExpiredReservationsActivity,
13
+ deleteExpiredRunnerSessionsActivity,
14
+ detectAndExpireStuckJobsActivity,
15
+ reapStaleProvisionedRunnersActivity,
16
+ };
17
+ }
@@ -0,0 +1,104 @@
1
+ import {
2
+ deleteExpiredEphemeralRegistrationTokens,
3
+ deleteExpiredRunnerReservations,
4
+ deleteExpiredRunnerSessions,
5
+ detectAndExpireStuckJobs,
6
+ reapStaleProvisionedRunners,
7
+ } from '#core/maintenance.js';
8
+ import {
9
+ deleteExpiredEphemeralRegistrationTokensActivity,
10
+ deleteExpiredReservationsActivity,
11
+ deleteExpiredRunnerSessionsActivity,
12
+ detectAndExpireStuckJobsActivity,
13
+ reapStaleProvisionedRunnersActivity,
14
+ } from './maintenance-activities.js';
15
+
16
+ vi.mock('#core/maintenance.js', () => ({
17
+ deleteExpiredEphemeralRegistrationTokens: vi.fn(),
18
+ deleteExpiredRunnerReservations: vi.fn(),
19
+ deleteExpiredRunnerSessions: vi.fn(),
20
+ detectAndExpireStuckJobs: vi.fn(),
21
+ reapStaleProvisionedRunners: vi.fn(),
22
+ }));
23
+
24
+ beforeEach(() => {
25
+ vi.clearAllMocks();
26
+ });
27
+
28
+ describe('detectAndExpireStuckJobsActivity', () => {
29
+ it('delegates to core maintenance', async () => {
30
+ vi.mocked(detectAndExpireStuckJobs).mockResolvedValueOnce({expired: 2});
31
+
32
+ const result = await detectAndExpireStuckJobsActivity({thresholdSeconds: 180});
33
+
34
+ expect(result).toEqual({expired: 2});
35
+ expect(detectAndExpireStuckJobs).toHaveBeenCalledWith({thresholdSeconds: 180});
36
+ });
37
+ });
38
+
39
+ describe('deleteExpiredReservationsActivity', () => {
40
+ it('delegates to core maintenance', async () => {
41
+ vi.mocked(deleteExpiredRunnerReservations).mockResolvedValueOnce({deleted: 3});
42
+
43
+ const result = await deleteExpiredReservationsActivity({limit: 50});
44
+
45
+ expect(result).toEqual({deleted: 3});
46
+ expect(deleteExpiredRunnerReservations).toHaveBeenCalledWith({limit: 50});
47
+ });
48
+ });
49
+
50
+ describe('reapStaleProvisionedRunnersActivity', () => {
51
+ it('delegates to core maintenance', async () => {
52
+ vi.mocked(reapStaleProvisionedRunners).mockResolvedValueOnce({
53
+ reaped: 4,
54
+ reservationsReleased: 2,
55
+ });
56
+
57
+ const result = await reapStaleProvisionedRunnersActivity({
58
+ thresholdSeconds: 300,
59
+ limit: 100,
60
+ });
61
+
62
+ expect(result).toEqual({reaped: 4, reservationsReleased: 2});
63
+ expect(reapStaleProvisionedRunners).toHaveBeenCalledWith({
64
+ thresholdSeconds: 300,
65
+ limit: 100,
66
+ });
67
+ });
68
+ });
69
+
70
+ describe('deleteExpiredRunnerSessionsActivity', () => {
71
+ it('delegates to core maintenance', async () => {
72
+ vi.mocked(deleteExpiredRunnerSessions).mockResolvedValueOnce({deleted: 4});
73
+
74
+ const result = await deleteExpiredRunnerSessionsActivity({
75
+ manualRetentionDays: 30,
76
+ ephemeralRetentionDays: 7,
77
+ limit: 25,
78
+ });
79
+
80
+ expect(result).toEqual({deleted: 4});
81
+ expect(deleteExpiredRunnerSessions).toHaveBeenCalledWith({
82
+ manualRetentionDays: 30,
83
+ ephemeralRetentionDays: 7,
84
+ limit: 25,
85
+ });
86
+ });
87
+ });
88
+
89
+ describe('deleteExpiredEphemeralRegistrationTokensActivity', () => {
90
+ it('delegates to core maintenance', async () => {
91
+ vi.mocked(deleteExpiredEphemeralRegistrationTokens).mockResolvedValueOnce({deleted: 6});
92
+
93
+ const result = await deleteExpiredEphemeralRegistrationTokensActivity({
94
+ retentionDays: 7,
95
+ limit: 25,
96
+ });
97
+
98
+ expect(result).toEqual({deleted: 6});
99
+ expect(deleteExpiredEphemeralRegistrationTokens).toHaveBeenCalledWith({
100
+ retentionDays: 7,
101
+ limit: 25,
102
+ });
103
+ });
104
+ });
@@ -0,0 +1,41 @@
1
+ import {
2
+ deleteExpiredEphemeralRegistrationTokens,
3
+ deleteExpiredRunnerReservations,
4
+ deleteExpiredRunnerSessions,
5
+ detectAndExpireStuckJobs,
6
+ reapStaleProvisionedRunners,
7
+ } from '#core/maintenance.js';
8
+
9
+ export function detectAndExpireStuckJobsActivity(params: {
10
+ thresholdSeconds: number;
11
+ }): Promise<{expired: number}> {
12
+ return detectAndExpireStuckJobs(params);
13
+ }
14
+
15
+ export function deleteExpiredReservationsActivity(params?: {
16
+ limit?: number;
17
+ }): Promise<{deleted: number}> {
18
+ return deleteExpiredRunnerReservations(params);
19
+ }
20
+
21
+ export function reapStaleProvisionedRunnersActivity(params?: {
22
+ thresholdSeconds: number;
23
+ limit: number;
24
+ }): Promise<{reaped: number; reservationsReleased: number}> {
25
+ return reapStaleProvisionedRunners(params);
26
+ }
27
+
28
+ export function deleteExpiredRunnerSessionsActivity(params?: {
29
+ manualRetentionDays?: number;
30
+ ephemeralRetentionDays?: number;
31
+ limit?: number;
32
+ }): Promise<{deleted: number}> {
33
+ return deleteExpiredRunnerSessions(params);
34
+ }
35
+
36
+ export function deleteExpiredEphemeralRegistrationTokensActivity(params?: {
37
+ retentionDays?: number;
38
+ limit?: number;
39
+ }): Promise<{deleted: number}> {
40
+ return deleteExpiredEphemeralRegistrationTokens(params);
41
+ }
@@ -0,0 +1 @@
1
+ export const RUNNERS_MAINTENANCE_TASK_QUEUE = 'runners-maintenance';
@@ -0,0 +1 @@
1
+ export {stuckJobDetector} from './stuck-job-detector.js';
@@ -0,0 +1,93 @@
1
+ const mocks = vi.hoisted(() => ({
2
+ deleteExpiredEphemeralRegistrationTokensActivity: vi.fn(),
3
+ deleteExpiredReservationsActivity: vi.fn(),
4
+ deleteExpiredRunnerSessionsActivity: vi.fn(),
5
+ detectAndExpireStuckJobsActivity: vi.fn(),
6
+ reapStaleProvisionedRunnersActivity: vi.fn(),
7
+ info: vi.fn(),
8
+ warn: vi.fn(),
9
+ }));
10
+
11
+ vi.mock('@temporalio/workflow', () => ({
12
+ log: {
13
+ info: mocks.info,
14
+ warn: mocks.warn,
15
+ },
16
+ proxyActivities: vi.fn(() => ({
17
+ deleteExpiredEphemeralRegistrationTokensActivity:
18
+ mocks.deleteExpiredEphemeralRegistrationTokensActivity,
19
+ deleteExpiredReservationsActivity: mocks.deleteExpiredReservationsActivity,
20
+ deleteExpiredRunnerSessionsActivity: mocks.deleteExpiredRunnerSessionsActivity,
21
+ detectAndExpireStuckJobsActivity: mocks.detectAndExpireStuckJobsActivity,
22
+ reapStaleProvisionedRunnersActivity: mocks.reapStaleProvisionedRunnersActivity,
23
+ })),
24
+ }));
25
+
26
+ describe('stuckJobDetector', () => {
27
+ beforeEach(() => {
28
+ vi.clearAllMocks();
29
+ mocks.deleteExpiredEphemeralRegistrationTokensActivity.mockResolvedValue({deleted: 0});
30
+ mocks.deleteExpiredReservationsActivity.mockResolvedValue({deleted: 0});
31
+ mocks.deleteExpiredRunnerSessionsActivity.mockResolvedValue({deleted: 0});
32
+ mocks.detectAndExpireStuckJobsActivity.mockResolvedValue({expired: 0});
33
+ mocks.reapStaleProvisionedRunnersActivity.mockResolvedValue({
34
+ reaped: 0,
35
+ reservationsReleased: 0,
36
+ });
37
+ });
38
+
39
+ it('runs expired session GC before stuck job expiry and logs deletions', async () => {
40
+ const {stuckJobDetector} = await import('./stuck-job-detector.js');
41
+ mocks.deleteExpiredRunnerSessionsActivity.mockResolvedValueOnce({deleted: 2});
42
+
43
+ await stuckJobDetector();
44
+
45
+ expect(mocks.deleteExpiredRunnerSessionsActivity).toHaveBeenCalledWith();
46
+ expect(mocks.detectAndExpireStuckJobsActivity).toHaveBeenCalledWith({thresholdSeconds: 180});
47
+ expect(mocks.info).toHaveBeenCalledWith('Stuck-job detector deleted expired runner sessions', {
48
+ deleted: 2,
49
+ });
50
+ });
51
+
52
+ it('continues stuck job expiry when expired session GC fails', async () => {
53
+ const {stuckJobDetector} = await import('./stuck-job-detector.js');
54
+ mocks.deleteExpiredRunnerSessionsActivity.mockRejectedValueOnce(new Error('database down'));
55
+
56
+ await stuckJobDetector();
57
+
58
+ expect(mocks.warn).toHaveBeenCalledWith(
59
+ 'Stuck-job detector failed to delete expired runner sessions',
60
+ {error: 'database down'},
61
+ );
62
+ expect(mocks.detectAndExpireStuckJobsActivity).toHaveBeenCalledWith({thresholdSeconds: 180});
63
+ });
64
+
65
+ it('runs expired ephemeral token GC before stuck job expiry and logs deletions', async () => {
66
+ const {stuckJobDetector} = await import('./stuck-job-detector.js');
67
+ mocks.deleteExpiredEphemeralRegistrationTokensActivity.mockResolvedValueOnce({deleted: 3});
68
+
69
+ await stuckJobDetector();
70
+
71
+ expect(mocks.deleteExpiredEphemeralRegistrationTokensActivity).toHaveBeenCalledWith();
72
+ expect(mocks.detectAndExpireStuckJobsActivity).toHaveBeenCalledWith({thresholdSeconds: 180});
73
+ expect(mocks.info).toHaveBeenCalledWith(
74
+ 'Stuck-job detector deleted expired ephemeral registration tokens',
75
+ {deleted: 3},
76
+ );
77
+ });
78
+
79
+ it('continues stuck job expiry when expired ephemeral token GC fails', async () => {
80
+ const {stuckJobDetector} = await import('./stuck-job-detector.js');
81
+ mocks.deleteExpiredEphemeralRegistrationTokensActivity.mockRejectedValueOnce(
82
+ new Error('database down'),
83
+ );
84
+
85
+ await stuckJobDetector();
86
+
87
+ expect(mocks.warn).toHaveBeenCalledWith(
88
+ 'Stuck-job detector failed to delete expired ephemeral registration tokens',
89
+ {error: 'database down'},
90
+ );
91
+ expect(mocks.detectAndExpireStuckJobsActivity).toHaveBeenCalledWith({thresholdSeconds: 180});
92
+ });
93
+ });
@@ -0,0 +1,67 @@
1
+ import {log, proxyActivities} from '@temporalio/workflow';
2
+ import {STUCK_JOB_THRESHOLD_SECONDS} from '#core/maintenance-policy.js';
3
+
4
+ import type {createRunnersMaintenanceActivities} from '../activities/index.js';
5
+
6
+ const {
7
+ deleteExpiredEphemeralRegistrationTokensActivity,
8
+ deleteExpiredReservationsActivity,
9
+ deleteExpiredRunnerSessionsActivity,
10
+ detectAndExpireStuckJobsActivity,
11
+ reapStaleProvisionedRunnersActivity,
12
+ } = proxyActivities<ReturnType<typeof createRunnersMaintenanceActivities>>({
13
+ startToCloseTimeout: '60s',
14
+ });
15
+
16
+ export async function stuckJobDetector(): Promise<void> {
17
+ try {
18
+ const {deleted} = await deleteExpiredReservationsActivity();
19
+ if (deleted > 0) {
20
+ log.info('Stuck-job detector deleted expired runner reservations', {deleted});
21
+ }
22
+ } catch (error) {
23
+ log.warn('Stuck-job detector failed to delete expired runner reservations', {
24
+ error: error instanceof Error ? error.message : String(error),
25
+ });
26
+ }
27
+
28
+ try {
29
+ const {deleted} = await deleteExpiredRunnerSessionsActivity();
30
+ if (deleted > 0) {
31
+ log.info('Stuck-job detector deleted expired runner sessions', {deleted});
32
+ }
33
+ } catch (error) {
34
+ log.warn('Stuck-job detector failed to delete expired runner sessions', {
35
+ error: error instanceof Error ? error.message : String(error),
36
+ });
37
+ }
38
+
39
+ try {
40
+ const {deleted} = await deleteExpiredEphemeralRegistrationTokensActivity();
41
+ if (deleted > 0) {
42
+ log.info('Stuck-job detector deleted expired ephemeral registration tokens', {deleted});
43
+ }
44
+ } catch (error) {
45
+ log.warn('Stuck-job detector failed to delete expired ephemeral registration tokens', {
46
+ error: error instanceof Error ? error.message : String(error),
47
+ });
48
+ }
49
+
50
+ const {expired} = await detectAndExpireStuckJobsActivity({
51
+ thresholdSeconds: STUCK_JOB_THRESHOLD_SECONDS,
52
+ });
53
+ if (expired > 0) {
54
+ log.info('Stuck-job detector expired job leases', {
55
+ expired,
56
+ thresholdSeconds: STUCK_JOB_THRESHOLD_SECONDS,
57
+ });
58
+ }
59
+
60
+ const {reaped, reservationsReleased} = await reapStaleProvisionedRunnersActivity();
61
+ if (reaped > 0) {
62
+ log.info('Stuck-job detector reaped stale provisioned runners', {
63
+ reaped,
64
+ reservationsReleased,
65
+ });
66
+ }
67
+ }
package/test/env.ts ADDED
@@ -0,0 +1,11 @@
1
+ process.env.POSTGRES_HOST ??= 'localhost';
2
+ process.env.POSTGRES_PORT ??= '5432';
3
+ process.env.POSTGRES_USERNAME ??= 'shipfox';
4
+ process.env.POSTGRES_PASSWORD ??= 'password';
5
+ process.env.POSTGRES_DATABASE = 'api_test';
6
+ process.env.POSTGRES_MAX_CONNECTIONS ??= '5';
7
+ process.env.WORKSPACE_JWT_SECRET = 'test-secret';
8
+ process.env.AUTH_JWT_SECRET = 'test-secret';
9
+ process.env.AUTH_JOB_LEASE_TOKEN_SECRET = 'test-lease-secret';
10
+ process.env.AUTH_RUNNER_SESSION_TOKEN_SECRET = 'test-runner-session-secret';
11
+ process.env.TZ = 'UTC';
@@ -0,0 +1,41 @@
1
+ import {extractDisplayPrefix, generateOpaqueToken, hashOpaqueToken} from '@shipfox/node-tokens';
2
+ import {Factory} from 'fishery';
3
+ import type {EphemeralRegistrationToken} from '#core/entities/ephemeral-registration-token.js';
4
+ import {createEphemeralRegistrationToken} from '#db/ephemeral-registration-tokens.js';
5
+
6
+ export interface EphemeralRegistrationTokenFactoryTransientParams {
7
+ rawToken?: string;
8
+ }
9
+
10
+ export const ephemeralRegistrationTokenFactory = Factory.define<
11
+ EphemeralRegistrationToken,
12
+ EphemeralRegistrationTokenFactoryTransientParams
13
+ >(({onCreate, transientParams}) => {
14
+ const rawToken = transientParams.rawToken ?? generateOpaqueToken('ephemeralRegistrationToken');
15
+
16
+ onCreate((token) => {
17
+ return createEphemeralRegistrationToken({
18
+ workspaceId: token.workspaceId,
19
+ provisionerId: token.provisionerId,
20
+ reservationId: token.reservationId,
21
+ provisionedRunnerId: token.provisionedRunnerId,
22
+ hashedToken: token.hashedToken,
23
+ prefix: token.prefix,
24
+ expiresAt: token.expiresAt,
25
+ });
26
+ });
27
+
28
+ return {
29
+ id: crypto.randomUUID(),
30
+ workspaceId: crypto.randomUUID(),
31
+ provisionerId: crypto.randomUUID(),
32
+ reservationId: null,
33
+ provisionedRunnerId: `provisioned-runner-${crypto.randomUUID()}`,
34
+ hashedToken: hashOpaqueToken(rawToken),
35
+ prefix: extractDisplayPrefix(rawToken),
36
+ expiresAt: new Date(Date.now() + 60_000),
37
+ consumedAt: null,
38
+ consumedSessionId: null,
39
+ createdAt: new Date(),
40
+ };
41
+ });
@@ -0,0 +1,37 @@
1
+ import {extractDisplayPrefix, generateOpaqueToken, hashOpaqueToken} from '@shipfox/node-tokens';
2
+ import {Factory} from 'fishery';
3
+ import type {ManualRegistrationToken} from '#core/entities/manual-registration-token.js';
4
+ import {createManualRegistrationToken} from '#db/manual-registration-tokens.js';
5
+
6
+ export interface ManualRegistrationTokenFactoryTransientParams {
7
+ rawToken?: string;
8
+ }
9
+
10
+ export const manualRegistrationTokenFactory = Factory.define<
11
+ ManualRegistrationToken,
12
+ ManualRegistrationTokenFactoryTransientParams
13
+ >(({onCreate, transientParams}) => {
14
+ const rawToken = transientParams.rawToken ?? generateOpaqueToken('manualRegistrationToken');
15
+
16
+ onCreate((token) => {
17
+ return createManualRegistrationToken({
18
+ workspaceId: token.workspaceId,
19
+ hashedToken: token.hashedToken,
20
+ prefix: token.prefix,
21
+ name: token.name ?? undefined,
22
+ expiresAt: token.expiresAt ?? undefined,
23
+ });
24
+ });
25
+
26
+ return {
27
+ id: crypto.randomUUID(),
28
+ workspaceId: crypto.randomUUID(),
29
+ hashedToken: hashOpaqueToken(rawToken),
30
+ prefix: extractDisplayPrefix(rawToken),
31
+ name: 'test runner',
32
+ expiresAt: null,
33
+ revokedAt: null,
34
+ createdAt: new Date(),
35
+ updatedAt: new Date(),
36
+ };
37
+ });
@@ -0,0 +1,44 @@
1
+ import {Factory} from 'fishery';
2
+ import {enqueueJobExecution} from '#db/job-executions.js';
3
+
4
+ interface PendingJobAttrs {
5
+ workspaceId: string;
6
+ workflowRunId: string;
7
+ workflowRunAttemptId: string;
8
+ jobId: string;
9
+ jobExecutionId: string;
10
+ projectId: string;
11
+ requiredLabels: string[];
12
+ }
13
+
14
+ export const pendingJobFactory = Factory.define<PendingJobAttrs>(({onCreate}) => {
15
+ const jobId = crypto.randomUUID();
16
+ const jobExecutionId = crypto.randomUUID();
17
+ const workflowRunId = crypto.randomUUID();
18
+ const workflowRunAttemptId = crypto.randomUUID();
19
+ const workspaceId = crypto.randomUUID();
20
+ const projectId = crypto.randomUUID();
21
+
22
+ onCreate(async (attrs) => {
23
+ await enqueueJobExecution({
24
+ workspaceId: attrs.workspaceId,
25
+ workflowRunId: attrs.workflowRunId,
26
+ workflowRunAttemptId: attrs.workflowRunAttemptId,
27
+ jobId: attrs.jobId,
28
+ jobExecutionId: attrs.jobExecutionId,
29
+ projectId: attrs.projectId,
30
+ requiredLabels: attrs.requiredLabels,
31
+ });
32
+ return attrs;
33
+ });
34
+
35
+ return {
36
+ workspaceId,
37
+ workflowRunId,
38
+ workflowRunAttemptId,
39
+ jobId,
40
+ jobExecutionId,
41
+ projectId,
42
+ requiredLabels: ['linux'],
43
+ };
44
+ });