@intx/db 0.2.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/README.md +13 -2
  2. package/dist/approval-store.d.ts +59 -0
  3. package/dist/approval-store.js +92 -0
  4. package/dist/asset-resolution.d.ts +2 -2
  5. package/dist/backfill-principal-keys.d.ts +9 -0
  6. package/dist/backfill-principal-keys.js +51 -0
  7. package/dist/client.d.ts +37 -2
  8. package/dist/client.js +6 -0
  9. package/dist/config.d.ts +1 -0
  10. package/dist/config.js +2 -0
  11. package/dist/connection.js +12 -8
  12. package/dist/credential-resolution.d.ts +181 -20
  13. package/dist/credential-resolution.js +251 -3
  14. package/dist/grant-store.js +35 -13
  15. package/dist/index.d.ts +20 -5
  16. package/dist/index.js +19 -5
  17. package/dist/model-source-resolution.d.ts +54 -7
  18. package/dist/model-source-resolution.js +177 -22
  19. package/dist/parse-row.d.ts +245 -34
  20. package/dist/parse-row.js +117 -18
  21. package/dist/principal-key-store.d.ts +51 -0
  22. package/dist/principal-key-store.js +82 -0
  23. package/dist/principal-store.d.ts +44 -0
  24. package/dist/principal-store.js +67 -0
  25. package/dist/rekey-credential-secrets.d.ts +13 -0
  26. package/dist/rekey-credential-secrets.js +61 -0
  27. package/dist/schema/approvals.d.ts +245 -0
  28. package/dist/schema/approvals.js +57 -0
  29. package/dist/schema/catalog.d.ts +17 -0
  30. package/dist/schema/catalog.js +7 -1
  31. package/dist/schema/credentials.d.ts +1 -1
  32. package/dist/schema/git-tokens.js +1 -1
  33. package/dist/schema/grants.js +8 -2
  34. package/dist/schema/index.d.ts +10 -4
  35. package/dist/schema/index.js +10 -4
  36. package/dist/schema/messages.d.ts +5 -5
  37. package/dist/schema/messages.js +20 -9
  38. package/dist/schema/offerings.js +8 -2
  39. package/dist/schema/{workflow-deployments.d.ts → principal-keys.d.ts} +29 -32
  40. package/dist/schema/principal-keys.js +28 -0
  41. package/dist/schema/principals.d.ts +2 -2
  42. package/dist/schema/principals.js +7 -5
  43. package/dist/schema/providers.d.ts +17 -0
  44. package/dist/schema/providers.js +5 -0
  45. package/dist/schema/roles.js +6 -2
  46. package/dist/schema/session-assets.d.ts +1 -38
  47. package/dist/schema/session-assets.js +36 -19
  48. package/dist/schema/sessions.js +7 -2
  49. package/dist/schema/sidecar-allocation.d.ts +385 -0
  50. package/dist/schema/sidecar-allocation.js +53 -0
  51. package/dist/schema/sidecar.d.ts +2 -2
  52. package/dist/schema/sidecar.js +3 -1
  53. package/dist/schema/signal-correlations.d.ts +177 -0
  54. package/dist/schema/signal-correlations.js +31 -0
  55. package/dist/schema/tenants.js +13 -3
  56. package/dist/schema/wallets.d.ts +3 -3
  57. package/dist/schema/wallets.js +4 -2
  58. package/dist/schema/{agents.d.ts → workflow-definitions.d.ts} +79 -113
  59. package/dist/schema/workflow-definitions.js +97 -0
  60. package/dist/schema/workflow-probe.d.ts +358 -0
  61. package/dist/schema/workflow-probe.js +51 -0
  62. package/dist/schema/workflow-run-dispatch.d.ts +345 -0
  63. package/dist/schema/workflow-run-dispatch.js +63 -0
  64. package/dist/schema/{agent-assets.d.ts → workflow-run-execution.d.ts} +44 -27
  65. package/dist/schema/workflow-run-execution.js +48 -0
  66. package/dist/schema/workflow-run-launch-spec.d.ts +200 -0
  67. package/dist/schema/workflow-run-launch-spec.js +26 -0
  68. package/dist/schema/{instances.d.ts → workflow-run.d.ts} +61 -66
  69. package/dist/schema/workflow-run.js +110 -0
  70. package/dist/sender-key-resolver.d.ts +91 -0
  71. package/dist/sender-key-resolver.js +184 -0
  72. package/dist/sidecar-allocation-store.d.ts +206 -0
  73. package/dist/sidecar-allocation-store.js +714 -0
  74. package/dist/signal-correlation-store.d.ts +36 -0
  75. package/dist/signal-correlation-store.js +63 -0
  76. package/dist/signer-identity.d.ts +12 -0
  77. package/dist/signer-identity.js +15 -0
  78. package/dist/tenant-hierarchy.d.ts +4 -1
  79. package/dist/tenant-hierarchy.js +37 -5
  80. package/dist/workflow-definition-store.d.ts +55 -0
  81. package/dist/workflow-definition-store.js +93 -0
  82. package/dist/workflow-probe-store.d.ts +47 -0
  83. package/dist/workflow-probe-store.js +124 -0
  84. package/dist/workflow-run-dispatch-store.d.ts +83 -0
  85. package/dist/workflow-run-dispatch-store.js +308 -0
  86. package/dist/workflow-run-launch-spec-store.d.ts +13 -0
  87. package/dist/workflow-run-launch-spec-store.js +30 -0
  88. package/dist/workflow-run-store.d.ts +54 -0
  89. package/dist/workflow-run-store.js +85 -0
  90. package/migrations/0037_credential_use_backfill.sql +39 -0
  91. package/migrations/0038_chilly_blacklash.sql +37 -0
  92. package/migrations/0039_quick_carnage.sql +2 -0
  93. package/migrations/0040_reshape_approval_origin.sql +24 -0
  94. package/migrations/0041_make_approval_timeout_at_nullable.sql +1 -0
  95. package/migrations/0042_youthful_mantis.sql +2 -0
  96. package/migrations/0043_signal_correlation_deployment_fk.sql +1 -0
  97. package/migrations/0044_model_offering_quirks.sql +1 -0
  98. package/migrations/0045_create_workflow_run.sql +13 -0
  99. package/migrations/0046_approval_signal_correlation_run_fk.sql +2 -0
  100. package/migrations/0047_create_workflow_definition.sql +30 -0
  101. package/migrations/0048_workflow_definition_origin_agent_id.sql +2 -0
  102. package/migrations/0049_curly_omega_flight.sql +2 -0
  103. package/migrations/0050_late_crystal.sql +10 -0
  104. package/migrations/0051_funny_madelyne_pryor.sql +1 -0
  105. package/migrations/0052_drop_inference_turn_instance_fk.sql +1 -0
  106. package/migrations/0053_session_mail_session_id_index.sql +1 -0
  107. package/migrations/0054_rekey_instance_grants_to_workflow_run.sql +10 -0
  108. package/migrations/0055_backfill_anchor_workflow_runs.sql +69 -0
  109. package/migrations/0056_repoint_deployment_fks_to_anchor_run.sql +32 -0
  110. package/migrations/0057_drop_workflow_deployment_projection.sql +22 -0
  111. package/migrations/0058_repoint_offering_fk_to_definition.sql +41 -0
  112. package/migrations/0059_repoint_transaction_fk_to_run.sql +10 -0
  113. package/migrations/0060_drop_session_mail_instance_fk.sql +6 -0
  114. package/migrations/0061_drop_session_asset_instance_fk.sql +7 -0
  115. package/migrations/0062_drop_agent_asset_table.sql +11 -0
  116. package/migrations/0063_repoint_agent_role_fk_to_definition.sql +39 -0
  117. package/migrations/0064_repoint_agent_session_fk_to_definition.sql +47 -0
  118. package/migrations/0065_add_workflow_definition_model_requirements.sql +37 -0
  119. package/migrations/0066_rekey_agent_definition_principals_to_workflow.sql +27 -0
  120. package/migrations/0067_add_workflow_definition_kind.sql +10 -0
  121. package/migrations/0068_drop_agent_tables_and_origin_agent_id.sql +31 -0
  122. package/migrations/0069_drop_workflow_definition_kind.sql +10 -0
  123. package/migrations/0070_flashy_proteus.sql +13 -0
  124. package/migrations/0072_add_workflow_definition_credential_bindings.sql +1 -0
  125. package/migrations/0073_grant_target_exactly_one_check.sql +13 -0
  126. package/migrations/0074_add_provider_api_base_url.sql +1 -0
  127. package/migrations/0075_workflow_run_launch_spec.sql +17 -0
  128. package/migrations/0076_sidecar_allocation.sql +40 -0
  129. package/migrations/0077_workflow_run_dispatch.sql +29 -0
  130. package/migrations/0078_workflow_run_dispatch_kind.sql +2 -0
  131. package/migrations/0079_rename_workflow_run_deployment_id_to_anchor_run_id.sql +4 -0
  132. package/migrations/0080_rename_correlation_approval_deployment_id_to_anchor_run_id.sql +9 -0
  133. package/migrations/0081_workflow_definition_content_hash_and_approved_wire_hash.sql +4 -0
  134. package/migrations/0082_blue_black_queen.sql +1 -0
  135. package/migrations/0083_replace_launch_spec_snapshot_with_frozen_bundle.sql +3 -0
  136. package/migrations/0084_delete_orphaned_credential_grants.sql +22 -0
  137. package/migrations/0085_add_approval_run_idx.sql +1 -0
  138. package/migrations/0086_cool_human_cannonball.sql +1 -0
  139. package/migrations/0087_drop_sidecar_placement.sql +3 -0
  140. package/migrations/0088_thick_sprite.sql +32 -0
  141. package/migrations/0089_tense_selene.sql +13 -0
  142. package/migrations/0090_tenant_domain_lower_unique.sql +2 -0
  143. package/migrations/0091_workflow_run_dispatch_sender_address.sql +21 -0
  144. package/migrations/0092_sidecar_destroy_failed.sql +4 -0
  145. package/migrations/0093_sidecar_initialization.sql +2 -0
  146. package/migrations/meta/0037_snapshot.json +3237 -0
  147. package/migrations/meta/0038_snapshot.json +3470 -0
  148. package/migrations/meta/0039_snapshot.json +3470 -0
  149. package/migrations/meta/0040_snapshot.json +3489 -0
  150. package/migrations/meta/0041_snapshot.json +3489 -0
  151. package/migrations/meta/0042_snapshot.json +3489 -0
  152. package/migrations/meta/0043_snapshot.json +3498 -0
  153. package/migrations/meta/0044_snapshot.json +3504 -0
  154. package/migrations/meta/0045_snapshot.json +3589 -0
  155. package/migrations/meta/0046_snapshot.json +3607 -0
  156. package/migrations/meta/0047_snapshot.json +3832 -0
  157. package/migrations/meta/0048_snapshot.json +3853 -0
  158. package/migrations/meta/0049_snapshot.json +3854 -0
  159. package/migrations/meta/0050_snapshot.json +3924 -0
  160. package/migrations/meta/0051_snapshot.json +3940 -0
  161. package/migrations/meta/0052_snapshot.json +3931 -0
  162. package/migrations/meta/0053_snapshot.json +3952 -0
  163. package/migrations/meta/0054_snapshot.json +3952 -0
  164. package/migrations/meta/0055_snapshot.json +3952 -0
  165. package/migrations/meta/0056_snapshot.json +3952 -0
  166. package/migrations/meta/0057_snapshot.json +3839 -0
  167. package/migrations/meta/0058_snapshot.json +3839 -0
  168. package/migrations/meta/0059_snapshot.json +3839 -0
  169. package/migrations/meta/0060_snapshot.json +3830 -0
  170. package/migrations/meta/0061_snapshot.json +3821 -0
  171. package/migrations/meta/0062_snapshot.json +3723 -0
  172. package/migrations/meta/0063_snapshot.json +3723 -0
  173. package/migrations/meta/0064_snapshot.json +3723 -0
  174. package/migrations/meta/0065_snapshot.json +3729 -0
  175. package/migrations/meta/0066_snapshot.json +3729 -0
  176. package/migrations/meta/0067_snapshot.json +3736 -0
  177. package/migrations/meta/0068_snapshot.json +3347 -0
  178. package/migrations/meta/0069_snapshot.json +3340 -0
  179. package/migrations/meta/0070_snapshot.json +3444 -0
  180. package/migrations/meta/0072_snapshot.json +3450 -0
  181. package/migrations/meta/0073_snapshot.json +3455 -0
  182. package/migrations/meta/0074_snapshot.json +3461 -0
  183. package/migrations/meta/0075_snapshot.json +3567 -0
  184. package/migrations/meta/0076_snapshot.json +3851 -0
  185. package/migrations/meta/0077_snapshot.json +4067 -0
  186. package/migrations/meta/0078_snapshot.json +4078 -0
  187. package/migrations/meta/0079_snapshot.json +4078 -0
  188. package/migrations/meta/0080_snapshot.json +4078 -0
  189. package/migrations/meta/0081_snapshot.json +4096 -0
  190. package/migrations/meta/0082_snapshot.json +4102 -0
  191. package/migrations/meta/0083_snapshot.json +4096 -0
  192. package/migrations/meta/0084_snapshot.json +4096 -0
  193. package/migrations/meta/0085_snapshot.json +4111 -0
  194. package/migrations/meta/0086_snapshot.json +4117 -0
  195. package/migrations/meta/0087_snapshot.json +4100 -0
  196. package/migrations/meta/0088_snapshot.json +4286 -0
  197. package/migrations/meta/0089_snapshot.json +4376 -0
  198. package/migrations/meta/0090_snapshot.json +4387 -0
  199. package/migrations/meta/0091_snapshot.json +4397 -0
  200. package/migrations/meta/0092_snapshot.json +4397 -0
  201. package/migrations/meta/0093_snapshot.json +4407 -0
  202. package/migrations/meta/_journal.json +392 -0
  203. package/package.json +7 -3
  204. package/dist/schema/agent-assets.js +0 -15
  205. package/dist/schema/agents.js +0 -57
  206. package/dist/schema/instances.js +0 -39
  207. package/dist/schema/workflow-deployments.js +0 -37
