@shipfox/api-runners 12.2.0 → 12.4.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 (149) hide show
  1. package/.turbo/turbo-build.log +4 -4
  2. package/CHANGELOG.md +34 -0
  3. package/dist/config.d.ts +1 -0
  4. package/dist/config.d.ts.map +1 -1
  5. package/dist/config.js +10 -3
  6. package/dist/config.js.map +1 -1
  7. package/dist/core/demand.d.ts +4 -2
  8. package/dist/core/demand.d.ts.map +1 -1
  9. package/dist/core/demand.js +22 -6
  10. package/dist/core/demand.js.map +1 -1
  11. package/dist/core/entities/reservation.d.ts +1 -0
  12. package/dist/core/entities/reservation.d.ts.map +1 -1
  13. package/dist/core/entities/reservation.js.map +1 -1
  14. package/dist/core/entities/runner-instance.d.ts +3 -0
  15. package/dist/core/entities/runner-instance.d.ts.map +1 -1
  16. package/dist/core/entities/runner-instance.js.map +1 -1
  17. package/dist/core/runner-activation.d.ts +4 -1
  18. package/dist/core/runner-activation.d.ts.map +1 -1
  19. package/dist/core/runner-activation.js +32 -3
  20. package/dist/core/runner-activation.js.map +1 -1
  21. package/dist/core/runner-control-sessions.d.ts +8 -4
  22. package/dist/core/runner-control-sessions.d.ts.map +1 -1
  23. package/dist/core/runner-control-sessions.js +69 -11
  24. package/dist/core/runner-control-sessions.js.map +1 -1
  25. package/dist/core/runner-instances.d.ts +1 -0
  26. package/dist/core/runner-instances.d.ts.map +1 -1
  27. package/dist/core/runner-instances.js +1 -0
  28. package/dist/core/runner-instances.js.map +1 -1
  29. package/dist/db/db.d.ts +68 -0
  30. package/dist/db/db.d.ts.map +1 -1
  31. package/dist/db/ephemeral-registration-tokens.d.ts.map +1 -1
  32. package/dist/db/ephemeral-registration-tokens.js +1 -1
  33. package/dist/db/ephemeral-registration-tokens.js.map +1 -1
  34. package/dist/db/index.d.ts +1 -1
  35. package/dist/db/index.d.ts.map +1 -1
  36. package/dist/db/index.js +1 -1
  37. package/dist/db/index.js.map +1 -1
  38. package/dist/db/reservation-locks.d.ts +6 -0
  39. package/dist/db/reservation-locks.d.ts.map +1 -0
  40. package/dist/db/reservation-locks.js +15 -0
  41. package/dist/db/reservation-locks.js.map +1 -0
  42. package/dist/db/reservations.d.ts +17 -3
  43. package/dist/db/reservations.d.ts.map +1 -1
  44. package/dist/db/reservations.js +256 -45
  45. package/dist/db/reservations.js.map +1 -1
  46. package/dist/db/runner-assignments.d.ts +17 -0
  47. package/dist/db/runner-assignments.d.ts.map +1 -1
  48. package/dist/db/runner-assignments.js +103 -8
  49. package/dist/db/runner-assignments.js.map +1 -1
  50. package/dist/db/runner-instances.d.ts +5 -2
  51. package/dist/db/runner-instances.d.ts.map +1 -1
  52. package/dist/db/runner-instances.js +134 -25
  53. package/dist/db/runner-instances.js.map +1 -1
  54. package/dist/db/runner-sessions.d.ts.map +1 -1
  55. package/dist/db/runner-sessions.js +22 -15
  56. package/dist/db/runner-sessions.js.map +1 -1
  57. package/dist/db/runner-states.d.ts +2 -0
  58. package/dist/db/runner-states.d.ts.map +1 -0
  59. package/dist/db/runner-states.js +7 -0
  60. package/dist/db/runner-states.js.map +1 -0
  61. package/dist/db/schema/reservations.d.ts +18 -0
  62. package/dist/db/schema/reservations.d.ts.map +1 -1
  63. package/dist/db/schema/reservations.js +7 -1
  64. package/dist/db/schema/reservations.js.map +1 -1
  65. package/dist/db/schema/runner-instances.d.ts +18 -0
  66. package/dist/db/schema/runner-instances.d.ts.map +1 -1
  67. package/dist/db/schema/runner-instances.js +8 -0
  68. package/dist/db/schema/runner-instances.js.map +1 -1
  69. package/dist/metrics/index.d.ts +2 -2
  70. package/dist/metrics/index.d.ts.map +1 -1
  71. package/dist/metrics/index.js +1 -1
  72. package/dist/metrics/index.js.map +1 -1
  73. package/dist/metrics/instance.d.ts +24 -2
  74. package/dist/metrics/instance.d.ts.map +1 -1
  75. package/dist/metrics/instance.js +23 -0
  76. package/dist/metrics/instance.js.map +1 -1
  77. package/dist/metrics/service.d.ts.map +1 -1
  78. package/dist/metrics/service.js +20 -4
  79. package/dist/metrics/service.js.map +1 -1
  80. package/dist/presentation/dto/poll-demand.d.ts.map +1 -1
  81. package/dist/presentation/dto/poll-demand.js +3 -0
  82. package/dist/presentation/dto/poll-demand.js.map +1 -1
  83. package/dist/presentation/dto/runner-instances.d.ts +4 -1
  84. package/dist/presentation/dto/runner-instances.d.ts.map +1 -1
  85. package/dist/presentation/dto/runner-instances.js +4 -1
  86. package/dist/presentation/dto/runner-instances.js.map +1 -1
  87. package/dist/presentation/routes/assign-runner-instances.d.ts.map +1 -1
  88. package/dist/presentation/routes/assign-runner-instances.js +3 -3
  89. package/dist/presentation/routes/assign-runner-instances.js.map +1 -1
  90. package/dist/presentation/routes/poll-demand.d.ts.map +1 -1
  91. package/dist/presentation/routes/poll-demand.js +1 -0
  92. package/dist/presentation/routes/poll-demand.js.map +1 -1
  93. package/dist/presentation/routes/reconcile-runner-instances.d.ts.map +1 -1
  94. package/dist/presentation/routes/reconcile-runner-instances.js +4 -2
  95. package/dist/presentation/routes/reconcile-runner-instances.js.map +1 -1
  96. package/dist/presentation/routes/runner-enrollment.d.ts +4 -1
  97. package/dist/presentation/routes/runner-enrollment.d.ts.map +1 -1
  98. package/dist/presentation/routes/runner-enrollment.js +11 -6
  99. package/dist/presentation/routes/runner-enrollment.js.map +1 -1
  100. package/dist/tsconfig.test.tsbuildinfo +1 -1
  101. package/drizzle/0005_previous_lenny_balinger.sql +5 -0
  102. package/drizzle/meta/0005_snapshot.json +2220 -0
  103. package/drizzle/meta/_journal.json +7 -0
  104. package/package.json +4 -4
  105. package/src/config.test.ts +24 -2
  106. package/src/config.ts +16 -3
  107. package/src/core/demand.test.ts +192 -3
  108. package/src/core/demand.ts +44 -4
  109. package/src/core/entities/reservation.ts +1 -0
  110. package/src/core/entities/runner-instance.ts +4 -0
  111. package/src/core/runner-activation.ts +48 -2
  112. package/src/core/runner-control-sessions.test.ts +230 -4
  113. package/src/core/runner-control-sessions.ts +84 -10
  114. package/src/core/runner-instances.test.ts +19 -0
  115. package/src/core/runner-instances.ts +2 -0
  116. package/src/core/runner-sessions.test.ts +121 -1
  117. package/src/db/ephemeral-registration-tokens.test.ts +20 -1
  118. package/src/db/ephemeral-registration-tokens.ts +1 -0
  119. package/src/db/index.ts +1 -0
  120. package/src/db/reservation-locks.ts +22 -0
  121. package/src/db/reservations.test.ts +1110 -31
  122. package/src/db/reservations.ts +434 -105
  123. package/src/db/runner-assignments.test.ts +105 -1
  124. package/src/db/runner-assignments.ts +178 -9
  125. package/src/db/runner-instances.test.ts +496 -2
  126. package/src/db/runner-instances.ts +247 -56
  127. package/src/db/runner-sessions.ts +26 -15
  128. package/src/db/runner-states.ts +7 -0
  129. package/src/db/schema/reservations.ts +5 -1
  130. package/src/db/schema/runner-instances.ts +9 -0
  131. package/src/metrics/index.ts +10 -1
  132. package/src/metrics/instance.ts +59 -2
  133. package/src/metrics/service.test.ts +106 -0
  134. package/src/metrics/service.ts +24 -4
  135. package/src/presentation/dto/poll-demand.ts +3 -0
  136. package/src/presentation/dto/runner-instances.ts +8 -1
  137. package/src/presentation/routes/assign-runner-instances.ts +6 -2
  138. package/src/presentation/routes/late-runner-enrollment.test.ts +447 -0
  139. package/src/presentation/routes/poll-demand.test.ts +79 -6
  140. package/src/presentation/routes/poll-demand.ts +1 -0
  141. package/src/presentation/routes/reconcile-runner-instances.test.ts +34 -2
  142. package/src/presentation/routes/reconcile-runner-instances.ts +7 -1
  143. package/src/presentation/routes/runner-enrollment.test.ts +316 -3
  144. package/src/presentation/routes/runner-enrollment.ts +8 -5
  145. package/test/factories/reservation.ts +2 -0
  146. package/test/factories/runner-instance.ts +6 -0
  147. package/test/fixtures/late-runner-enrollment.ts +103 -0
  148. package/test/index.ts +5 -0
  149. package/tsconfig.build.tsbuildinfo +1 -1
