@shipfox/api-runners 4.0.0 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (414) hide show
  1. package/.turbo/turbo-build.log +6 -6
  2. package/CHANGELOG.md +83 -0
  3. package/dist/config.d.ts +5 -1
  4. package/dist/config.d.ts.map +1 -1
  5. package/dist/config.js +53 -6
  6. package/dist/config.js.map +1 -1
  7. package/dist/core/capacity-assignments.d.ts +7 -0
  8. package/dist/core/capacity-assignments.d.ts.map +1 -0
  9. package/dist/core/capacity-assignments.js +6 -0
  10. package/dist/core/capacity-assignments.js.map +1 -0
  11. package/dist/core/demand.d.ts +6 -6
  12. package/dist/core/demand.d.ts.map +1 -1
  13. package/dist/core/demand.js +10 -10
  14. package/dist/core/demand.js.map +1 -1
  15. package/dist/core/entities/capacity-assignment.d.ts +9 -0
  16. package/dist/core/entities/capacity-assignment.d.ts.map +1 -0
  17. package/dist/core/entities/capacity-assignment.js +3 -0
  18. package/dist/core/entities/capacity-assignment.js.map +1 -0
  19. package/dist/core/entities/ephemeral-registration-token.d.ts +1 -1
  20. package/dist/core/entities/ephemeral-registration-token.d.ts.map +1 -1
  21. package/dist/core/entities/ephemeral-registration-token.js.map +1 -1
  22. package/dist/core/entities/provisioner-capability-snapshot.d.ts +13 -0
  23. package/dist/core/entities/provisioner-capability-snapshot.d.ts.map +1 -0
  24. package/dist/core/entities/provisioner-capability-snapshot.js +3 -0
  25. package/dist/core/entities/provisioner-capability-snapshot.js.map +1 -0
  26. package/dist/core/entities/provisioner-token.d.ts +10 -2
  27. package/dist/core/entities/provisioner-token.d.ts.map +1 -1
  28. package/dist/core/entities/provisioner-token.js.map +1 -1
  29. package/dist/core/entities/{provisioned-runner.d.ts → runner-instance.d.ts} +6 -6
  30. package/dist/core/entities/runner-instance.d.ts.map +1 -0
  31. package/dist/core/entities/runner-instance.js +3 -0
  32. package/dist/core/entities/runner-instance.js.map +1 -0
  33. package/dist/core/entities/runner-session.d.ts +4 -2
  34. package/dist/core/entities/runner-session.d.ts.map +1 -1
  35. package/dist/core/entities/runner-session.js.map +1 -1
  36. package/dist/core/ephemeral-registration-tokens.d.ts +5 -5
  37. package/dist/core/ephemeral-registration-tokens.d.ts.map +1 -1
  38. package/dist/core/ephemeral-registration-tokens.js +12 -12
  39. package/dist/core/ephemeral-registration-tokens.js.map +1 -1
  40. package/dist/core/errors.d.ts +16 -4
  41. package/dist/core/errors.d.ts.map +1 -1
  42. package/dist/core/errors.js +22 -4
  43. package/dist/core/errors.js.map +1 -1
  44. package/dist/core/index.d.ts +10 -4
  45. package/dist/core/index.d.ts.map +1 -1
  46. package/dist/core/index.js +6 -2
  47. package/dist/core/index.js.map +1 -1
  48. package/dist/core/job-executions.d.ts +2 -0
  49. package/dist/core/job-executions.d.ts.map +1 -1
  50. package/dist/core/job-executions.js +1 -2
  51. package/dist/core/job-executions.js.map +1 -1
  52. package/dist/core/maintenance.d.ts +1 -1
  53. package/dist/core/maintenance.d.ts.map +1 -1
  54. package/dist/core/maintenance.js +5 -5
  55. package/dist/core/maintenance.js.map +1 -1
  56. package/dist/core/provisioner-capability-snapshots.d.ts +5 -0
  57. package/dist/core/provisioner-capability-snapshots.d.ts.map +1 -0
  58. package/dist/core/provisioner-capability-snapshots.js +10 -0
  59. package/dist/core/provisioner-capability-snapshots.js.map +1 -0
  60. package/dist/core/provisioner-tokens.d.ts +10 -0
  61. package/dist/core/provisioner-tokens.d.ts.map +1 -1
  62. package/dist/core/provisioner-tokens.js +23 -1
  63. package/dist/core/provisioner-tokens.js.map +1 -1
  64. package/dist/core/rate-limit.d.ts.map +1 -1
  65. package/dist/core/rate-limit.js +2 -7
  66. package/dist/core/rate-limit.js.map +1 -1
  67. package/dist/core/runner-activation.d.ts +13 -0
  68. package/dist/core/runner-activation.d.ts.map +1 -0
  69. package/dist/core/runner-activation.js +36 -0
  70. package/dist/core/runner-activation.js.map +1 -0
  71. package/dist/core/runner-control-sessions.d.ts +46 -0
  72. package/dist/core/runner-control-sessions.d.ts.map +1 -0
  73. package/dist/core/runner-control-sessions.js +121 -0
  74. package/dist/core/runner-control-sessions.js.map +1 -0
  75. package/dist/core/runner-instances.d.ts +79 -0
  76. package/dist/core/runner-instances.d.ts.map +1 -0
  77. package/dist/core/runner-instances.js +168 -0
  78. package/dist/core/runner-instances.js.map +1 -0
  79. package/dist/core/runner-sessions.d.ts +8 -2
  80. package/dist/core/runner-sessions.d.ts.map +1 -1
  81. package/dist/core/runner-sessions.js +8 -5
  82. package/dist/core/runner-sessions.js.map +1 -1
  83. package/dist/db/capacity-assignments.d.ts +7 -0
  84. package/dist/db/capacity-assignments.d.ts.map +1 -0
  85. package/dist/db/capacity-assignments.js +72 -0
  86. package/dist/db/capacity-assignments.js.map +1 -0
  87. package/dist/db/db.d.ts +2837 -1009
  88. package/dist/db/db.d.ts.map +1 -1
  89. package/dist/db/db.js +11 -2
  90. package/dist/db/db.js.map +1 -1
  91. package/dist/db/ephemeral-registration-tokens.d.ts +2 -2
  92. package/dist/db/ephemeral-registration-tokens.d.ts.map +1 -1
  93. package/dist/db/ephemeral-registration-tokens.js +21 -21
  94. package/dist/db/ephemeral-registration-tokens.js.map +1 -1
  95. package/dist/db/index.d.ts +7 -4
  96. package/dist/db/index.d.ts.map +1 -1
  97. package/dist/db/index.js +5 -2
  98. package/dist/db/index.js.map +1 -1
  99. package/dist/db/job-executions.d.ts +6 -6
  100. package/dist/db/job-executions.d.ts.map +1 -1
  101. package/dist/db/job-executions.js +20 -19
  102. package/dist/db/job-executions.js.map +1 -1
  103. package/dist/db/provisioner-capability-snapshots.d.ts +21 -0
  104. package/dist/db/provisioner-capability-snapshots.d.ts.map +1 -0
  105. package/dist/db/provisioner-capability-snapshots.js +57 -0
  106. package/dist/db/provisioner-capability-snapshots.js.map +1 -0
  107. package/dist/db/provisioner-tokens.d.ts +7 -2
  108. package/dist/db/provisioner-tokens.d.ts.map +1 -1
  109. package/dist/db/provisioner-tokens.js +59 -10
  110. package/dist/db/provisioner-tokens.js.map +1 -1
  111. package/dist/db/reservations.d.ts +18 -0
  112. package/dist/db/reservations.d.ts.map +1 -1
  113. package/dist/db/reservations.js +88 -1
  114. package/dist/db/reservations.js.map +1 -1
  115. package/dist/db/runner-instances.d.ts +91 -0
  116. package/dist/db/runner-instances.d.ts.map +1 -0
  117. package/dist/db/runner-instances.js +511 -0
  118. package/dist/db/runner-instances.js.map +1 -0
  119. package/dist/db/runner-sessions.d.ts +5 -0
  120. package/dist/db/runner-sessions.d.ts.map +1 -1
  121. package/dist/db/runner-sessions.js +48 -2
  122. package/dist/db/runner-sessions.js.map +1 -1
  123. package/dist/db/schema/capacity-assignments.d.ts +113 -0
  124. package/dist/db/schema/capacity-assignments.d.ts.map +1 -0
  125. package/dist/db/schema/capacity-assignments.js +29 -0
  126. package/dist/db/schema/capacity-assignments.js.map +1 -0
  127. package/dist/db/schema/ephemeral-registration-tokens.d.ts +2 -2
  128. package/dist/db/schema/ephemeral-registration-tokens.js +3 -3
  129. package/dist/db/schema/ephemeral-registration-tokens.js.map +1 -1
  130. package/dist/db/schema/provisioner-capability-snapshots.d.ts +207 -0
  131. package/dist/db/schema/provisioner-capability-snapshots.d.ts.map +1 -0
  132. package/dist/db/schema/provisioner-capability-snapshots.js +38 -0
  133. package/dist/db/schema/provisioner-capability-snapshots.js.map +1 -0
  134. package/dist/db/schema/provisioner-tokens.d.ts +19 -1
  135. package/dist/db/schema/provisioner-tokens.d.ts.map +1 -1
  136. package/dist/db/schema/provisioner-tokens.js +29 -3
  137. package/dist/db/schema/provisioner-tokens.js.map +1 -1
  138. package/dist/db/schema/runner-activation-tokens.d.ts +161 -0
  139. package/dist/db/schema/runner-activation-tokens.d.ts.map +1 -0
  140. package/dist/db/schema/runner-activation-tokens.js +27 -0
  141. package/dist/db/schema/runner-activation-tokens.js.map +1 -0
  142. package/dist/db/schema/runner-control-sessions.d.ts +338 -0
  143. package/dist/db/schema/runner-control-sessions.d.ts.map +1 -0
  144. package/dist/db/schema/runner-control-sessions.js +52 -0
  145. package/dist/db/schema/runner-control-sessions.js.map +1 -0
  146. package/dist/db/schema/{provisioned-runners.d.ts → runner-instances.d.ts} +88 -34
  147. package/dist/db/schema/runner-instances.d.ts.map +1 -0
  148. package/dist/db/schema/{provisioned-runners.js → runner-instances.js} +19 -14
  149. package/dist/db/schema/runner-instances.js.map +1 -0
  150. package/dist/db/schema/runner-sessions.d.ts +39 -5
  151. package/dist/db/schema/runner-sessions.d.ts.map +1 -1
  152. package/dist/db/schema/runner-sessions.js +13 -6
  153. package/dist/db/schema/runner-sessions.js.map +1 -1
  154. package/dist/db/schema/running-job-executions.d.ts +2 -2
  155. package/dist/db/schema/running-job-executions.js +4 -4
  156. package/dist/db/schema/running-job-executions.js.map +1 -1
  157. package/dist/index.d.ts +7 -2
  158. package/dist/index.d.ts.map +1 -1
  159. package/dist/index.js +45 -38
  160. package/dist/index.js.map +1 -1
  161. package/dist/installation-provisioning.d.ts +14 -0
  162. package/dist/installation-provisioning.d.ts.map +1 -0
  163. package/dist/installation-provisioning.js +7 -0
  164. package/dist/installation-provisioning.js.map +1 -0
  165. package/dist/metrics/index.d.ts +1 -1
  166. package/dist/metrics/index.d.ts.map +1 -1
  167. package/dist/metrics/index.js +1 -1
  168. package/dist/metrics/index.js.map +1 -1
  169. package/dist/metrics/instance.d.ts +11 -7
  170. package/dist/metrics/instance.d.ts.map +1 -1
  171. package/dist/metrics/instance.js +13 -7
  172. package/dist/metrics/instance.js.map +1 -1
  173. package/dist/presentation/auth/index.d.ts +1 -0
  174. package/dist/presentation/auth/index.d.ts.map +1 -1
  175. package/dist/presentation/auth/index.js +1 -0
  176. package/dist/presentation/auth/index.js.map +1 -1
  177. package/dist/presentation/auth/provisioner-token-auth.d.ts.map +1 -1
  178. package/dist/presentation/auth/provisioner-token-auth.js +5 -1
  179. package/dist/presentation/auth/provisioner-token-auth.js.map +1 -1
  180. package/dist/presentation/auth/runner-control-session-auth.d.ts +4 -0
  181. package/dist/presentation/auth/runner-control-session-auth.d.ts.map +1 -0
  182. package/dist/presentation/auth/runner-control-session-auth.js +29 -0
  183. package/dist/presentation/auth/runner-control-session-auth.js.map +1 -0
  184. package/dist/presentation/auth/runner-registration-token-auth.d.ts +5 -1
  185. package/dist/presentation/auth/runner-registration-token-auth.d.ts.map +1 -1
  186. package/dist/presentation/auth/runner-registration-token-auth.js +21 -1
  187. package/dist/presentation/auth/runner-registration-token-auth.js.map +1 -1
  188. package/dist/presentation/dto/index.d.ts +2 -2
  189. package/dist/presentation/dto/index.d.ts.map +1 -1
  190. package/dist/presentation/dto/index.js +2 -2
  191. package/dist/presentation/dto/index.js.map +1 -1
  192. package/dist/presentation/dto/mint-registration-tokens.d.ts +5 -3
  193. package/dist/presentation/dto/mint-registration-tokens.d.ts.map +1 -1
  194. package/dist/presentation/dto/mint-registration-tokens.js +4 -4
  195. package/dist/presentation/dto/mint-registration-tokens.js.map +1 -1
  196. package/dist/presentation/dto/poll-demand.d.ts.map +1 -1
  197. package/dist/presentation/dto/poll-demand.js +7 -1
  198. package/dist/presentation/dto/poll-demand.js.map +1 -1
  199. package/dist/presentation/dto/provisioner-token.d.ts +1 -0
  200. package/dist/presentation/dto/provisioner-token.d.ts.map +1 -1
  201. package/dist/presentation/dto/provisioner-token.js +5 -1
  202. package/dist/presentation/dto/provisioner-token.js.map +1 -1
  203. package/dist/presentation/dto/{provisioned-runners.d.ts → runner-instances.d.ts} +9 -9
  204. package/dist/presentation/dto/runner-instances.d.ts.map +1 -0
  205. package/dist/presentation/dto/{provisioned-runners.js → runner-instances.js} +6 -6
  206. package/dist/presentation/dto/runner-instances.js.map +1 -0
  207. package/dist/presentation/index.d.ts +2 -2
  208. package/dist/presentation/index.d.ts.map +1 -1
  209. package/dist/presentation/index.js +2 -2
  210. package/dist/presentation/index.js.map +1 -1
  211. package/dist/presentation/inter-module.d.ts +5 -0
  212. package/dist/presentation/inter-module.d.ts.map +1 -0
  213. package/dist/presentation/inter-module.js +43 -0
  214. package/dist/presentation/inter-module.js.map +1 -0
  215. package/dist/presentation/routes/assign-capacity.d.ts +2 -0
  216. package/dist/presentation/routes/assign-capacity.d.ts.map +1 -0
  217. package/dist/presentation/routes/assign-capacity.js +44 -0
  218. package/dist/presentation/routes/assign-capacity.js.map +1 -0
  219. package/dist/presentation/routes/get-provisioner-me.d.ts.map +1 -1
  220. package/dist/presentation/routes/get-provisioner-me.js +5 -1
  221. package/dist/presentation/routes/get-provisioner-me.js.map +1 -1
  222. package/dist/presentation/routes/heartbeat.d.ts +2 -1
  223. package/dist/presentation/routes/heartbeat.d.ts.map +1 -1
  224. package/dist/presentation/routes/heartbeat.js +60 -50
  225. package/dist/presentation/routes/heartbeat.js.map +1 -1
  226. package/dist/presentation/routes/index.d.ts +3 -1
  227. package/dist/presentation/routes/index.d.ts.map +1 -1
  228. package/dist/presentation/routes/index.js +83 -50
  229. package/dist/presentation/routes/index.js.map +1 -1
  230. package/dist/presentation/routes/mint-registration-tokens.d.ts.map +1 -1
  231. package/dist/presentation/routes/mint-registration-tokens.js +5 -5
  232. package/dist/presentation/routes/mint-registration-tokens.js.map +1 -1
  233. package/dist/presentation/routes/planned-capacity.d.ts +3 -0
  234. package/dist/presentation/routes/planned-capacity.d.ts.map +1 -0
  235. package/dist/presentation/routes/planned-capacity.js +54 -0
  236. package/dist/presentation/routes/planned-capacity.js.map +1 -0
  237. package/dist/presentation/routes/poll-demand.d.ts +2 -0
  238. package/dist/presentation/routes/poll-demand.d.ts.map +1 -1
  239. package/dist/presentation/routes/poll-demand.js +86 -45
  240. package/dist/presentation/routes/poll-demand.js.map +1 -1
  241. package/dist/presentation/routes/reconcile-runner-instances.d.ts +2 -0
  242. package/dist/presentation/routes/reconcile-runner-instances.d.ts.map +1 -0
  243. package/dist/presentation/routes/reconcile-runner-instances.js +27 -0
  244. package/dist/presentation/routes/reconcile-runner-instances.js.map +1 -0
  245. package/dist/presentation/routes/register.d.ts +2 -1
  246. package/dist/presentation/routes/register.d.ts.map +1 -1
  247. package/dist/presentation/routes/register.js +55 -52
  248. package/dist/presentation/routes/register.js.map +1 -1
  249. package/dist/presentation/routes/report-runner-instances.d.ts +2 -0
  250. package/dist/presentation/routes/report-runner-instances.d.ts.map +1 -0
  251. package/dist/presentation/routes/report-runner-instances.js +37 -0
  252. package/dist/presentation/routes/report-runner-instances.js.map +1 -0
  253. package/dist/presentation/routes/request-job.d.ts +2 -1
  254. package/dist/presentation/routes/request-job.d.ts.map +1 -1
  255. package/dist/presentation/routes/request-job.js +39 -36
  256. package/dist/presentation/routes/request-job.js.map +1 -1
  257. package/dist/presentation/routes/runner-enrollment.d.ts +7 -0
  258. package/dist/presentation/routes/runner-enrollment.d.ts.map +1 -0
  259. package/dist/presentation/routes/runner-enrollment.js +185 -0
  260. package/dist/presentation/routes/runner-enrollment.js.map +1 -0
  261. package/dist/temporal/activities/index.d.ts +2 -2
  262. package/dist/temporal/activities/index.d.ts.map +1 -1
  263. package/dist/temporal/activities/index.js +2 -2
  264. package/dist/temporal/activities/index.js.map +1 -1
  265. package/dist/temporal/activities/maintenance-activities.d.ts +1 -1
  266. package/dist/temporal/activities/maintenance-activities.d.ts.map +1 -1
  267. package/dist/temporal/activities/maintenance-activities.js +3 -3
  268. package/dist/temporal/activities/maintenance-activities.js.map +1 -1
  269. package/dist/temporal/workflows/index.bundle.js +3 -3
  270. package/dist/temporal/workflows/stuck-job-detector.js +2 -2
  271. package/dist/temporal/workflows/stuck-job-detector.js.map +1 -1
  272. package/dist/tsconfig.test.tsbuildinfo +1 -1
  273. package/drizzle/0000_initial.sql +102 -24
  274. package/drizzle/meta/0000_snapshot.json +483 -63
  275. package/package.json +31 -36
  276. package/src/config.test.ts +0 -26
  277. package/src/config.ts +47 -6
  278. package/src/core/capacity-assignments.ts +10 -0
  279. package/src/core/demand.test.ts +29 -29
  280. package/src/core/demand.ts +22 -22
  281. package/src/core/entities/capacity-assignment.ts +8 -0
  282. package/src/core/entities/ephemeral-registration-token.ts +1 -1
  283. package/src/core/entities/provisioner-capability-snapshot.ts +12 -0
  284. package/src/core/entities/provisioner-token.ts +8 -2
  285. package/src/core/entities/{provisioned-runner.ts → runner-instance.ts} +5 -5
  286. package/src/core/entities/runner-session.ts +4 -2
  287. package/src/core/ephemeral-registration-tokens.test.ts +11 -11
  288. package/src/core/ephemeral-registration-tokens.ts +18 -20
  289. package/src/core/errors.ts +23 -4
  290. package/src/core/index.ts +39 -17
  291. package/src/core/job-executions.ts +3 -2
  292. package/src/core/maintenance.test.ts +13 -17
  293. package/src/core/maintenance.ts +5 -5
  294. package/src/core/provisioner-capability-snapshots.test.ts +35 -0
  295. package/src/core/provisioner-capability-snapshots.ts +12 -0
  296. package/src/core/provisioner-tokens.test.ts +20 -0
  297. package/src/core/provisioner-tokens.ts +33 -0
  298. package/src/core/rate-limit.test.ts +11 -27
  299. package/src/core/rate-limit.ts +3 -11
  300. package/src/core/runner-activation.ts +72 -0
  301. package/src/core/runner-control-sessions.ts +190 -0
  302. package/src/core/{provisioned-runners.test.ts → runner-instances.test.ts} +24 -24
  303. package/src/core/runner-instances.ts +310 -0
  304. package/src/core/runner-sessions.test.ts +112 -22
  305. package/src/core/runner-sessions.ts +25 -14
  306. package/src/db/capacity-assignments.test.ts +159 -0
  307. package/src/db/capacity-assignments.ts +116 -0
  308. package/src/db/db.ts +11 -2
  309. package/src/db/ephemeral-registration-tokens.test.ts +9 -9
  310. package/src/db/ephemeral-registration-tokens.ts +26 -26
  311. package/src/db/index.ts +31 -21
  312. package/src/db/job-executions.test.ts +13 -11
  313. package/src/db/job-executions.ts +33 -27
  314. package/src/db/provisioner-capability-snapshots.test.ts +175 -0
  315. package/src/db/provisioner-capability-snapshots.ts +130 -0
  316. package/src/db/provisioner-tokens.test.ts +139 -2
  317. package/src/db/provisioner-tokens.ts +123 -15
  318. package/src/db/reservations.test.ts +49 -0
  319. package/src/db/reservations.ts +146 -1
  320. package/src/db/{provisioned-runners.test.ts → runner-instances.test.ts} +429 -353
  321. package/src/db/runner-instances.ts +942 -0
  322. package/src/db/runner-sessions.test.ts +19 -6
  323. package/src/db/runner-sessions.ts +78 -2
  324. package/src/db/schema/capacity-assignments.ts +34 -0
  325. package/src/db/schema/ephemeral-registration-tokens.ts +4 -4
  326. package/src/db/schema/provisioner-capability-snapshots.ts +46 -0
  327. package/src/db/schema/provisioner-tokens.ts +33 -2
  328. package/src/db/schema/runner-activation-tokens.ts +22 -0
  329. package/src/db/schema/runner-control-sessions.ts +46 -0
  330. package/src/db/schema/{provisioned-runners.ts → runner-instances.ts} +26 -27
  331. package/src/db/schema/runner-sessions.ts +11 -7
  332. package/src/db/schema/running-job-executions.ts +5 -5
  333. package/src/index.test.ts +26 -0
  334. package/src/index.ts +39 -19
  335. package/src/installation-provisioning.ts +14 -0
  336. package/src/metrics/index.ts +5 -5
  337. package/src/metrics/instance.ts +25 -14
  338. package/src/presentation/auth/index.ts +4 -0
  339. package/src/presentation/auth/provisioner-token-auth.ts +8 -4
  340. package/src/presentation/auth/runner-control-session-auth.ts +31 -0
  341. package/src/presentation/auth/runner-registration-token-auth.ts +39 -3
  342. package/src/presentation/dto/index.ts +5 -5
  343. package/src/presentation/dto/mint-registration-tokens.ts +8 -15
  344. package/src/presentation/dto/poll-demand.ts +3 -1
  345. package/src/presentation/dto/provisioner-token.ts +5 -0
  346. package/src/presentation/dto/{provisioned-runners.ts → runner-instances.ts} +13 -15
  347. package/src/presentation/index.ts +2 -1
  348. package/src/presentation/inter-module.test.ts +15 -0
  349. package/src/presentation/inter-module.ts +53 -0
  350. package/src/presentation/routes/assign-capacity.ts +48 -0
  351. package/src/presentation/routes/get-provisioner-me.ts +3 -4
  352. package/src/presentation/routes/heartbeat.test.ts +4 -3
  353. package/src/presentation/routes/heartbeat.ts +67 -64
  354. package/src/presentation/routes/index.ts +93 -43
  355. package/src/presentation/routes/list-active-runners.test.ts +23 -23
  356. package/src/presentation/routes/manual-registration-tokens.test.ts +4 -3
  357. package/src/presentation/routes/mint-registration-tokens.test.ts +12 -12
  358. package/src/presentation/routes/mint-registration-tokens.ts +5 -7
  359. package/src/presentation/routes/planned-capacity.ts +49 -0
  360. package/src/presentation/routes/poll-demand-release.test.ts +4 -4
  361. package/src/presentation/routes/poll-demand.test.ts +161 -22
  362. package/src/presentation/routes/poll-demand.ts +105 -45
  363. package/src/presentation/routes/provisioner-me.test.ts +37 -3
  364. package/src/presentation/routes/{reconcile-provisioned-runners.test.ts → reconcile-runner-instances.test.ts} +66 -61
  365. package/src/presentation/routes/reconcile-runner-instances.ts +30 -0
  366. package/src/presentation/routes/register.test.ts +9 -5
  367. package/src/presentation/routes/register.ts +61 -57
  368. package/src/presentation/routes/{report-provisioned-runners.test.ts → report-runner-instances.test.ts} +28 -28
  369. package/src/presentation/routes/{report-provisioned-runners.ts → report-runner-instances.ts} +14 -14
  370. package/src/presentation/routes/request-job.test.ts +5 -3
  371. package/src/presentation/routes/request-job.ts +41 -37
  372. package/src/presentation/routes/runner-enrollment.test.ts +259 -0
  373. package/src/presentation/routes/runner-enrollment.ts +174 -0
  374. package/src/temporal/activities/index.ts +2 -2
  375. package/src/temporal/activities/maintenance-activities.test.ts +7 -7
  376. package/src/temporal/activities/maintenance-activities.ts +3 -3
  377. package/src/temporal/workflows/stuck-job-detector.test.ts +3 -3
  378. package/src/temporal/workflows/stuck-job-detector.ts +2 -2
  379. package/test/env.ts +1 -3
  380. package/test/factories/ephemeral-registration-token.ts +2 -2
  381. package/test/factories/provisioner-token.ts +3 -1
  382. package/test/factories/runner-instance.ts +57 -0
  383. package/test/factories/runner-session.ts +3 -1
  384. package/test/fixtures/auth-inter-module.ts +30 -0
  385. package/test/globalSetup.ts +5 -1
  386. package/test/index.ts +2 -1
  387. package/tsconfig.build.tsbuildinfo +1 -1
  388. package/dist/core/entities/provisioned-runner.d.ts.map +0 -1
  389. package/dist/core/entities/provisioned-runner.js +0 -3
  390. package/dist/core/entities/provisioned-runner.js.map +0 -1
  391. package/dist/core/provisioned-runners.d.ts +0 -67
  392. package/dist/core/provisioned-runners.d.ts.map +0 -1
  393. package/dist/core/provisioned-runners.js +0 -162
  394. package/dist/core/provisioned-runners.js.map +0 -1
  395. package/dist/db/provisioned-runners.d.ts +0 -79
  396. package/dist/db/provisioned-runners.d.ts.map +0 -1
  397. package/dist/db/provisioned-runners.js +0 -466
  398. package/dist/db/provisioned-runners.js.map +0 -1
  399. package/dist/db/schema/provisioned-runners.d.ts.map +0 -1
  400. package/dist/db/schema/provisioned-runners.js.map +0 -1
  401. package/dist/presentation/dto/provisioned-runners.d.ts.map +0 -1
  402. package/dist/presentation/dto/provisioned-runners.js.map +0 -1
  403. package/dist/presentation/routes/reconcile-provisioned-runners.d.ts +0 -2
  404. package/dist/presentation/routes/reconcile-provisioned-runners.d.ts.map +0 -1
  405. package/dist/presentation/routes/reconcile-provisioned-runners.js +0 -27
  406. package/dist/presentation/routes/reconcile-provisioned-runners.js.map +0 -1
  407. package/dist/presentation/routes/report-provisioned-runners.d.ts +0 -2
  408. package/dist/presentation/routes/report-provisioned-runners.d.ts.map +0 -1
  409. package/dist/presentation/routes/report-provisioned-runners.js +0 -37
  410. package/dist/presentation/routes/report-provisioned-runners.js.map +0 -1
  411. package/src/core/provisioned-runners.ts +0 -301
  412. package/src/db/provisioned-runners.ts +0 -886
  413. package/src/presentation/routes/reconcile-provisioned-runners.ts +0 -30
  414. package/test/factories/provisioned-runner.ts +0 -78
