@shipfox/api-runners 12.3.0 → 12.4.1

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 (153) hide show
  1. package/.turbo/turbo-build.log +4 -4
  2. package/CHANGELOG.md +36 -0
  3. package/dist/config.d.ts +1 -0
  4. package/dist/config.d.ts.map +1 -1
  5. package/dist/config.js +9 -2
  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/errors.d.ts +5 -1
  18. package/dist/core/errors.d.ts.map +1 -1
  19. package/dist/core/errors.js +9 -2
  20. package/dist/core/errors.js.map +1 -1
  21. package/dist/core/runner-activation.d.ts +4 -1
  22. package/dist/core/runner-activation.d.ts.map +1 -1
  23. package/dist/core/runner-activation.js +32 -3
  24. package/dist/core/runner-activation.js.map +1 -1
  25. package/dist/core/runner-control-sessions.d.ts +8 -4
  26. package/dist/core/runner-control-sessions.d.ts.map +1 -1
  27. package/dist/core/runner-control-sessions.js +116 -20
  28. package/dist/core/runner-control-sessions.js.map +1 -1
  29. package/dist/core/runner-instances.d.ts +1 -0
  30. package/dist/core/runner-instances.d.ts.map +1 -1
  31. package/dist/core/runner-instances.js +1 -0
  32. package/dist/core/runner-instances.js.map +1 -1
  33. package/dist/db/db.d.ts +68 -0
  34. package/dist/db/db.d.ts.map +1 -1
  35. package/dist/db/ephemeral-registration-tokens.d.ts.map +1 -1
  36. package/dist/db/ephemeral-registration-tokens.js +1 -1
  37. package/dist/db/ephemeral-registration-tokens.js.map +1 -1
  38. package/dist/db/job-executions.d.ts.map +1 -1
  39. package/dist/db/job-executions.js +35 -4
  40. package/dist/db/job-executions.js.map +1 -1
  41. package/dist/db/reservation-locks.d.ts +6 -0
  42. package/dist/db/reservation-locks.d.ts.map +1 -0
  43. package/dist/db/reservation-locks.js +15 -0
  44. package/dist/db/reservation-locks.js.map +1 -0
  45. package/dist/db/reservations.d.ts +17 -3
  46. package/dist/db/reservations.d.ts.map +1 -1
  47. package/dist/db/reservations.js +246 -51
  48. package/dist/db/reservations.js.map +1 -1
  49. package/dist/db/runner-assignments.d.ts +25 -1
  50. package/dist/db/runner-assignments.d.ts.map +1 -1
  51. package/dist/db/runner-assignments.js +160 -18
  52. package/dist/db/runner-assignments.js.map +1 -1
  53. package/dist/db/runner-instances.d.ts +11 -2
  54. package/dist/db/runner-instances.d.ts.map +1 -1
  55. package/dist/db/runner-instances.js +167 -24
  56. package/dist/db/runner-instances.js.map +1 -1
  57. package/dist/db/runner-sessions.d.ts.map +1 -1
  58. package/dist/db/runner-sessions.js +14 -1
  59. package/dist/db/runner-sessions.js.map +1 -1
  60. package/dist/db/runner-states.d.ts +2 -0
  61. package/dist/db/runner-states.d.ts.map +1 -0
  62. package/dist/db/runner-states.js +7 -0
  63. package/dist/db/runner-states.js.map +1 -0
  64. package/dist/db/schema/reservations.d.ts +18 -0
  65. package/dist/db/schema/reservations.d.ts.map +1 -1
  66. package/dist/db/schema/reservations.js +7 -1
  67. package/dist/db/schema/reservations.js.map +1 -1
  68. package/dist/db/schema/runner-instances.d.ts +18 -0
  69. package/dist/db/schema/runner-instances.d.ts.map +1 -1
  70. package/dist/db/schema/runner-instances.js +8 -0
  71. package/dist/db/schema/runner-instances.js.map +1 -1
  72. package/dist/metrics/index.d.ts +2 -2
  73. package/dist/metrics/index.d.ts.map +1 -1
  74. package/dist/metrics/index.js +1 -1
  75. package/dist/metrics/index.js.map +1 -1
  76. package/dist/metrics/instance.d.ts +72 -2
  77. package/dist/metrics/instance.d.ts.map +1 -1
  78. package/dist/metrics/instance.js +161 -1
  79. package/dist/metrics/instance.js.map +1 -1
  80. package/dist/metrics/service.d.ts.map +1 -1
  81. package/dist/metrics/service.js +25 -4
  82. package/dist/metrics/service.js.map +1 -1
  83. package/dist/presentation/dto/poll-demand.d.ts.map +1 -1
  84. package/dist/presentation/dto/poll-demand.js +3 -0
  85. package/dist/presentation/dto/poll-demand.js.map +1 -1
  86. package/dist/presentation/dto/runner-instances.d.ts +4 -1
  87. package/dist/presentation/dto/runner-instances.d.ts.map +1 -1
  88. package/dist/presentation/dto/runner-instances.js +4 -1
  89. package/dist/presentation/dto/runner-instances.js.map +1 -1
  90. package/dist/presentation/routes/assign-runner-instances.d.ts.map +1 -1
  91. package/dist/presentation/routes/assign-runner-instances.js +10 -4
  92. package/dist/presentation/routes/assign-runner-instances.js.map +1 -1
  93. package/dist/presentation/routes/poll-demand.d.ts.map +1 -1
  94. package/dist/presentation/routes/poll-demand.js +1 -0
  95. package/dist/presentation/routes/poll-demand.js.map +1 -1
  96. package/dist/presentation/routes/reconcile-runner-instances.d.ts.map +1 -1
  97. package/dist/presentation/routes/reconcile-runner-instances.js +4 -2
  98. package/dist/presentation/routes/reconcile-runner-instances.js.map +1 -1
  99. package/dist/presentation/routes/runner-enrollment.d.ts +4 -1
  100. package/dist/presentation/routes/runner-enrollment.d.ts.map +1 -1
  101. package/dist/presentation/routes/runner-enrollment.js +11 -6
  102. package/dist/presentation/routes/runner-enrollment.js.map +1 -1
  103. package/dist/tsconfig.test.tsbuildinfo +1 -1
  104. package/drizzle/0005_previous_lenny_balinger.sql +5 -0
  105. package/drizzle/meta/0005_snapshot.json +2220 -0
  106. package/drizzle/meta/_journal.json +7 -0
  107. package/package.json +6 -6
  108. package/src/config.test.ts +22 -0
  109. package/src/config.ts +15 -2
  110. package/src/core/demand.test.ts +192 -3
  111. package/src/core/demand.ts +44 -4
  112. package/src/core/entities/reservation.ts +1 -0
  113. package/src/core/entities/runner-instance.ts +4 -0
  114. package/src/core/errors.ts +30 -1
  115. package/src/core/runner-activation.ts +48 -2
  116. package/src/core/runner-control-sessions.test.ts +135 -3
  117. package/src/core/runner-control-sessions.ts +141 -19
  118. package/src/core/runner-instances.test.ts +19 -0
  119. package/src/core/runner-instances.ts +2 -0
  120. package/src/core/runner-sessions.test.ts +121 -1
  121. package/src/db/ephemeral-registration-tokens.test.ts +20 -1
  122. package/src/db/ephemeral-registration-tokens.ts +1 -0
  123. package/src/db/job-executions.test.ts +98 -12
  124. package/src/db/job-executions.ts +52 -4
  125. package/src/db/reservation-locks.ts +22 -0
  126. package/src/db/reservations.test.ts +733 -22
  127. package/src/db/reservations.ts +408 -144
  128. package/src/db/runner-assignments.test.ts +105 -1
  129. package/src/db/runner-assignments.ts +245 -25
  130. package/src/db/runner-instances.test.ts +357 -2
  131. package/src/db/runner-instances.ts +289 -55
  132. package/src/db/runner-sessions.ts +19 -1
  133. package/src/db/runner-states.ts +7 -0
  134. package/src/db/schema/reservations.ts +5 -1
  135. package/src/db/schema/runner-instances.ts +9 -0
  136. package/src/metrics/index.ts +24 -1
  137. package/src/metrics/instance.test.ts +118 -0
  138. package/src/metrics/instance.ts +247 -3
  139. package/src/metrics/service.test.ts +46 -1
  140. package/src/metrics/service.ts +55 -8
  141. package/src/presentation/dto/poll-demand.ts +3 -0
  142. package/src/presentation/dto/runner-instances.ts +8 -1
  143. package/src/presentation/routes/assign-runner-instances.ts +14 -2
  144. package/src/presentation/routes/late-runner-enrollment.test.ts +219 -2
  145. package/src/presentation/routes/poll-demand.test.ts +56 -0
  146. package/src/presentation/routes/poll-demand.ts +1 -0
  147. package/src/presentation/routes/reconcile-runner-instances.test.ts +34 -2
  148. package/src/presentation/routes/reconcile-runner-instances.ts +7 -1
  149. package/src/presentation/routes/runner-enrollment.test.ts +316 -3
  150. package/src/presentation/routes/runner-enrollment.ts +8 -5
  151. package/test/factories/reservation.ts +2 -0
  152. package/test/factories/runner-instance.ts +6 -0
  153. package/tsconfig.build.tsbuildinfo +1 -1