@@ -31,6 +31,21 @@ export const runnerBootstrapExchangeCount = meter.createCounter<{
31
31
  description: 'Runner bootstrap-token exchanges by outcome',
32
32
  });
33
33
 
34
+ export type RunnerActivationTokenNotIssuedReason =
35
+ | 'runner-not-found'
36
+ | 'missing-workspace'
37
+ | 'existing-session'
38
+ | 'not-running';
39
+
40
+ export type RunnerActivationTokenNotIssuedSurface = 'enrollment' | 'poll';
41
+
42
+ export const runnerActivationTokenNotIssuedCount = meter.createCounter<{
43
+ reason: RunnerActivationTokenNotIssuedReason;
44
+ surface: RunnerActivationTokenNotIssuedSurface;
45
+ }>('runners_runner_activation_token_not_issued', {
46
+ description: 'Runner activation token issuance skips by reason and surface',
47
+ });
48
+
34
49
  export const runnerControlHeartbeatCount = meter.createCounter<Record<string, never>>(
35
50
  'runners_runner_control_heartbeat',
36
51
  {description: 'Pre-workspace runner-control heartbeats accepted'},
@@ -67,17 +82,23 @@ export const providerRunnerAbsentTerminatedCount = meter.createCounter<Record<st
67
82
 
68
83
  export const providerRunnerTerminateIntentIssuedCount = meter.createCounter<{
69
84
  surface: 'poll-demand' | 'reconcile';
70
- reason: 'job-cancelled' | 'terminal-state';
85
+ reason: 'activation-timeout' | 'job-cancelled' | 'terminal-state';
71
86
  }>('runners_provider_runner_terminate_intent_issued', {
72
87
  description: 'Provisioned runner terminate intents returned to provisioners',
73
88
  });
74
89
 
75
90
  export const providerRunnerTerminateIntentHonoredCount = meter.createCounter<{
76
- reason: 'job-cancelled';
91
+ reason: 'activation-timeout' | 'job-cancelled';
77
92
  }>('runners_provider_runner_terminate_intent_honored', {
78
93
  description: 'Provisioned runner terminate intents honored by first transition to terminated',
79
94
  });
80
95
 
96
+ export const providerRunnerActivationOutcomeCount = meter.createCounter<{
97
+ outcome: 'reaped' | 'rebound';
98
+ }>('runners_provider_runner_activation_outcome', {
99
+ description: 'Demand-backed runner activation outcomes by recovery action',
100
+ });
101
+
81
102
  export const reservationReleasedCount = meter.createCounter<Record<string, never>>(
82
103
  'runners_reservation_released',
83
104
  {description: 'Reservation units released from terminal provisioned runner reports'},
@@ -95,6 +116,18 @@ export const runnerReservationPromotionFailureCount = meter.createCounter<{
95
116
  description: 'Runner reservation promotion failures during enrollment by reason',
96
117
  });
97
118
 
119
+ export type RunnerReservationCapacityFailureReason =
120
+ | 'reservation-not-found'
121
+ | 'reservation-kind-mismatch'
122
+ | 'reservation-expired'
123
+ | 'capacity-exhausted';
124
+
125
+ export const runnerReservationCapacityFailureCount = meter.createCounter<{
126
+ reason: RunnerReservationCapacityFailureReason;
127
+ }>('runners_reservation_capacity_failures', {
128
+ description: 'Runner reservation admission shortfalls by reason',
129
+ });
130
+
98
131
  export type RunnersRateLimitAction = 'provisioner-mint' | 'ephemeral-register';
99
132
  export type RunnersRateLimitScope = 'provisioner' | 'ephemeral-token';
100
133
  export type RunnersRateLimitOutcome = 'allowed' | 'blocked' | 'unavailable';
@@ -125,6 +158,21 @@ export function recordRunnerReservationPromotionFailure(
125
158
  recordMetric(() => runnerReservationPromotionFailureCount.add(1, {reason}));
126
159
  }
127
160
 
161
+ export function recordRunnerActivationTokenNotIssued(params: {
162
+ reason: RunnerActivationTokenNotIssuedReason;
163
+ surface: RunnerActivationTokenNotIssuedSurface;
164
+ }): void {
165
+ recordMetric(() => runnerActivationTokenNotIssuedCount.add(1, params));
166
+ }
167
+
168
+ export function recordRunnerReservationCapacityFailure(
169
+ reason: RunnerReservationCapacityFailureReason,
170
+ count: number,
171
+ ): void {
172
+ if (count <= 0) return;
173
+ recordMetric(() => runnerReservationCapacityFailureCount.add(count, {reason}));
174
+ }
175
+
128
176
  export function recordRunnersRateLimitCheck(params: {
129
177
  action: RunnersRateLimitAction;
130
178
  scope: RunnersRateLimitScope;
@@ -142,3 +190,12 @@ export function recordRunnersRateLimitCheck(params: {
142
190
  export function recordRunnersRateLimitPruneFailure(): void {
143
191
  recordMetric(() => rateLimitPruneFailureCount.add(1));
144
192
  }
193
+
194
+ export function recordProviderRunnerActivationOutcome(params: {
195
+ outcome: 'reaped' | 'rebound';
196
+ count?: number;
197
+ }): void {
198
+ recordMetric(() =>
199
+ providerRunnerActivationOutcomeCount.add(params.count ?? 1, {outcome: params.outcome}),
200
+ );
201
+ }
@@ -0,0 +1,106 @@
1
+ const mocks = vi.hoisted(() => {
2
+ const gauges = {
3
+ enrolledRunnersWithoutRecentReport: {},
4
+ pendingJobExecutions: {},
5
+ runningJobExecutions: {},
6
+ };
7
+ const gaugeByName = {
8
+ runners_enrolled_without_recent_report: gauges.enrolledRunnersWithoutRecentReport,
9
+ runners_pending_job_executions: gauges.pendingJobExecutions,
10
+ runners_running_job_executions: gauges.runningJobExecutions,
11
+ };
12
+ return {
13
+ addBatchObservableCallback: vi.fn(),
14
+ countStaleEnrolledRunnerInstances: vi.fn(),
15
+ createObservableGauge: vi.fn((name: string) => gaugeByName[name as keyof typeof gaugeByName]),
16
+ gauges,
17
+ getMeter: vi.fn(),
18
+ getJobExecutionQueueDepth: vi.fn(),
19
+ getServiceMetricsProvider: vi.fn(),
20
+ };
21
+ });
22
+
23
+ vi.mock('@shipfox/node-opentelemetry', () => ({
24
+ getServiceMetricsProvider: mocks.getServiceMetricsProvider,
25
+ }));
26
+ vi.mock('#config.js', () => ({
27
+ config: {RUNNER_STALE_PROVISIONED_RUNNER_THRESHOLD_SECONDS: 300},
28
+ }));
29
+ vi.mock('#db/job-executions.js', () => ({
30
+ getJobExecutionQueueDepth: mocks.getJobExecutionQueueDepth,
31
+ }));
32
+ vi.mock('#db/runner-instances.js', () => ({
33
+ countStaleEnrolledRunnerInstances: mocks.countStaleEnrolledRunnerInstances,
34
+ }));
35
+
36
+ let registerRunnersServiceMetrics: typeof import('./service.js').registerRunnersServiceMetrics;
37
+
38
+ beforeAll(async () => {
39
+ // This package intentionally runs with isolate: false. Reset the shared module cache before
40
+ // importing the service so a previous file's real OpenTelemetry module cannot bypass this mock.
41
+ vi.resetModules();
42
+ ({registerRunnersServiceMetrics} = await import('./service.js'));
43
+ });
44
+
45
+ describe('registerRunnersServiceMetrics', () => {
46
+ beforeEach(() => {
47
+ mocks.addBatchObservableCallback.mockReset();
48
+ mocks.countStaleEnrolledRunnerInstances.mockReset();
49
+ mocks.createObservableGauge.mockClear();
50
+ mocks.getJobExecutionQueueDepth.mockReset();
51
+ mocks.getMeter.mockReset();
52
+ mocks.getServiceMetricsProvider.mockReset();
53
+ mocks.getJobExecutionQueueDepth.mockResolvedValue({
54
+ pendingJobExecutions: 0,
55
+ runningJobExecutions: 0,
56
+ });
57
+ mocks.getMeter.mockReturnValue({
58
+ createObservableGauge: mocks.createObservableGauge,
59
+ addBatchObservableCallback: mocks.addBatchObservableCallback,
60
+ });
61
+ mocks.getServiceMetricsProvider.mockReturnValue({getMeter: mocks.getMeter});
62
+ });
63
+
64
+ it('observes enrolled runners without recent reports after the grace window', async () => {
65
+ mocks.countStaleEnrolledRunnerInstances.mockResolvedValue(2);
66
+
67
+ registerRunnersServiceMetrics();
68
+ const callback = mocks.addBatchObservableCallback.mock.calls[0]?.[0];
69
+ if (typeof callback !== 'function') throw new Error('Expected metrics callback');
70
+ const observer = {observe: vi.fn()};
71
+
72
+ await callback(observer);
73
+
74
+ expect(mocks.createObservableGauge).toHaveBeenCalledWith(
75
+ 'runners_enrolled_without_recent_report',
76
+ {
77
+ description:
78
+ 'Running enrolled runners with a live control session, no workspace or runner session, and no recent provisioner report after the stale-runner grace window',
79
+ },
80
+ );
81
+ expect(mocks.countStaleEnrolledRunnerInstances).toHaveBeenCalledWith({graceSeconds: 300});
82
+ expect(observer.observe).toHaveBeenCalledWith(
83
+ mocks.gauges.enrolledRunnersWithoutRecentReport,
84
+ 2,
85
+ );
86
+ });
87
+
88
+ it('keeps queue gauges observable when the enrolled-runner query fails', async () => {
89
+ mocks.getJobExecutionQueueDepth.mockResolvedValue({
90
+ pendingJobExecutions: 3,
91
+ runningJobExecutions: 4,
92
+ });
93
+ mocks.countStaleEnrolledRunnerInstances.mockRejectedValue(new Error('database unavailable'));
94
+
95
+ registerRunnersServiceMetrics();
96
+ const callback = mocks.addBatchObservableCallback.mock.calls[0]?.[0];
97
+ if (typeof callback !== 'function') throw new Error('Expected metrics callback');
98
+ const observer = {observe: vi.fn()};
99
+
100
+ await callback(observer);
101
+
102
+ expect(observer.observe).toHaveBeenCalledWith(mocks.gauges.pendingJobExecutions, 3);
103
+ expect(observer.observe).toHaveBeenCalledWith(mocks.gauges.runningJobExecutions, 4);
104
+ expect(observer.observe).toHaveBeenCalledTimes(2);
105
+ });
106
+ });
@@ -1,5 +1,7 @@
1
1
  import {getServiceMetricsProvider} from '@shipfox/node-opentelemetry';
2
+ import {config} from '#config.js';
2
3
  import {getJobExecutionQueueDepth} from '#db/job-executions.js';
4
+ import {countStaleEnrolledRunnerInstances} from '#db/runner-instances.js';
3
5
 
4
6
  export function registerRunnersServiceMetrics(): void {
5
7
  const meter = getServiceMetricsProvider().getMeter('runners');
@@ -10,13 +12,31 @@ export function registerRunnersServiceMetrics(): void {
10
12
  const runningJobExecutions = meter.createObservableGauge('runners_running_job_executions', {
11
13
  description: 'Job executions currently claimed by a runner and in progress',
12
14
  });
15
+ const enrolledRunnersWithoutRecentReport = meter.createObservableGauge(
16
+ 'runners_enrolled_without_recent_report',
17
+ {
18
+ description:
19
+ 'Running enrolled runners with a live control session, no workspace or runner session, and no recent provisioner report after the stale-runner grace window',
20
+ },
21
+ );
13
22
 
14
23
  meter.addBatchObservableCallback(
15
24
  async (observer) => {
16
- const depth = await getJobExecutionQueueDepth();
17
- observer.observe(pendingJobExecutions, depth.pendingJobExecutions);
18
- observer.observe(runningJobExecutions, depth.runningJobExecutions);
25
+ const [depthResult, staleEnrolledRunnerCountResult] = await Promise.allSettled([
26
+ getJobExecutionQueueDepth(),
27
+ countStaleEnrolledRunnerInstances({
28
+ graceSeconds: config.RUNNER_STALE_PROVISIONED_RUNNER_THRESHOLD_SECONDS,
29
+ }),
30
+ ]);
31
+
32
+ if (depthResult.status === 'fulfilled') {
33
+ observer.observe(pendingJobExecutions, depthResult.value.pendingJobExecutions);
34
+ observer.observe(runningJobExecutions, depthResult.value.runningJobExecutions);
35
+ }
36
+ if (staleEnrolledRunnerCountResult.status === 'fulfilled') {
37
+ observer.observe(enrolledRunnersWithoutRecentReport, staleEnrolledRunnerCountResult.value);
38
+ }
19
39
  },
20
- [pendingJobExecutions, runningJobExecutions],
40
+ [pendingJobExecutions, runningJobExecutions, enrolledRunnersWithoutRecentReport],
21
41
  );
22
42
  }
@@ -17,6 +17,9 @@ export function toPollDemandResponseDto(result: PollDemandResult): PollDemandRes
17
17
  count: reservation.count,
18
18
  expires_at: reservation.expiresAt.toISOString(),
19
19
  })),
20
+ ...(result.newlyReservedCount !== undefined
21
+ ? {newly_reserved_count: result.newlyReservedCount}
22
+ : {}),
20
23
  terminate_provider_runner_ids: result.terminateRunnerInstanceIds,
21
24
  };
22
25
  }
@@ -14,10 +14,14 @@ export function toReportRunnerInstancesResponseDto(result: ReportRunnerInstances
14
14
  };
15
15
  }