@@ -0,0 +1,310 @@
1
+ import type {RunnerInstance, RunnerInstanceState} from '#core/entities/runner-instance.js';
2
+ import {
3
+ attachProviderRunnerId as attachProviderRunnerIdDb,
4
+ createPlannedProvisionedCapacity as createPlannedProvisionedCapacityDb,
5
+ isTerminalState,
6
+ listActiveRunnerInstances,
7
+ listActiveRunningJobExecutions,
8
+ type RunnerInstanceReportEvent,
9
+ reconcileRunnerInstances as reconcileRunnerInstancesDb,
10
+ reportRunnerInstances as reportRunnerInstancesDb,
11
+ } from '#db/index.js';
12
+ import type {
13
+ ActiveRunningJobExecution,
14
+ RunnerInstanceBoundJobExecution,
15
+ } from '#db/job-executions.js';
16
+ import {
17
+ providerRunnerAbsentTerminatedCount,
18
+ providerRunnerReconcileCallCount,
19
+ providerRunnerReportCount,
20
+ providerRunnerTerminateIntentHonoredCount,
21
+ providerRunnerTerminateIntentIssuedCount,
22
+ reservationReleasedCount,
23
+ } from '#metrics/instance.js';
24
+ import {config} from '../config.js';
25
+
26
+ export interface ReportRunnerInstancesParams {
27
+ workspaceId: string | null;
28
+ provisionerId: string;
29
+ events: RunnerInstanceReportEvent[];
30
+ }
31
+
32
+ export function createPlannedProvisionedCapacity(params: {
33
+ provisionerId: string;
34
+ providerKind: string | null;
35
+ templateKey: string | null;
36
+ }): Promise<{capacityId: string}> {
37
+ return createPlannedProvisionedCapacityDb(params);
38
+ }
39
+
40
+ export function attachProviderRunnerId(params: {
41
+ capacityId: string;
42
+ provisionerId: string;
43
+ providerRunnerId: string;
44
+ }): Promise<boolean> {
45
+ return attachProviderRunnerIdDb(params);
46
+ }
47
+
48
+ export interface ReportRunnerInstancesResult {
49
+ accepted: number;
50
+ reservationsReleased: number;
51
+ }
52
+
53
+ export interface ReconcileRunnerInstancesParams {
54
+ workspaceId: string | null;
55
+ provisionerId: string;
56
+ observedRunnerInstanceIds: string[];
57
+ }
58
+
59
+ export type ReconcileDesiredIntent = 'keep' | 'terminate';
60
+ type ReconcileDesiredIntentReason = 'job-cancelled' | 'terminal-state';
61
+
62
+ export interface ReconciledBoundJobExecution {
63
+ jobId: string;
64
+ jobExecutionId: string;
65
+ workflowRunAttemptId: string;
66
+ lastHeartbeatAt: Date;
67
+ cancellationRequestedAt: Date | null;
68
+ }
69
+
70
+ export interface ReconciledRunnerInstance {
71
+ providerRunnerId: string;
72
+ state: RunnerInstanceState | null;
73
+ reservationId: string | null;
74
+ runnerSessionId: string | null;
75
+ boundJobExecution: ReconciledBoundJobExecution | null;
76
+ desiredIntent: ReconcileDesiredIntent;
77
+ desiredIntentReason: ReconcileDesiredIntentReason | null;
78
+ }
79
+
80
+ export interface ReconcileRunnerInstancesResult {
81
+ runners: ReconciledRunnerInstance[];
82
+ terminatedAbsentRunnerInstanceIds: string[];
83
+ }
84
+
85
+ export type ActiveRunnerState = 'starting' | 'running' | 'stopping' | 'busy';
86
+
87
+ export interface ActiveRunner {
88
+ runnerSessionId: string | null;
89
+ providerRunnerId: string | null;
90
+ provisionerId: string | null;
91
+ state: ActiveRunnerState;
92
+ labels: string[];
93
+ templateKey: string | null;
94
+ providerKind: string | null;
95
+ jobId: string | null;
96
+ workflowRunAttemptId: string | null;
97
+ projectId: string | null;
98
+ reportedAt: Date | null;
99
+ lastHeartbeatAt: Date | null;
100
+ }
101
+
102
+ export async function reportRunnerInstances(
103
+ params: ReportRunnerInstancesParams,
104
+ ): Promise<ReportRunnerInstancesResult> {
105
+ const result = await reportRunnerInstancesDb(params);
106
+
107
+ for (const event of params.events) {
108
+ providerRunnerReportCount.add(1, {state: event.state});
109
+ }
110
+ for (const intent of result.terminateIntentsHonored) {
111
+ providerRunnerTerminateIntentHonoredCount.add(1, {reason: intent.reason});
112
+ }
113
+ if (result.reservationsReleased > 0) reservationReleasedCount.add(result.reservationsReleased);
114
+
115
+ return result;
116
+ }
117
+
118
+ export async function reconcileRunnerInstances(
119
+ params: ReconcileRunnerInstancesParams,
120
+ ): Promise<ReconcileRunnerInstancesResult> {
121
+ const result = await reconcileRunnerInstancesDb({
122
+ ...params,
123
+ terminateGraceSeconds: config.RUNNER_RECONCILE_TERMINATE_GRACE_SECONDS,
124
+ });
125
+
126
+ if (result.reservationsReleased > 0) reservationReleasedCount.add(result.reservationsReleased);
127
+ providerRunnerReconcileCallCount.add(1);
128
+ if (result.absentIds.length > 0) providerRunnerAbsentTerminatedCount.add(result.absentIds.length);
129
+
130
+ const runners = reconcileRunnerInstancesFromDbResult({
131
+ observedRunnerInstanceIds: params.observedRunnerInstanceIds,
132
+ observedRows: result.observedRows,
133
+ boundJobExecutionsByRunnerInstanceId: result.boundJobExecutionsByRunnerInstanceId,
134
+ });
135
+ for (const runner of runners) {
136
+ if (runner.desiredIntentReason) {
137
+ providerRunnerTerminateIntentIssuedCount.add(1, {
138
+ surface: 'reconcile',
139
+ reason: runner.desiredIntentReason,
140
+ });
141
+ }
142
+ }
143
+
144
+ return {
145
+ runners,
146
+ terminatedAbsentRunnerInstanceIds: result.absentIds,
147
+ };
148
+ }
149
+
150
+ export function reconcileRunnerInstancesFromDbResult(params: {
151
+ observedRunnerInstanceIds: string[];
152
+ observedRows: RunnerInstance[];
153
+ boundJobExecutionsByRunnerInstanceId: Map<string, RunnerInstanceBoundJobExecution>;
154
+ }): ReconciledRunnerInstance[] {
155
+ const rowsByRunnerInstanceId = new Map(
156
+ params.observedRows.map((row) => [row.providerRunnerId, row]),
157
+ );
158
+
159
+ return params.observedRunnerInstanceIds.map((providerRunnerId) => {
160
+ const row = rowsByRunnerInstanceId.get(providerRunnerId);
161
+ const boundJobExecution = params.boundJobExecutionsByRunnerInstanceId.get(providerRunnerId);
162
+
163
+ const desiredIntentReason = getDesiredIntentReason(row, boundJobExecution);
164
+
165
+ return {
166
+ providerRunnerId,
167
+ state: row?.state ?? null,
168
+ reservationId: row?.reservationId ?? null,
169
+ runnerSessionId: row?.runnerSessionId ?? null,
170
+ boundJobExecution: boundJobExecution
171
+ ? toReconciledBoundJobExecution(boundJobExecution)
172
+ : null,
173
+ desiredIntent: desiredIntentReason ? 'terminate' : 'keep',
174
+ desiredIntentReason,
175
+ };
176
+ });
177
+ }
178
+
179
+ export async function listActiveRunners(params: {workspaceId: string}): Promise<ActiveRunner[]> {
180
+ const [providerRunnerRows, jobExecutionRows] = await Promise.all([
181
+ listActiveRunnerInstances({
182
+ workspaceId: params.workspaceId,
183
+ windowSeconds: config.RUNNER_ACTIVE_WINDOW_SECONDS,
184
+ }),
185
+ listActiveRunningJobExecutions({
186
+ workspaceId: params.workspaceId,
187
+ windowSeconds: config.RUNNER_ACTIVE_WINDOW_SECONDS,
188
+ }),
189
+ ]);
190
+
191
+ return mergeActiveRunners(providerRunnerRows, jobExecutionRows);
192
+ }
193
+
194
+ function toReconciledBoundJobExecution(
195
+ jobExecution: RunnerInstanceBoundJobExecution,
196
+ ): ReconciledBoundJobExecution {
197
+ return {
198
+ jobId: jobExecution.jobId,
199
+ jobExecutionId: jobExecution.jobExecutionId,
200
+ workflowRunAttemptId: jobExecution.workflowRunAttemptId,
201
+ lastHeartbeatAt: jobExecution.lastHeartbeatAt,
202
+ cancellationRequestedAt: jobExecution.cancellationRequestedAt,
203
+ };
204
+ }
205
+
206
+ export function desiredIntent(
207
+ row: RunnerInstance | undefined,
208
+ boundJobExecution: RunnerInstanceBoundJobExecution | undefined,
209
+ ): ReconcileDesiredIntent {
210
+ return getDesiredIntentReason(row, boundJobExecution) ? 'terminate' : 'keep';
211
+ }
212
+
213
+ function getDesiredIntentReason(
214
+ row: RunnerInstance | undefined,
215
+ boundJobExecution: RunnerInstanceBoundJobExecution | undefined,
216
+ ): ReconcileDesiredIntentReason | null {
217
+ if (!row) return null;
218
+ if (isTerminalState(row.state)) return 'terminal-state';
219
+ if (boundJobExecution?.cancellationRequestedAt) return 'job-cancelled';
220
+ return null;
221
+ }
222
+
223
+ function mergeActiveRunners(
224
+ providerRunners: RunnerInstance[],
225
+ jobExecutions: ActiveRunningJobExecution[],
226
+ ): ActiveRunner[] {
227
+ const jobExecutionsByRunnerSessionId = new Map<string, ActiveRunningJobExecution[]>();
228
+ const jobExecutionsByRunnerInstanceId = new Map<string, ActiveRunningJobExecution[]>();
229
+ for (const jobExecution of jobExecutions) {
230
+ const runnerJobExecutions =
231
+ jobExecutionsByRunnerSessionId.get(jobExecution.runnerSessionId) ?? [];
232
+ runnerJobExecutions.push(jobExecution);
233
+ jobExecutionsByRunnerSessionId.set(jobExecution.runnerSessionId, runnerJobExecutions);
234
+
235
+ if (jobExecution.provisionerId && jobExecution.providerRunnerId) {
236
+ const key = providerRunnerKey(jobExecution.provisionerId, jobExecution.providerRunnerId);
237
+ const providerRunnerJobExecutions = jobExecutionsByRunnerInstanceId.get(key) ?? [];
238
+ providerRunnerJobExecutions.push(jobExecution);
239
+ jobExecutionsByRunnerInstanceId.set(key, providerRunnerJobExecutions);
240
+ }
241
+ }
242
+
243
+ const merged: ActiveRunner[] = [];
244
+ const usedJobExecutionIds = new Set<string>();
245
+
246
+ for (const providerRunner of providerRunners) {
247
+ const providerRunnerJobExecutions =
248
+ jobExecutionsByRunnerInstanceId.get(
249
+ providerRunnerKey(providerRunner.provisionerId, providerRunner.providerRunnerId),
250
+ ) ??
251
+ (providerRunner.runnerSessionId
252
+ ? jobExecutionsByRunnerSessionId.get(providerRunner.runnerSessionId)
253
+ : undefined);
254
+ if (!providerRunnerJobExecutions || providerRunnerJobExecutions.length === 0) {
255
+ merged.push(toActiveRunner(providerRunner, undefined));
256
+ continue;
257
+ }
258
+
259
+ let emitted = false;
260
+ for (const jobExecution of providerRunnerJobExecutions) {
261
+ if (usedJobExecutionIds.has(jobExecution.jobExecutionId)) continue;
262
+ usedJobExecutionIds.add(jobExecution.jobExecutionId);
263
+ merged.push(toActiveRunner(providerRunner, jobExecution));
264
+ emitted = true;
265
+ }
266
+ if (!emitted) merged.push(toActiveRunner(providerRunner, undefined));
267
+ }
268
+
269
+ for (const jobExecution of jobExecutions) {
270
+ if (usedJobExecutionIds.has(jobExecution.jobExecutionId)) continue;
271
+ merged.push(toActiveRunner(undefined, jobExecution));
272
+ }
273
+
274
+ return merged.sort(compareActiveRunners);
275
+ }
276
+
277
+ function providerRunnerKey(provisionerId: string, providerRunnerId: string): string {
278
+ return `${provisionerId}:${providerRunnerId}`;
279
+ }
280
+
281
+ function toActiveRunner(
282
+ providerRunner: RunnerInstance | undefined,
283
+ jobExecution: ActiveRunningJobExecution | undefined,
284
+ ): ActiveRunner {
285
+ return {
286
+ runnerSessionId: providerRunner?.runnerSessionId ?? jobExecution?.runnerSessionId ?? null,
287
+ providerRunnerId: providerRunner?.providerRunnerId ?? jobExecution?.providerRunnerId ?? null,
288
+ provisionerId: providerRunner?.provisionerId ?? jobExecution?.provisionerId ?? null,
289
+ state: jobExecution ? 'busy' : toActiveRunnerState(providerRunner?.state ?? 'running'),
290
+ labels: providerRunner?.labels ?? jobExecution?.runnerLabels ?? [],
291
+ templateKey: providerRunner?.templateKey ?? null,
292
+ providerKind: providerRunner?.providerKind ?? null,
293
+ jobId: jobExecution?.jobId ?? null,
294
+ workflowRunAttemptId: jobExecution?.workflowRunAttemptId ?? null,
295
+ projectId: jobExecution?.projectId ?? null,
296
+ reportedAt: providerRunner?.reportedAt ?? null,
297
+ lastHeartbeatAt: jobExecution?.lastHeartbeatAt ?? null,
298
+ };
299
+ }
300
+
301
+ function toActiveRunnerState(state: RunnerInstanceState): ActiveRunnerState {
302
+ if (state === 'starting' || state === 'stopping') return state;
303
+ return 'running';
304
+ }
305
+
306
+ function compareActiveRunners(a: ActiveRunner, b: ActiveRunner): number {
307
+ const aTime = Math.max(a.lastHeartbeatAt?.getTime() ?? 0, a.reportedAt?.getTime() ?? 0);
308
+ const bTime = Math.max(b.lastHeartbeatAt?.getTime() ?? 0, b.reportedAt?.getTime() ?? 0);
309
+ return bTime - aTime;
310
+ }
@@ -2,18 +2,21 @@ import {verifyRunnerSessionToken} from '@shipfox/api-auth';
2
2
  import {and, eq} from 'drizzle-orm';