@@ -0,0 +1,714 @@
1
+ import { type } from "arktype";
2
+ import { and, asc, eq, gt, inArray, isNull, lte, notInArray, or, sql, } from "drizzle-orm";
3
+ import { sidecarAllocationStatuses, } from "@intx/types";
4
+ import { createWorkflowRunDispatchStore } from "./workflow-run-dispatch-store.js";
5
+ import { isLiveWorkflowRunStatus, liveWorkflowRunStatuses, principal, sidecar, sidecarAllocation, workflowRun, workflowRunLaunchSpec, } from "./schema/index.js";
6
+ const SidecarAllocationStatusValidator = type.enumerated(...sidecarAllocationStatuses);
7
+ const SidecarProvisionerApiVersion = type("1");
8
+ const activeStatuses = [
9
+ "pending",
10
+ "provisioning",
11
+ "allocated",
12
+ "replacing",
13
+ "releasing",
14
+ ];
15
+ function parseSidecarAllocationRow(row) {
16
+ return {
17
+ id: row.id,
18
+ anchorRunId: row.anchorRunId,
19
+ tenantId: row.tenantId,
20
+ provisionerId: row.provisionerId,
21
+ provisionerApiVersion: SidecarProvisionerApiVersion.assert(row.provisionerApiVersion),
22
+ provisionerBindingFingerprint: row.provisionerBindingFingerprint,
23
+ ...(row.sidecarId !== null ? { sidecarId: row.sidecarId } : {}),
24
+ status: SidecarAllocationStatusValidator.assert(row.status),
25
+ generation: row.generation,
26
+ ...(row.ensureAcceptedGeneration !== null
27
+ ? { ensureAcceptedGeneration: row.ensureAcceptedGeneration }
28
+ : {}),
29
+ ...(row.externalRef !== null ? { externalRef: row.externalRef } : {}),
30
+ ...(row.nextAttemptAt !== null ? { nextAttemptAt: row.nextAttemptAt } : {}),
31
+ ...(row.reconciliationLeaseId !== null
32
+ ? { reconciliationLeaseId: row.reconciliationLeaseId }
33
+ : {}),
34
+ ...(row.reconciliationLeaseExpiresAt !== null
35
+ ? { reconciliationLeaseExpiresAt: row.reconciliationLeaseExpiresAt }
36
+ : {}),
37
+ ...(row.initializationLeaseId !== null
38
+ ? { initializationLeaseId: row.initializationLeaseId }
39
+ : {}),
40
+ ensureAttempts: row.ensureAttempts,
41
+ destroyAttempts: row.destroyAttempts,
42
+ ...(row.connectDeadline !== null
43
+ ? { connectDeadline: row.connectDeadline }
44
+ : {}),
45
+ ...(row.failureCode !== null ? { failureCode: row.failureCode } : {}),
46
+ ...(row.failureMessage !== null
47
+ ? { failureMessage: row.failureMessage }
48
+ : {}),
49
+ createdAt: row.createdAt,
50
+ updatedAt: row.updatedAt,
51
+ };
52
+ }
53
+ function databaseTimestamp(override) {
54
+ return override ?? sql `now()`;
55
+ }
56
+ function leaseCondition(expectedLeaseId) {
57
+ return expectedLeaseId === undefined
58
+ ? []
59
+ : [
60
+ eq(sidecarAllocation.reconciliationLeaseId, expectedLeaseId),
61
+ gt(sidecarAllocation.reconciliationLeaseExpiresAt, sql `clock_timestamp()`),
62
+ ];
63
+ }
64
+ export function createSidecarAllocationStore(db) {
65
+ const workflowRunDispatchStore = createWorkflowRunDispatchStore(db);
66
+ function initializationConditions(args, expectedMarker, { requireCurrentLease = true } = {}) {
67
+ return and(eq(sidecarAllocation.id, args.allocationId), eq(sidecarAllocation.anchorRunId, args.anchorRunId), eq(sidecarAllocation.tenantId, args.tenantId), eq(sidecarAllocation.status, "allocated"), eq(sidecarAllocation.generation, args.generation), eq(sidecarAllocation.ensureAcceptedGeneration, args.generation), ...(requireCurrentLease ? leaseCondition(args.leaseId) : []), expectedMarker === null
68
+ ? isNull(sidecarAllocation.initializationLeaseId)
69
+ : eq(sidecarAllocation.initializationLeaseId, expectedMarker));
70
+ }
71
+ async function writeInitialization(args, completion) {
72
+ args.signal.throwIfAborted();
73
+ return db.transaction(async (tx) => {
74
+ const condition = initializationConditions(args, completion === undefined ? null : args.leaseId);
75
+ const [allocation] = await tx
76
+ .select({ id: sidecarAllocation.id })
77
+ .from(sidecarAllocation)
78
+ .where(condition)
79
+ .for("update");
80
+ args.signal.throwIfAborted();
81
+ if (allocation === undefined)
82
+ return null;
83
+ let previousPublicKey = null;
84
+ if (completion === undefined) {
85
+ const [previous] = await tx
86
+ .select({ publicKey: workflowRun.publicKey })
87
+ .from(workflowRun)
88
+ .where(and(eq(workflowRun.id, args.anchorRunId), eq(workflowRun.anchorRunId, args.anchorRunId), eq(workflowRun.tenantId, args.tenantId)))
89
+ .for("update");
90
+ if (previous === undefined)
91
+ throw new Error("Initialization anchor is missing");
92
+ previousPublicKey = previous.publicKey;
93
+ }
94
+ const [anchor] = await tx
95
+ .update(workflowRun)
96
+ .set({
97
+ publicKey: completion?.publicKey ?? null,
98
+ ...(completion?.credentialRefs !== undefined
99
+ ? { credentialRefs: completion.credentialRefs }
100
+ : {}),
101
+ })
102
+ .where(and(eq(workflowRun.id, args.anchorRunId), eq(workflowRun.anchorRunId, args.anchorRunId), eq(workflowRun.tenantId, args.tenantId)))
103
+ .returning({ id: workflowRun.id });
104
+ if (anchor === undefined)
105
+ throw new Error("Initialization anchor is missing");
106
+ // Check the lease again after waiting on the anchor row. Both writes roll
107
+ // back if ownership expired while the transaction held the allocation lock.
108
+ const [updated] = await tx
109
+ .update(sidecarAllocation)
110
+ .set({
111
+ initializationLeaseId: completion === undefined ? args.leaseId : null,
112
+ })
113
+ .where(condition)
114
+ .returning({ id: sidecarAllocation.id });
115
+ args.signal.throwIfAborted();
116
+ if (updated === undefined)
117
+ throw new Error("Initialization lease expired");
118
+ return { previousPublicKey };
119
+ });
120
+ }
121
+ async function initializationCompleted(tx, args) {
122
+ const [allocation] = await tx
123
+ .select({
124
+ anchorRunId: sidecarAllocation.anchorRunId,
125
+ initializationLeaseId: sidecarAllocation.initializationLeaseId,
126
+ })
127
+ .from(sidecarAllocation)
128
+ .where(and(eq(sidecarAllocation.id, args.allocationId), eq(sidecarAllocation.generation, args.expectedGeneration), ...leaseCondition(args.expectedLeaseId)))
129
+ .for("update");
130
+ if (allocation === undefined || allocation.initializationLeaseId !== null)
131
+ return false;
132
+ // Read the key after acquiring the allocation lock, so a completion that
133
+ // committed while we waited is visible even if its response was lost.
134
+ const [anchor] = await tx
135
+ .select({ publicKey: workflowRun.publicKey })
136
+ .from(workflowRun)
137
+ .where(eq(workflowRun.id, allocation.anchorRunId));
138
+ return anchor !== undefined && anchor.publicKey !== null;
139
+ }
140
+ async function insertSidecarIdentity(tx, args) {
141
+ await tx.insert(sidecar).values({
142
+ id: args.sidecarId,
143
+ url: null,
144
+ tokenHashSha256: args.tokenHashSha256,
145
+ status: "offline",
146
+ createdAt: args.now,
147
+ updatedAt: args.now,
148
+ });
149
+ }
150
+ async function failRunningRuns(tx, anchorRunId, now) {
151
+ // Fail every live run anchored here -- both "running" runs and a "deployed"
152
+ // anchor torn down before its first trigger -- so a release settles them.
153
+ const failedRuns = await tx
154
+ .update(workflowRun)
155
+ .set({ status: "failed", endedAt: now })
156
+ .where(and(eq(workflowRun.anchorRunId, anchorRunId), inArray(workflowRun.status, [...liveWorkflowRunStatuses])))
157
+ .returning({ principalId: workflowRun.principalId });
158
+ const principalIds = failedRuns.flatMap(({ principalId }) => principalId === null ? [] : [principalId]);
159
+ if (principalIds.length > 0) {
160
+ await tx
161
+ .update(principal)
162
+ .set({ status: "deactivated", updatedAt: now })
163
+ .where(inArray(principal.id, principalIds));
164
+ }
165
+ }
166
+ async function beginRelease(args, tx) {
167
+ const now = databaseTimestamp(args.now);
168
+ const [updated] = await (tx ?? db)
169
+ .update(sidecarAllocation)
170
+ .set({
171
+ status: "releasing",
172
+ generation: args.expectedGeneration + 1,
173
+ initializationLeaseId: null,
174
+ nextAttemptAt: now,
175
+ reconciliationLeaseId: null,
176
+ reconciliationLeaseExpiresAt: null,
177
+ connectDeadline: null,
178
+ ...(args.failureCode !== undefined
179
+ ? { failureCode: args.failureCode }
180
+ : {}),
181
+ ...(args.failureMessage !== undefined
182
+ ? { failureMessage: args.failureMessage }
183
+ : {}),
184
+ updatedAt: now,
185
+ })
186
+ .where(and(eq(sidecarAllocation.id, args.allocationId), eq(sidecarAllocation.status, args.expectedStatus), eq(sidecarAllocation.generation, args.expectedGeneration), ...leaseCondition(args.expectedLeaseId), ...(args.expectedInitializationLeaseId !== undefined
187
+ ? [
188
+ eq(sidecarAllocation.initializationLeaseId, args.expectedInitializationLeaseId),
189
+ ]
190
+ : [])))
191
+ .returning();
192
+ return updated === undefined ? null : parseSidecarAllocationRow(updated);
193
+ }
194
+ return {
195
+ beginInitialization(args) {
196
+ return writeInitialization(args);
197
+ },
198
+ async completeInitialization(args) {
199
+ return (await writeInitialization(args, args)) !== null;
200
+ },
201
+ async clearUnsentInitialization(args) {
202
+ // No signal gate: the sole caller invokes this only for a proven-unsent
203
+ // frame, which is reachable precisely when the attempt was cancelled.
204
+ // The WHERE clause is the guard. Marker equality proves no newer
205
+ // attempt began, and generation/status equality proves the allocation
206
+ // did not move on. The reconciliation lease is deliberately not
207
+ // required: a disconnect nulls the lease while leaving this attempt's
208
+ // marker behind, and only this attempt's own clear may remove it. Restore
209
+ // its previous key in the same transaction so absence of the marker cannot
210
+ // expose a live workflow as keyless or certify a different attempt.
211
+ return db.transaction(async (tx) => {
212
+ const condition = initializationConditions(args, args.leaseId, {
213
+ requireCurrentLease: false,
214
+ });
215
+ const [allocation] = await tx
216
+ .select({ id: sidecarAllocation.id })
217
+ .from(sidecarAllocation)
218
+ .where(condition)
219
+ .for("update");
220
+ if (allocation === undefined)
221
+ return false;
222
+ const [anchor] = await tx
223
+ .update(workflowRun)
224
+ .set({ publicKey: args.previousPublicKey })
225
+ .where(and(eq(workflowRun.id, args.anchorRunId), eq(workflowRun.anchorRunId, args.anchorRunId), eq(workflowRun.tenantId, args.tenantId)))
226
+ .returning({ id: workflowRun.id });
227
+ if (anchor === undefined)
228
+ throw new Error("Initialization anchor is missing");
229
+ const [updated] = await tx
230
+ .update(sidecarAllocation)
231
+ .set({ initializationLeaseId: null })
232
+ .where(condition)
233
+ .returning({ id: sidecarAllocation.id });
234
+ if (updated === undefined)
235
+ throw new Error("Initialization attempt changed before rollback");
236
+ return true;
237
+ });
238
+ },
239
+ async createPending(args, tx) {
240
+ const executor = tx ?? db;
241
+ const [anchor] = await executor
242
+ .select({
243
+ tenantId: workflowRun.tenantId,
244
+ anchorRunId: workflowRun.anchorRunId,
245
+ status: workflowRun.status,
246
+ })
247
+ .from(workflowRun)
248
+ .where(eq(workflowRun.id, args.anchorRunId))
249
+ .limit(1);
250
+ if (anchor === undefined) {
251
+ throw new Error(`sidecarAllocationStore.createPending: anchor run ${args.anchorRunId} does not exist`);
252
+ }
253
+ if (anchor.tenantId !== args.tenantId ||
254
+ anchor.anchorRunId !== args.anchorRunId) {
255
+ throw new Error(`sidecarAllocationStore.createPending: run ${args.anchorRunId} is not an anchor for tenant ${args.tenantId}`);
256
+ }
257
+ if (!isLiveWorkflowRunStatus(anchor.status)) {
258
+ throw new Error(`sidecarAllocationStore.createPending: anchor run ${args.anchorRunId} is ${anchor.status}, expected a live run`);
259
+ }
260
+ const launchSpec = await executor.query.workflowRunLaunchSpec.findFirst({
261
+ columns: { anchorRunId: true },
262
+ where: eq(workflowRunLaunchSpec.anchorRunId, args.anchorRunId),
263
+ });
264
+ if (launchSpec === undefined) {
265
+ throw new Error(`sidecarAllocationStore.createPending: anchor run ${args.anchorRunId} has no launch specification`);
266
+ }
267
+ if (args.provisionerApiVersion !== 1) {
268
+ throw new Error("sidecarAllocationStore.createPending: unsupported API version");
269
+ }
270
+ const now = databaseTimestamp(args.now);
271
+ const [inserted] = await executor
272
+ .insert(sidecarAllocation)
273
+ .values({
274
+ id: args.id,
275
+ anchorRunId: args.anchorRunId,
276
+ tenantId: args.tenantId,
277
+ provisionerId: args.provisionerId,
278
+ provisionerApiVersion: args.provisionerApiVersion,
279
+ provisionerBindingFingerprint: args.provisionerBindingFingerprint,
280
+ status: "pending",
281
+ generation: 0,
282
+ nextAttemptAt: now,
283
+ createdAt: now,
284
+ updatedAt: now,
285
+ })
286
+ .returning();
287
+ if (inserted === undefined) {
288
+ throw new Error(`sidecarAllocationStore.createPending: insert returned no row for ${args.id}`);
289
+ }
290
+ return parseSidecarAllocationRow(inserted);
291
+ },
292
+ async createAdopted(args, tx) {
293
+ const createdAt = databaseTimestamp(args.now);
294
+ const [inserted] = await (tx ?? db)
295
+ .insert(sidecarAllocation)
296
+ .values({
297
+ id: args.id,
298
+ anchorRunId: args.anchorRunId,
299
+ tenantId: args.tenantId,
300
+ provisionerId: args.provisionerId,
301
+ provisionerApiVersion: args.provisionerApiVersion,
302
+ provisionerBindingFingerprint: args.provisionerBindingFingerprint,
303
+ sidecarId: args.sidecarId,
304
+ status: "allocated",
305
+ generation: args.generation,
306
+ ensureAcceptedGeneration: args.generation,
307
+ ...(args.externalRef !== undefined
308
+ ? { externalRef: args.externalRef }
309
+ : {}),
310
+ connectDeadline: args.connectDeadline,
311
+ nextAttemptAt: createdAt,
312
+ createdAt,
313
+ updatedAt: createdAt,
314
+ })
315
+ .returning();
316
+ if (inserted === undefined) {
317
+ throw new Error(`sidecarAllocationStore.createAdopted: insert returned no row for ${args.id}`);
318
+ }
319
+ return parseSidecarAllocationRow(inserted);
320
+ },
321
+ async bindInitialSidecar(args) {
322
+ return db.transaction(async (tx) => {
323
+ const [allocation] = await tx
324
+ .select()
325
+ .from(sidecarAllocation)
326
+ .where(and(eq(sidecarAllocation.id, args.allocationId), ...leaseCondition(args.expectedLeaseId)))
327
+ .limit(1)
328
+ .for("update");
329
+ if (allocation === undefined ||
330
+ allocation.status !== "pending" ||
331
+ allocation.generation !== args.expectedGeneration ||
332
+ (args.expectedLeaseId !== undefined &&
333
+ allocation.reconciliationLeaseId !== args.expectedLeaseId)) {
334
+ return null;
335
+ }
336
+ const now = databaseTimestamp(args.now);
337
+ await insertSidecarIdentity(tx, {
338
+ sidecarId: args.sidecarId,
339
+ tokenHashSha256: args.tokenHashSha256,
340
+ now,
341
+ });
342
+ const [updated] = await tx
343
+ .update(sidecarAllocation)
344
+ .set({
345
+ sidecarId: args.sidecarId,
346
+ status: "provisioning",
347
+ generation: args.expectedGeneration + 1,
348
+ ensureAcceptedGeneration: null,
349
+ externalRef: null,
350
+ connectDeadline: args.connectDeadline,
351
+ nextAttemptAt: now,
352
+ failureCode: null,
353
+ failureMessage: null,
354
+ updatedAt: now,
355
+ })
356
+ .where(and(eq(sidecarAllocation.id, args.allocationId), eq(sidecarAllocation.status, "pending"), eq(sidecarAllocation.generation, args.expectedGeneration), ...leaseCondition(args.expectedLeaseId)))
357
+ .returning();
358
+ if (updated === undefined) {
359
+ throw new Error(`sidecarAllocationStore.bindInitialSidecar: locked allocation ${args.allocationId} changed before update`);
360
+ }
361
+ return parseSidecarAllocationRow(updated);
362
+ });
363
+ },
364
+ async bindReplacementSidecar(args) {
365
+ return db.transaction(async (tx) => {
366
+ const [allocation] = await tx
367
+ .select()
368
+ .from(sidecarAllocation)
369
+ .where(and(eq(sidecarAllocation.id, args.allocationId), ...leaseCondition(args.expectedLeaseId)))
370
+ .limit(1)
371
+ .for("update");
372
+ if (allocation === undefined ||
373
+ allocation.status !== "replacing" ||
374
+ allocation.generation !== args.generation ||
375
+ (args.expectedLeaseId !== undefined &&
376
+ allocation.reconciliationLeaseId !== args.expectedLeaseId)) {
377
+ return null;
378
+ }
379
+ const now = databaseTimestamp(args.now);
380
+ await insertSidecarIdentity(tx, {
381
+ sidecarId: args.sidecarId,
382
+ tokenHashSha256: args.tokenHashSha256,
383
+ now,
384
+ });
385
+ const [updated] = await tx
386
+ .update(sidecarAllocation)
387
+ .set({
388
+ sidecarId: args.sidecarId,
389
+ status: "provisioning",
390
+ ensureAcceptedGeneration: null,
391
+ externalRef: null,
392
+ connectDeadline: args.connectDeadline,
393
+ nextAttemptAt: now,
394
+ destroyAttempts: sql `${sidecarAllocation.destroyAttempts} + 1`,
395
+ failureCode: null,
396
+ failureMessage: null,
397
+ updatedAt: now,
398
+ })
399
+ .where(and(eq(sidecarAllocation.id, args.allocationId), eq(sidecarAllocation.status, "replacing"), eq(sidecarAllocation.generation, args.generation), ...leaseCondition(args.expectedLeaseId)))
400
+ .returning();
401
+ if (updated === undefined) {
402
+ throw new Error(`sidecarAllocationStore.bindReplacementSidecar: locked allocation ${args.allocationId} changed before update`);
403
+ }
404
+ return parseSidecarAllocationRow(updated);
405
+ });
406
+ },
407
+ async markAllocated(args) {
408
+ const [updated] = await db
409
+ .update(sidecarAllocation)
410
+ .set({
411
+ status: "allocated",
412
+ ensureAcceptedGeneration: args.generation,
413
+ externalRef: args.externalRef ?? null,
414
+ nextAttemptAt: sidecarAllocation.connectDeadline,
415
+ ensureAttempts: sql `${sidecarAllocation.ensureAttempts} + 1`,
416
+ failureCode: null,
417
+ failureMessage: null,
418
+ updatedAt: databaseTimestamp(args.now),
419
+ })
420
+ .where(and(eq(sidecarAllocation.id, args.allocationId), eq(sidecarAllocation.status, "provisioning"), eq(sidecarAllocation.generation, args.generation), ...leaseCondition(args.expectedLeaseId)))
421
+ .returning();
422
+ return updated === undefined ? null : parseSidecarAllocationRow(updated);
423
+ },
424
+ async scheduleRetry(args) {
425
+ const [updated] = await db
426
+ .update(sidecarAllocation)
427
+ .set({
428
+ nextAttemptAt: args.nextAttemptAt,
429
+ reconciliationLeaseId: null,
430
+ reconciliationLeaseExpiresAt: null,
431
+ ...(args.attempt === "ensure"
432
+ ? { ensureAttempts: sql `${sidecarAllocation.ensureAttempts} + 1` }
433
+ : {}),
434
+ ...(args.attempt === "destroy"
435
+ ? { destroyAttempts: sql `${sidecarAllocation.destroyAttempts} + 1` }
436
+ : {}),
437
+ ...(args.failure !== undefined
438
+ ? {
439
+ failureCode: args.failure.code,
440
+ failureMessage: args.failure.message,
441
+ }
442
+ : {}),
443
+ updatedAt: databaseTimestamp(args.now),
444
+ })
445
+ .where(and(eq(sidecarAllocation.id, args.allocationId), eq(sidecarAllocation.status, args.expectedStatus), eq(sidecarAllocation.generation, args.expectedGeneration), ...leaseCondition(args.expectedLeaseId)))
446
+ .returning();
447
+ return updated === undefined ? null : parseSidecarAllocationRow(updated);
448
+ },
449
+ async beginReplacement(args) {
450
+ const now = databaseTimestamp(args.now);
451
+ return db.transaction(async (tx) => {
452
+ if (args.onlyIfInitializationIncomplete &&
453
+ (await initializationCompleted(tx, args)))
454
+ return null;
455
+ const [updated] = await tx
456
+ .update(sidecarAllocation)
457
+ .set({
458
+ status: "replacing",
459
+ generation: args.expectedGeneration + 1,
460
+ initializationLeaseId: null,
461
+ ensureAcceptedGeneration: null,
462
+ nextAttemptAt: args.nextAttemptAt,
463
+ reconciliationLeaseId: null,
464
+ reconciliationLeaseExpiresAt: null,
465
+ connectDeadline: null,
466
+ failureCode: args.failureCode,
467
+ failureMessage: args.failureMessage,
468
+ updatedAt: now,
469
+ })
470
+ .where(and(eq(sidecarAllocation.id, args.allocationId), eq(sidecarAllocation.status, args.expectedStatus), eq(sidecarAllocation.generation, args.expectedGeneration), ...leaseCondition(args.expectedLeaseId), ...(args.expectedInitializationLeaseId !== undefined
471
+ ? [
472
+ eq(sidecarAllocation.initializationLeaseId, args.expectedInitializationLeaseId),
473
+ ]
474
+ : [])))
475
+ .returning();
476
+ if (updated === undefined)
477
+ return null;
478
+ // The anchor public key is durable proof that this allocation
479
+ // generation completed restore and deployment. Clear it atomically
480
+ // with the generation advance so a replacement cannot mistake stale
481
+ // local sidecar state for a successfully restored workflow.
482
+ await tx
483
+ .update(workflowRun)
484
+ .set({ publicKey: null })
485
+ .where(eq(workflowRun.id, updated.anchorRunId));
486
+ return parseSidecarAllocationRow(updated);
487
+ });
488
+ },
489
+ beginRelease,
490
+ async beginUnrecoverableRelease(args) {
491
+ const now = databaseTimestamp(args.now);
492
+ return db.transaction(async (tx) => {
493
+ if (args.onlyIfInitializationIncomplete &&
494
+ (await initializationCompleted(tx, args)))
495
+ return null;
496
+ const releasing = await beginRelease({
497
+ ...args,
498
+ expectedStatus: "allocated",
499
+ }, tx);
500
+ if (releasing === null)
501
+ return null;
502
+ await failRunningRuns(tx, releasing.anchorRunId, now);
503
+ await workflowRunDispatchStore.failUnsettled(releasing.anchorRunId, args.failureCode, args.failureMessage, now, tx);
504
+ return releasing;
505
+ });
506
+ },
507
+ async markReleased(args) {
508
+ const [updated] = await db
509
+ .update(sidecarAllocation)
510
+ .set({
511
+ status: "released",
512
+ nextAttemptAt: null,
513
+ reconciliationLeaseId: null,
514
+ reconciliationLeaseExpiresAt: null,
515
+ connectDeadline: null,
516
+ destroyAttempts: sql `${sidecarAllocation.destroyAttempts} + 1`,
517
+ updatedAt: databaseTimestamp(args.now),
518
+ })
519
+ .where(and(eq(sidecarAllocation.id, args.allocationId), eq(sidecarAllocation.status, "releasing"), eq(sidecarAllocation.generation, args.generation), ...leaseCondition(args.expectedLeaseId)))
520
+ .returning();
521
+ return updated === undefined ? null : parseSidecarAllocationRow(updated);
522
+ },
523
+ async markDestroyFailed(args) {
524
+ const now = databaseTimestamp(args.now);
525
+ return db.transaction(async (tx) => {
526
+ const [updated] = await tx
527
+ .update(sidecarAllocation)
528
+ .set({
529
+ status: "destroy_failed",
530
+ failureCode: args.code,
531
+ failureMessage: args.message,
532
+ nextAttemptAt: null,
533
+ reconciliationLeaseId: null,
534
+ reconciliationLeaseExpiresAt: null,
535
+ connectDeadline: null,
536
+ destroyAttempts: sql `${sidecarAllocation.destroyAttempts} + 1`,
537
+ updatedAt: now,
538
+ })
539
+ .where(and(eq(sidecarAllocation.id, args.allocationId), inArray(sidecarAllocation.status, ["replacing", "releasing"]), eq(sidecarAllocation.generation, args.expectedGeneration), ...leaseCondition(args.expectedLeaseId)))
540
+ .returning();
541
+ if (updated === undefined)
542
+ return null;
543
+ await failRunningRuns(tx, updated.anchorRunId, now);
544
+ await workflowRunDispatchStore.failUnsettled(updated.anchorRunId, args.code, args.message, now, tx);
545
+ return parseSidecarAllocationRow(updated);
546
+ });
547
+ },
548
+ async failWithoutInfrastructure(args, tx) {
549
+ const now = databaseTimestamp(args.now);
550
+ const fail = async (executor) => {
551
+ const [updated] = await executor
552
+ .update(sidecarAllocation)
553
+ .set({
554
+ status: "failed",
555
+ failureCode: args.code,
556
+ failureMessage: args.message,
557
+ nextAttemptAt: null,
558
+ reconciliationLeaseId: null,
559
+ reconciliationLeaseExpiresAt: null,
560
+ connectDeadline: null,
561
+ updatedAt: now,
562
+ })
563
+ .where(and(eq(sidecarAllocation.id, args.allocationId), eq(sidecarAllocation.status, args.expectedStatus), eq(sidecarAllocation.generation, args.expectedGeneration), ...leaseCondition(args.expectedLeaseId)))
564
+ .returning();
565
+ if (updated === undefined)
566
+ return null;
567
+ await failRunningRuns(executor, updated.anchorRunId, now);
568
+ await workflowRunDispatchStore.failUnsettled(updated.anchorRunId, args.code, args.message, now, executor);
569
+ return parseSidecarAllocationRow(updated);
570
+ };
571
+ return tx === undefined ? db.transaction(fail) : fail(tx);
572
+ },
573
+ async findById(id) {
574
+ const row = await db.query.sidecarAllocation.findFirst({
575
+ where: eq(sidecarAllocation.id, id),
576
+ });
577
+ return row === undefined ? null : parseSidecarAllocationRow(row);
578
+ },
579
+ async findByAnchorRunId(anchorRunId) {
580
+ const row = await db.query.sidecarAllocation.findFirst({
581
+ where: eq(sidecarAllocation.anchorRunId, anchorRunId),
582
+ });
583
+ return row === undefined ? null : parseSidecarAllocationRow(row);
584
+ },
585
+ async claimNextReconcilable(args) {
586
+ return db.transaction(async (tx) => {
587
+ const [candidate] = await tx
588
+ .select({ id: sidecarAllocation.id })
589
+ .from(sidecarAllocation)
590
+ .where(and(inArray(sidecarAllocation.status, activeStatuses), lte(sidecarAllocation.nextAttemptAt, sql `now()`), ...(args.excludedAllocationIds !== undefined &&
591
+ args.excludedAllocationIds.length > 0
592
+ ? [
593
+ notInArray(sidecarAllocation.id, [
594
+ ...args.excludedAllocationIds,
595
+ ]),
596
+ ]
597
+ : []), or(isNull(sidecarAllocation.reconciliationLeaseExpiresAt), lte(sidecarAllocation.reconciliationLeaseExpiresAt, sql `now()`))))
598
+ .orderBy(asc(sidecarAllocation.nextAttemptAt), asc(sidecarAllocation.createdAt))
599
+ .limit(1)
600
+ .for("update", { skipLocked: true });
601
+ if (candidate === undefined)
602
+ return null;
603
+ const [claimed] = await tx
604
+ .update(sidecarAllocation)
605
+ .set({
606
+ reconciliationLeaseId: args.leaseId,
607
+ reconciliationLeaseExpiresAt: sql `now() + (${args.leaseDurationMs} * interval '1 millisecond')`,
608
+ })
609
+ .where(eq(sidecarAllocation.id, candidate.id))
610
+ .returning();
611
+ return claimed === undefined
612
+ ? null
613
+ : parseSidecarAllocationRow(claimed);
614
+ });
615
+ },
616
+ async extendReconciliationLease(allocationId, leaseId, leaseDurationMs) {
617
+ const [updated] = await db
618
+ .update(sidecarAllocation)
619
+ .set({
620
+ reconciliationLeaseExpiresAt: sql `clock_timestamp() + (${leaseDurationMs} * interval '1 millisecond')`,
621
+ })
622
+ .where(and(eq(sidecarAllocation.id, allocationId), ...leaseCondition(leaseId)))
623
+ .returning({ id: sidecarAllocation.id });
624
+ return updated !== undefined;
625
+ },
626
+ async isReconciliationLeaseCurrent(allocationId, generation, leaseId) {
627
+ const [allocation] = await db
628
+ .select({ id: sidecarAllocation.id })
629
+ .from(sidecarAllocation)
630
+ .where(and(eq(sidecarAllocation.id, allocationId), eq(sidecarAllocation.generation, generation), ...leaseCondition(leaseId)))
631
+ .limit(1);
632
+ return allocation !== undefined;
633
+ },
634
+ async markConnectionReady(args) {
635
+ const [updated] = await db
636
+ .update(sidecarAllocation)
637
+ .set({
638
+ connectDeadline: null,
639
+ nextAttemptAt: null,
640
+ reconciliationLeaseId: null,
641
+ reconciliationLeaseExpiresAt: null,
642
+ failureCode: null,
643
+ failureMessage: null,
644
+ updatedAt: databaseTimestamp(args.now),
645
+ })
646
+ .where(and(eq(sidecarAllocation.id, args.allocationId), eq(sidecarAllocation.status, "allocated"), eq(sidecarAllocation.generation, args.generation), eq(sidecarAllocation.ensureAcceptedGeneration, args.generation), isNull(sidecarAllocation.initializationLeaseId), ...leaseCondition(args.expectedLeaseId)))
647
+ .returning();
648
+ return updated === undefined ? null : parseSidecarAllocationRow(updated);
649
+ },
650
+ async markConnectionLost(args) {
651
+ const [updated] = await db
652
+ .update(sidecarAllocation)
653
+ .set({
654
+ connectDeadline: args.connectDeadline,
655
+ nextAttemptAt: args.connectDeadline,
656
+ reconciliationLeaseId: null,
657
+ reconciliationLeaseExpiresAt: null,
658
+ updatedAt: databaseTimestamp(args.now),
659
+ })
660
+ .where(and(eq(sidecarAllocation.id, args.allocationId), eq(sidecarAllocation.status, "allocated"), eq(sidecarAllocation.generation, args.generation), eq(sidecarAllocation.ensureAcceptedGeneration, args.generation)))
661
+ .returning();
662
+ return updated === undefined ? null : parseSidecarAllocationRow(updated);
663
+ },
664
+ async scheduleReconnectIfUnscheduled(args) {
665
+ const connectDeadline = sql.param(args.connectDeadline, sidecarAllocation.connectDeadline);
666
+ const [updated] = await db
667
+ .update(sidecarAllocation)
668
+ .set({
669
+ connectDeadline: sql `coalesce(${sidecarAllocation.connectDeadline}, ${connectDeadline})`,
670
+ nextAttemptAt: sql `coalesce(${sidecarAllocation.connectDeadline}, ${connectDeadline})`,
671
+ updatedAt: databaseTimestamp(args.now),
672
+ })
673
+ .where(and(eq(sidecarAllocation.id, args.allocationId), eq(sidecarAllocation.status, "allocated"), eq(sidecarAllocation.generation, args.generation), eq(sidecarAllocation.ensureAcceptedGeneration, args.generation), isNull(sidecarAllocation.nextAttemptAt), isNull(sidecarAllocation.reconciliationLeaseId), isNull(sidecarAllocation.reconciliationLeaseExpiresAt)))
674
+ .returning();
675
+ return updated === undefined ? null : parseSidecarAllocationRow(updated);
676
+ },
677
+ async parkReconciliation(allocationId, leaseId, policy) {
678
+ const fallbackNextAttemptAt = policy.kind === "await-connection"
679
+ ? policy.fallbackNextAttemptAt
680
+ : policy.notBefore;
681
+ const fallback = sql.param(fallbackNextAttemptAt, sidecarAllocation.nextAttemptAt);
682
+ const nextAttemptAt = policy.kind === "await-connection"
683
+ ? sql `case when ${sidecarAllocation.connectDeadline} is null then ${fallback} else coalesce(${sidecarAllocation.nextAttemptAt}, ${sidecarAllocation.connectDeadline}) end`
684
+ : sql `greatest(coalesce(${sidecarAllocation.nextAttemptAt}, ${sidecarAllocation.connectDeadline}, ${fallback}), ${fallback})`;
685
+ const [updated] = await db
686
+ .update(sidecarAllocation)
687
+ .set({
688
+ nextAttemptAt,
689
+ reconciliationLeaseId: null,
690
+ reconciliationLeaseExpiresAt: null,
691
+ })
692
+ .where(and(eq(sidecarAllocation.id, allocationId), ...leaseCondition(leaseId)))
693
+ .returning({ id: sidecarAllocation.id });
694
+ return updated !== undefined;
695
+ },
696
+ async wakeReconciliation(allocationId, generation) {
697
+ const [updated] = await db
698
+ .update(sidecarAllocation)
699
+ .set({
700
+ nextAttemptAt: sql `now()`,
701
+ })
702
+ .where(and(eq(sidecarAllocation.id, allocationId), eq(sidecarAllocation.generation, generation), inArray(sidecarAllocation.status, activeStatuses)))
703
+ .returning({ id: sidecarAllocation.id });
704
+ return updated !== undefined;
705
+ },
706
+ async listActive() {
707
+ const rows = await db
708
+ .select()
709
+ .from(sidecarAllocation)
710
+ .where(inArray(sidecarAllocation.status, activeStatuses));
711
+ return rows.map(parseSidecarAllocationRow);
712
+ },
713
+ };
714
+ }