@@ -1,13 +1,13 @@
1
1
  import type { RunnerInstance, RunnerInstanceState } from '#core/entities/runner-instance.js';
2
2
  import type { Tx } from './db.js';
3
3
  import { type RunnerInstanceBoundJobExecution } from './job-executions.js';
4
- export declare const terminalStates: readonly ["stopped", "failed", "terminated"];
5
4
  export declare const activeStates: readonly ["starting", "running", "stopping"];
6
5
  export declare const divergenceCountStates: readonly ["starting", "running"];
7
- export type RunnerInstanceTerminateIntentReason = 'job-cancelled';
6
+ export type RunnerInstanceTerminateIntentReason = 'activation-timeout' | 'job-cancelled';
8
7
  export interface RunnerInstanceTerminateIntent {
9
8
  providerRunnerId: string;
10
9
  reason: RunnerInstanceTerminateIntentReason;
10
+ activationTimeoutRetry?: boolean;
11
11
  }
12
12
  export interface ActiveRunnerInstanceTemplateCount {
13
13
  templateKey: string;
@@ -64,6 +64,15 @@ export declare function listActiveRunnerInstanceCountsByTemplateTx(tx: Tx, param
64
64
  export declare function countStaleEnrolledRunnerInstances(params: {
65
65
  graceSeconds: number;
66
66
  }): Promise<number>;
67
+ export type ProviderRunnerPhase = 'control_session' | 'enrollment' | 'assignment' | 'activation' | 'idle';
68
+ export interface ProviderRunnerPhaseMetric {
69
+ phase: ProviderRunnerPhase;
70
+ provider: string;
71
+ launchKind: 'demand' | 'warm' | 'manual';
72
+ count: number;
73
+ oldestAgeMilliseconds: number;
74
+ }
75
+ export declare function listProviderRunnerByPhaseMetrics(): Promise<ProviderRunnerPhaseMetric[]>;
67
76
  export declare function listActiveRunnerInstances(params: {
68
77
  workspaceId: string;
69
78
  windowSeconds: number;
@@ -1 +1 @@
1
- {"version":3,"file":"runner-instances.d.ts","sourceRoot":"","sources":["../../src/db/runner-instances.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAC,cAAc,EAAE,mBAAmB,EAAC,MAAM,mCAAmC,CAAC;AAE3F,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,SAAS,CAAC;AAEhC,OAAO,EAEL,KAAK,+BAA+B,EACrC,MAAM,qBAAqB,CAAC;AAU7B,eAAO,MAAM,cAAc,8CAIwB,CAAC;AACpD,eAAO,MAAM,YAAY,8CAI0B,CAAC;AACpD,eAAO,MAAM,qBAAqB,kCAG/B,CAAC;AAEJ,MAAM,MAAM,mCAAmC,GAAG,eAAe,CAAC;AAElE,MAAM,WAAW,6BAA6B;IAC5C,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,mCAAmC,CAAC;CAC7C;AAED,MAAM,WAAW,iCAAiC;IAChD,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAC;IAC9C,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,yBAAyB;IACxC,gBAAgB,EAAE,MAAM,CAAC;IACzB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,EAAE,mBAAmB,CAAC;IAC3B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,IAAI,CAAC;CAClB;AAED,MAAM,WAAW,2BAA2B;IAC1C,KAAK,EAAE,cAAc,GAAG,WAAW,CAAC;IACpC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,yBAAyB,EAAE,CAAC;CACrC;AAED,MAAM,WAAW,8BAA8B;IAC7C,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,yBAAyB,EAAE,MAAM,EAAE,CAAC;IACpC,qBAAqB,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,gCAAgC;IAC/C,YAAY,EAAE,cAAc,EAAE,CAAC;IAC/B,oCAAoC,EAAE,GAAG,CAAC,MAAM,EAAE,+BAA+B,CAAC,CAAC;IACnF,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,8BAA8B;IAC7C,MAAM,EAAE,MAAM,CAAC;IACf,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AAED,wBAAsB,8BAA8B,CAAC,MAAM,EAAE;IAC3D,gBAAgB,EAAE,MAAM,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;CAC1B,GAAG,OAAO,CAAC,OAAO,CAAC,CAcnB;AAiBD,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,2BAA2B,GAAG,OAAO,CAAC;IACxF,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,uBAAuB,EAAE,6BAA6B,EAAE,CAAC;CAC1D,CAAC,CA+ED;AAED,wBAAsB,0CAA0C,CAC9D,EAAE,EAAE,EAAE,EACN,MAAM,EAAE;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAC,GACnD,OAAO,CAAC,iCAAiC,EAAE,CAAC,CAuB9C;AAED,wBAAsB,iCAAiC,CAAC,MAAM,EAAE;IAC9D,YAAY,EAAE,MAAM,CAAC;CACtB,GAAG,OAAO,CAAC,MAAM,CAAC,CA0BlB;AAED,wBAAsB,yBAAyB,CAAC,MAAM,EAAE;IACtD,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAe5B;AAED,wBAAsB,+BAA+B,CAAC,MAAM,EAAE;IAC5D,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAKpB;AAED,wBAAsB,oCAAoC,CACxD,EAAE,EAAE,EAAE,EACN,MAAM,EAAE;IACN,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;CACf,GACA,OAAO,CAAC,6BAA6B,EAAE,CAAC,CAsB1C;AAqFD,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,8BAA8B,GACrC,OAAO,CAAC,gCAAgC,CAAC,CAqG3C;AAED,wBAAsB,wBAAwB,CAAC,MAAM,EAAE;IACrD,gBAAgB,EAAE,MAAM,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC,8BAA8B,CAAC,CA4D1C;AAkcD,wBAAgB,eAAe,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAEnE"}
1
+ {"version":3,"file":"runner-instances.d.ts","sourceRoot":"","sources":["../../src/db/runner-instances.ts"],"names":[],"mappings":"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"}
@@ -1,10 +1,15 @@
1
1
  import { logger } from '@shipfox/node-opentelemetry';
2
2
  import { and, asc, desc, eq, exists, gt, inArray, isNotNull, isNull, lt, notExists, notInArray, or, sql } from 'drizzle-orm';
3
3
  import { alias } from 'drizzle-orm/pg-core';
4
+ import { config } from '#config.js';
4
5
  import { sanitizeRunnerLabels } from '#core/runner-labels.js';
6
+ import { recordRunnerReservationCapacityFailure } from '#metrics/index.js';
5
7
  import { db } from './db.js';
6
8
  import { listRunningJobExecutionsByRunnerInstanceTx } from './job-executions.js';
9
+ import { lockRunnerReservationAdvisoryKeysTx } from './reservation-locks.js';
7
10
  import { releaseReservationUnits } from './reservations.js';
11
+ import { validateRunnerReservationCapacityTx } from './runner-assignments.js';
12
+ import { terminalStates } from './runner-states.js';
8
13
  import { ephemeralRegistrationTokens } from './schema/ephemeral-registration-tokens.js';
9
14
  import { provisionerTokens } from './schema/provisioner-tokens.js';
10
15
  import { reservations } from './schema/reservations.js';
@@ -12,11 +17,6 @@ import { runnerControlSessions } from './schema/runner-control-sessions.js';
12
17
  import { providerRunners, toRunnerInstance } from './schema/runner-instances.js';
13
18
  import { runnerSessions } from './schema/runner-sessions.js';
14
19
  import { runningJobExecutions } from './schema/running-job-executions.js';
15
- export const terminalStates = [
16
- 'stopped',
17
- 'failed',
18
- 'terminated'
19
- ];
20
20
  export const activeStates = [
21
21
  'starting',
22
22
  'running',
@@ -51,8 +51,9 @@ export async function reportRunnerInstances(params) {
51
51
  await tx.execute(sql`select pg_advisory_xact_lock(hashtext(${params.workspaceId ?? params.provisionerId}))`);
52
52
  }
53
53
  const events = await hydrateRunnerSessionIdsFromConsumedTokens(tx, params, aggregatedEvents);
54
- const terminateIntentsHonored = await listTerminateIntentsHonoredByTerminatedReportsTx(tx, params, events);
55
- const values = events.map((event)=>({
54
+ const reservationSafeEvents = await guardReportedReservationIdsTx(tx, params, events);
55
+ const terminateIntentsHonored = await listTerminateIntentsHonoredByTerminatedReportsTx(tx, params, reservationSafeEvents);
56
+ const values = reservationSafeEvents.map((event)=>({
56
57
  workspaceId: params.workspaceId,
57
58
  provisionerId: params.provisionerId,
58
59
  providerRunnerId: event.providerRunnerId,
@@ -101,14 +102,92 @@ export async function reportRunnerInstances(params) {
101
102
  OR ${providerRunnerMilestoneUpdateCondition()}
102
103
  `
103
104
  });
104
- const reservationsReleased = hasTerminalEvent ? await releaseTerminalRunnerInstanceReservations(tx, params, events) : 0;
105
+ const reservationsReleased = hasTerminalEvent ? await releaseTerminalRunnerInstanceReservations(tx, params, reservationSafeEvents) : 0;
105
106
  return {
106
- accepted: events.length,
107
+ accepted: reservationSafeEvents.length,
107
108
  reservationsReleased,
108
109
  terminateIntentsHonored
109
110
  };
110
111
  });
111
112
  }
113
+ async function guardReportedReservationIdsTx(tx, params, events) {
114
+ const reservationIds = [
115
+ ...new Set(events.flatMap((event)=>event.reservationId ? [
116
+ event.reservationId
117
+ ] : []))
118
+ ].sort();
119
+ if (reservationIds.length === 0) return events;
120
+ await lockRunnerReservationAdvisoryKeysTx(tx, {
121
+ provisionerId: params.provisionerId,
122
+ reservationIds
123
+ });
124
+ const providerRunnerIds = events.map((event)=>event.providerRunnerId);
125
+ const existingRows = await tx.select({
126
+ providerRunnerId: providerRunners.providerRunnerId,
127
+ reservationId: providerRunners.reservationId,
128
+ intendedReservationId: providerRunners.intendedReservationId,
129
+ reservationReleasedAt: providerRunners.reservationReleasedAt
130
+ }).from(providerRunners).where(and(eq(providerRunners.provisionerId, params.provisionerId), inArray(providerRunners.providerRunnerId, providerRunnerIds))).for('update');
131
+ const existingByProviderRunnerId = new Map(existingRows.flatMap((row)=>row.providerRunnerId ? [
132
+ [
133
+ row.providerRunnerId,
134
+ row
135
+ ]
136
+ ] : []));
137
+ const candidateReservationByProviderRunnerId = new Map();
138
+ for (const event of events){
139
+ if (!event.reservationId) continue;
140
+ if (isTerminalState(event.state)) continue;
141
+ const existing = existingByProviderRunnerId.get(event.providerRunnerId);
142
+ if (existing?.reservationId || existing?.intendedReservationId === event.reservationId) continue;
143
+ if (existing?.intendedReservationId || existing?.reservationReleasedAt) continue;
144
+ candidateReservationByProviderRunnerId.set(event.providerRunnerId, event.reservationId);
145
+ }
146
+ const validation = await validateRunnerReservationCapacityTx(tx, {
147
+ provisionerId: params.provisionerId,
148
+ requests: [
149
+ ...candidateReservationByProviderRunnerId.values()
150
+ ].map((reservationId)=>({
151
+ reservationId,
152
+ count: 1
153
+ }))
154
+ }, {
155
+ advisoryLocksHeld: true
156
+ });
157
+ for (const { reason, count } of validation.unavailableByReservation.values())recordRunnerReservationCapacityFailure(reason, count);
158
+ const remainingAcceptedByReservation = new Map(validation.acceptedByReservation);
159
+ return events.map((event)=>{
160
+ const existing = existingByProviderRunnerId.get(event.providerRunnerId);
161
+ const reservationId = candidateReservationByProviderRunnerId.get(event.providerRunnerId);
162
+ if (!reservationId) {
163
+ if (event.reservationId && isTerminalState(event.state)) {
164
+ // Terminal cleanup releases the IDs stored on the projection row. An arbitrary
165
+ // terminal report must not consume reservation capacity during that cleanup.
166
+ const matchesExistingReservation = existing !== undefined && (existing.reservationId === event.reservationId || existing.intendedReservationId === event.reservationId);
167
+ if (!matchesExistingReservation) return {
168
+ ...event,
169
+ reservationId: null
170
+ };
171
+ }
172
+ if (event.reservationId && existing?.intendedReservationId && existing.intendedReservationId !== event.reservationId) return {
173
+ ...event,
174
+ reservationId: null
175
+ };
176
+ if (event.reservationId && existing?.reservationReleasedAt) return {
177
+ ...event,
178
+ reservationId: null
179
+ };
180
+ return event;
181
+ }
182
+ const remaining = remainingAcceptedByReservation.get(reservationId) ?? 0;
183
+ if (remaining === 0) return {
184
+ ...event,
185
+ reservationId: null
186
+ };
187
+ remainingAcceptedByReservation.set(reservationId, remaining - 1);
188
+ return event;
189
+ });
190
+ }
112
191
  export async function listActiveRunnerInstanceCountsByTemplateTx(tx, params) {
113
192
  const rows = await tx.select({
114
193
  templateKey: providerRunners.templateKey,
@@ -131,6 +210,49 @@ export async function countStaleEnrolledRunnerInstances(params) {
131
210
  }).from(runnerControlSessions).where(and(eq(runnerControlSessions.runnerInstanceId, providerRunners.id), isNull(runnerControlSessions.closedAt), gt(runnerControlSessions.expiresAt, sql`now()`))))));
132
211
  return row?.count ?? 0;
133
212
  }
213
+ export async function listProviderRunnerByPhaseMetrics() {
214
+ const phase = sql`case
215
+ when ${runnerControlSessions.id} is null then 'control_session'
216
+ when ${providerRunners.state} <> 'running' then 'enrollment'
217
+ when ${providerRunners.intendedReservationId} is not null
218
+ and ${providerRunners.workspaceId} is null then 'assignment'
219
+ when ${providerRunners.workspaceId} is not null then 'activation'
220
+ else 'idle'
221
+ end`;
222
+ const startedAt = sql`case
223
+ when ${runnerControlSessions.id} is null then ${providerRunners.createdAt}
224
+ when ${providerRunners.state} <> 'running' then ${runnerControlSessions.createdAt}
225
+ when ${providerRunners.intendedReservationId} is not null
226
+ and ${providerRunners.workspaceId} is null then ${runnerControlSessions.createdAt}
227
+ when ${providerRunners.workspaceId} is not null
228
+ then coalesce(${providerRunners.assignedAt}, ${runnerControlSessions.createdAt})
229
+ else coalesce(
230
+ ${runnerControlSessions.createdAt},
231
+ ${providerRunners.assignedAt},
232
+ ${providerRunners.createdAt}
233
+ )
234
+ end`;
235
+ const rows = await db().select({
236
+ phase,
237
+ provider: sql`coalesce(${providerRunners.providerKind}, 'unknown')`,
238
+ launchKind: providerRunners.launchKind,
239
+ count: sql`count(*)::int`,
240
+ oldestAgeMilliseconds: sql`coalesce(
241
+ max(extract(epoch from (now() - (${startedAt}))) * 1000),
242
+ 0
243
+ )::double precision`
244
+ }).from(providerRunners).leftJoin(runnerControlSessions, and(eq(runnerControlSessions.runnerInstanceId, providerRunners.id), isNull(runnerControlSessions.closedAt), gt(runnerControlSessions.expiresAt, sql`now()`))).where(and(inArray(providerRunners.state, [
245
+ 'starting',
246
+ 'running'
247
+ ]), isNull(providerRunners.runnerSessionId))).groupBy(phase, providerRunners.providerKind, providerRunners.launchKind);
248
+ return rows.map((row)=>({
249
+ phase: row.phase,
250
+ provider: row.provider,
251
+ launchKind: row.launchKind,
252
+ count: row.count,
253
+ oldestAgeMilliseconds: Math.max(0, row.oldestAgeMilliseconds)
254
+ }));
255
+ }
134
256
  export async function listActiveRunnerInstances(params) {
135
257
  const rows = await db().select().from(providerRunners).where(and(eq(providerRunners.workspaceId, params.workspaceId), inArray(providerRunners.state, activeStates), sql`${providerRunners.updatedAt} > now() - (${params.windowSeconds} || ' seconds')::interval`)).orderBy(desc(providerRunners.updatedAt), desc(providerRunners.id)).limit(params.limit ?? 1000);
136
258
  return rows.map(toRunnerInstance);
@@ -153,21 +275,47 @@ export async function listProvisionerTerminateIntentRowsTx(tx, params) {
153
275
  returnedCount: returnedRows.length
154
276
  }, 'provisioner terminate intents truncated by poll-demand limit');
155
277
  }
156
- return returnedRows.flatMap((row)=>row.providerRunnerId ? [
157
- {
158
- ...row,
159
- providerRunnerId: row.providerRunnerId
160
- }
278
+ const activationTimeoutRunnerIds = returnedRows.flatMap((row)=>row.reason === 'activation-timeout' && row.providerRunnerId ? [
279
+ row.providerRunnerId
161
280
  ] : []);
281
+ if (activationTimeoutRunnerIds.length > 0) {
282
+ await tx.update(providerRunners).set({
283
+ reservationReleasedAt: sql`now()`,
284
+ updatedAt: sql`now()`
285
+ }).where(and(eq(providerRunners.workspaceId, params.workspaceId), eq(providerRunners.provisionerId, params.provisionerId), inArray(providerRunners.providerRunnerId, activationTimeoutRunnerIds), isNull(providerRunners.reservationReleasedAt)));
286
+ }
287
+ return returnedRows.flatMap((row)=>toRunnerInstanceTerminateIntent(row));
288
+ }
289
+ function toRunnerInstanceTerminateIntent(row) {
290
+ if (!row.providerRunnerId) return [];
291
+ return [
292
+ {
293
+ providerRunnerId: row.providerRunnerId,
294
+ reason: row.reason,
295
+ ...row.reason === 'activation-timeout' && row.activationTimeoutRetry ? {
296
+ activationTimeoutRetry: true
297
+ } : {}
298
+ }
299
+ ];
162
300
  }
163
301
  function provisionerTerminateIntentsQuery(tx, params) {
164
302
  const newerRunningJobExecutions = alias(runningJobExecutions, 'newer_running_jobs');
303
+ const latestCancelledJob = exists(tx.select({
304
+ id: runningJobExecutions.id
305
+ }).from(runningJobExecutions).where(and(eq(runningJobExecutions.workspaceId, params.workspaceId), eq(runningJobExecutions.provisionerId, params.provisionerId), eq(runningJobExecutions.providerRunnerId, providerRunners.providerRunnerId), isNotNull(runningJobExecutions.cancellationRequestedAt), notExists(tx.select({
306
+ id: newerRunningJobExecutions.id
307
+ }).from(newerRunningJobExecutions).where(and(eq(newerRunningJobExecutions.workspaceId, runningJobExecutions.workspaceId), eq(newerRunningJobExecutions.provisionerId, runningJobExecutions.provisionerId), eq(newerRunningJobExecutions.providerRunnerId, runningJobExecutions.providerRunnerId), or(gt(newerRunningJobExecutions.startedAt, runningJobExecutions.startedAt), and(eq(newerRunningJobExecutions.startedAt, runningJobExecutions.startedAt), gt(newerRunningJobExecutions.jobExecutionId, runningJobExecutions.jobExecutionId)))))))));
308
+ const activationTimeout = and(eq(providerRunners.launchKind, 'demand'), isNull(providerRunners.runnerSessionId), lt(providerRunners.createdAt, sql`now() - (${config.RUNNER_DEMAND_ACTIVATION_TIMEOUT_SECONDS} || ' seconds')::interval`), notExists(tx.select({
309
+ id: reservations.id
310
+ }).from(reservations).where(and(or(eq(reservations.id, providerRunners.reservationId), eq(reservations.id, providerRunners.intendedReservationId)), eq(reservations.workspaceId, params.workspaceId), eq(reservations.provisionerId, params.provisionerId), gt(reservations.expiresAt, sql`now()`)))));
165
311
  return tx.select({
166
312
  providerRunnerId: providerRunners.providerRunnerId,
167
- reason: sql`'job-cancelled'`
168
- }).from(runningJobExecutions).innerJoin(providerRunners, and(eq(providerRunners.workspaceId, runningJobExecutions.workspaceId), eq(providerRunners.provisionerId, runningJobExecutions.provisionerId), eq(providerRunners.providerRunnerId, runningJobExecutions.providerRunnerId))).where(and(eq(runningJobExecutions.workspaceId, params.workspaceId), eq(runningJobExecutions.provisionerId, params.provisionerId), isNotNull(providerRunners.providerRunnerId), isNotNull(runningJobExecutions.cancellationRequestedAt), inArray(providerRunners.state, activeStates), params.providerRunnerIds && params.providerRunnerIds.length > 0 ? inArray(providerRunners.providerRunnerId, params.providerRunnerIds) : undefined, notExists(tx.select({
169
- id: newerRunningJobExecutions.id
170
- }).from(newerRunningJobExecutions).where(and(eq(newerRunningJobExecutions.workspaceId, runningJobExecutions.workspaceId), eq(newerRunningJobExecutions.provisionerId, runningJobExecutions.provisionerId), eq(newerRunningJobExecutions.providerRunnerId, runningJobExecutions.providerRunnerId), or(gt(newerRunningJobExecutions.startedAt, runningJobExecutions.startedAt), and(eq(newerRunningJobExecutions.startedAt, runningJobExecutions.startedAt), gt(newerRunningJobExecutions.jobExecutionId, runningJobExecutions.jobExecutionId)))))))).groupBy(providerRunners.providerRunnerId);
313
+ activationTimeoutRetry: sql`${providerRunners.reservationReleasedAt} is not null`,
314
+ reason: sql`case
315
+ when ${activationTimeout} then 'activation-timeout'
316
+ else 'job-cancelled'
317
+ end`
318
+ }).from(providerRunners).where(and(eq(providerRunners.workspaceId, params.workspaceId), eq(providerRunners.provisionerId, params.provisionerId), isNotNull(providerRunners.providerRunnerId), inArray(providerRunners.state, activeStates), params.providerRunnerIds && params.providerRunnerIds.length > 0 ? inArray(providerRunners.providerRunnerId, params.providerRunnerIds) : undefined, or(latestCancelledJob, activationTimeout)));
171
319
  }
172
320
  async function listTerminateIntentsHonoredByTerminatedReportsTx(tx, params, events) {
173
321
  if (!params.workspaceId) return [];
@@ -180,12 +328,7 @@ async function listTerminateIntentsHonoredByTerminatedReportsTx(tx, params, even
180
328
  provisionerId: params.provisionerId,
181
329
  providerRunnerIds: terminatedRunnerInstanceIds
182
330
  }).orderBy(asc(providerRunners.providerRunnerId));
183
- return rows.flatMap((row)=>row.providerRunnerId ? [
184
- {
185
- ...row,
186
- providerRunnerId: row.providerRunnerId
187
- }
188
- ] : []);
331
+ return rows.flatMap((row)=>toRunnerInstanceTerminateIntent(row));
189
332
  }
190
333
  export async function reconcileRunnerInstances(params) {
191
334
  const observedRunnerInstanceIds = [