@shipfox/api-runners 10.0.0 → 10.2.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 (118) hide show
  1. package/.turbo/turbo-build.log +4 -4
  2. package/CHANGELOG.md +28 -0
  3. package/dist/config.d.ts.map +1 -1
  4. package/dist/config.js +8 -13
  5. package/dist/config.js.map +1 -1
  6. package/dist/core/admin-runner-instances.d.ts +34 -0
  7. package/dist/core/admin-runner-instances.d.ts.map +1 -0
  8. package/dist/core/admin-runner-instances.js +58 -0
  9. package/dist/core/admin-runner-instances.js.map +1 -0
  10. package/dist/core/errors.d.ts +6 -0
  11. package/dist/core/errors.d.ts.map +1 -1
  12. package/dist/core/errors.js +12 -0
  13. package/dist/core/errors.js.map +1 -1
  14. package/dist/core/index.d.ts +3 -2
  15. package/dist/core/index.d.ts.map +1 -1
  16. package/dist/core/index.js +3 -2
  17. package/dist/core/index.js.map +1 -1
  18. package/dist/core/provisioner-tokens.d.ts +33 -0
  19. package/dist/core/provisioner-tokens.d.ts.map +1 -1
  20. package/dist/core/provisioner-tokens.js +107 -2
  21. package/dist/core/provisioner-tokens.js.map +1 -1
  22. package/dist/db/admin-runner-instances.d.ts +34 -0
  23. package/dist/db/admin-runner-instances.d.ts.map +1 -0
  24. package/dist/db/admin-runner-instances.js +62 -0
  25. package/dist/db/admin-runner-instances.js.map +1 -0
  26. package/dist/db/db.d.ts +290 -0
  27. package/dist/db/db.d.ts.map +1 -1
  28. package/dist/db/db.js +2 -0
  29. package/dist/db/db.js.map +1 -1
  30. package/dist/db/index.d.ts +3 -1
  31. package/dist/db/index.d.ts.map +1 -1
  32. package/dist/db/index.js +2 -1
  33. package/dist/db/index.js.map +1 -1
  34. package/dist/db/job-executions.d.ts.map +1 -1
  35. package/dist/db/job-executions.js +11 -0
  36. package/dist/db/job-executions.js.map +1 -1
  37. package/dist/db/provisioner-tokens.d.ts +38 -0
  38. package/dist/db/provisioner-tokens.d.ts.map +1 -1
  39. package/dist/db/provisioner-tokens.js +157 -3
  40. package/dist/db/provisioner-tokens.js.map +1 -1
  41. package/dist/db/reservations.d.ts.map +1 -1
  42. package/dist/db/reservations.js +60 -8
  43. package/dist/db/reservations.js.map +1 -1
  44. package/dist/db/schema/admin-command-results.d.ts +134 -0
  45. package/dist/db/schema/admin-command-results.d.ts.map +1 -0
  46. package/dist/db/schema/admin-command-results.js +18 -0
  47. package/dist/db/schema/admin-command-results.js.map +1 -0
  48. package/dist/db/schema/runner-instances.d.ts +17 -0
  49. package/dist/db/schema/runner-instances.d.ts.map +1 -1
  50. package/dist/db/schema/runner-instances.js +3 -0
  51. package/dist/db/schema/runner-instances.js.map +1 -1
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +6 -1
  54. package/dist/index.js.map +1 -1
  55. package/dist/presentation/dto/admin-provisioner-token.d.ts +16 -0
  56. package/dist/presentation/dto/admin-provisioner-token.d.ts.map +1 -0
  57. package/dist/presentation/dto/admin-provisioner-token.js +22 -0
  58. package/dist/presentation/dto/admin-provisioner-token.js.map +1 -0
  59. package/dist/presentation/dto/admin-runner-instances.d.ts +23 -0
  60. package/dist/presentation/dto/admin-runner-instances.d.ts.map +1 -0
  61. package/dist/presentation/dto/admin-runner-instances.js +19 -0
  62. package/dist/presentation/dto/admin-runner-instances.js.map +1 -0
  63. package/dist/presentation/dto/index.d.ts +2 -0
  64. package/dist/presentation/dto/index.d.ts.map +1 -1
  65. package/dist/presentation/dto/index.js +2 -0
  66. package/dist/presentation/dto/index.js.map +1 -1
  67. package/dist/presentation/routes/admin-provisioner-tokens.d.ts +4 -0
  68. package/dist/presentation/routes/admin-provisioner-tokens.d.ts.map +1 -0
  69. package/dist/presentation/routes/admin-provisioner-tokens.js +167 -0
  70. package/dist/presentation/routes/admin-provisioner-tokens.js.map +1 -0
  71. package/dist/presentation/routes/admin-runner-instances.d.ts +3 -0
  72. package/dist/presentation/routes/admin-runner-instances.d.ts.map +1 -0
  73. package/dist/presentation/routes/admin-runner-instances.js +63 -0
  74. package/dist/presentation/routes/admin-runner-instances.js.map +1 -0
  75. package/dist/presentation/routes/index.d.ts.map +1 -1
  76. package/dist/presentation/routes/index.js +11 -1
  77. package/dist/presentation/routes/index.js.map +1 -1
  78. package/dist/presentation/routes/runner-enrollment.d.ts +1 -0
  79. package/dist/presentation/routes/runner-enrollment.d.ts.map +1 -1
  80. package/dist/presentation/routes/runner-enrollment.js +11 -4
  81. package/dist/presentation/routes/runner-enrollment.js.map +1 -1
  82. package/dist/tsconfig.test.tsbuildinfo +1 -1
  83. package/drizzle/0002_special_shadowcat.sql +1 -0
  84. package/drizzle/0003_flaky_sally_floyd.sql +11 -0
  85. package/drizzle/meta/0002_snapshot.json +2099 -0
  86. package/drizzle/meta/0003_snapshot.json +2178 -0
  87. package/drizzle/meta/_journal.json +14 -0
  88. package/package.json +10 -9
  89. package/src/config.test.ts +11 -0
  90. package/src/config.ts +19 -8
  91. package/src/core/admin-runner-instances.ts +125 -0
  92. package/src/core/errors.ts +14 -0
  93. package/src/core/index.ts +12 -0
  94. package/src/core/provisioner-tokens.ts +157 -1
  95. package/src/db/admin-runner-instances.ts +121 -0
  96. package/src/db/db.ts +2 -0
  97. package/src/db/index.ts +10 -0
  98. package/src/db/job-executions.test.ts +17 -0
  99. package/src/db/job-executions.ts +22 -0
  100. package/src/db/provisioner-tokens.ts +263 -9
  101. package/src/db/reservations.test.ts +253 -0
  102. package/src/db/reservations.ts +170 -15
  103. package/src/db/schema/admin-command-results.ts +29 -0
  104. package/src/db/schema/runner-instances.ts +1 -0
  105. package/src/index.test.ts +1 -1
  106. package/src/index.ts +6 -1
  107. package/src/presentation/dto/admin-provisioner-token.ts +23 -0
  108. package/src/presentation/dto/admin-runner-instances.ts +19 -0
  109. package/src/presentation/dto/index.ts +2 -0
  110. package/src/presentation/routes/admin-provisioner-tokens.test.ts +227 -0
  111. package/src/presentation/routes/admin-provisioner-tokens.ts +177 -0
  112. package/src/presentation/routes/admin-runner-instances.test.ts +671 -0
  113. package/src/presentation/routes/admin-runner-instances.ts +67 -0
  114. package/src/presentation/routes/index.ts +8 -0
  115. package/src/presentation/routes/runner-enrollment.test.ts +60 -2
  116. package/src/presentation/routes/runner-enrollment.ts +24 -4
  117. package/test/globalSetup.ts +1 -0
  118. package/tsconfig.build.tsbuildinfo +1 -1
@@ -1,5 +1,10 @@
1
- import { and, desc, eq, gt, inArray, isNull, or, sql } from 'drizzle-orm';
1
+ import { timestampIdCursorWhere } from '@shipfox/node-drizzle';
2
+ import { writeOutboxEvent } from '@shipfox/node-outbox';
3
+ import { and, desc, eq, gt, inArray, isNotNull, isNull, lte, or, sql } from 'drizzle-orm';
4
+ import { ProvisionerAdminIdempotencyKeyReuseError } from '#core/errors.js';
2
5
  import { db } from './db.js';
6
+ import { runnersAdminCommandResults } from './schema/admin-command-results.js';
7
+ import { runnersOutbox } from './schema/outbox.js';
3
8
  import { provisionerTokens, toProvisionerToken } from './schema/provisioner-tokens.js';
4
9
  import { runnerActivationTokens } from './schema/runner-activation-tokens.js';
5
10
  import { runnerBootstrapTokens, runnerControlSessions } from './schema/runner-control-sessions.js';
@@ -27,8 +32,8 @@ async function cascadeProvisionerRevocation(tx, provisionerId) {
27
32
  revokedAt: sql`now()`
28
33
  }).where(and(eq(runnerSessions.provisionerId, provisionerId), eq(runnerSessions.claimsUsed, 0), isNull(runnerSessions.revokedAt)));
29
34
  }