3
3
  import {db} from '#db/db.js';
4
4
  import {ephemeralRegistrationTokens} from '#db/schema/ephemeral-registration-tokens.js';
5
- import {provisionedRunners} from '#db/schema/provisioned-runners.js';
5
+ import {runnerActivationTokens} from '#db/schema/runner-activation-tokens.js';
6
+ import {providerRunners} from '#db/schema/runner-instances.js';
6
7
  import {runnerSessions} from '#db/schema/runner-sessions.js';
7
8
  import {
8
9
  ephemeralRegistrationTokenFactory,
9
10
  manualRegistrationTokenFactory,
10
- provisionedRunnerFactory,
11
+ providerRunnerFactory,
12
+ runnersTestAuthClient,
11
13
  } from '#test/index.js';
12
14
  import {
13
15
  EmptyRunnerLabelsError,
14
16
  RegistrationTokenConsumedError,
15
17
  RegistrationTokenWorkspaceMismatchError,
16
18
  } from './errors.js';
19
+ import {issueRunnerActivationToken} from './runner-activation.js';
17
20
  import {registerRunnerSession} from './runner-sessions.js';
18
21
 
19
22
  describe('registerRunnerSession', () => {
@@ -28,6 +31,7 @@ describe('registerRunnerSession', () => {
28
31
 
29
32
  it('canonicalizes labels, stores them, and embeds them in the session token', async () => {
30
33
  const result = await registerRunnerSession({
34
+ auth: runnersTestAuthClient,
31
35
  credential: {kind: 'manual', registrationTokenId, workspaceId},
32
36
  labels: [' Linux ', 'x64', 'linux'],
33
37
  });
@@ -37,7 +41,7 @@ describe('registerRunnerSession', () => {
37
41
  expect(result.session.labels).toEqual(['linux', 'x64']);
38
42
  expect(result.session.registrationTokenKind).toBe('manual');
39
43
  expect(result.session.provisionerId).toBeNull();
40
- expect(result.session.provisionedRunnerId).toBeNull();
44
+ expect(result.session.providerRunnerId).toBeNull();
41
45
  expect(result.session.maxClaims).toBeNull();
42
46
  expect(result.session.claimsUsed).toBe(0);
43
47
 
@@ -55,6 +59,7 @@ describe('registerRunnerSession', () => {
55
59
  it('throws EmptyRunnerLabelsError when labels canonicalize to empty', async () => {
56
60
  await expect(
57
61
  registerRunnerSession({
62
+ auth: runnersTestAuthClient,
58
63
  credential: {kind: 'manual', registrationTokenId, workspaceId},
59
64
  labels: [' ', '\t'],
60
65
  }),
@@ -65,13 +70,14 @@ describe('registerRunnerSession', () => {
65
70
  const token = await ephemeralRegistrationTokenFactory.create({workspaceId});
66
71
 
67
72
  const result = await registerRunnerSession({
73
+ auth: runnersTestAuthClient,
68
74
  credential: {
69
75
  kind: 'ephemeral',
70
76
  ephemeralTokenId: token.id,
71
77
  workspaceId,
72
78
  provisionerId: token.provisionerId,
73
79
  reservationId: token.reservationId,
74
- provisionedRunnerId: token.provisionedRunnerId,
80
+ providerRunnerId: token.providerRunnerId,
75
81
  },
76
82
  labels: [' Linux ', 'x64'],
77
83
  });
@@ -80,7 +86,7 @@ describe('registerRunnerSession', () => {
80
86
  expect(result.maxClaims).toBe(1);
81
87
  expect(result.session.registrationTokenKind).toBe('ephemeral');
82
88
  expect(result.session.provisionerId).toBe(token.provisionerId);
83
- expect(result.session.provisionedRunnerId).toBe(token.provisionedRunnerId);
89
+ expect(result.session.providerRunnerId).toBe(token.providerRunnerId);
84
90
  expect(result.session.maxClaims).toBe(1);
85
91
  expect(result.session.claimsUsed).toBe(0);
86
92
 
@@ -96,7 +102,7 @@ describe('registerRunnerSession', () => {
96
102
  .from(runnerSessions)
97
103
  .where(eq(runnerSessions.id, result.session.id));
98
104
  expect(session?.provisionerId).toBe(token.provisionerId);
99
- expect(session?.provisionedRunnerId).toBe(token.provisionedRunnerId);
105
+ expect(session?.providerRunnerId).toBe(token.providerRunnerId);
100
106
 
101
107
  const claims = await verifyRunnerSessionToken(result.sessionToken);
102
108
  expect(claims?.maxClaims).toBe(1);
@@ -104,37 +110,38 @@ describe('registerRunnerSession', () => {
104
110
 
105
111
  it('links an existing provisioned runner row when consuming an ephemeral token', async () => {
106
112
  const token = await ephemeralRegistrationTokenFactory.create({workspaceId});
107
- await provisionedRunnerFactory.create({
113
+ await providerRunnerFactory.create({
108
114
  workspaceId,
109
115
  provisionerId: token.provisionerId,
110
- provisionedRunnerId: token.provisionedRunnerId,
116
+ providerRunnerId: token.providerRunnerId,
111
117
  runnerSessionId: null,
112
118
  state: 'starting',
113
119
  });
114
120
 
115
121
  const result = await registerRunnerSession({
122
+ auth: runnersTestAuthClient,
116
123
  credential: {
117
124
  kind: 'ephemeral',
118
125
  ephemeralTokenId: token.id,
119
126
  workspaceId,
120
127
  provisionerId: token.provisionerId,
121
128
  reservationId: token.reservationId,
122
- provisionedRunnerId: token.provisionedRunnerId,
129
+ providerRunnerId: token.providerRunnerId,
123
130
  },
124
131
  labels: ['linux'],
125
132
  });
126
133
 
127
- const [provisionedRunner] = await db()
134
+ const [providerRunner] = await db()
128
135
  .select()
129
- .from(provisionedRunners)
136
+ .from(providerRunners)
130
137
  .where(
131
138
  and(
132
- eq(provisionedRunners.workspaceId, workspaceId),
133
- eq(provisionedRunners.provisionerId, token.provisionerId),
134
- eq(provisionedRunners.provisionedRunnerId, token.provisionedRunnerId),
139
+ eq(providerRunners.workspaceId, workspaceId),
140
+ eq(providerRunners.provisionerId, token.provisionerId),
141
+ eq(providerRunners.providerRunnerId, token.providerRunnerId),
135
142
  ),
136
143
  );
137
- expect(provisionedRunner?.runnerSessionId).toBe(result.session.id);
144
+ expect(providerRunner?.runnerSessionId).toBe(result.session.id);
138
145
  });
139
146
 
140
147
  it('rejects a second consume of the same ephemeral token', async () => {
@@ -145,14 +152,14 @@ describe('registerRunnerSession', () => {
145
152
  workspaceId,
146
153
  provisionerId: token.provisionerId,
147
154
  reservationId: token.reservationId,
148
- provisionedRunnerId: token.provisionedRunnerId,
155
+ providerRunnerId: token.providerRunnerId,
149
156
  };
150
157
 
151
- await registerRunnerSession({credential, labels: ['linux']});
158
+ await registerRunnerSession({auth: runnersTestAuthClient, credential, labels: ['linux']});
152
159
 
153
- await expect(registerRunnerSession({credential, labels: ['linux']})).rejects.toBeInstanceOf(
154
- RegistrationTokenConsumedError,
155
- );
160
+ await expect(
161
+ registerRunnerSession({auth: runnersTestAuthClient, credential, labels: ['linux']}),
162
+ ).rejects.toBeInstanceOf(RegistrationTokenConsumedError);
156
163
  });
157
164
 
158
165
  it('rejects consuming an ephemeral token for a different workspace', async () => {
@@ -160,13 +167,14 @@ describe('registerRunnerSession', () => {
160
167
 
161
168
  await expect(
162
169
  registerRunnerSession({
170
+ auth: runnersTestAuthClient,
163
171
  credential: {
164
172
  kind: 'ephemeral',
165
173
  ephemeralTokenId: token.id,
166
174
  workspaceId: crypto.randomUUID(),
167
175
  provisionerId: token.provisionerId,
168
176
  reservationId: token.reservationId,
169
- provisionedRunnerId: token.provisionedRunnerId,
177
+ providerRunnerId: token.providerRunnerId,
170
178
  },
171
179
  labels: ['linux'],
172
180
  }),
@@ -183,7 +191,7 @@ describe('registerRunnerSession', () => {
183
191
  registrationTokenId: crypto.randomUUID(),
184
192
  registrationTokenKind: 'ephemeral',
185
193
  provisionerId: crypto.randomUUID(),
186
- provisionedRunnerId: `provisioned-runner-${crypto.randomUUID()}`,
194
+ providerRunnerId: `provisioned-runner-${crypto.randomUUID()}`,
187
195
  labels: ['linux'],
188
196
  maxClaims: null,
189
197
  claimsUsed: 0,
@@ -207,3 +215,85 @@ describe('registerRunnerSession', () => {
207
215
  ).rejects.toThrow();
208
216
  });
209
217
  });
218
+
219
+ describe('activation runner sessions', () => {
220
+ let workspaceId: string;
221
+ let provisionerId: string;
222
+ let runnerInstanceId: string;
223
+
224
+ beforeEach(async () => {
225
+ workspaceId = crypto.randomUUID();
226
+ provisionerId = crypto.randomUUID();
227
+ const runner = await providerRunnerFactory.create({workspaceId, provisionerId});
228
+ runnerInstanceId = runner.id;
229
+ });
230
+
231
+ it('replaces an unconsumed activation token when assignment delivery is retried', async () => {
232
+ const firstToken = await issueRunnerActivationToken({
233
+ runnerInstanceId,
234
+ provisionerId,
235
+ ttlSeconds: 60,
236
+ });
237
+ const replacementToken = await issueRunnerActivationToken({
238
+ runnerInstanceId,
239
+ provisionerId,
240
+ ttlSeconds: 60,
241
+ });
242
+
243
+ const tokens = await db()
244
+ .select()
245
+ .from(runnerActivationTokens)
246
+ .where(eq(runnerActivationTokens.runnerInstanceId, runnerInstanceId));
247
+
248
+ expect(firstToken).not.toBeNull();
249
+ expect(replacementToken).not.toBeNull();
250
+ expect(replacementToken).not.toBe(firstToken);
251
+ expect(tokens).toHaveLength(2);
252
+ expect(tokens.filter((token) => token.revokedAt === null)).toHaveLength(1);
253
+ expect(tokens.filter((token) => token.revokedAt !== null)).toHaveLength(1);
254
+ });
255
+
256
+ it('allows only one concurrent registration to consume an activation token', async () => {
257
+ const rawToken = await issueRunnerActivationToken({
258
+ runnerInstanceId,
259
+ provisionerId,
260
+ ttlSeconds: 60,
261
+ });
262
+ const [activationToken] = await db()
263
+ .select()
264
+ .from(runnerActivationTokens)
265
+ .where(eq(runnerActivationTokens.runnerInstanceId, runnerInstanceId));
266
+ if (!rawToken || !activationToken) throw new Error('Activation token was not created');
267
+
268
+ const registrations = await Promise.allSettled([
269
+ registerRunnerSession({
270
+ auth: runnersTestAuthClient,
271
+ credential: {kind: 'activation', activationTokenId: activationToken.id, workspaceId},
272
+ labels: ['linux'],
273
+ }),
274
+ registerRunnerSession({
275
+ auth: runnersTestAuthClient,
276
+ credential: {kind: 'activation', activationTokenId: activationToken.id, workspaceId},
277
+ labels: ['linux'],
278
+ }),
279
+ ]);
280
+
281
+ const [storedToken] = await db()
282
+ .select()
283
+ .from(runnerActivationTokens)
284
+ .where(eq(runnerActivationTokens.id, activationToken.id));
285
+ const sessions = await db()
286
+ .select()
287
+ .from(runnerSessions)
288
+ .where(eq(runnerSessions.runnerInstanceId, runnerInstanceId));
289
+
290
+ expect(
291
+ registrations.filter((registration) => registration.status === 'fulfilled'),
292
+ ).toHaveLength(1);
293
+ expect(registrations.filter((registration) => registration.status === 'rejected')).toHaveLength(
294
+ 1,
295
+ );
296
+ expect(storedToken?.consumedAt).toBeInstanceOf(Date);
297
+ expect(sessions).toHaveLength(1);
298
+ });
299
+ });
@@ -1,15 +1,18 @@
1
- import {issueRunnerSessionToken} from '@shipfox/api-auth';
1
+ import type {AuthInterModuleClient} from '@shipfox/api-auth-dto/inter-module';
2
2
  import type {RunnerToolCapabilitiesDto} from '@shipfox/api-runners-dto';
3
3
  import {canonicalizeLabels} from '@shipfox/runner-labels';
4
4
  import {createRunnerSessionConsumingEphemeralToken} from '#db/ephemeral-registration-tokens.js';
5
- import {createRunnerSession} from '#db/runner-sessions.js';
5
+ import {
6
+ createRunnerSession,
7
+ createRunnerSessionConsumingActivationToken,
8
+ } from '#db/runner-sessions.js';
6
9
  import type {RunnerSession} from './entities/runner-session.js';
7
10
  import {EmptyRunnerLabelsError} from './errors.js';
8
11
 
9
12
  export interface RegisterRunnerSessionResult {
10
13
  session: RunnerSession;
11
14
  sessionToken: string;
12
- mode: 'manual' | 'ephemeral';
15
+ mode: 'manual' | 'ephemeral' | 'activation';
13
16
  maxClaims: number | null;
14
17
  }
15
18
 
@@ -25,10 +28,12 @@ export type RunnerRegistrationCredential =
25
28
  workspaceId: string;
26
29
  provisionerId: string;
27
30
  reservationId: string | null;
28
- provisionedRunnerId: string;
29
- };
31
+ providerRunnerId: string;
32
+ }
33
+ | {kind: 'activation'; activationTokenId: string; workspaceId: string};
30
34
 
31
35
  export async function registerRunnerSession(params: {
36
+ auth: AuthInterModuleClient;
32
37
  credential: RunnerRegistrationCredential;
33
38
  labels: string[];
34
39
  toolCapabilities?: RunnerToolCapabilitiesDto | null;
@@ -37,7 +42,7 @@ export async function registerRunnerSession(params: {
37
42
  if (labels.length === 0) throw new EmptyRunnerLabelsError();
38
43
 
39
44
  const mode = params.credential.kind;
40
- const maxClaims = params.credential.kind === 'ephemeral' ? 1 : null;
45
+ const maxClaims = params.credential.kind === 'manual' ? null : 1;
41
46
  const session =
42
47
  params.credential.kind === 'manual'
43
48
  ? await createRunnerSession({
@@ -47,14 +52,20 @@ export async function registerRunnerSession(params: {
47
52
  labels,
48
53
  toolCapabilities: params.toolCapabilities ?? null,
49
54
  })
50
- : await createRunnerSessionConsumingEphemeralToken({
51
- ephemeralTokenId: params.credential.ephemeralTokenId,
52
- workspaceId: params.credential.workspaceId,
53
- labels,
54
- toolCapabilities: params.toolCapabilities ?? null,
55
- maxClaims: 1,
56
- });
57
- const sessionToken = await issueRunnerSessionToken({
55
+ : params.credential.kind === 'ephemeral'
56
+ ? await createRunnerSessionConsumingEphemeralToken({
57
+ ephemeralTokenId: params.credential.ephemeralTokenId,
58
+ workspaceId: params.credential.workspaceId,
59
+ labels,
60
+ toolCapabilities: params.toolCapabilities ?? null,
61
+ maxClaims: 1,
62
+ })
63
+ : await createRunnerSessionConsumingActivationToken({
64
+ activationTokenId: params.credential.activationTokenId,
65
+ labels,
66
+ toolCapabilities: params.toolCapabilities ?? null,
67
+ });
68
+ const {token: sessionToken} = await params.auth.mintRunnerSessionToken({
58
69
  runnerSessionId: session.id,
59
70
  workspaceId: session.workspaceId,
60
71
  scope: session.scope,