@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,239 @@
1
+ import {setTimeout as sleep} from 'node:timers/promises';
2
+ import {logger} from '@shipfox/node-opentelemetry';
3
+ import {config} from '#config.js';
4
+ import {db} from '#db/db.js';
5
+ import {
6
+ type ActiveProvisionedRunnerTemplateCount,
7
+ listActiveProvisionedRunnerCountsByTemplateTx,
8
+ listProvisionerTerminateIntentRowsTx,
9
+ type ProvisionedRunnerTerminateIntent,
10
+ } from '#db/provisioned-runners.js';
11
+ import {
12
+ type DemandStat,
13
+ deleteReservationsByIds,
14
+ pollDemandAndReserveTx,
15
+ type ReservationGrant,
16
+ type ReservationTemplate,
17
+ } from '#db/reservations.js';
18
+ import {
19
+ provisionedRunnerCountDivergenceCount,
20
+ provisionedRunnerTerminateIntentIssuedCount,
21
+ } from '#metrics/instance.js';
22
+
23
+ export interface PollDemandParams {
24
+ workspaceId: string;
25
+ provisionerId: string;
26
+ maxReservations: number;
27
+ waitSeconds?: number | undefined;
28
+ ttlSeconds: number;
29
+ terminateIntentLimit: number;
30
+ templates: ReservationTemplate[];
31
+ signal: AbortSignal;
32
+ }
33
+
34
+ export interface PollDemandResult {
35
+ stats: DemandStat[];
36
+ reservations: ReservationGrant[];
37
+ terminateProvisionedRunnerIds: string[];
38
+ }
39
+
40
+ export interface ProvisionedRunnerCountDivergence {
41
+ templateKey: string;
42
+ state: 'starting' | 'running';
43
+ direction: 'backend-higher' | 'advertised-higher';
44
+ delta: number;
45
+ }
46
+
47
+ interface PollDemandSnapshot {
48
+ result: PollDemandResult;
49
+ divergences: ProvisionedRunnerCountDivergence[];
50
+ terminateIntents: ProvisionedRunnerTerminateIntent[];
51
+ }
52
+
53
+ export async function pollDemand(params: PollDemandParams): Promise<PollDemandResult> {
54
+ const waitSeconds = Math.min(
55
+ params.waitSeconds ?? config.RESERVATION_LONG_POLL_MAX_WAIT_SECONDS,
56
+ config.RESERVATION_LONG_POLL_MAX_WAIT_SECONDS,
57
+ );
58
+ const deadlineMs = Date.now() + Math.max(0, waitSeconds) * 1000;
59
+ const totalCapacity = params.templates.reduce(
60
+ (total, template) => total + template.availableSlots,
61
+ 0,
62
+ );
63
+ let interval = config.RESERVATION_POLL_INTERVAL_MS;
64
+ let lastSnapshot: PollDemandSnapshot = {
65
+ result: {stats: [], reservations: [], terminateProvisionedRunnerIds: []},
66
+ divergences: [],
67
+ terminateIntents: [],
68
+ };
69
+
70
+ while (true) {
71
+ if (params.signal.aborted) return lastSnapshot.result;
72
+
73
+ const previousSnapshot = lastSnapshot;
74
+ const deadlinePassed = Date.now() >= deadlineMs;
75
+ const snapshot = await db().transaction(async (tx) => {
76
+ const demand = await pollDemandAndReserveTx(tx, {
77
+ workspaceId: params.workspaceId,
78
+ provisionerId: params.provisionerId,
79
+ maxReservations: params.maxReservations,
80
+ ttlSeconds: params.ttlSeconds,
81
+ templates: params.templates,
82
+ });
83
+ const terminateIntents = await listProvisionerTerminateIntentRowsTx(tx, {
84
+ workspaceId: params.workspaceId,
85
+ provisionerId: params.provisionerId,
86
+ limit: params.terminateIntentLimit,
87
+ });
88
+ const result: PollDemandResult = {
89
+ ...demand,
90
+ terminateProvisionedRunnerIds: terminateIntents.map((intent) => intent.provisionedRunnerId),
91
+ };
92
+
93
+ if (!shouldReturn(result, params.maxReservations, totalCapacity, deadlinePassed)) {
94
+ return {result, terminateIntents, divergences: []};
95
+ }
96
+
97
+ return {
98
+ result,
99
+ terminateIntents,
100
+ divergences: calculateProvisionedRunnerCountDivergences({
101
+ advertisedTemplates: params.templates,
102
+ backendCounts: await listActiveProvisionedRunnerCountsByTemplateTx(tx, {
103
+ workspaceId: params.workspaceId,
104
+ provisionerId: params.provisionerId,
105
+ }),
106
+ }),
107
+ };
108
+ });
109
+ if (params.signal.aborted) {
110
+ await releaseReservationGrants(snapshot.result.reservations);
111
+ return previousSnapshot.result;
112
+ }
113
+
114
+ lastSnapshot = snapshot;
115
+
116
+ if (shouldReturn(lastSnapshot.result, params.maxReservations, totalCapacity, deadlinePassed)) {
117
+ recordPollDemandMetrics(params, lastSnapshot);
118
+ return lastSnapshot.result;
119
+ }
120
+
121
+ const remainingWaitMs = Math.max(0, deadlineMs - Date.now());
122
+ try {
123
+ await sleep(Math.min(withJitter(interval), remainingWaitMs), undefined, {
124
+ signal: params.signal,
125
+ });
126
+ } catch (error) {
127
+ if (params.signal.aborted) return lastSnapshot.result;
128
+ throw error;
129
+ }
130
+ interval = nextBackoffInterval(interval);
131
+ }
132
+ }
133
+
134
+ export async function releaseReservationGrants(reservations: ReservationGrant[]): Promise<void> {
135
+ await deleteReservationsByIds(reservations.map((reservation) => reservation.reservationId));
136
+ }
137
+
138
+ export function shouldReturn(
139
+ result: PollDemandResult,
140
+ maxReservations: number,
141
+ totalCapacity: number,
142
+ deadlinePassed: boolean,
143
+ ): boolean {
144
+ return (
145
+ maxReservations === 0 ||
146
+ totalCapacity === 0 ||
147
+ result.reservations.length > 0 ||
148
+ result.terminateProvisionedRunnerIds.length > 0 ||
149
+ deadlinePassed
150
+ );
151
+ }
152
+
153
+ export function nextBackoffInterval(ms: number): number {
154
+ return Math.min(ms * 1.5, config.RESERVATION_POLL_MAX_INTERVAL_MS);
155
+ }
156
+
157
+ export function withJitter(ms: number): number {
158
+ return Math.random() * ms;
159
+ }
160
+
161
+ export function calculateProvisionedRunnerCountDivergences(params: {
162
+ advertisedTemplates: ReservationTemplate[];
163
+ backendCounts: ActiveProvisionedRunnerTemplateCount[];
164
+ }): ProvisionedRunnerCountDivergence[] {
165
+ const advertisedCounts = new Map<string, number>();
166
+ const backendCounts = new Map<string, number>();
167
+
168
+ for (const template of params.advertisedTemplates) {
169
+ addCount(advertisedCounts, countKey(template.templateKey, 'starting'), template.starting);
170
+ addCount(advertisedCounts, countKey(template.templateKey, 'running'), template.running);
171
+ }
172
+ for (const count of params.backendCounts) {
173
+ addCount(backendCounts, countKey(count.templateKey, count.state), count.count);
174
+ }
175
+
176
+ const keys = [...new Set([...advertisedCounts.keys(), ...backendCounts.keys()])].sort();
177
+ return keys.flatMap((key) => {
178
+ const advertised = advertisedCounts.get(key) ?? 0;
179
+ const backend = backendCounts.get(key) ?? 0;
180
+ if (advertised === backend) return [];
181
+
182
+ const [templateKey, state] = splitCountKey(key);
183
+ return [
184
+ {
185
+ templateKey,
186
+ state,
187
+ direction: backend > advertised ? 'backend-higher' : 'advertised-higher',
188
+ delta: Math.abs(backend - advertised),
189
+ },
190
+ ];
191
+ });
192
+ }
193
+
194
+ function recordPollDemandMetrics(params: PollDemandParams, snapshot: PollDemandSnapshot): void {
195
+ for (const divergence of snapshot.divergences) {
196
+ logger().debug(
197
+ {
198
+ workspaceId: params.workspaceId,
199
+ provisionerId: params.provisionerId,
200
+ templateKey: divergence.templateKey,
201
+ state: divergence.state,
202
+ direction: divergence.direction,
203
+ delta: divergence.delta,
204
+ },
205
+ 'provisioned runner count divergence observed',
206
+ );
207
+
208
+ const attributes = {
209
+ state: divergence.state,
210
+ direction: divergence.direction,
211
+ ...(config.PROVISIONED_RUNNER_COUNT_DIVERGENCE_TEMPLATE_KEY_LABEL_ENABLED
212
+ ? {template_key: divergence.templateKey}
213
+ : {}),
214
+ };
215
+ provisionedRunnerCountDivergenceCount.add(divergence.delta, attributes);
216
+ }
217
+ for (const intent of snapshot.terminateIntents) {
218
+ provisionedRunnerTerminateIntentIssuedCount.add(1, {
219
+ surface: 'poll-demand',
220
+ reason: intent.reason,
221
+ });
222
+ }
223
+ }
224
+
225
+ function addCount(counts: Map<string, number>, key: string, count: number): void {
226
+ counts.set(key, (counts.get(key) ?? 0) + count);
227
+ }
228
+
229
+ function countKey(templateKey: string, state: 'starting' | 'running'): string {
230
+ return `${templateKey}\0${state}`;
231
+ }
232
+
233
+ function splitCountKey(key: string): [string, 'starting' | 'running'] {
234
+ const [templateKey, state] = key.split('\0');
235
+ if (!templateKey || (state !== 'starting' && state !== 'running')) {
236
+ throw new Error(`Invalid provisioned runner count key: ${key}`);
237
+ }
238
+ return [templateKey, state];
239
+ }
@@ -0,0 +1,13 @@
1
+ export interface EphemeralRegistrationToken {
2
+ id: string;
3
+ workspaceId: string;
4
+ provisionerId: string;
5
+ reservationId: string | null;
6
+ provisionedRunnerId: string;
7
+ hashedToken: string;
8
+ prefix: string;
9
+ expiresAt: Date;
10
+ consumedAt: Date | null;
11
+ consumedSessionId: string | null;
12
+ createdAt: Date;
13
+ }
@@ -0,0 +1,11 @@
1
+ export interface ManualRegistrationToken {
2
+ id: string;
3
+ workspaceId: string;
4
+ hashedToken: string;
5
+ prefix: string;
6
+ name: string | null;
7
+ expiresAt: Date | null;
8
+ revokedAt: Date | null;
9
+ createdAt: Date;
10
+ updatedAt: Date;
11
+ }
@@ -0,0 +1,30 @@
1
+ export type ProvisionedRunnerState =
2
+ | 'starting'
3
+ | 'running'
4
+ | 'stopping'
5
+ | 'stopped'
6
+ | 'failed'
7
+ | 'terminated';
8
+
9
+ export interface ProvisionedRunner {
10
+ id: string;
11
+ workspaceId: string;
12
+ provisionerId: string;
13
+ provisionedRunnerId: string;
14
+ reservationId: string | null;
15
+ templateKey: string | null;
16
+ labels: string[];
17
+ state: ProvisionedRunnerState;
18
+ reason: string | null;
19
+ runnerSessionId: string | null;
20
+ providerKind: string | null;
21
+ reportedAt: Date;
22
+ startedAt: Date | null;
23
+ stoppingAt: Date | null;
24
+ stoppedAt: Date | null;
25
+ failedAt: Date | null;
26
+ terminatedAt: Date | null;
27
+ reservationReleasedAt: Date | null;
28
+ createdAt: Date;
29
+ updatedAt: Date;
30
+ }
@@ -0,0 +1,16 @@
1
+ export interface ProvisionerToken {
2
+ id: string;
3
+ workspaceId: string;
4
+ hashedToken: string;
5
+ prefix: string;
6
+ name: string | null;
7
+ createdByUserId: string;
8
+ revokedByUserId: string | null;
9
+ expiresAt: Date | null;
10
+ revokedAt: Date | null;
11
+ lastSeenAt: Date | null;
12
+ createdAt: Date;
13
+ updatedAt: Date;
14
+ }
15
+
16
+ export type ActiveProvisionerToken = ProvisionerToken & {lastSeenAt: Date};
@@ -0,0 +1,9 @@
1
+ export interface Reservation {
2
+ id: string;
3
+ workspaceId: string;
4
+ provisionerId: string;
5
+ requiredLabels: string[];
6
+ count: number;
7
+ createdAt: Date;
8
+ expiresAt: Date;
9
+ }
@@ -0,0 +1,18 @@
1
+ import type {RunnerToolCapabilitiesDto} from '@shipfox/api-runners-dto';
2
+
3
+ export interface RunnerSession {
4
+ id: string;
5
+ workspaceId: string;
6
+ scope: 'workspace';
7
+ registrationTokenId: string;
8
+ registrationTokenKind: 'manual' | 'ephemeral';
9
+ provisionerId: string | null;
10
+ provisionedRunnerId: string | null;
11
+ labels: string[];
12
+ toolCapabilities: RunnerToolCapabilitiesDto | null;
13
+ toolCapabilitiesReportedAt: Date | null;
14
+ maxClaims: number | null;
15
+ claimsUsed: number;
16
+ createdAt: Date;
17
+ updatedAt: Date;
18
+ }
@@ -0,0 +1,108 @@
1
+ import {hashOpaqueToken, tokenTypeParts} from '@shipfox/node-tokens';
2
+ import {eq} from 'drizzle-orm';
3
+ import {db} from '#db/db.js';
4
+ import {ephemeralRegistrationTokens} from '#db/schema/ephemeral-registration-tokens.js';
5
+ import {mintEphemeralRegistrationToken} from './ephemeral-registration-tokens.js';
6
+ import {ActiveEphemeralRegistrationTokenExistsError} from './errors.js';
7
+
8
+ describe('mintEphemeralRegistrationToken', () => {
9
+ it('mints a token, stores only the hash, and carries the ert prefix', async () => {
10
+ const workspaceId = crypto.randomUUID();
11
+ const provisionerId = crypto.randomUUID();
12
+
13
+ const result = await mintEphemeralRegistrationToken({
14
+ workspaceId,
15
+ provisionerId,
16
+ provisionedRunnerId: 'gh-runner-7',
17
+ ttlSeconds: 600,
18
+ });
19
+
20
+ expect(result.rawToken.startsWith(`sf_${tokenTypeParts.ephemeralRegistrationToken}_`)).toBe(
21
+ true,
22
+ );
23
+ expect(result.token.workspaceId).toBe(workspaceId);
24
+ expect(result.token.provisionerId).toBe(provisionerId);
25
+ expect(result.token.provisionedRunnerId).toBe('gh-runner-7');
26
+ expect(result.token.reservationId).toBeNull();
27
+ expect(result.token.prefix).toBe(result.rawToken.slice(0, 12));
28
+ const [row] = await db()
29
+ .select()
30
+ .from(ephemeralRegistrationTokens)
31
+ .where(eq(ephemeralRegistrationTokens.id, result.token.id));
32
+ expect(row?.hashedToken).toBe(hashOpaqueToken(result.rawToken));
33
+ expect(row?.hashedToken).not.toBe(result.rawToken);
34
+ });
35
+
36
+ it('derives expiresAt from ttlSeconds', async () => {
37
+ const ttlSeconds = 900;
38
+ const before = Date.now();
39
+
40
+ const result = await mintEphemeralRegistrationToken({
41
+ workspaceId: crypto.randomUUID(),
42
+ provisionerId: crypto.randomUUID(),
43
+ provisionedRunnerId: 'gh-runner-7',
44
+ ttlSeconds,
45
+ });
46
+
47
+ const after = Date.now();
48
+ const expiresMs = result.token.expiresAt.getTime();
49
+ expect(expiresMs).toBeGreaterThanOrEqual(before + ttlSeconds * 1000);
50
+ expect(expiresMs).toBeLessThanOrEqual(after + ttlSeconds * 1000);
51
+ });
52
+
53
+ it('passes through a reservationId when provided', async () => {
54
+ const reservationId = crypto.randomUUID();
55
+
56
+ const result = await mintEphemeralRegistrationToken({
57
+ workspaceId: crypto.randomUUID(),
58
+ provisionerId: crypto.randomUUID(),
59
+ provisionedRunnerId: 'gh-runner-7',
60
+ reservationId,
61
+ ttlSeconds: 600,
62
+ });
63
+
64
+ expect(result.token.reservationId).toBe(reservationId);
65
+ });
66
+
67
+ it('rejects a second active token for the same provisioned runner', async () => {
68
+ const workspaceId = crypto.randomUUID();
69
+ const provisionerId = crypto.randomUUID();
70
+ const provisionedRunnerId = 'gh-runner-7';
71
+ await mintEphemeralRegistrationToken({
72
+ workspaceId,
73
+ provisionerId,
74
+ provisionedRunnerId,
75
+ ttlSeconds: 600,
76
+ });
77
+
78
+ await expect(
79
+ mintEphemeralRegistrationToken({
80
+ workspaceId,
81
+ provisionerId,
82
+ provisionedRunnerId,
83
+ ttlSeconds: 600,
84
+ }),
85
+ ).rejects.toBeInstanceOf(ActiveEphemeralRegistrationTokenExistsError);
86
+ });
87
+
88
+ it('allows minting a replacement after the previous token expires', async () => {
89
+ const workspaceId = crypto.randomUUID();
90
+ const provisionerId = crypto.randomUUID();
91
+ const provisionedRunnerId = 'gh-runner-7';
92
+ await mintEphemeralRegistrationToken({
93
+ workspaceId,
94
+ provisionerId,
95
+ provisionedRunnerId,
96
+ ttlSeconds: -1,
97
+ });
98
+
99
+ const replacement = await mintEphemeralRegistrationToken({
100
+ workspaceId,
101
+ provisionerId,
102
+ provisionedRunnerId,
103
+ ttlSeconds: 600,
104
+ });
105
+
106
+ expect(replacement.token.provisionedRunnerId).toBe(provisionedRunnerId);
107
+ });
108
+ });
@@ -0,0 +1,112 @@
1
+ import {extractDisplayPrefix, generateOpaqueToken, hashOpaqueToken} from '@shipfox/node-tokens';
2
+ import {
3
+ createEphemeralRegistrationToken,
4
+ createEphemeralRegistrationTokensBatch,
5
+ } from '#db/ephemeral-registration-tokens.js';
6
+ import type {EphemeralRegistrationToken} from './entities/ephemeral-registration-token.js';
7
+ import {RegistrationTokenBatchTooLargeError} from './errors.js';
8
+
9
+ export interface MintEphemeralRegistrationTokenParams {
10
+ workspaceId: string;
11
+ provisionerId: string;
12
+ provisionedRunnerId: string;
13
+ reservationId?: string | null | undefined;
14
+ ttlSeconds: number;
15
+ }
16
+
17
+ export interface MintEphemeralRegistrationTokenResult {
18
+ token: EphemeralRegistrationToken;
19
+ rawToken: string;
20
+ }
21
+
22
+ export interface MintEphemeralRegistrationTokensBatchProvisionedRunner {
23
+ provisionedRunnerId: string;
24
+ }
25
+
26
+ export interface MintEphemeralRegistrationTokensBatchParams {
27
+ workspaceId: string;
28
+ provisionerId: string;
29
+ reservationId: string;
30
+ provisionedRunners: MintEphemeralRegistrationTokensBatchProvisionedRunner[];
31
+ ttlSeconds: number;
32
+ maxBatchSize: number;
33
+ }
34
+
35
+ export interface MintEphemeralRegistrationTokensBatchResult {
36
+ provisionedRunnerId: string;
37
+ token: EphemeralRegistrationToken;
38
+ rawToken: string;
39
+ }
40
+
41
+ export async function mintEphemeralRegistrationToken(
42
+ params: MintEphemeralRegistrationTokenParams,
43
+ ): Promise<MintEphemeralRegistrationTokenResult> {
44
+ const expiresAt = new Date(Date.now() + params.ttlSeconds * 1000);
45
+ const row = buildEphemeralTokenRow(params.provisionedRunnerId, expiresAt);
46
+ const token = await createEphemeralRegistrationToken({
47
+ workspaceId: params.workspaceId,
48
+ provisionerId: params.provisionerId,
49
+ reservationId: params.reservationId ?? null,
50
+ provisionedRunnerId: params.provisionedRunnerId,
51
+ hashedToken: row.hashedToken,
52
+ prefix: row.prefix,
53
+ expiresAt: row.expiresAt,
54
+ });
55
+
56
+ return {token, rawToken: row.rawToken};
57
+ }
58
+
59
+ export async function mintEphemeralRegistrationTokensBatch(
60
+ params: MintEphemeralRegistrationTokensBatchParams,
61
+ ): Promise<MintEphemeralRegistrationTokensBatchResult[]> {
62
+ if (params.provisionedRunners.length > params.maxBatchSize) {
63
+ throw new RegistrationTokenBatchTooLargeError(
64
+ params.provisionedRunners.length,
65
+ params.maxBatchSize,
66
+ );
67
+ }
68
+
69
+ const expiresAt = new Date(Date.now() + params.ttlSeconds * 1000);
70
+ const rows = params.provisionedRunners.map((provisionedRunner) =>
71
+ buildEphemeralTokenRow(provisionedRunner.provisionedRunnerId, expiresAt),
72
+ );
73
+ const tokens = await createEphemeralRegistrationTokensBatch({
74
+ workspaceId: params.workspaceId,
75
+ provisionerId: params.provisionerId,
76
+ reservationId: params.reservationId,
77
+ expiresAt,
78
+ rows,
79
+ });
80
+ const tokensByProvisionedRunnerId = new Map(
81
+ tokens.map((token) => [token.provisionedRunnerId, token]),
82
+ );
83
+
84
+ return rows.map((row) => {
85
+ const token = tokensByProvisionedRunnerId.get(row.provisionedRunnerId);
86
+ if (!token) {
87
+ throw new Error(
88
+ `Inserted token not returned for provisioned runner: ${row.provisionedRunnerId}`,
89
+ );
90
+ }
91
+ return {provisionedRunnerId: row.provisionedRunnerId, rawToken: row.rawToken, token};
92
+ });
93
+ }
94
+
95
+ interface EphemeralTokenRow {
96
+ provisionedRunnerId: string;
97
+ rawToken: string;
98
+ hashedToken: string;
99
+ prefix: string;
100
+ expiresAt: Date;
101
+ }
102
+
103
+ function buildEphemeralTokenRow(provisionedRunnerId: string, expiresAt: Date): EphemeralTokenRow {
104
+ const rawToken = generateOpaqueToken('ephemeralRegistrationToken');
105
+ return {
106
+ provisionedRunnerId,
107
+ rawToken,
108
+ hashedToken: hashOpaqueToken(rawToken),
109
+ prefix: extractDisplayPrefix(rawToken),
110
+ expiresAt,
111
+ };
112
+ }
@@ -0,0 +1,128 @@
1
+ export class RunningJobExecutionNotFoundError extends Error {
2
+ constructor(jobExecutionId: string) {
3
+ super(`Running job execution not found: ${jobExecutionId}`);
4
+ this.name = 'RunningJobExecutionNotFoundError';
5
+ }
6
+ }
7
+
8
+ export class ManualRegistrationTokenNotFoundError extends Error {
9
+ constructor(tokenId: string) {
10
+ super(`Manual registration token not found: ${tokenId}`);
11
+ this.name = 'ManualRegistrationTokenNotFoundError';
12
+ }
13
+ }
14
+
15
+ export class ProvisionerTokenNotFoundError extends Error {
16
+ constructor(tokenId: string) {
17
+ super(`Provisioner token not found: ${tokenId}`);
18
+ this.name = 'ProvisionerTokenNotFoundError';
19
+ }
20
+ }
21
+
22
+ export class EmptyRunnerLabelsError extends Error {
23
+ constructor() {
24
+ super('Runner labels cannot be empty');
25
+ this.name = 'EmptyRunnerLabelsError';
26
+ }
27
+ }
28
+
29
+ export class EmptyRequiredLabelsError extends Error {
30
+ constructor() {
31
+ super('Required labels cannot be empty');
32
+ this.name = 'EmptyRequiredLabelsError';
33
+ }
34
+ }
35
+
36
+ export class RegistrationTokenConsumedError extends Error {
37
+ constructor(public readonly ephemeralTokenId: string) {
38
+ super(`Ephemeral registration token has already been consumed: ${ephemeralTokenId}`);
39
+ this.name = 'RegistrationTokenConsumedError';
40
+ }
41
+ }
42
+
43
+ export class RegistrationTokenExpiredError extends Error {
44
+ constructor(public readonly ephemeralTokenId: string) {
45
+ super(`Ephemeral registration token has expired: ${ephemeralTokenId}`);
46
+ this.name = 'RegistrationTokenExpiredError';
47
+ }
48
+ }
49
+
50
+ export class RegistrationTokenWorkspaceMismatchError extends Error {
51
+ constructor(
52
+ public readonly ephemeralTokenId: string,
53
+ public readonly workspaceId: string,
54
+ ) {
55
+ super(
56
+ `Ephemeral registration token ${ephemeralTokenId} does not belong to workspace ${workspaceId}`,
57
+ );
58
+ this.name = 'RegistrationTokenWorkspaceMismatchError';
59
+ }
60
+ }
61
+
62
+ export class ActiveEphemeralRegistrationTokenExistsError extends Error {
63
+ constructor(
64
+ public readonly workspaceId: string,
65
+ public readonly provisionerId: string,
66
+ public readonly provisionedRunnerId: string,
67
+ ) {
68
+ super(
69
+ `Active ephemeral registration token already exists for provisioned runner ${provisionedRunnerId} in workspace ${workspaceId}`,
70
+ );
71
+ this.name = 'ActiveEphemeralRegistrationTokenExistsError';
72
+ }
73
+ }
74
+
75
+ export class ReservationNotFoundError extends Error {
76
+ constructor(public readonly reservationId: string) {
77
+ super(`Reservation not found: ${reservationId}`);
78
+ this.name = 'ReservationNotFoundError';
79
+ }
80
+ }
81
+
82
+ export class ReservationExpiredError extends Error {
83
+ constructor(public readonly reservationId: string) {
84
+ super(`Reservation has expired: ${reservationId}`);
85
+ this.name = 'ReservationExpiredError';
86
+ }
87
+ }
88
+
89
+ export class RegistrationTokenBatchTooLargeError extends Error {
90
+ constructor(
91
+ public readonly requested: number,
92
+ public readonly max: number,
93
+ ) {
94
+ super(
95
+ `Registration token batch requested ${requested} provisioned runners, exceeding max ${max}`,
96
+ );
97
+ this.name = 'RegistrationTokenBatchTooLargeError';
98
+ }
99
+ }
100
+
101
+ export class RegistrationTokenBatchExceedsReservationError extends Error {
102
+ constructor(
103
+ public readonly requested: number,
104
+ public readonly reservationCount: number,
105
+ public readonly alreadyMinted: number = 0,
106
+ ) {
107
+ super(
108
+ `Registration token batch requested ${requested} provisioned runners with ${alreadyMinted} already minted, exceeding reservation count ${reservationCount}`,
109
+ );
110
+ this.name = 'RegistrationTokenBatchExceedsReservationError';
111
+ }
112
+ }
113
+
114
+ export class ActiveEphemeralRegistrationTokensExistError extends Error {
115
+ constructor(public readonly provisionedRunnerIds: string[]) {
116
+ super(
117
+ `Active ephemeral registration tokens already exist for provisioned runners: ${provisionedRunnerIds.join(', ')}`,
118
+ );
119
+ this.name = 'ActiveEphemeralRegistrationTokensExistError';
120
+ }
121
+ }
122
+
123
+ export class RunnerSessionExhaustedError extends Error {
124
+ constructor(public readonly runnerSessionId: string) {
125
+ super(`Runner session claim limit exhausted: ${runnerSessionId}`);
126
+ this.name = 'RunnerSessionExhaustedError';
127
+ }
128
+ }