16
16
 
17
- export function toReconcileRunnerInstancesResponseDto(result: ReconcileRunnerInstancesResult): {
17
+ export function toReconcileRunnerInstancesResponseDto(
18
+ result: ReconcileRunnerInstancesResult,
19
+ options: {includeIntendedReservationId: boolean},
20
+ ): {
18
21
  runners: Array<{
19
22
  provider_runner_id: string;
20
23
  state: ReconcileRunnerInstancesResult['runners'][number]['state'];
24
+ intended_reservation_id?: string | null;
21
25
  reservation_id: string | null;
22
26
  runner_session_id: string | null;
23
27
  bound_job: {
@@ -34,6 +38,9 @@ export function toReconcileRunnerInstancesResponseDto(result: ReconcileRunnerIns
34
38
  runners: result.runners.map((runner) => ({
35
39
  provider_runner_id: runner.providerRunnerId,
36
40
  state: runner.state,
41
+ ...(options.includeIntendedReservationId
42
+ ? {intended_reservation_id: runner.intendedReservationId}
43
+ : {}),
37
44
  reservation_id: runner.reservationId,
38
45
  runner_session_id: runner.runnerSessionId,
39
46
  bound_job: runner.boundJobExecution
@@ -2,6 +2,8 @@ import {requireProvisionerContext} from '@shipfox/api-auth-context';
2
2
  import {
3
3
  assignRunnerInstancesBodySchema,
4
4
  assignRunnerInstancesResponseSchema,
5
+ RESERVATION_EXPIRED_ERROR_CODE,
6
+ RUNNER_INSTANCE_NOT_ASSIGNABLE_ERROR_CODE,
5
7
  } from '@shipfox/api-runners-dto';
6
8
  import {ClientError, defineRoute} from '@shipfox/node-fastify';
7
9
  import {
@@ -24,14 +26,16 @@ export const assignRunnerInstancesRoute = defineRoute({
24
26
  if (error instanceof ReservationNotFoundError)
25
27
  throw new ClientError('Reservation not found', 'reservation-not-found', {status: 404});
26
28
  if (error instanceof ReservationExpiredError)
27
- throw new ClientError('Reservation has expired', 'reservation-expired', {status: 409});
29
+ throw new ClientError('Reservation has expired', RESERVATION_EXPIRED_ERROR_CODE, {
30
+ status: 409,
31
+ });
28
32
  if (
29
33
  error instanceof RunnerInstanceAlreadyAssignedError ||
30
34
  error instanceof RunnerInstanceNotAssignableError
31
35
  )
32
36
  throw new ClientError(
33
37
  'Runner instance cannot be assigned',
34
- 'runner-instance-not-assignable',
38
+ RUNNER_INSTANCE_NOT_ASSIGNABLE_ERROR_CODE,
35
39
  {status: 409},
36
40
  );
37
41
  throw error;