30
- export async function createProvisionerToken(params) {
31
- const rows = await db().insert(provisionerTokens).values({
35
+ async function insertProvisionerToken(tx, params) {
36
+ const rows = await tx.insert(provisionerTokens).values({
32
37
  scope: params.scope,
33
38
  workspaceId: params.workspaceId ?? null,
34
39
  hashedToken: params.hashedToken,
@@ -39,8 +44,81 @@ export async function createProvisionerToken(params) {
39
44
  }).returning();
40
45
  const row = rows[0];
41
46
  if (!row) throw new Error('Insert returned no rows');
47
+ return row;
48
+ }
49
+ export async function createProvisionerToken(params) {
50
+ const row = await db().transaction((tx)=>insertProvisionerToken(tx, params));
42
51
  return toProvisionerToken(row);
43
52
  }
53
+ async function lockAdminCommand(tx, params) {
54
+ await tx.execute(sql`select pg_advisory_xact_lock(hashtext(${`runners_admin_command:${params.actorId}:${params.idempotencyKeyFingerprint}`}))`);
55
+ }
56
+ async function loadAdminCommandResult(tx, params) {
57
+ const rows = await tx.select().from(runnersAdminCommandResults).where(and(eq(runnersAdminCommandResults.actorId, params.actorId), eq(runnersAdminCommandResults.idempotencyKeyFingerprint, params.idempotencyKeyFingerprint))).limit(1);
58
+ const result = rows[0];
59
+ if (!result) return undefined;
60
+ if (result.command !== params.command || result.requestFingerprint !== params.requestFingerprint) {
61
+ throw new ProvisionerAdminIdempotencyKeyReuseError();
62
+ }
63
+ return result;
64
+ }
65
+ async function storeAdminCommandResult(tx, params, result) {
66
+ await tx.insert(runnersAdminCommandResults).values({
67
+ actorId: params.actorId,
68
+ idempotencyKeyFingerprint: params.idempotencyKeyFingerprint,
69
+ command: params.command,
70
+ requestFingerprint: params.requestFingerprint,
71
+ result
72
+ });
73
+ }
74
+ async function writeAdministrationAction(tx, event) {
75
+ await writeOutboxEvent(tx, runnersOutbox, {
76
+ type: 'administration.action.performed',
77
+ payload: event
78
+ });
79
+ }
80
+ export async function createInstallationProvisionerTokenWithAudit(params) {
81
+ const command = 'runners.provisioner_token.create';
82
+ return await db().transaction(async (tx)=>{
83
+ await lockAdminCommand(tx, params);
84
+ const existing = await loadAdminCommandResult(tx, {
85
+ actorId: params.actorId,
86
+ idempotencyKeyFingerprint: params.idempotencyKeyFingerprint,
87
+ requestFingerprint: params.requestFingerprint,
88
+ command
89
+ });
90
+ if (existing) {
91
+ const rows = await tx.select().from(provisionerTokens).where(eq(provisionerTokens.id, existing.result.provisionerTokenId)).limit(1);
92
+ const row = rows[0];
93
+ if (!row) throw new Error('Idempotent provisioner token result is missing');
94
+ return {
95
+ token: toProvisionerToken(row),
96
+ correlationId: existing.result.correlationId,
97
+ replayed: true
98
+ };
99
+ }
100
+ const expiresAt = params.ttlSeconds === undefined ? undefined : new Date(Date.now() + params.ttlSeconds * 1000);
101
+ const row = await insertProvisionerToken(tx, {
102
+ scope: 'installation',
103
+ hashedToken: params.hashedToken,
104
+ prefix: params.prefix,
105
+ createdByUserId: params.actorId,
106
+ name: params.name,
107
+ expiresAt
108
+ });
109
+ const event = params.event(row.id);
110
+ await writeAdministrationAction(tx, event);
111
+ await storeAdminCommandResult(tx, params, {
112
+ provisionerTokenId: row.id,
113
+ correlationId: event.correlationId
114
+ });
115
+ return {
116
+ token: toProvisionerToken(row),
117
+ correlationId: event.correlationId,
118
+ replayed: false
119
+ };
120
+ });
121
+ }
44
122
  export async function revokeInstallationProvisionerToken(params) {
45
123
  const rows = await db().transaction(async (tx)=>{
46
124
  const rows = await tx.update(provisionerTokens).set({
@@ -59,6 +137,82 @@ export async function revokeInstallationProvisionerToken(params) {
59
137
  const existingRow = existingRows[0];
60
138
  return existingRow ? toProvisionerToken(existingRow) : undefined;
61
139
  }
140
+ export async function revokeInstallationProvisionerTokenWithAudit(params) {
141
+ const command = 'runners.provisioner_token.revoke';
142
+ return await db().transaction(async (tx)=>{
143
+ await lockAdminCommand(tx, params);
144
+ const existing = await loadAdminCommandResult(tx, {
145
+ actorId: params.actorId,
146
+ idempotencyKeyFingerprint: params.idempotencyKeyFingerprint,
147
+ requestFingerprint: params.requestFingerprint,
148
+ command
149
+ });
150
+ if (existing) {
151
+ const rows = await tx.select().from(provisionerTokens).where(eq(provisionerTokens.id, existing.result.provisionerTokenId)).limit(1);
152
+ const row = rows[0];
153
+ if (!row) throw new Error('Idempotent provisioner token result is missing');
154
+ return {
155
+ token: toProvisionerToken(row),
156
+ correlationId: existing.result.correlationId,
157
+ replayed: true
158
+ };
159
+ }
160
+ const updatedRows = await tx.update(provisionerTokens).set({
161
+ revokedAt: new Date(),
162
+ revokedByUserId: params.actorId,
163
+ updatedAt: new Date()
164
+ }).where(and(eq(provisionerTokens.id, params.tokenId), eq(provisionerTokens.scope, 'installation'), isNull(provisionerTokens.revokedAt))).returning();
165
+ const row = updatedRows[0];
166
+ if (row) await cascadeProvisionerRevocation(tx, params.tokenId);
167
+ const existingRows = row ? [
168
+ row
169
+ ] : await tx.select().from(provisionerTokens).where(and(eq(provisionerTokens.id, params.tokenId), eq(provisionerTokens.scope, 'installation'))).limit(1);
170
+ const tokenRow = existingRows[0];
171
+ if (!tokenRow) return undefined;
172
+ const changed = row !== undefined;
173
+ const event = changed ? params.event(tokenRow.id) : undefined;
174
+ if (event) await writeAdministrationAction(tx, event);
175
+ await storeAdminCommandResult(tx, params, {
176
+ provisionerTokenId: tokenRow.id,
177
+ correlationId: event?.correlationId ?? params.correlationId
178
+ });
179
+ return {
180
+ token: toProvisionerToken(tokenRow),
181
+ correlationId: event?.correlationId ?? params.correlationId,
182
+ replayed: false
183
+ };
184
+ });
185
+ }
186
+ export async function listInstallationProvisionerTokens(params) {
187
+ const conditions = [
188
+ eq(provisionerTokens.scope, 'installation')
189
+ ];
190
+ const cursor = timestampIdCursorWhere({
191
+ timestampColumn: provisionerTokens.createdAt,
192
+ idColumn: provisionerTokens.id,
193
+ cursor: params.cursor
194
+ });
195
+ if (cursor) conditions.push(cursor);
196
+ if (params.status === 'active') {
197
+ conditions.push(isNull(provisionerTokens.revokedAt));
198
+ const activeExpiry = or(isNull(provisionerTokens.expiresAt), gt(provisionerTokens.expiresAt, sql`now()`));
199
+ if (activeExpiry) conditions.push(activeExpiry);
200
+ }
201
+ if (params.status === 'expired') {
202
+ conditions.push(isNull(provisionerTokens.revokedAt), isNotNull(provisionerTokens.expiresAt), lte(provisionerTokens.expiresAt, sql`now()`));
203
+ }
204
+ if (params.status === 'revoked') conditions.push(isNotNull(provisionerTokens.revokedAt));
205
+ const rows = await db().select().from(provisionerTokens).where(and(...conditions)).orderBy(desc(provisionerTokens.createdAt), desc(provisionerTokens.id)).limit(params.limit + 1);
206
+ const pageRows = rows.length > params.limit ? rows.slice(0, params.limit) : rows;
207
+ const last = pageRows.at(-1);
208
+ return {
209
+ tokens: pageRows.map(toProvisionerToken),
210
+ nextCursor: rows.length > params.limit && last ? {
211
+ createdAt: last.createdAt,
212
+ id: last.id
213
+ } : null
214
+ };
215
+ }
62
216
  export async function listUsableProvisionerTokensByWorkspaceId(workspaceId) {
63
217
  const now = new Date();
64
218
  const rows = await db().select().from(provisionerTokens).where(and(eq(provisionerTokens.workspaceId, workspaceId), eq(provisionerTokens.scope, 'workspace'), isNull(provisionerTokens.revokedAt), or(isNull(provisionerTokens.expiresAt), gt(provisionerTokens.expiresAt, now)))).orderBy(desc(provisionerTokens.createdAt), desc(provisionerTokens.id));
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/db/provisioner-tokens.ts"],"sourcesContent":["import {and, desc, eq, gt, inArray, isNull, or, sql} from 'drizzle-orm';\nimport type {\n ActiveProvisionerToken,\n ProvisionerScope,\n ProvisionerToken,\n} from '#core/entities/provisioner-token.js';\nimport {db} from './db.js';\nimport {provisionerTokens, toProvisionerToken} from './schema/provisioner-tokens.js';\nimport {runnerActivationTokens} from './schema/runner-activation-tokens.js';\nimport {runnerBootstrapTokens, runnerControlSessions} from './schema/runner-control-sessions.js';\nimport {providerRunners} from './schema/runner-instances.js';\nimport {runnerSessions} from './schema/runner-sessions.js';\n\nexport interface CreateProvisionerTokenParams {\n scope: ProvisionerScope;\n workspaceId?: string | undefined;\n hashedToken: string;\n prefix: string;\n createdByUserId: string;\n name?: string | undefined;\n expiresAt?: Date | undefined;\n}\n\nasync function cascadeProvisionerRevocation(\n tx: Parameters<Parameters<ReturnType<typeof db>['transaction']>[0]>[0],\n provisionerId: string,\n) {\n await tx\n .update(runnerBootstrapTokens)\n .set({revokedAt: sql`now()`})\n .where(eq(runnerBootstrapTokens.provisionerId, provisionerId));\n await tx\n .update(runnerControlSessions)\n .set({closedAt: sql`now()`, closeReason: 'provisioner-revoked'})\n .where(\n and(\n eq(runnerControlSessions.provisionerId, provisionerId),\n isNull(runnerControlSessions.closedAt),\n ),\n );\n await tx\n .update(runnerActivationTokens)\n .set({revokedAt: sql`now()`})\n .where(\n and(\n isNull(runnerActivationTokens.consumedAt),\n isNull(runnerActivationTokens.revokedAt),\n inArray(\n runnerActivationTokens.runnerInstanceId,\n tx\n .select({id: providerRunners.id})\n .from(providerRunners)\n .where(eq(providerRunners.provisionerId, provisionerId)),\n ),\n ),\n );\n await tx\n .update(providerRunners)\n .set({state: 'terminated', terminatedAt: sql`now()`, updatedAt: sql`now()`})\n .where(\n and(\n eq(providerRunners.provisionerId, provisionerId),\n isNull(providerRunners.runnerSessionId),\n ),\n );\n await tx\n .update(runnerSessions)\n .set({revokedAt: sql`now()`})\n .where(\n and(\n eq(runnerSessions.provisionerId, provisionerId),\n eq(runnerSessions.claimsUsed, 0),\n isNull(runnerSessions.revokedAt),\n ),\n );\n}\n\nexport async function createProvisionerToken(\n params: CreateProvisionerTokenParams,\n): Promise<ProvisionerToken> {\n const rows = await db()\n .insert(provisionerTokens)\n .values({\n scope: params.scope,\n workspaceId: params.workspaceId ?? null,\n hashedToken: params.hashedToken,\n prefix: params.prefix,\n createdByUserId: params.createdByUserId,\n name: params.name ?? null,\n expiresAt: params.expiresAt ?? null,\n })\n .returning();\n\n const row = rows[0];\n if (!row) throw new Error('Insert returned no rows');\n return toProvisionerToken(row);\n}\n\nexport async function revokeInstallationProvisionerToken(params: {\n tokenId: string;\n revokedByUserId: string;\n}): Promise<ProvisionerToken | undefined> {\n const rows = await db().transaction(async (tx) => {\n const rows = await tx\n .update(provisionerTokens)\n .set({revokedAt: new Date(), revokedByUserId: params.revokedByUserId, updatedAt: new Date()})\n .where(\n and(\n eq(provisionerTokens.id, params.tokenId),\n eq(provisionerTokens.scope, 'installation'),\n isNull(provisionerTokens.revokedAt),\n ),\n )\n .returning();\n if (rows[0]) {\n await cascadeProvisionerRevocation(tx, params.tokenId);\n }\n return rows;\n });\n\n const row = rows[0];\n if (row) return toProvisionerToken(row);\n\n const existingRows = await db()\n .select()\n .from(provisionerTokens)\n .where(\n and(eq(provisionerTokens.id, params.tokenId), eq(provisionerTokens.scope, 'installation')),\n )\n .limit(1);\n const existingRow = existingRows[0];\n return existingRow ? toProvisionerToken(existingRow) : undefined;\n}\n\nexport async function listUsableProvisionerTokensByWorkspaceId(\n workspaceId: string,\n): Promise<ProvisionerToken[]> {\n const now = new Date();\n const rows = await db()\n .select()\n .from(provisionerTokens)\n .where(\n and(\n eq(provisionerTokens.workspaceId, workspaceId),\n eq(provisionerTokens.scope, 'workspace'),\n isNull(provisionerTokens.revokedAt),\n or(isNull(provisionerTokens.expiresAt), gt(provisionerTokens.expiresAt, now)),\n ),\n )\n .orderBy(desc(provisionerTokens.createdAt), desc(provisionerTokens.id));\n\n return rows.map(toProvisionerToken);\n}\n\nexport async function resolveProvisionerTokenByHash(\n hashedToken: string,\n): Promise<ProvisionerToken | undefined> {\n const rows = await db()\n .select()\n .from(provisionerTokens)\n .where(eq(provisionerTokens.hashedToken, hashedToken))\n .limit(1);\n\n const row = rows[0];\n if (!row) return undefined;\n return toProvisionerToken(row);\n}\n\nexport async function revokeProvisionerToken(params: {\n tokenId: string;\n workspaceId: string;\n revokedByUserId: string;\n}): Promise<ProvisionerToken | undefined> {\n const rows = await db().transaction(async (tx) => {\n const rows = await tx\n .update(provisionerTokens)\n .set({revokedAt: new Date(), revokedByUserId: params.revokedByUserId, updatedAt: new Date()})\n .where(\n and(\n eq(provisionerTokens.id, params.tokenId),\n eq(provisionerTokens.workspaceId, params.workspaceId),\n eq(provisionerTokens.scope, 'workspace'),\n isNull(provisionerTokens.revokedAt),\n ),\n )\n .returning();\n if (rows[0]) await cascadeProvisionerRevocation(tx, params.tokenId);\n return rows;\n });\n\n const row = rows[0];\n if (row) return toProvisionerToken(row);\n\n const existingRows = await db()\n .select()\n .from(provisionerTokens)\n .where(\n and(\n eq(provisionerTokens.id, params.tokenId),\n eq(provisionerTokens.workspaceId, params.workspaceId),\n eq(provisionerTokens.scope, 'workspace'),\n ),\n )\n .limit(1);\n\n const existingRow = existingRows[0];\n if (!existingRow) return undefined;\n return toProvisionerToken(existingRow);\n}\n\nexport async function touchProvisionerLastSeen(params: {\n tokenId: string;\n throttleSeconds: number;\n}): Promise<void> {\n await db()\n .update(provisionerTokens)\n .set({lastSeenAt: sql`now()`, updatedAt: sql`now()`})\n .where(\n and(\n eq(provisionerTokens.id, params.tokenId),\n or(\n isNull(provisionerTokens.lastSeenAt),\n sql`${provisionerTokens.lastSeenAt} < now() - (${params.throttleSeconds} || ' seconds')::interval`,\n ),\n ),\n );\n}\n\nexport async function listActiveProvisionerTokens(params: {\n workspaceId: string;\n windowSeconds: number;\n}): Promise<ActiveProvisionerToken[]> {\n const rows = await db()\n .select()\n .from(provisionerTokens)\n .where(\n and(\n eq(provisionerTokens.workspaceId, params.workspaceId),\n eq(provisionerTokens.scope, 'workspace'),\n isNull(provisionerTokens.revokedAt),\n or(isNull(provisionerTokens.expiresAt), gt(provisionerTokens.expiresAt, sql`now()`)),\n sql`${provisionerTokens.lastSeenAt} > now() - (${params.windowSeconds} || ' seconds')::interval`,\n ),\n )\n .orderBy(desc(provisionerTokens.lastSeenAt), desc(provisionerTokens.id));\n\n return rows.map(toProvisionerToken).filter((token): token is ActiveProvisionerToken => {\n return token.lastSeenAt !== null;\n });\n}\n"],"names":["and","desc","eq","gt","inArray","isNull","or","sql","db","provisionerTokens","toProvisionerToken","runnerActivationTokens","runnerBootstrapTokens","runnerControlSessions","providerRunners","runnerSessions","cascadeProvisionerRevocation","tx","provisionerId","update","set","revokedAt","where","closedAt","closeReason","consumedAt","runnerInstanceId","select","id","from","state","terminatedAt","updatedAt","runnerSessionId","claimsUsed","createProvisionerToken","params","rows","insert","values","scope","workspaceId","hashedToken","prefix","createdByUserId","name","expiresAt","returning","row","Error","revokeInstallationProvisionerToken","transaction","Date","revokedByUserId","tokenId","existingRows","limit","existingRow","undefined","listUsableProvisionerTokensByWorkspaceId","now","orderBy","createdAt","map","resolveProvisionerTokenByHash","revokeProvisionerToken","touchProvisionerLastSeen","lastSeenAt","throttleSeconds","listActiveProvisionerTokens","windowSeconds","filter","token"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/db/provisioner-tokens.ts"],"sourcesContent":["import type {\n AdministrationActionEvent,\n AdministrationActionEventMap,\n} from '@shipfox/api-common-dto';\nimport {type TimestampIdCursor, timestampIdCursorWhere} from '@shipfox/node-drizzle';\nimport {writeOutboxEvent} from '@shipfox/node-outbox';\nimport {and, desc, eq, gt, inArray, isNotNull, isNull, lte, or, type SQL, sql} from 'drizzle-orm';\nimport type {\n ActiveProvisionerToken,\n ProvisionerScope,\n ProvisionerToken,\n} from '#core/entities/provisioner-token.js';\nimport {ProvisionerAdminIdempotencyKeyReuseError} from '#core/errors.js';\nimport {db} from './db.js';\nimport {runnersAdminCommandResults} from './schema/admin-command-results.js';\nimport {runnersOutbox} from './schema/outbox.js';\nimport {provisionerTokens, toProvisionerToken} from './schema/provisioner-tokens.js';\nimport {runnerActivationTokens} from './schema/runner-activation-tokens.js';\nimport {runnerBootstrapTokens, runnerControlSessions} from './schema/runner-control-sessions.js';\nimport {providerRunners} from './schema/runner-instances.js';\nimport {runnerSessions} from './schema/runner-sessions.js';\n\ntype Tx = Parameters<Parameters<ReturnType<typeof db>['transaction']>[0]>[0];\n\nexport interface CreateProvisionerTokenParams {\n scope: ProvisionerScope;\n workspaceId?: string | undefined;\n hashedToken: string;\n prefix: string;\n createdByUserId: string;\n name?: string | undefined;\n expiresAt?: Date | undefined;\n}\n\nasync function cascadeProvisionerRevocation(tx: Tx, provisionerId: string) {\n await tx\n .update(runnerBootstrapTokens)\n .set({revokedAt: sql`now()`})\n .where(eq(runnerBootstrapTokens.provisionerId, provisionerId));\n await tx\n .update(runnerControlSessions)\n .set({closedAt: sql`now()`, closeReason: 'provisioner-revoked'})\n .where(\n and(\n eq(runnerControlSessions.provisionerId, provisionerId),\n isNull(runnerControlSessions.closedAt),\n ),\n );\n await tx\n .update(runnerActivationTokens)\n .set({revokedAt: sql`now()`})\n .where(\n and(\n isNull(runnerActivationTokens.consumedAt),\n isNull(runnerActivationTokens.revokedAt),\n inArray(\n runnerActivationTokens.runnerInstanceId,\n tx\n .select({id: providerRunners.id})\n .from(providerRunners)\n .where(eq(providerRunners.provisionerId, provisionerId)),\n ),\n ),\n );\n await tx\n .update(providerRunners)\n .set({state: 'terminated', terminatedAt: sql`now()`, updatedAt: sql`now()`})\n .where(\n and(\n eq(providerRunners.provisionerId, provisionerId),\n isNull(providerRunners.runnerSessionId),\n ),\n );\n await tx\n .update(runnerSessions)\n .set({revokedAt: sql`now()`})\n .where(\n and(\n eq(runnerSessions.provisionerId, provisionerId),\n eq(runnerSessions.claimsUsed, 0),\n isNull(runnerSessions.revokedAt),\n ),\n );\n}\n\nasync function insertProvisionerToken(tx: Tx, params: CreateProvisionerTokenParams) {\n const rows = await tx\n .insert(provisionerTokens)\n .values({\n scope: params.scope,\n workspaceId: params.workspaceId ?? null,\n hashedToken: params.hashedToken,\n prefix: params.prefix,\n createdByUserId: params.createdByUserId,\n name: params.name ?? null,\n expiresAt: params.expiresAt ?? null,\n })\n .returning();\n\n const row = rows[0];\n if (!row) throw new Error('Insert returned no rows');\n return row;\n}\n\nexport async function createProvisionerToken(\n params: CreateProvisionerTokenParams,\n): Promise<ProvisionerToken> {\n const row = await db().transaction((tx) => insertProvisionerToken(tx, params));\n return toProvisionerToken(row);\n}\n\ninterface AuditedProvisionerTokenCommandParams {\n actorId: string;\n command: string;\n correlationId: string;\n idempotencyKeyFingerprint: string;\n requestFingerprint: string;\n event: (tokenId: string) => AdministrationActionEvent;\n}\n\nasync function lockAdminCommand(\n tx: Tx,\n params: Pick<AuditedProvisionerTokenCommandParams, 'actorId' | 'idempotencyKeyFingerprint'>,\n): Promise<void> {\n await tx.execute(\n sql`select pg_advisory_xact_lock(hashtext(${`runners_admin_command:${params.actorId}:${params.idempotencyKeyFingerprint}`}))`,\n );\n}\n\nasync function loadAdminCommandResult(\n tx: Tx,\n params: Pick<\n AuditedProvisionerTokenCommandParams,\n 'actorId' | 'idempotencyKeyFingerprint' | 'requestFingerprint'\n > & {command: string},\n) {\n const rows = await tx\n .select()\n .from(runnersAdminCommandResults)\n .where(\n and(\n eq(runnersAdminCommandResults.actorId, params.actorId),\n eq(runnersAdminCommandResults.idempotencyKeyFingerprint, params.idempotencyKeyFingerprint),\n ),\n )\n .limit(1);\n const result = rows[0];\n if (!result) return undefined;\n if (\n result.command !== params.command ||\n result.requestFingerprint !== params.requestFingerprint\n ) {\n throw new ProvisionerAdminIdempotencyKeyReuseError();\n }\n return result;\n}\n\nasync function storeAdminCommandResult(\n tx: Tx,\n params: AuditedProvisionerTokenCommandParams,\n result: {provisionerTokenId: string; correlationId: string},\n): Promise<void> {\n await tx.insert(runnersAdminCommandResults).values({\n actorId: params.actorId,\n idempotencyKeyFingerprint: params.idempotencyKeyFingerprint,\n command: params.command,\n requestFingerprint: params.requestFingerprint,\n result,\n });\n}\n\nasync function writeAdministrationAction(tx: Tx, event: AdministrationActionEvent): Promise<void> {\n await writeOutboxEvent<AdministrationActionEventMap>(tx, runnersOutbox, {\n type: 'administration.action.performed',\n payload: event,\n });\n}\n\nexport async function createInstallationProvisionerTokenWithAudit(\n params: AuditedProvisionerTokenCommandParams & {\n name?: string | undefined;\n ttlSeconds?: number | undefined;\n hashedToken: string;\n prefix: string;\n },\n): Promise<{token: ProvisionerToken; correlationId: string; replayed: boolean}> {\n const command = 'runners.provisioner_token.create';\n return await db().transaction(async (tx) => {\n await lockAdminCommand(tx, params);\n const existing = await loadAdminCommandResult(tx, {\n actorId: params.actorId,\n idempotencyKeyFingerprint: params.idempotencyKeyFingerprint,\n requestFingerprint: params.requestFingerprint,\n command,\n });\n if (existing) {\n const rows = await tx\n .select()\n .from(provisionerTokens)\n .where(eq(provisionerTokens.id, existing.result.provisionerTokenId))\n .limit(1);\n const row = rows[0];\n if (!row) throw new Error('Idempotent provisioner token result is missing');\n return {\n token: toProvisionerToken(row),\n correlationId: existing.result.correlationId,\n replayed: true,\n };\n }\n\n const expiresAt =\n params.ttlSeconds === undefined ? undefined : new Date(Date.now() + params.ttlSeconds * 1000);\n const row = await insertProvisionerToken(tx, {\n scope: 'installation',\n hashedToken: params.hashedToken,\n prefix: params.prefix,\n createdByUserId: params.actorId,\n name: params.name,\n expiresAt,\n });\n const event = params.event(row.id);\n await writeAdministrationAction(tx, event);\n await storeAdminCommandResult(tx, params, {\n provisionerTokenId: row.id,\n correlationId: event.correlationId,\n });\n return {token: toProvisionerToken(row), correlationId: event.correlationId, replayed: false};\n });\n}\n\nexport async function revokeInstallationProvisionerToken(params: {\n tokenId: string;\n revokedByUserId: string;\n}): Promise<ProvisionerToken | undefined> {\n const rows = await db().transaction(async (tx) => {\n const rows = await tx\n .update(provisionerTokens)\n .set({revokedAt: new Date(), revokedByUserId: params.revokedByUserId, updatedAt: new Date()})\n .where(\n and(\n eq(provisionerTokens.id, params.tokenId),\n eq(provisionerTokens.scope, 'installation'),\n isNull(provisionerTokens.revokedAt),\n ),\n )\n .returning();\n if (rows[0]) {\n await cascadeProvisionerRevocation(tx, params.tokenId);\n }\n return rows;\n });\n\n const row = rows[0];\n if (row) return toProvisionerToken(row);\n\n const existingRows = await db()\n .select()\n .from(provisionerTokens)\n .where(\n and(eq(provisionerTokens.id, params.tokenId), eq(provisionerTokens.scope, 'installation')),\n )\n .limit(1);\n const existingRow = existingRows[0];\n return existingRow ? toProvisionerToken(existingRow) : undefined;\n}\n\nexport async function revokeInstallationProvisionerTokenWithAudit(\n params: AuditedProvisionerTokenCommandParams & {tokenId: string},\n): Promise<{token: ProvisionerToken; correlationId: string; replayed: boolean} | undefined> {\n const command = 'runners.provisioner_token.revoke';\n return await db().transaction(async (tx) => {\n await lockAdminCommand(tx, params);\n const existing = await loadAdminCommandResult(tx, {\n actorId: params.actorId,\n idempotencyKeyFingerprint: params.idempotencyKeyFingerprint,\n requestFingerprint: params.requestFingerprint,\n command,\n });\n if (existing) {\n const rows = await tx\n .select()\n .from(provisionerTokens)\n .where(eq(provisionerTokens.id, existing.result.provisionerTokenId))\n .limit(1);\n const row = rows[0];\n if (!row) throw new Error('Idempotent provisioner token result is missing');\n return {\n token: toProvisionerToken(row),\n correlationId: existing.result.correlationId,\n replayed: true,\n };\n }\n\n const updatedRows = await tx\n .update(provisionerTokens)\n .set({revokedAt: new Date(), revokedByUserId: params.actorId, updatedAt: new Date()})\n .where(\n and(\n eq(provisionerTokens.id, params.tokenId),\n eq(provisionerTokens.scope, 'installation'),\n isNull(provisionerTokens.revokedAt),\n ),\n )\n .returning();\n const row = updatedRows[0];\n if (row) await cascadeProvisionerRevocation(tx, params.tokenId);\n\n const existingRows = row\n ? [row]\n : await tx\n .select()\n .from(provisionerTokens)\n .where(\n and(\n eq(provisionerTokens.id, params.tokenId),\n eq(provisionerTokens.scope, 'installation'),\n ),\n )\n .limit(1);\n const tokenRow = existingRows[0];\n if (!tokenRow) return undefined;\n\n const changed = row !== undefined;\n const event = changed ? params.event(tokenRow.id) : undefined;\n if (event) await writeAdministrationAction(tx, event);\n await storeAdminCommandResult(tx, params, {\n provisionerTokenId: tokenRow.id,\n correlationId: event?.correlationId ?? params.correlationId,\n });\n return {\n token: toProvisionerToken(tokenRow),\n correlationId: event?.correlationId ?? params.correlationId,\n replayed: false,\n };\n });\n}\n\nexport type InstallationProvisionerTokenStatus = 'active' | 'expired' | 'revoked';\n\nexport interface ListInstallationProvisionerTokensParams {\n limit: number;\n cursor?: TimestampIdCursor | undefined;\n status?: InstallationProvisionerTokenStatus | undefined;\n}\n\nexport async function listInstallationProvisionerTokens(\n params: ListInstallationProvisionerTokensParams,\n): Promise<{tokens: ProvisionerToken[]; nextCursor: TimestampIdCursor | null}> {\n const conditions: SQL[] = [eq(provisionerTokens.scope, 'installation')];\n const cursor = timestampIdCursorWhere({\n timestampColumn: provisionerTokens.createdAt,\n idColumn: provisionerTokens.id,\n cursor: params.cursor,\n });\n if (cursor) conditions.push(cursor);\n\n if (params.status === 'active') {\n conditions.push(isNull(provisionerTokens.revokedAt));\n const activeExpiry = or(\n isNull(provisionerTokens.expiresAt),\n gt(provisionerTokens.expiresAt, sql`now()`),\n );\n if (activeExpiry) conditions.push(activeExpiry);\n }\n if (params.status === 'expired') {\n conditions.push(\n isNull(provisionerTokens.revokedAt),\n isNotNull(provisionerTokens.expiresAt),\n lte(provisionerTokens.expiresAt, sql`now()`),\n );\n }\n if (params.status === 'revoked') conditions.push(isNotNull(provisionerTokens.revokedAt));\n\n const rows = await db()\n .select()\n .from(provisionerTokens)\n .where(and(...conditions))\n .orderBy(desc(provisionerTokens.createdAt), desc(provisionerTokens.id))\n .limit(params.limit + 1);\n const pageRows = rows.length > params.limit ? rows.slice(0, params.limit) : rows;\n const last = pageRows.at(-1);\n return {\n tokens: pageRows.map(toProvisionerToken),\n nextCursor:\n rows.length > params.limit && last ? {createdAt: last.createdAt, id: last.id} : null,\n };\n}\n\nexport async function listUsableProvisionerTokensByWorkspaceId(\n workspaceId: string,\n): Promise<ProvisionerToken[]> {\n const now = new Date();\n const rows = await db()\n .select()\n .from(provisionerTokens)\n .where(\n and(\n eq(provisionerTokens.workspaceId, workspaceId),\n eq(provisionerTokens.scope, 'workspace'),\n isNull(provisionerTokens.revokedAt),\n or(isNull(provisionerTokens.expiresAt), gt(provisionerTokens.expiresAt, now)),\n ),\n )\n .orderBy(desc(provisionerTokens.createdAt), desc(provisionerTokens.id));\n\n return rows.map(toProvisionerToken);\n}\n\nexport async function resolveProvisionerTokenByHash(\n hashedToken: string,\n): Promise<ProvisionerToken | undefined> {\n const rows = await db()\n .select()\n .from(provisionerTokens)\n .where(eq(provisionerTokens.hashedToken, hashedToken))\n .limit(1);\n\n const row = rows[0];\n if (!row) return undefined;\n return toProvisionerToken(row);\n}\n\nexport async function revokeProvisionerToken(params: {\n tokenId: string;\n workspaceId: string;\n revokedByUserId: string;\n}): Promise<ProvisionerToken | undefined> {\n const rows = await db().transaction(async (tx) => {\n const rows = await tx\n .update(provisionerTokens)\n .set({revokedAt: new Date(), revokedByUserId: params.revokedByUserId, updatedAt: new Date()})\n .where(\n and(\n eq(provisionerTokens.id, params.tokenId),\n eq(provisionerTokens.workspaceId, params.workspaceId),\n eq(provisionerTokens.scope, 'workspace'),\n isNull(provisionerTokens.revokedAt),\n ),\n )\n .returning();\n if (rows[0]) await cascadeProvisionerRevocation(tx, params.tokenId);\n return rows;\n });\n\n const row = rows[0];\n if (row) return toProvisionerToken(row);\n\n const existingRows = await db()\n .select()\n .from(provisionerTokens)\n .where(\n and(\n eq(provisionerTokens.id, params.tokenId),\n eq(provisionerTokens.workspaceId, params.workspaceId),\n eq(provisionerTokens.scope, 'workspace'),\n ),\n )\n .limit(1);\n\n const existingRow = existingRows[0];\n if (!existingRow) return undefined;\n return toProvisionerToken(existingRow);\n}\n\nexport async function touchProvisionerLastSeen(params: {\n tokenId: string;\n throttleSeconds: number;\n}): Promise<void> {\n await db()\n .update(provisionerTokens)\n .set({lastSeenAt: sql`now()`, updatedAt: sql`now()`})\n .where(\n and(\n eq(provisionerTokens.id, params.tokenId),\n or(\n isNull(provisionerTokens.lastSeenAt),\n sql`${provisionerTokens.lastSeenAt} < now() - (${params.throttleSeconds} || ' seconds')::interval`,\n ),\n ),\n );\n}\n\nexport async function listActiveProvisionerTokens(params: {\n workspaceId: string;\n windowSeconds: number;\n}): Promise<ActiveProvisionerToken[]> {\n const rows = await db()\n .select()\n .from(provisionerTokens)\n .where(\n and(\n eq(provisionerTokens.workspaceId, params.workspaceId),\n eq(provisionerTokens.scope, 'workspace'),\n isNull(provisionerTokens.revokedAt),\n or(isNull(provisionerTokens.expiresAt), gt(provisionerTokens.expiresAt, sql`now()`)),\n sql`${provisionerTokens.lastSeenAt} > now() - (${params.windowSeconds} || ' seconds')::interval`,\n ),\n )\n .orderBy(desc(provisionerTokens.lastSeenAt), desc(provisionerTokens.id));\n\n return rows.map(toProvisionerToken).filter((token): token is ActiveProvisionerToken => {\n return token.lastSeenAt !== null;\n });\n}\n"],"names":["timestampIdCursorWhere","writeOutboxEvent","and","desc","eq","gt","inArray","isNotNull","isNull","lte","or","sql","ProvisionerAdminIdempotencyKeyReuseError","db","runnersAdminCommandResults","runnersOutbox","provisionerTokens","toProvisionerToken","runnerActivationTokens","runnerBootstrapTokens","runnerControlSessions","providerRunners","runnerSessions","cascadeProvisionerRevocation","tx","provisionerId","update","set","revokedAt","where","closedAt","closeReason","consumedAt","runnerInstanceId","select","id","from","state","terminatedAt","updatedAt","runnerSessionId","claimsUsed","insertProvisionerToken","params","rows","insert","values","scope","workspaceId","hashedToken","prefix","createdByUserId","name","expiresAt","returning","row","Error","createProvisionerToken","transaction","lockAdminCommand","execute","actorId","idempotencyKeyFingerprint","loadAdminCommandResult","limit","result","undefined","command","requestFingerprint","storeAdminCommandResult","writeAdministrationAction","event","type","payload","createInstallationProvisionerTokenWithAudit","existing","provisionerTokenId","token","correlationId","replayed","ttlSeconds","Date","now","revokeInstallationProvisionerToken","revokedByUserId","tokenId","existingRows","existingRow","revokeInstallationProvisionerTokenWithAudit","updatedRows","tokenRow","changed","listInstallationProvisionerTokens","conditions","cursor","timestampColumn","createdAt","idColumn","push","status","activeExpiry","orderBy","pageRows","length","slice","last","at","tokens","map","nextCursor","listUsableProvisionerTokensByWorkspaceId","resolveProvisionerTokenByHash","revokeProvisionerToken","touchProvisionerLastSeen","lastSeenAt","throttleSeconds","listActiveProvisionerTokens","windowSeconds","filter"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"reservations.d.ts","sourceRoot":"","sources":["../../src/db/reservations.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,SAAS,CAAC;AAOhC,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,0BAA0B;IACzC,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,mBAAmB,EAAE,CAAC;IACjC,uBAAuB,CAAC,EAAE,MAAM,CAAC;CAClC;AAED,MAAM,WAAW,sCAAsC;IACrD,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,mBAAmB,EAAE,CAAC;IACjC,uBAAuB,EAAE,MAAM,CAAC;IAChC,oBAAoB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC1C,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,cAAc,CAAC,EAAE,CAAC,YAAY,EAAE,gBAAgB,EAAE,KAAK,IAAI,CAAC;CAC7D;AAcD,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,0BAA0B,GACjC,OAAO,CAAC;IAAC,KAAK,EAAE,UAAU,EAAE,CAAC;IAAC,YAAY,EAAE,gBAAgB,EAAE,CAAA;CAAC,CAAC,CAIlE;AAED,wBAAsB,sBAAsB,CAC1C,EAAE,EAAE,EAAE,EACN,MAAM,EAAE,0BAA0B,GACjC,OAAO,CAAC;IAAC,KAAK,EAAE,UAAU,EAAE,CAAC;IAAC,YAAY,EAAE,gBAAgB,EAAE,CAAA;CAAC,CAAC,CAGlE;AAED,wBAAsB,gCAAgC,CACpD,MAAM,EAAE,sCAAsC,GAC7C,OAAO,CAAC;IAAC,KAAK,EAAE,UAAU,EAAE,CAAC;IAAC,YAAY,EAAE,gBAAgB,EAAE,CAAA;CAAC,CAAC,CA2ClE;AA8JD,wBAAsB,4BAA4B,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAMtE;AA0BD,wBAAsB,yBAAyB,CAAC,MAAM,CAAC,EAAE;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAc1F;AAED,wBAAsB,uBAAuB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAS5E;AAED,wBAAsB,uBAAuB,CAC3C,EAAE,EAAE,EAAE,EACN,MAAM,EAAE;IACN,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,KAAK,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAC,CAAC,CAAC;CACzD,GACA,OAAO,CAAC,MAAM,CAAC,CA0CjB"}
1
+ {"version":3,"file":"reservations.d.ts","sourceRoot":"","sources":["../../src/db/reservations.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,SAAS,CAAC;AAUhC,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,0BAA0B;IACzC,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,mBAAmB,EAAE,CAAC;IACjC,uBAAuB,CAAC,EAAE,MAAM,CAAC;CAClC;AAED,MAAM,WAAW,sCAAsC;IACrD,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,mBAAmB,EAAE,CAAC;IACjC,uBAAuB,EAAE,MAAM,CAAC;IAChC,oBAAoB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC1C,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,cAAc,CAAC,EAAE,CAAC,YAAY,EAAE,gBAAgB,EAAE,KAAK,IAAI,CAAC;CAC7D;AAcD,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,0BAA0B,GACjC,OAAO,CAAC;IAAC,KAAK,EAAE,UAAU,EAAE,CAAC;IAAC,YAAY,EAAE,gBAAgB,EAAE,CAAA;CAAC,CAAC,CAIlE;AAED,wBAAsB,sBAAsB,CAC1C,EAAE,EAAE,EAAE,EACN,MAAM,EAAE,0BAA0B,GACjC,OAAO,CAAC;IAAC,KAAK,EAAE,UAAU,EAAE,CAAC;IAAC,YAAY,EAAE,gBAAgB,EAAE,CAAA;CAAC,CAAC,CAGlE;AAED,wBAAsB,gCAAgC,CACpD,MAAM,EAAE,sCAAsC,GAC7C,OAAO,CAAC;IAAC,KAAK,EAAE,UAAU,EAAE,CAAC;IAAC,YAAY,EAAE,gBAAgB,EAAE,CAAA;CAAC,CAAC,CA2ClE;AAsOD,wBAAsB,4BAA4B,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAMtE;AA0BD,wBAAsB,yBAAyB,CAAC,MAAM,CAAC,EAAE;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAc1F;AAED,wBAAsB,uBAAuB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAM5E;AAyED,wBAAsB,uBAAuB,CAC3C,EAAE,EAAE,EAAE,EACN,MAAM,EAAE;IACN,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,KAAK,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAC,CAAC,CAAC;CACzD,GACA,OAAO,CAAC,MAAM,CAAC,CA0CjB"}
@@ -1,10 +1,13 @@
1
1
  import { canonicalizeLabels } from '@shipfox/runner-labels';
2
- import { and, asc, eq, gt, inArray, lt, sql } from 'drizzle-orm';
2
+ import { and, arrayContains, asc, eq, exists, gt, inArray, isNotNull, isNull, lt, sql } from 'drizzle-orm';
3
3
  import { db } from './db.js';
4
4
  import { pendingJobExecutions } from './schema/pending-job-executions.js';
5
5
  import { provisionerCapabilitySnapshots } from './schema/provisioner-capability-snapshots.js';
6
6
  import { provisionerTokens } from './schema/provisioner-tokens.js';
7
7
  import { reservations } from './schema/reservations.js';
8
+ import { runnerActivationTokens } from './schema/runner-activation-tokens.js';
9
+ import { runnerControlSessions } from './schema/runner-control-sessions.js';
10
+ import { providerRunners } from './schema/runner-instances.js';
8
11
  export async function pollDemandAndReserve(params) {
9
12
  return await db().transaction(async (tx)=>{
10
13
  return await pollDemandAndReserveTx(tx, params);
@@ -126,6 +129,13 @@ async function pollDemandAndReserveLockedTx(tx, params) {
126
129
  if (!inserted) throw new Error('Insert returned no rows');
127
130
  remainingMaxReservations -= grant;
128
131
  drawSlots(satisfyingTemplates, grant);
132
+ await bindIdleRunnerInstancesTx(tx, {
133
+ provisionerId: params.provisionerId,
134
+ reservationId: inserted.id,
135
+ requiredLabels: demand.requiredLabels,
136
+ count: grant,
137
+ workspaceId: params.workspaceId
138
+ });
129
139
  reservedAfterGrant += grant;
130
140
  grants.push({
131
141
  reservationId: inserted.id,
@@ -148,6 +158,20 @@ async function pollDemandAndReserveLockedTx(tx, params) {
148
158
  reservations: grants
149
159
  };
150
160
  }
161
+ async function bindIdleRunnerInstancesTx(tx, params) {
162
+ const idleRunners = await tx.select({
163
+ id: providerRunners.id
164
+ }).from(providerRunners).where(and(eq(providerRunners.provisionerId, params.provisionerId), isNull(providerRunners.workspaceId), isNull(providerRunners.reservationId), isNull(providerRunners.intendedReservationId), isNull(providerRunners.runnerSessionId), isNotNull(providerRunners.providerRunnerId), eq(providerRunners.state, 'running'), arrayContains(providerRunners.labels, params.requiredLabels), exists(tx.select({
165
+ id: runnerControlSessions.id
166
+ }).from(runnerControlSessions).where(and(eq(runnerControlSessions.runnerInstanceId, providerRunners.id), eq(runnerControlSessions.provisionerId, params.provisionerId), isNull(runnerControlSessions.closedAt), gt(runnerControlSessions.expiresAt, sql`now()`)))))).orderBy(asc(providerRunners.createdAt), asc(providerRunners.id)).limit(params.count).for('update');
167
+ if (idleRunners.length === 0) return;
168
+ await tx.update(providerRunners).set({
169
+ workspaceId: params.workspaceId,
170
+ reservationId: params.reservationId,
171
+ assignedAt: sql`now()`,
172
+ updatedAt: sql`now()`
173
+ }).where(and(inArray(providerRunners.id, idleRunners.map((runner)=>runner.id)), isNull(providerRunners.workspaceId), isNull(providerRunners.reservationId), isNull(providerRunners.runnerSessionId)));
174
+ }
151
175
  async function listInstallationDemandWorkspaceIds(eligibleWorkspaceIds) {
152
176
  if (eligibleWorkspaceIds.size === 0) return [];
153
177
  const rows = await db().select({
@@ -171,17 +195,45 @@ async function listActiveWorkspaceCapabilityLabelsTx(tx, params) {
171
195
  return rows.map((row)=>row.labels);
172
196
  }
173
197
  export async function deleteExpiredReservations(params) {
174
- const expiredIds = db().select({
175
- id: reservations.id
176
- }).from(reservations).where(lt(reservations.expiresAt, sql`now()`)).orderBy(asc(reservations.expiresAt)).limit(params?.limit ?? 1000);
177
- const deleted = await db().delete(reservations).where(inArray(reservations.id, expiredIds)).returning({
178
- id: reservations.id
198
+ return await db().transaction(async (tx)=>{
199
+ const expiredRows = await tx.select({
200
+ id: reservations.id
201
+ }).from(reservations).where(lt(reservations.expiresAt, sql`now()`)).orderBy(asc(reservations.expiresAt)).limit(params?.limit ?? 1000);
202
+ return await deleteReservationsWithCleanupTx(tx, expiredRows.map((reservation)=>reservation.id), {
203
+ expiredOnly: true
204
+ });
179
205
  });
180
- return deleted.length;
181
206
  }
182
207
  export async function deleteReservationsByIds(ids) {
183
208
  if (ids.length === 0) return 0;
184
- const deleted = await db().delete(reservations).where(inArray(reservations.id, ids)).returning({
209
+ return await db().transaction(async (tx)=>{
210
+ return await deleteReservationsWithCleanupTx(tx, ids);
211
+ });
212
+ }
213
+ async function deleteReservationsWithCleanupTx(tx, ids, params = {}) {
214
+ if (ids.length === 0) return 0;
215
+ const assignedRunners = await tx.select({
216
+ id: providerRunners.id,
217
+ reservationId: providerRunners.reservationId
218
+ }).from(providerRunners).where(and(inArray(providerRunners.reservationId, ids), isNull(providerRunners.runnerSessionId), isNull(providerRunners.reservationReleasedAt))).for('update');
219
+ const reservationRows = await tx.select({
220
+ id: reservations.id
221
+ }).from(reservations).where(and(inArray(reservations.id, ids), params.expiredOnly ? lt(reservations.expiresAt, sql`now()`) : undefined)).for('update');
222
+ const reservationIds = reservationRows.map((reservation)=>reservation.id);
223
+ if (reservationIds.length === 0) return 0;
224
+ const assignedRunnerIds = assignedRunners.filter((runner)=>runner.reservationId && reservationIds.includes(runner.reservationId)).map((runner)=>runner.id);
225
+ if (assignedRunnerIds.length > 0) {
226
+ await tx.update(runnerActivationTokens).set({
227
+ revokedAt: sql`now()`
228
+ }).where(and(inArray(runnerActivationTokens.runnerInstanceId, assignedRunnerIds), isNull(runnerActivationTokens.consumedAt), isNull(runnerActivationTokens.revokedAt)));
229
+ await tx.update(providerRunners).set({
230
+ workspaceId: null,
231
+ reservationId: null,
232
+ assignedAt: null,
233
+ updatedAt: sql`now()`
234
+ }).where(and(inArray(providerRunners.id, assignedRunnerIds), isNull(providerRunners.runnerSessionId), isNull(providerRunners.reservationReleasedAt)));
235
+ }
236
+ const deleted = await tx.delete(reservations).where(inArray(reservations.id, reservationIds)).returning({
185
237
  id: reservations.id
186
238
  });
187
239
  return deleted.length;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/db/reservations.ts"],"sourcesContent":["import {canonicalizeLabels} from '@shipfox/runner-labels';\nimport {and, asc, eq, gt, inArray, lt, sql} from 'drizzle-orm';\nimport type {Tx} from './db.js';\nimport {db} from './db.js';\nimport {pendingJobExecutions} from './schema/pending-job-executions.js';\nimport {provisionerCapabilitySnapshots} from './schema/provisioner-capability-snapshots.js';\nimport {provisionerTokens} from './schema/provisioner-tokens.js';\nimport {reservations} from './schema/reservations.js';\n\nexport interface ReservationTemplate {\n templateKey: string;\n labels: string[];\n availableSlots: number;\n starting: number;\n running: number;\n}\n\nexport interface DemandStat {\n workspaceId?: string;\n labels: string[];\n queued: number;\n reserved: number;\n oldestQueuedAt: Date;\n}\n\nexport interface ReservationGrant {\n reservationId: string;\n workspaceId?: string;\n labels: string[];\n count: number;\n expiresAt: Date;\n}\n\nexport interface PollDemandAndReserveParams {\n workspaceId: string;\n provisionerId: string;\n maxReservations: number;\n ttlSeconds: number;\n templates: ReservationTemplate[];\n capabilityWindowSeconds?: number;\n}\n\nexport interface InstallationPollDemandAndReserveParams {\n provisionerId: string;\n maxReservations: number;\n ttlSeconds: number;\n templates: ReservationTemplate[];\n capabilityWindowSeconds: number;\n eligibleWorkspaceIds: ReadonlySet<string>;\n signal?: AbortSignal;\n onReservations?: (reservations: ReservationGrant[]) => void;\n}\n\ninterface NormalizedTemplate {\n templateKey: string;\n labels: string[];\n remainingSlots: number;\n}\n\ninterface DemandRow {\n requiredLabels: string[];\n queued: number;\n oldestQueuedAt: Date;\n}\n\nexport async function pollDemandAndReserve(\n params: PollDemandAndReserveParams,\n): Promise<{stats: DemandStat[]; reservations: ReservationGrant[]}> {\n return await db().transaction(async (tx) => {\n return await pollDemandAndReserveTx(tx, params);\n });\n}\n\nexport async function pollDemandAndReserveTx(\n tx: Tx,\n params: PollDemandAndReserveParams,\n): Promise<{stats: DemandStat[]; reservations: ReservationGrant[]}> {\n await tx.execute(sql`select pg_advisory_xact_lock(hashtext(${params.workspaceId}))`);\n return await pollDemandAndReserveLockedTx(tx, params);\n}\n\nexport async function pollInstallationDemandAndReserve(\n params: InstallationPollDemandAndReserveParams,\n): Promise<{stats: DemandStat[]; reservations: ReservationGrant[]}> {\n const candidateWorkspaceIds = await listInstallationDemandWorkspaceIds(\n params.eligibleWorkspaceIds,\n );\n const results: Array<{stats: DemandStat[]; reservations: ReservationGrant[]}> = [];\n let remainingMaxReservations = params.maxReservations;\n const remainingTemplates = params.templates.map((template) => ({\n ...template,\n labels: [...canonicalizeLabels(template.labels)],\n remainingSlots: template.availableSlots,\n }));\n for (const workspaceId of candidateWorkspaceIds) {\n if (params.signal?.aborted || remainingMaxReservations === 0) break;\n const result = await db().transaction(async (tx) => {\n const lockResult = await tx.execute<{locked: boolean}>(\n sql`select pg_try_advisory_xact_lock(hashtext(${workspaceId})) as locked`,\n );\n const locked = lockResult.rows[0];\n if (!locked?.locked) return {stats: [], reservations: []};\n return await pollDemandAndReserveLockedTx(tx, {\n workspaceId,\n provisionerId: params.provisionerId,\n maxReservations: remainingMaxReservations,\n ttlSeconds: params.ttlSeconds,\n templates: remainingTemplates.map((template) => ({\n ...template,\n availableSlots: template.remainingSlots,\n })),\n capabilityWindowSeconds: params.capabilityWindowSeconds,\n });\n });\n results.push(result);\n consumeInstallationTemplateSlots(remainingTemplates, result.reservations);\n params.onReservations?.(result.reservations);\n remainingMaxReservations -= result.reservations.reduce(\n (total, reservation) => total + reservation.count,\n 0,\n );\n }\n return {\n stats: results.flatMap((result) => result.stats),\n reservations: results.flatMap((result) => result.reservations),\n };\n}\n\nfunction consumeInstallationTemplateSlots(\n templates: NormalizedTemplate[],\n reservations: ReservationGrant[],\n): void {\n for (const reservation of reservations) {\n const satisfyingTemplates = templates\n .filter((template) => isSubset(reservation.labels, template.labels))\n .sort(\n (a, b) => a.labels.length - b.labels.length || a.templateKey.localeCompare(b.templateKey),\n );\n drawSlots(satisfyingTemplates, reservation.count);\n }\n}\n\nasync function pollDemandAndReserveLockedTx(\n tx: Tx,\n params: PollDemandAndReserveParams,\n): Promise<{stats: DemandStat[]; reservations: ReservationGrant[]}> {\n let demandRows = (\n await tx\n .select({\n requiredLabels: pendingJobExecutions.requiredLabels,\n queued: sql<number>`count(*)::int`,\n oldestQueuedAt: sql<Date | string>`min(${pendingJobExecutions.createdAt})`,\n })\n .from(pendingJobExecutions)\n .where(eq(pendingJobExecutions.workspaceId, params.workspaceId))\n .groupBy(pendingJobExecutions.requiredLabels)\n ).map((row) => ({\n ...row,\n oldestQueuedAt: new Date(row.oldestQueuedAt),\n }));\n if (params.capabilityWindowSeconds !== undefined) {\n const capabilityLabels = await listActiveWorkspaceCapabilityLabelsTx(tx, {\n workspaceId: params.workspaceId,\n windowSeconds: params.capabilityWindowSeconds,\n });\n demandRows = demandRows.filter(\n (demand) => !capabilityLabels.some((labels) => isSubset(demand.requiredLabels, labels)),\n );\n }\n\n const activeReservationRows = await tx\n .select({\n requiredLabels: reservations.requiredLabels,\n reserved: sql<number>`coalesce(sum(${reservations.count}), 0)::int`,\n })\n .from(reservations)\n .where(\n and(eq(reservations.workspaceId, params.workspaceId), gt(reservations.expiresAt, sql`now()`)),\n )\n .groupBy(reservations.requiredLabels);\n\n const reservedByLabels = new Map(\n activeReservationRows.map((row) => [labelKey(row.requiredLabels), row.reserved]),\n );\n const activeProvisionerReservationRows = await tx\n .select({\n requiredLabels: reservations.requiredLabels,\n reserved: sql<number>`coalesce(sum(${reservations.count}), 0)::int`,\n })\n .from(reservations)\n .where(\n and(\n eq(reservations.workspaceId, params.workspaceId),\n eq(reservations.provisionerId, params.provisionerId),\n gt(reservations.expiresAt, sql`now()`),\n ),\n )\n .groupBy(reservations.requiredLabels);\n\n const templates = params.templates.map((template) => ({\n templateKey: template.templateKey,\n labels: [...canonicalizeLabels(template.labels)],\n remainingSlots: template.availableSlots,\n }));\n deductProvisionerReservations(templates, activeProvisionerReservationRows);\n const stats: DemandStat[] = [];\n const grants: ReservationGrant[] = [];\n let remainingMaxReservations = params.maxReservations;\n const workspaceIdField =\n params.capabilityWindowSeconds === undefined ? {} : {workspaceId: params.workspaceId};\n\n for (const demand of sortDemandRows(demandRows)) {\n const satisfyingTemplates = templates\n .filter((template) => isSubset(demand.requiredLabels, template.labels))\n .sort(\n (a, b) => a.labels.length - b.labels.length || a.templateKey.localeCompare(b.templateKey),\n );\n\n if (satisfyingTemplates.length === 0) continue;\n\n const reserved = reservedByLabels.get(labelKey(demand.requiredLabels)) ?? 0;\n const unreserved = Math.max(0, demand.queued - reserved);\n const capacity = satisfyingTemplates.reduce(\n (total, template) => total + template.remainingSlots,\n 0,\n );\n const grant = Math.min(unreserved, capacity, remainingMaxReservations);\n let reservedAfterGrant = reserved;\n\n if (grant > 0 && params.maxReservations > 0) {\n const [inserted] = await tx\n .insert(reservations)\n .values({\n workspaceId: params.workspaceId,\n provisionerId: params.provisionerId,\n requiredLabels: demand.requiredLabels,\n count: grant,\n expiresAt: sql`now() + (${params.ttlSeconds} || ' seconds')::interval`,\n })\n .returning({id: reservations.id, expiresAt: reservations.expiresAt});\n\n if (!inserted) throw new Error('Insert returned no rows');\n\n remainingMaxReservations -= grant;\n drawSlots(satisfyingTemplates, grant);\n reservedAfterGrant += grant;\n grants.push({\n reservationId: inserted.id,\n ...workspaceIdField,\n labels: demand.requiredLabels,\n count: grant,\n expiresAt: inserted.expiresAt,\n });\n }\n\n stats.push({\n ...workspaceIdField,\n labels: demand.requiredLabels,\n queued: demand.queued,\n reserved: reservedAfterGrant,\n oldestQueuedAt: demand.oldestQueuedAt,\n });\n }\n\n return {stats, reservations: grants};\n}\n\nasync function listInstallationDemandWorkspaceIds(eligibleWorkspaceIds: ReadonlySet<string>) {\n if (eligibleWorkspaceIds.size === 0) return [];\n const rows = await db()\n .select({\n workspaceId: pendingJobExecutions.workspaceId,\n oldestQueuedAt: sql<Date>`min(${pendingJobExecutions.createdAt})`,\n })\n .from(pendingJobExecutions)\n .where(inArray(pendingJobExecutions.workspaceId, [...eligibleWorkspaceIds]))\n .groupBy(pendingJobExecutions.workspaceId)\n .orderBy(\n asc(sql`min(${pendingJobExecutions.createdAt})`),\n asc(pendingJobExecutions.workspaceId),\n );\n return rows.map((row) => row.workspaceId);\n}\n\nexport async function listQueuedDemandWorkspaceIds(): Promise<string[]> {\n const rows = await db()\n .select({workspaceId: pendingJobExecutions.workspaceId})\n .from(pendingJobExecutions)\n .groupBy(pendingJobExecutions.workspaceId);\n return rows.map((row) => row.workspaceId);\n}\n\nasync function listActiveWorkspaceCapabilityLabelsTx(\n tx: Tx,\n params: {workspaceId: string; windowSeconds: number},\n): Promise<string[][]> {\n const rows = await tx\n .select({labels: provisionerCapabilitySnapshots.labels})\n .from(provisionerCapabilitySnapshots)\n .innerJoin(\n provisionerTokens,\n eq(provisionerTokens.id, provisionerCapabilitySnapshots.provisionerId),\n )\n .where(\n and(\n eq(provisionerCapabilitySnapshots.workspaceId, params.workspaceId),\n eq(provisionerTokens.workspaceId, params.workspaceId),\n eq(provisionerTokens.scope, 'workspace'),\n sql`${provisionerCapabilitySnapshots.advertisedAt} > now() - (${params.windowSeconds} || ' seconds')::interval`,\n sql`${provisionerTokens.revokedAt} is null`,\n sql`(${provisionerTokens.expiresAt} is null or ${provisionerTokens.expiresAt} > now())`,\n ),\n );\n return rows.map((row) => row.labels);\n}\n\nexport async function deleteExpiredReservations(params?: {limit?: number}): Promise<number> {\n const expiredIds = db()\n .select({id: reservations.id})\n .from(reservations)\n .where(lt(reservations.expiresAt, sql`now()`))\n .orderBy(asc(reservations.expiresAt))\n .limit(params?.limit ?? 1000);\n\n const deleted = await db()\n .delete(reservations)\n .where(inArray(reservations.id, expiredIds))\n .returning({id: reservations.id});\n\n return deleted.length;\n}\n\nexport async function deleteReservationsByIds(ids: string[]): Promise<number> {\n if (ids.length === 0) return 0;\n\n const deleted = await db()\n .delete(reservations)\n .where(inArray(reservations.id, ids))\n .returning({id: reservations.id});\n\n return deleted.length;\n}\n\nexport async function releaseReservationUnits(\n tx: Tx,\n params: {\n workspaceId: string;\n provisionerId: string;\n releases: Array<{reservationId: string; count: number}>;\n },\n): Promise<number> {\n let released = 0;\n\n for (const release of params.releases) {\n if (release.count <= 0) continue;\n\n const decremented = await tx\n .update(reservations)\n .set({count: sql`${reservations.count} - ${release.count}`})\n .where(\n and(\n eq(reservations.id, release.reservationId),\n eq(reservations.workspaceId, params.workspaceId),\n eq(reservations.provisionerId, params.provisionerId),\n gt(reservations.count, release.count),\n gt(reservations.expiresAt, sql`now()`),\n ),\n )\n .returning({id: reservations.id});\n\n if (decremented.length > 0) {\n released += release.count;\n continue;\n }\n\n const deleted = await tx\n .delete(reservations)\n .where(\n and(\n eq(reservations.id, release.reservationId),\n eq(reservations.workspaceId, params.workspaceId),\n eq(reservations.provisionerId, params.provisionerId),\n sql`${reservations.count} <= ${release.count}`,\n gt(reservations.expiresAt, sql`now()`),\n ),\n )\n .returning({count: reservations.count});\n\n released += deleted.reduce((total, row) => total + row.count, 0);\n }\n\n return released;\n}\n\nfunction deductProvisionerReservations(\n templates: NormalizedTemplate[],\n activeReservations: {requiredLabels: string[]; reserved: number}[],\n): void {\n for (const reservation of sortReservationRows(activeReservations)) {\n const satisfyingTemplates = templates\n .filter((template) => isSubset(reservation.requiredLabels, template.labels))\n .sort(\n (a, b) => a.labels.length - b.labels.length || a.templateKey.localeCompare(b.templateKey),\n );\n drawSlots(satisfyingTemplates, reservation.reserved);\n }\n}\n\nfunction sortDemandRows(rows: DemandRow[]): DemandRow[] {\n return [...rows].sort((a, b) => {\n const specificity = b.requiredLabels.length - a.requiredLabels.length;\n if (specificity !== 0) return specificity;\n return a.oldestQueuedAt.getTime() - b.oldestQueuedAt.getTime();\n });\n}\n\nfunction sortReservationRows<T extends {requiredLabels: string[]}>(rows: T[]): T[] {\n return [...rows].sort((a, b) => {\n const specificity = b.requiredLabels.length - a.requiredLabels.length;\n if (specificity !== 0) return specificity;\n return labelKey(a.requiredLabels).localeCompare(labelKey(b.requiredLabels));\n });\n}\n\nfunction isSubset(requiredLabels: string[], availableLabels: string[]): boolean {\n return requiredLabels.every((label) => availableLabels.includes(label));\n}\n\nfunction drawSlots(templates: NormalizedTemplate[], count: number): void {\n let remaining = count;\n for (const template of templates) {\n if (remaining === 0) return;\n const used = Math.min(template.remainingSlots, remaining);\n template.remainingSlots -= used;\n remaining -= used;\n }\n}\n\nfunction labelKey(labels: string[]): string {\n return JSON.stringify(labels);\n}\n"],"names":["canonicalizeLabels","and","asc","eq","gt","inArray","lt","sql","db","pendingJobExecutions","provisionerCapabilitySnapshots","provisionerTokens","reservations","pollDemandAndReserve","params","transaction","tx","pollDemandAndReserveTx","execute","workspaceId","pollDemandAndReserveLockedTx","pollInstallationDemandAndReserve","candidateWorkspaceIds","listInstallationDemandWorkspaceIds","eligibleWorkspaceIds","results","remainingMaxReservations","maxReservations","remainingTemplates","templates","map","template","labels","remainingSlots","availableSlots","signal","aborted","result","lockResult","locked","rows","stats","provisionerId","ttlSeconds","capabilityWindowSeconds","push","consumeInstallationTemplateSlots","onReservations","reduce","total","reservation","count","flatMap","satisfyingTemplates","filter","isSubset","sort","a","b","length","templateKey","localeCompare","drawSlots","demandRows","select","requiredLabels","queued","oldestQueuedAt","createdAt","from","where","groupBy","row","Date","undefined","capabilityLabels","listActiveWorkspaceCapabilityLabelsTx","windowSeconds","demand","some","activeReservationRows","reserved","expiresAt","reservedByLabels","Map","labelKey","activeProvisionerReservationRows","deductProvisionerReservations","grants","workspaceIdField","sortDemandRows","get","unreserved","Math","max","capacity","grant","min","reservedAfterGrant","inserted","insert","values","returning","id","Error","reservationId","size","orderBy","listQueuedDemandWorkspaceIds","innerJoin","scope","advertisedAt","revokedAt","deleteExpiredReservations","expiredIds","limit","deleted","delete","deleteReservationsByIds","ids","releaseReservationUnits","released","release","releases","decremented","update","set","activeReservations","sortReservationRows","specificity","getTime","availableLabels","every","label","includes","remaining","used","JSON","stringify"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/db/reservations.ts"],"sourcesContent":["import {canonicalizeLabels} from '@shipfox/runner-labels';\nimport {\n and,\n arrayContains,\n asc,\n eq,\n exists,\n gt,\n inArray,\n isNotNull,\n isNull,\n lt,\n sql,\n} from 'drizzle-orm';\nimport type {Tx} from './db.js';\nimport {db} from './db.js';\nimport {pendingJobExecutions} from './schema/pending-job-executions.js';\nimport {provisionerCapabilitySnapshots} from './schema/provisioner-capability-snapshots.js';\nimport {provisionerTokens} from './schema/provisioner-tokens.js';\nimport {reservations} from './schema/reservations.js';\nimport {runnerActivationTokens} from './schema/runner-activation-tokens.js';\nimport {runnerControlSessions} from './schema/runner-control-sessions.js';\nimport {providerRunners} from './schema/runner-instances.js';\n\nexport interface ReservationTemplate {\n templateKey: string;\n labels: string[];\n availableSlots: number;\n starting: number;\n running: number;\n}\n\nexport interface DemandStat {\n workspaceId?: string;\n labels: string[];\n queued: number;\n reserved: number;\n oldestQueuedAt: Date;\n}\n\nexport interface ReservationGrant {\n reservationId: string;\n workspaceId?: string;\n labels: string[];\n count: number;\n expiresAt: Date;\n}\n\nexport interface PollDemandAndReserveParams {\n workspaceId: string;\n provisionerId: string;\n maxReservations: number;\n ttlSeconds: number;\n templates: ReservationTemplate[];\n capabilityWindowSeconds?: number;\n}\n\nexport interface InstallationPollDemandAndReserveParams {\n provisionerId: string;\n maxReservations: number;\n ttlSeconds: number;\n templates: ReservationTemplate[];\n capabilityWindowSeconds: number;\n eligibleWorkspaceIds: ReadonlySet<string>;\n signal?: AbortSignal;\n onReservations?: (reservations: ReservationGrant[]) => void;\n}\n\ninterface NormalizedTemplate {\n templateKey: string;\n labels: string[];\n remainingSlots: number;\n}\n\ninterface DemandRow {\n requiredLabels: string[];\n queued: number;\n oldestQueuedAt: Date;\n}\n\nexport async function pollDemandAndReserve(\n params: PollDemandAndReserveParams,\n): Promise<{stats: DemandStat[]; reservations: ReservationGrant[]}> {\n return await db().transaction(async (tx) => {\n return await pollDemandAndReserveTx(tx, params);\n });\n}\n\nexport async function pollDemandAndReserveTx(\n tx: Tx,\n params: PollDemandAndReserveParams,\n): Promise<{stats: DemandStat[]; reservations: ReservationGrant[]}> {\n await tx.execute(sql`select pg_advisory_xact_lock(hashtext(${params.workspaceId}))`);\n return await pollDemandAndReserveLockedTx(tx, params);\n}\n\nexport async function pollInstallationDemandAndReserve(\n params: InstallationPollDemandAndReserveParams,\n): Promise<{stats: DemandStat[]; reservations: ReservationGrant[]}> {\n const candidateWorkspaceIds = await listInstallationDemandWorkspaceIds(\n params.eligibleWorkspaceIds,\n );\n const results: Array<{stats: DemandStat[]; reservations: ReservationGrant[]}> = [];\n let remainingMaxReservations = params.maxReservations;\n const remainingTemplates = params.templates.map((template) => ({\n ...template,\n labels: [...canonicalizeLabels(template.labels)],\n remainingSlots: template.availableSlots,\n }));\n for (const workspaceId of candidateWorkspaceIds) {\n if (params.signal?.aborted || remainingMaxReservations === 0) break;\n const result = await db().transaction(async (tx) => {\n const lockResult = await tx.execute<{locked: boolean}>(\n sql`select pg_try_advisory_xact_lock(hashtext(${workspaceId})) as locked`,\n );\n const locked = lockResult.rows[0];\n if (!locked?.locked) return {stats: [], reservations: []};\n return await pollDemandAndReserveLockedTx(tx, {\n workspaceId,\n provisionerId: params.provisionerId,\n maxReservations: remainingMaxReservations,\n ttlSeconds: params.ttlSeconds,\n templates: remainingTemplates.map((template) => ({\n ...template,\n availableSlots: template.remainingSlots,\n })),\n capabilityWindowSeconds: params.capabilityWindowSeconds,\n });\n });\n results.push(result);\n consumeInstallationTemplateSlots(remainingTemplates, result.reservations);\n params.onReservations?.(result.reservations);\n remainingMaxReservations -= result.reservations.reduce(\n (total, reservation) => total + reservation.count,\n 0,\n );\n }\n return {\n stats: results.flatMap((result) => result.stats),\n reservations: results.flatMap((result) => result.reservations),\n };\n}\n\nfunction consumeInstallationTemplateSlots(\n templates: NormalizedTemplate[],\n reservations: ReservationGrant[],\n): void {\n for (const reservation of reservations) {\n const satisfyingTemplates = templates\n .filter((template) => isSubset(reservation.labels, template.labels))\n .sort(\n (a, b) => a.labels.length - b.labels.length || a.templateKey.localeCompare(b.templateKey),\n );\n drawSlots(satisfyingTemplates, reservation.count);\n }\n}\n\nasync function pollDemandAndReserveLockedTx(\n tx: Tx,\n params: PollDemandAndReserveParams,\n): Promise<{stats: DemandStat[]; reservations: ReservationGrant[]}> {\n let demandRows = (\n await tx\n .select({\n requiredLabels: pendingJobExecutions.requiredLabels,\n queued: sql<number>`count(*)::int`,\n oldestQueuedAt: sql<Date | string>`min(${pendingJobExecutions.createdAt})`,\n })\n .from(pendingJobExecutions)\n .where(eq(pendingJobExecutions.workspaceId, params.workspaceId))\n .groupBy(pendingJobExecutions.requiredLabels)\n ).map((row) => ({\n ...row,\n oldestQueuedAt: new Date(row.oldestQueuedAt),\n }));\n if (params.capabilityWindowSeconds !== undefined) {\n const capabilityLabels = await listActiveWorkspaceCapabilityLabelsTx(tx, {\n workspaceId: params.workspaceId,\n windowSeconds: params.capabilityWindowSeconds,\n });\n demandRows = demandRows.filter(\n (demand) => !capabilityLabels.some((labels) => isSubset(demand.requiredLabels, labels)),\n );\n }\n\n const activeReservationRows = await tx\n .select({\n requiredLabels: reservations.requiredLabels,\n reserved: sql<number>`coalesce(sum(${reservations.count}), 0)::int`,\n })\n .from(reservations)\n .where(\n and(eq(reservations.workspaceId, params.workspaceId), gt(reservations.expiresAt, sql`now()`)),\n )\n .groupBy(reservations.requiredLabels);\n\n const reservedByLabels = new Map(\n activeReservationRows.map((row) => [labelKey(row.requiredLabels), row.reserved]),\n );\n const activeProvisionerReservationRows = await tx\n .select({\n requiredLabels: reservations.requiredLabels,\n reserved: sql<number>`coalesce(sum(${reservations.count}), 0)::int`,\n })\n .from(reservations)\n .where(\n and(\n eq(reservations.workspaceId, params.workspaceId),\n eq(reservations.provisionerId, params.provisionerId),\n gt(reservations.expiresAt, sql`now()`),\n ),\n )\n .groupBy(reservations.requiredLabels);\n\n const templates = params.templates.map((template) => ({\n templateKey: template.templateKey,\n labels: [...canonicalizeLabels(template.labels)],\n remainingSlots: template.availableSlots,\n }));\n deductProvisionerReservations(templates, activeProvisionerReservationRows);\n const stats: DemandStat[] = [];\n const grants: ReservationGrant[] = [];\n let remainingMaxReservations = params.maxReservations;\n const workspaceIdField =\n params.capabilityWindowSeconds === undefined ? {} : {workspaceId: params.workspaceId};\n\n for (const demand of sortDemandRows(demandRows)) {\n const satisfyingTemplates = templates\n .filter((template) => isSubset(demand.requiredLabels, template.labels))\n .sort(\n (a, b) => a.labels.length - b.labels.length || a.templateKey.localeCompare(b.templateKey),\n );\n\n if (satisfyingTemplates.length === 0) continue;\n\n const reserved = reservedByLabels.get(labelKey(demand.requiredLabels)) ?? 0;\n const unreserved = Math.max(0, demand.queued - reserved);\n const capacity = satisfyingTemplates.reduce(\n (total, template) => total + template.remainingSlots,\n 0,\n );\n const grant = Math.min(unreserved, capacity, remainingMaxReservations);\n let reservedAfterGrant = reserved;\n\n if (grant > 0 && params.maxReservations > 0) {\n const [inserted] = await tx\n .insert(reservations)\n .values({\n workspaceId: params.workspaceId,\n provisionerId: params.provisionerId,\n requiredLabels: demand.requiredLabels,\n count: grant,\n expiresAt: sql`now() + (${params.ttlSeconds} || ' seconds')::interval`,\n })\n .returning({id: reservations.id, expiresAt: reservations.expiresAt});\n\n if (!inserted) throw new Error('Insert returned no rows');\n\n remainingMaxReservations -= grant;\n drawSlots(satisfyingTemplates, grant);\n await bindIdleRunnerInstancesTx(tx, {\n provisionerId: params.provisionerId,\n reservationId: inserted.id,\n requiredLabels: demand.requiredLabels,\n count: grant,\n workspaceId: params.workspaceId,\n });\n reservedAfterGrant += grant;\n grants.push({\n reservationId: inserted.id,\n ...workspaceIdField,\n labels: demand.requiredLabels,\n count: grant,\n expiresAt: inserted.expiresAt,\n });\n }\n\n stats.push({\n ...workspaceIdField,\n labels: demand.requiredLabels,\n queued: demand.queued,\n reserved: reservedAfterGrant,\n oldestQueuedAt: demand.oldestQueuedAt,\n });\n }\n\n return {stats, reservations: grants};\n}\n\nasync function bindIdleRunnerInstancesTx(\n tx: Tx,\n params: {\n provisionerId: string;\n reservationId: string;\n workspaceId: string;\n requiredLabels: string[];\n count: number;\n },\n): Promise<void> {\n const idleRunners = await tx\n .select({id: providerRunners.id})\n .from(providerRunners)\n .where(\n and(\n eq(providerRunners.provisionerId, params.provisionerId),\n isNull(providerRunners.workspaceId),\n isNull(providerRunners.reservationId),\n isNull(providerRunners.intendedReservationId),\n isNull(providerRunners.runnerSessionId),\n isNotNull(providerRunners.providerRunnerId),\n eq(providerRunners.state, 'running'),\n arrayContains(providerRunners.labels, params.requiredLabels),\n exists(\n tx\n .select({id: runnerControlSessions.id})\n .from(runnerControlSessions)\n .where(\n and(\n eq(runnerControlSessions.runnerInstanceId, providerRunners.id),\n eq(runnerControlSessions.provisionerId, params.provisionerId),\n isNull(runnerControlSessions.closedAt),\n gt(runnerControlSessions.expiresAt, sql`now()`),\n ),\n ),\n ),\n ),\n )\n .orderBy(asc(providerRunners.createdAt), asc(providerRunners.id))\n .limit(params.count)\n .for('update');\n\n if (idleRunners.length === 0) return;\n\n await tx\n .update(providerRunners)\n .set({\n workspaceId: params.workspaceId,\n reservationId: params.reservationId,\n assignedAt: sql`now()`,\n updatedAt: sql`now()`,\n })\n .where(\n and(\n inArray(\n providerRunners.id,\n idleRunners.map((runner) => runner.id),\n ),\n isNull(providerRunners.workspaceId),\n isNull(providerRunners.reservationId),\n isNull(providerRunners.runnerSessionId),\n ),\n );\n}\n\nasync function listInstallationDemandWorkspaceIds(eligibleWorkspaceIds: ReadonlySet<string>) {\n if (eligibleWorkspaceIds.size === 0) return [];\n const rows = await db()\n .select({\n workspaceId: pendingJobExecutions.workspaceId,\n oldestQueuedAt: sql<Date>`min(${pendingJobExecutions.createdAt})`,\n })\n .from(pendingJobExecutions)\n .where(inArray(pendingJobExecutions.workspaceId, [...eligibleWorkspaceIds]))\n .groupBy(pendingJobExecutions.workspaceId)\n .orderBy(\n asc(sql`min(${pendingJobExecutions.createdAt})`),\n asc(pendingJobExecutions.workspaceId),\n );\n return rows.map((row) => row.workspaceId);\n}\n\nexport async function listQueuedDemandWorkspaceIds(): Promise<string[]> {\n const rows = await db()\n .select({workspaceId: pendingJobExecutions.workspaceId})\n .from(pendingJobExecutions)\n .groupBy(pendingJobExecutions.workspaceId);\n return rows.map((row) => row.workspaceId);\n}\n\nasync function listActiveWorkspaceCapabilityLabelsTx(\n tx: Tx,\n params: {workspaceId: string; windowSeconds: number},\n): Promise<string[][]> {\n const rows = await tx\n .select({labels: provisionerCapabilitySnapshots.labels})\n .from(provisionerCapabilitySnapshots)\n .innerJoin(\n provisionerTokens,\n eq(provisionerTokens.id, provisionerCapabilitySnapshots.provisionerId),\n )\n .where(\n and(\n eq(provisionerCapabilitySnapshots.workspaceId, params.workspaceId),\n eq(provisionerTokens.workspaceId, params.workspaceId),\n eq(provisionerTokens.scope, 'workspace'),\n sql`${provisionerCapabilitySnapshots.advertisedAt} > now() - (${params.windowSeconds} || ' seconds')::interval`,\n sql`${provisionerTokens.revokedAt} is null`,\n sql`(${provisionerTokens.expiresAt} is null or ${provisionerTokens.expiresAt} > now())`,\n ),\n );\n return rows.map((row) => row.labels);\n}\n\nexport async function deleteExpiredReservations(params?: {limit?: number}): Promise<number> {\n return await db().transaction(async (tx) => {\n const expiredRows = await tx\n .select({id: reservations.id})\n .from(reservations)\n .where(lt(reservations.expiresAt, sql`now()`))\n .orderBy(asc(reservations.expiresAt))\n .limit(params?.limit ?? 1000);\n return await deleteReservationsWithCleanupTx(\n tx,\n expiredRows.map((reservation) => reservation.id),\n {expiredOnly: true},\n );\n });\n}\n\nexport async function deleteReservationsByIds(ids: string[]): Promise<number> {\n if (ids.length === 0) return 0;\n\n return await db().transaction(async (tx) => {\n return await deleteReservationsWithCleanupTx(tx, ids);\n });\n}\n\nasync function deleteReservationsWithCleanupTx(\n tx: Tx,\n ids: string[],\n params: {expiredOnly?: boolean} = {},\n): Promise<number> {\n if (ids.length === 0) return 0;\n\n const assignedRunners = await tx\n .select({id: providerRunners.id, reservationId: providerRunners.reservationId})\n .from(providerRunners)\n .where(\n and(\n inArray(providerRunners.reservationId, ids),\n isNull(providerRunners.runnerSessionId),\n isNull(providerRunners.reservationReleasedAt),\n ),\n )\n .for('update');\n const reservationRows = await tx\n .select({id: reservations.id})\n .from(reservations)\n .where(\n and(\n inArray(reservations.id, ids),\n params.expiredOnly ? lt(reservations.expiresAt, sql`now()`) : undefined,\n ),\n )\n .for('update');\n const reservationIds = reservationRows.map((reservation) => reservation.id);\n\n if (reservationIds.length === 0) return 0;\n\n const assignedRunnerIds = assignedRunners\n .filter((runner) => runner.reservationId && reservationIds.includes(runner.reservationId))\n .map((runner) => runner.id);\n if (assignedRunnerIds.length > 0) {\n await tx\n .update(runnerActivationTokens)\n .set({revokedAt: sql`now()`})\n .where(\n and(\n inArray(runnerActivationTokens.runnerInstanceId, assignedRunnerIds),\n isNull(runnerActivationTokens.consumedAt),\n isNull(runnerActivationTokens.revokedAt),\n ),\n );\n await tx\n .update(providerRunners)\n .set({\n workspaceId: null,\n reservationId: null,\n assignedAt: null,\n updatedAt: sql`now()`,\n })\n .where(\n and(\n inArray(providerRunners.id, assignedRunnerIds),\n isNull(providerRunners.runnerSessionId),\n isNull(providerRunners.reservationReleasedAt),\n ),\n );\n }\n\n const deleted = await tx\n .delete(reservations)\n .where(inArray(reservations.id, reservationIds))\n .returning({id: reservations.id});\n\n return deleted.length;\n}\n\nexport async function releaseReservationUnits(\n tx: Tx,\n params: {\n workspaceId: string;\n provisionerId: string;\n releases: Array<{reservationId: string; count: number}>;\n },\n): Promise<number> {\n let released = 0;\n\n for (const release of params.releases) {\n if (release.count <= 0) continue;\n\n const decremented = await tx\n .update(reservations)\n .set({count: sql`${reservations.count} - ${release.count}`})\n .where(\n and(\n eq(reservations.id, release.reservationId),\n eq(reservations.workspaceId, params.workspaceId),\n eq(reservations.provisionerId, params.provisionerId),\n gt(reservations.count, release.count),\n gt(reservations.expiresAt, sql`now()`),\n ),\n )\n .returning({id: reservations.id});\n\n if (decremented.length > 0) {\n released += release.count;\n continue;\n }\n\n const deleted = await tx\n .delete(reservations)\n .where(\n and(\n eq(reservations.id, release.reservationId),\n eq(reservations.workspaceId, params.workspaceId),\n eq(reservations.provisionerId, params.provisionerId),\n sql`${reservations.count} <= ${release.count}`,\n gt(reservations.expiresAt, sql`now()`),\n ),\n )\n .returning({count: reservations.count});\n\n released += deleted.reduce((total, row) => total + row.count, 0);\n }\n\n return released;\n}\n\nfunction deductProvisionerReservations(\n templates: NormalizedTemplate[],\n activeReservations: {requiredLabels: string[]; reserved: number}[],\n): void {\n for (const reservation of sortReservationRows(activeReservations)) {\n const satisfyingTemplates = templates\n .filter((template) => isSubset(reservation.requiredLabels, template.labels))\n .sort(\n (a, b) => a.labels.length - b.labels.length || a.templateKey.localeCompare(b.templateKey),\n );\n drawSlots(satisfyingTemplates, reservation.reserved);\n }\n}\n\nfunction sortDemandRows(rows: DemandRow[]): DemandRow[] {\n return [...rows].sort((a, b) => {\n const specificity = b.requiredLabels.length - a.requiredLabels.length;\n if (specificity !== 0) return specificity;\n return a.oldestQueuedAt.getTime() - b.oldestQueuedAt.getTime();\n });\n}\n\nfunction sortReservationRows<T extends {requiredLabels: string[]}>(rows: T[]): T[] {\n return [...rows].sort((a, b) => {\n const specificity = b.requiredLabels.length - a.requiredLabels.length;\n if (specificity !== 0) return specificity;\n return labelKey(a.requiredLabels).localeCompare(labelKey(b.requiredLabels));\n });\n}\n\nfunction isSubset(requiredLabels: string[], availableLabels: string[]): boolean {\n return requiredLabels.every((label) => availableLabels.includes(label));\n}\n\nfunction drawSlots(templates: NormalizedTemplate[], count: number): void {\n let remaining = count;\n for (const template of templates) {\n if (remaining === 0) return;\n const used = Math.min(template.remainingSlots, remaining);\n template.remainingSlots -= used;\n remaining -= used;\n }\n}\n\nfunction labelKey(labels: string[]): string {\n return JSON.stringify(labels);\n}\n"],"names":["canonicalizeLabels","and","arrayContains","asc","eq","exists","gt","inArray","isNotNull","isNull","lt","sql","db","pendingJobExecutions","provisionerCapabilitySnapshots","provisionerTokens","reservations","runnerActivationTokens","runnerControlSessions","providerRunners","pollDemandAndReserve","params","transaction","tx","pollDemandAndReserveTx","execute","workspaceId","pollDemandAndReserveLockedTx","pollInstallationDemandAndReserve","candidateWorkspaceIds","listInstallationDemandWorkspaceIds","eligibleWorkspaceIds","results","remainingMaxReservations","maxReservations","remainingTemplates","templates","map","template","labels","remainingSlots","availableSlots","signal","aborted","result","lockResult","locked","rows","stats","provisionerId","ttlSeconds","capabilityWindowSeconds","push","consumeInstallationTemplateSlots","onReservations","reduce","total","reservation","count","flatMap","satisfyingTemplates","filter","isSubset","sort","a","b","length","templateKey","localeCompare","drawSlots","demandRows","select","requiredLabels","queued","oldestQueuedAt","createdAt","from","where","groupBy","row","Date","undefined","capabilityLabels","listActiveWorkspaceCapabilityLabelsTx","windowSeconds","demand","some","activeReservationRows","reserved","expiresAt","reservedByLabels","Map","labelKey","activeProvisionerReservationRows","deductProvisionerReservations","grants","workspaceIdField","sortDemandRows","get","unreserved","Math","max","capacity","grant","min","reservedAfterGrant","inserted","insert","values","returning","id","Error","bindIdleRunnerInstancesTx","reservationId","idleRunners","intendedReservationId","runnerSessionId","providerRunnerId","state","runnerInstanceId","closedAt","orderBy","limit","for","update","set","assignedAt","updatedAt","runner","size","listQueuedDemandWorkspaceIds","innerJoin","scope","advertisedAt","revokedAt","deleteExpiredReservations","expiredRows","deleteReservationsWithCleanupTx","expiredOnly","deleteReservationsByIds","ids","assignedRunners","reservationReleasedAt","reservationRows","reservationIds","assignedRunnerIds","includes","consumedAt","deleted","delete","releaseReservationUnits","released","release","releases","decremented","activeReservations","sortReservationRows","specificity","getTime","availableLabels","every","label","remaining","used","JSON","stringify"],"mappings":"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"}