@intx/db 0.1.2 → 0.3.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 (241) hide show
  1. package/LICENSE +176 -0
  2. package/README.md +23 -4
  3. package/dist/approval-store.d.ts +52 -0
  4. package/dist/approval-store.js +79 -0
  5. package/dist/asset-resolution.d.ts +52 -0
  6. package/dist/asset-resolution.js +66 -0
  7. package/dist/catalog-resolution.d.ts +76 -0
  8. package/dist/catalog-resolution.js +151 -0
  9. package/dist/client.d.ts +19 -0
  10. package/dist/client.js +18 -0
  11. package/dist/config.d.ts +11 -0
  12. package/dist/config.js +16 -0
  13. package/dist/connection.d.ts +3 -0
  14. package/dist/connection.js +26 -0
  15. package/dist/credential-resolution.d.ts +222 -0
  16. package/dist/credential-resolution.js +255 -0
  17. package/dist/grant-store.d.ts +3 -0
  18. package/dist/grant-store.js +59 -0
  19. package/dist/index.d.ts +22 -0
  20. package/dist/index.js +20 -0
  21. package/dist/migrate.d.ts +36 -0
  22. package/dist/migrate.js +135 -0
  23. package/dist/model-source-resolution.d.ts +115 -0
  24. package/dist/model-source-resolution.js +291 -0
  25. package/dist/parse-row.d.ts +373 -0
  26. package/dist/parse-row.js +232 -0
  27. package/dist/pg-error.d.ts +3 -0
  28. package/dist/pg-error.js +19 -0
  29. package/dist/pricing.d.ts +15 -0
  30. package/dist/pricing.js +24 -0
  31. package/dist/rekey-credential-secrets.d.ts +13 -0
  32. package/dist/rekey-credential-secrets.js +61 -0
  33. package/dist/schema/approvals.d.ts +245 -0
  34. package/dist/schema/approvals.js +52 -0
  35. package/dist/schema/assets.d.ts +143 -0
  36. package/{src/schema/assets.ts → dist/schema/assets.js} +7 -16
  37. package/dist/schema/auth.d.ts +606 -0
  38. package/dist/schema/auth.js +47 -0
  39. package/dist/schema/catalog.d.ts +811 -0
  40. package/dist/schema/catalog.js +108 -0
  41. package/dist/schema/column-types.d.ts +26 -0
  42. package/dist/schema/column-types.js +15 -0
  43. package/dist/schema/credentials.d.ts +305 -0
  44. package/{src/schema/credentials.ts → dist/schema/credentials.js} +16 -22
  45. package/dist/schema/git-tokens.d.ts +256 -0
  46. package/{src/schema/git-tokens.ts → dist/schema/git-tokens.js} +16 -39
  47. package/dist/schema/grants.d.ts +211 -0
  48. package/dist/schema/grants.js +30 -0
  49. package/dist/schema/index.d.ts +25 -0
  50. package/dist/schema/index.js +25 -0
  51. package/dist/schema/messages.d.ts +414 -0
  52. package/dist/schema/messages.js +82 -0
  53. package/dist/schema/oauth-clients.d.ts +246 -0
  54. package/{src/schema/oauth-clients.ts → dist/schema/oauth-clients.js} +8 -14
  55. package/dist/schema/offerings.d.ts +160 -0
  56. package/dist/schema/offerings.js +24 -0
  57. package/dist/schema/principals.d.ts +128 -0
  58. package/dist/schema/principals.js +23 -0
  59. package/dist/schema/providers.d.ts +237 -0
  60. package/dist/schema/providers.js +22 -0
  61. package/dist/schema/roles.d.ts +242 -0
  62. package/dist/schema/roles.js +37 -0
  63. package/dist/schema/session-assets.d.ts +92 -0
  64. package/dist/schema/session-assets.js +45 -0
  65. package/dist/schema/sessions.d.ts +143 -0
  66. package/dist/schema/sessions.js +29 -0
  67. package/dist/schema/sidecar-allocation.d.ts +406 -0
  68. package/dist/schema/sidecar-allocation.js +57 -0
  69. package/dist/schema/sidecar.d.ts +145 -0
  70. package/dist/schema/sidecar.js +28 -0
  71. package/dist/schema/signal-correlations.d.ts +177 -0
  72. package/dist/schema/signal-correlations.js +31 -0
  73. package/dist/schema/tenants.d.ts +235 -0
  74. package/{src/schema/tenants.ts → dist/schema/tenants.js} +10 -27
  75. package/dist/schema/wallets.d.ts +354 -0
  76. package/dist/schema/wallets.js +43 -0
  77. package/dist/schema/workflow-definitions.d.ts +371 -0
  78. package/dist/schema/workflow-definitions.js +97 -0
  79. package/dist/schema/workflow-run-dispatch.d.ts +328 -0
  80. package/dist/schema/workflow-run-dispatch.js +56 -0
  81. package/dist/schema/workflow-run-execution.d.ts +126 -0
  82. package/dist/schema/workflow-run-execution.js +48 -0
  83. package/dist/schema/workflow-run-launch-spec.d.ts +200 -0
  84. package/dist/schema/workflow-run-launch-spec.js +26 -0
  85. package/dist/schema/workflow-run.d.ts +230 -0
  86. package/dist/schema/workflow-run.js +102 -0
  87. package/dist/sidecar-allocation-store.d.ts +171 -0
  88. package/dist/sidecar-allocation-store.js +512 -0
  89. package/dist/signal-correlation-store.d.ts +36 -0
  90. package/dist/signal-correlation-store.js +63 -0
  91. package/dist/tenant-hierarchy.d.ts +23 -0
  92. package/dist/tenant-hierarchy.js +60 -0
  93. package/dist/workflow-definition-store.d.ts +55 -0
  94. package/dist/workflow-definition-store.js +93 -0
  95. package/dist/workflow-run-dispatch-store.d.ts +80 -0
  96. package/dist/workflow-run-dispatch-store.js +298 -0
  97. package/dist/workflow-run-launch-spec-store.d.ts +13 -0
  98. package/dist/workflow-run-launch-spec-store.js +30 -0
  99. package/dist/workflow-run-store.d.ts +54 -0
  100. package/dist/workflow-run-store.js +85 -0
  101. package/migrations/0029_loose_warlock.sql +1 -0
  102. package/migrations/0030_session_asset_audit_split.sql +5 -0
  103. package/migrations/0031_gigantic_nicolaos.sql +68 -0
  104. package/migrations/0032_lethal_misty_knight.sql +1 -0
  105. package/migrations/0033_old_payback.sql +1 -0
  106. package/migrations/0034_sleepy_songbird.sql +11 -0
  107. package/migrations/0035_violet_giant_man.sql +3 -0
  108. package/migrations/0036_sharp_the_executioner.sql +2 -0
  109. package/migrations/0037_credential_use_backfill.sql +39 -0
  110. package/migrations/0038_chilly_blacklash.sql +37 -0
  111. package/migrations/0039_quick_carnage.sql +2 -0
  112. package/migrations/0040_reshape_approval_origin.sql +24 -0
  113. package/migrations/0041_make_approval_timeout_at_nullable.sql +1 -0
  114. package/migrations/0042_youthful_mantis.sql +2 -0
  115. package/migrations/0043_signal_correlation_deployment_fk.sql +1 -0
  116. package/migrations/0044_model_offering_quirks.sql +1 -0
  117. package/migrations/0045_create_workflow_run.sql +13 -0
  118. package/migrations/0046_approval_signal_correlation_run_fk.sql +2 -0
  119. package/migrations/0047_create_workflow_definition.sql +30 -0
  120. package/migrations/0048_workflow_definition_origin_agent_id.sql +2 -0
  121. package/migrations/0049_curly_omega_flight.sql +2 -0
  122. package/migrations/0050_late_crystal.sql +10 -0
  123. package/migrations/0051_funny_madelyne_pryor.sql +1 -0
  124. package/migrations/0052_drop_inference_turn_instance_fk.sql +1 -0
  125. package/migrations/0053_session_mail_session_id_index.sql +1 -0
  126. package/migrations/0054_rekey_instance_grants_to_workflow_run.sql +10 -0
  127. package/migrations/0055_backfill_anchor_workflow_runs.sql +69 -0
  128. package/migrations/0056_repoint_deployment_fks_to_anchor_run.sql +32 -0
  129. package/migrations/0057_drop_workflow_deployment_projection.sql +22 -0
  130. package/migrations/0058_repoint_offering_fk_to_definition.sql +41 -0
  131. package/migrations/0059_repoint_transaction_fk_to_run.sql +10 -0
  132. package/migrations/0060_drop_session_mail_instance_fk.sql +6 -0
  133. package/migrations/0061_drop_session_asset_instance_fk.sql +7 -0
  134. package/migrations/0062_drop_agent_asset_table.sql +11 -0
  135. package/migrations/0063_repoint_agent_role_fk_to_definition.sql +39 -0
  136. package/migrations/0064_repoint_agent_session_fk_to_definition.sql +47 -0
  137. package/migrations/0065_add_workflow_definition_model_requirements.sql +37 -0
  138. package/migrations/0066_rekey_agent_definition_principals_to_workflow.sql +27 -0
  139. package/migrations/0067_add_workflow_definition_kind.sql +10 -0
  140. package/migrations/0068_drop_agent_tables_and_origin_agent_id.sql +31 -0
  141. package/migrations/0069_drop_workflow_definition_kind.sql +10 -0
  142. package/migrations/0070_flashy_proteus.sql +13 -0
  143. package/migrations/0072_add_workflow_definition_credential_bindings.sql +1 -0
  144. package/migrations/0073_grant_target_exactly_one_check.sql +13 -0
  145. package/migrations/0074_add_provider_api_base_url.sql +1 -0
  146. package/migrations/0075_workflow_run_launch_spec.sql +17 -0
  147. package/migrations/0076_sidecar_allocation.sql +40 -0
  148. package/migrations/0077_workflow_run_dispatch.sql +29 -0
  149. package/migrations/0078_workflow_run_dispatch_kind.sql +2 -0
  150. package/migrations/0079_rename_workflow_run_deployment_id_to_anchor_run_id.sql +4 -0
  151. package/migrations/0080_rename_correlation_approval_deployment_id_to_anchor_run_id.sql +9 -0
  152. package/migrations/0081_workflow_definition_content_hash_and_approved_wire_hash.sql +4 -0
  153. package/migrations/0082_blue_black_queen.sql +1 -0
  154. package/migrations/0083_replace_launch_spec_snapshot_with_frozen_bundle.sql +3 -0
  155. package/migrations/0084_delete_orphaned_credential_grants.sql +22 -0
  156. package/migrations/meta/0029_snapshot.json +2664 -0
  157. package/migrations/meta/0030_snapshot.json +2670 -0
  158. package/migrations/meta/0031_snapshot.json +3100 -0
  159. package/migrations/meta/0032_snapshot.json +3106 -0
  160. package/migrations/meta/0033_snapshot.json +3112 -0
  161. package/migrations/meta/0034_snapshot.json +3198 -0
  162. package/migrations/meta/0035_snapshot.json +3225 -0
  163. package/migrations/meta/0036_snapshot.json +3237 -0
  164. package/migrations/meta/0037_snapshot.json +3237 -0
  165. package/migrations/meta/0038_snapshot.json +3470 -0
  166. package/migrations/meta/0039_snapshot.json +3470 -0
  167. package/migrations/meta/0040_snapshot.json +3489 -0
  168. package/migrations/meta/0041_snapshot.json +3489 -0
  169. package/migrations/meta/0042_snapshot.json +3489 -0
  170. package/migrations/meta/0043_snapshot.json +3498 -0
  171. package/migrations/meta/0044_snapshot.json +3504 -0
  172. package/migrations/meta/0045_snapshot.json +3589 -0
  173. package/migrations/meta/0046_snapshot.json +3607 -0
  174. package/migrations/meta/0047_snapshot.json +3832 -0
  175. package/migrations/meta/0048_snapshot.json +3853 -0
  176. package/migrations/meta/0049_snapshot.json +3854 -0
  177. package/migrations/meta/0050_snapshot.json +3924 -0
  178. package/migrations/meta/0051_snapshot.json +3940 -0
  179. package/migrations/meta/0052_snapshot.json +3931 -0
  180. package/migrations/meta/0053_snapshot.json +3952 -0
  181. package/migrations/meta/0054_snapshot.json +3952 -0
  182. package/migrations/meta/0055_snapshot.json +3952 -0
  183. package/migrations/meta/0056_snapshot.json +3952 -0
  184. package/migrations/meta/0057_snapshot.json +3839 -0
  185. package/migrations/meta/0058_snapshot.json +3839 -0
  186. package/migrations/meta/0059_snapshot.json +3839 -0
  187. package/migrations/meta/0060_snapshot.json +3830 -0
  188. package/migrations/meta/0061_snapshot.json +3821 -0
  189. package/migrations/meta/0062_snapshot.json +3723 -0
  190. package/migrations/meta/0063_snapshot.json +3723 -0
  191. package/migrations/meta/0064_snapshot.json +3723 -0
  192. package/migrations/meta/0065_snapshot.json +3729 -0
  193. package/migrations/meta/0066_snapshot.json +3729 -0
  194. package/migrations/meta/0067_snapshot.json +3736 -0
  195. package/migrations/meta/0068_snapshot.json +3347 -0
  196. package/migrations/meta/0069_snapshot.json +3340 -0
  197. package/migrations/meta/0070_snapshot.json +3444 -0
  198. package/migrations/meta/0072_snapshot.json +3450 -0
  199. package/migrations/meta/0073_snapshot.json +3455 -0
  200. package/migrations/meta/0074_snapshot.json +3461 -0
  201. package/migrations/meta/0075_snapshot.json +3567 -0
  202. package/migrations/meta/0076_snapshot.json +3851 -0
  203. package/migrations/meta/0077_snapshot.json +4067 -0
  204. package/migrations/meta/0078_snapshot.json +4078 -0
  205. package/migrations/meta/0079_snapshot.json +4078 -0
  206. package/migrations/meta/0080_snapshot.json +4078 -0
  207. package/migrations/meta/0081_snapshot.json +4096 -0
  208. package/migrations/meta/0082_snapshot.json +4102 -0
  209. package/migrations/meta/0083_snapshot.json +4096 -0
  210. package/migrations/meta/0084_snapshot.json +4096 -0
  211. package/migrations/meta/_journal.json +385 -0
  212. package/package.json +25 -11
  213. package/drizzle.config.ts +0 -23
  214. package/src/client.ts +0 -24
  215. package/src/config.ts +0 -19
  216. package/src/connection.ts +0 -27
  217. package/src/credential-resolution.ts +0 -378
  218. package/src/grant-store.ts +0 -51
  219. package/src/index.ts +0 -32
  220. package/src/migrate.test.ts +0 -35
  221. package/src/migrate.ts +0 -168
  222. package/src/parse-row.test.ts +0 -113
  223. package/src/parse-row.ts +0 -185
  224. package/src/schema/agent-assets.ts +0 -21
  225. package/src/schema/agents.ts +0 -49
  226. package/src/schema/auth.ts +0 -51
  227. package/src/schema/grants.ts +0 -26
  228. package/src/schema/index.ts +0 -19
  229. package/src/schema/instances.ts +0 -36
  230. package/src/schema/messages.ts +0 -108
  231. package/src/schema/offerings.ts +0 -20
  232. package/src/schema/principals.ts +0 -28
  233. package/src/schema/providers.ts +0 -23
  234. package/src/schema/roles.ts +0 -51
  235. package/src/schema/session-assets.ts +0 -49
  236. package/src/schema/sessions.ts +0 -26
  237. package/src/schema/sidecar.ts +0 -14
  238. package/src/schema/wallets.ts +0 -44
  239. package/src/tenant-hierarchy.ts +0 -34
  240. package/tsconfig.json +0 -4
  241. package/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,171 @@
1
+ import { SidecarPlacementRequirement, type SidecarAllocationStatus } from "@intx/types";
2
+ import type { DB, DBExecutor } from "./client.js";
3
+ type DBHandle = DB["db"];
4
+ declare const activeStatuses: readonly ["pending", "provisioning", "allocated", "replacing", "releasing"];
5
+ export type SidecarAllocation = {
6
+ readonly id: string;
7
+ readonly anchorRunId: string;
8
+ readonly tenantId: string;
9
+ readonly provisionerId: string;
10
+ readonly provisionerApiVersion: 1;
11
+ readonly provisionerBindingFingerprint: string;
12
+ readonly sidecarId?: string;
13
+ readonly placement: SidecarPlacementRequirement;
14
+ readonly status: SidecarAllocationStatus;
15
+ readonly generation: number;
16
+ readonly ensureAcceptedGeneration?: number;
17
+ readonly externalRef?: string;
18
+ readonly nextAttemptAt?: Date;
19
+ readonly reconciliationLeaseId?: string;
20
+ readonly reconciliationLeaseExpiresAt?: Date;
21
+ readonly ensureAttempts: number;
22
+ readonly destroyAttempts: number;
23
+ readonly connectDeadline?: Date;
24
+ readonly failureCode?: string;
25
+ readonly failureMessage?: string;
26
+ readonly createdAt: Date;
27
+ readonly updatedAt: Date;
28
+ };
29
+ export type CreatePendingSidecarAllocationArgs = {
30
+ readonly id: string;
31
+ readonly anchorRunId: string;
32
+ readonly tenantId: string;
33
+ readonly provisionerId: string;
34
+ readonly provisionerApiVersion: 1;
35
+ readonly provisionerBindingFingerprint: string;
36
+ readonly placement?: SidecarPlacementRequirement;
37
+ readonly now?: Date;
38
+ };
39
+ export type ClaimSidecarAllocationArgs = {
40
+ readonly leaseId: string;
41
+ readonly leaseDurationMs: number;
42
+ };
43
+ export type ParkSidecarReconciliationPolicy = {
44
+ readonly kind: "await-connection";
45
+ readonly fallbackNextAttemptAt: Date;
46
+ } | {
47
+ readonly kind: "retry-after-error";
48
+ readonly notBefore: Date;
49
+ };
50
+ export type BindInitialSidecarArgs = {
51
+ readonly allocationId: string;
52
+ readonly expectedGeneration: number;
53
+ readonly sidecarId: string;
54
+ readonly tokenHashSha256: Uint8Array;
55
+ readonly connectDeadline: Date;
56
+ readonly expectedLeaseId?: string;
57
+ readonly now?: Date;
58
+ };
59
+ export type BindReplacementSidecarArgs = {
60
+ readonly allocationId: string;
61
+ readonly generation: number;
62
+ readonly sidecarId: string;
63
+ readonly tokenHashSha256: Uint8Array;
64
+ readonly connectDeadline: Date;
65
+ readonly expectedLeaseId?: string;
66
+ readonly now?: Date;
67
+ };
68
+ export type MarkSidecarAllocatedArgs = {
69
+ readonly allocationId: string;
70
+ readonly generation: number;
71
+ readonly externalRef?: string;
72
+ readonly expectedLeaseId?: string;
73
+ readonly now?: Date;
74
+ };
75
+ export type ScheduleSidecarAllocationRetryArgs = {
76
+ readonly allocationId: string;
77
+ readonly expectedStatus: (typeof activeStatuses)[number];
78
+ readonly expectedGeneration: number;
79
+ readonly nextAttemptAt: Date;
80
+ readonly expectedLeaseId?: string;
81
+ readonly attempt?: "ensure" | "destroy";
82
+ readonly failure?: {
83
+ readonly code: string;
84
+ readonly message: string;
85
+ };
86
+ readonly now?: Date;
87
+ };
88
+ export type BeginSidecarReplacementArgs = {
89
+ readonly allocationId: string;
90
+ readonly expectedStatus: "provisioning" | "allocated";
91
+ readonly expectedGeneration: number;
92
+ readonly expectedLeaseId: string;
93
+ readonly nextAttemptAt: Date;
94
+ readonly failureCode: string;
95
+ readonly failureMessage: string;
96
+ readonly now?: Date;
97
+ };
98
+ export type BeginSidecarReleaseArgs = {
99
+ readonly allocationId: string;
100
+ readonly expectedStatus: Exclude<(typeof activeStatuses)[number], "releasing">;
101
+ readonly expectedGeneration: number;
102
+ readonly failureCode?: string;
103
+ readonly failureMessage?: string;
104
+ readonly expectedLeaseId?: string;
105
+ readonly now?: Date;
106
+ };
107
+ export type BeginUnrecoverableSidecarReleaseArgs = {
108
+ readonly allocationId: string;
109
+ readonly expectedGeneration: number;
110
+ readonly expectedLeaseId: string;
111
+ readonly failureCode: string;
112
+ readonly failureMessage: string;
113
+ readonly now?: Date;
114
+ };
115
+ export type MarkSidecarReleasedArgs = {
116
+ readonly allocationId: string;
117
+ readonly generation: number;
118
+ readonly expectedLeaseId?: string;
119
+ readonly now?: Date;
120
+ };
121
+ export type MarkSidecarConnectionReadyArgs = {
122
+ readonly allocationId: string;
123
+ readonly generation: number;
124
+ readonly expectedLeaseId?: string;
125
+ readonly now?: Date;
126
+ };
127
+ export type MarkSidecarConnectionLostArgs = {
128
+ readonly allocationId: string;
129
+ readonly generation: number;
130
+ readonly connectDeadline: Date;
131
+ readonly now?: Date;
132
+ };
133
+ export type ScheduleSidecarReconnectIfUnscheduledArgs = {
134
+ readonly allocationId: string;
135
+ readonly generation: number;
136
+ readonly connectDeadline: Date;
137
+ readonly now?: Date;
138
+ };
139
+ export type FailSidecarAllocationArgs = {
140
+ readonly allocationId: string;
141
+ readonly expectedStatus: "pending" | "provisioning";
142
+ readonly expectedGeneration: number;
143
+ readonly code: string;
144
+ readonly message: string;
145
+ readonly expectedLeaseId?: string;
146
+ readonly now?: Date;
147
+ };
148
+ export declare function createSidecarAllocationStore(db: DBHandle): {
149
+ createPending(args: CreatePendingSidecarAllocationArgs, tx?: DBExecutor): Promise<SidecarAllocation>;
150
+ bindInitialSidecar(args: BindInitialSidecarArgs): Promise<SidecarAllocation | null>;
151
+ bindReplacementSidecar(args: BindReplacementSidecarArgs): Promise<SidecarAllocation | null>;
152
+ markAllocated(args: MarkSidecarAllocatedArgs): Promise<SidecarAllocation | null>;
153
+ scheduleRetry(args: ScheduleSidecarAllocationRetryArgs): Promise<SidecarAllocation | null>;
154
+ beginReplacement(args: BeginSidecarReplacementArgs): Promise<SidecarAllocation | null>;
155
+ beginRelease: (args: BeginSidecarReleaseArgs, tx?: DBExecutor) => Promise<SidecarAllocation | null>;
156
+ beginUnrecoverableRelease(args: BeginUnrecoverableSidecarReleaseArgs): Promise<SidecarAllocation | null>;
157
+ markReleased(args: MarkSidecarReleasedArgs): Promise<SidecarAllocation | null>;
158
+ failWithoutInfrastructure(args: FailSidecarAllocationArgs, tx?: DBExecutor): Promise<SidecarAllocation | null>;
159
+ findById(id: string): Promise<SidecarAllocation | null>;
160
+ findByAnchorRunId(anchorRunId: string): Promise<SidecarAllocation | null>;
161
+ claimNextReconcilable(args: ClaimSidecarAllocationArgs): Promise<SidecarAllocation | null>;
162
+ extendReconciliationLease(allocationId: string, leaseId: string, leaseDurationMs: number): Promise<boolean>;
163
+ markConnectionReady(args: MarkSidecarConnectionReadyArgs): Promise<SidecarAllocation | null>;
164
+ markConnectionLost(args: MarkSidecarConnectionLostArgs): Promise<SidecarAllocation | null>;
165
+ scheduleReconnectIfUnscheduled(args: ScheduleSidecarReconnectIfUnscheduledArgs): Promise<SidecarAllocation | null>;
166
+ parkReconciliation(allocationId: string, leaseId: string, policy: ParkSidecarReconciliationPolicy): Promise<boolean>;
167
+ wakeReconciliation(allocationId: string, generation: number): Promise<boolean>;
168
+ listActive(): Promise<SidecarAllocation[]>;
169
+ };
170
+ export type SidecarAllocationStore = ReturnType<typeof createSidecarAllocationStore>;
171
+ export {};
@@ -0,0 +1,512 @@
1
+ import { type } from "arktype";
2
+ import { and, asc, eq, inArray, isNull, lte, or, sql } from "drizzle-orm";
3
+ import { SidecarPlacementRequirement, 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
+ const placement = SidecarPlacementRequirement.assert({
17
+ sharing: row.placementSharing,
18
+ reuse: row.sidecarReuse,
19
+ });
20
+ return {
21
+ id: row.id,
22
+ anchorRunId: row.anchorRunId,
23
+ tenantId: row.tenantId,
24
+ provisionerId: row.provisionerId,
25
+ provisionerApiVersion: SidecarProvisionerApiVersion.assert(row.provisionerApiVersion),
26
+ provisionerBindingFingerprint: row.provisionerBindingFingerprint,
27
+ ...(row.sidecarId !== null ? { sidecarId: row.sidecarId } : {}),
28
+ placement,
29
+ status: SidecarAllocationStatusValidator.assert(row.status),
30
+ generation: row.generation,
31
+ ...(row.ensureAcceptedGeneration !== null
32
+ ? { ensureAcceptedGeneration: row.ensureAcceptedGeneration }
33
+ : {}),
34
+ ...(row.externalRef !== null ? { externalRef: row.externalRef } : {}),
35
+ ...(row.nextAttemptAt !== null ? { nextAttemptAt: row.nextAttemptAt } : {}),
36
+ ...(row.reconciliationLeaseId !== null
37
+ ? { reconciliationLeaseId: row.reconciliationLeaseId }
38
+ : {}),
39
+ ...(row.reconciliationLeaseExpiresAt !== null
40
+ ? { reconciliationLeaseExpiresAt: row.reconciliationLeaseExpiresAt }
41
+ : {}),
42
+ ensureAttempts: row.ensureAttempts,
43
+ destroyAttempts: row.destroyAttempts,
44
+ ...(row.connectDeadline !== null
45
+ ? { connectDeadline: row.connectDeadline }
46
+ : {}),
47
+ ...(row.failureCode !== null ? { failureCode: row.failureCode } : {}),
48
+ ...(row.failureMessage !== null
49
+ ? { failureMessage: row.failureMessage }
50
+ : {}),
51
+ createdAt: row.createdAt,
52
+ updatedAt: row.updatedAt,
53
+ };
54
+ }
55
+ function databaseTimestamp(override) {
56
+ return override ?? sql `now()`;
57
+ }
58
+ function leaseCondition(expectedLeaseId) {
59
+ return expectedLeaseId === undefined
60
+ ? []
61
+ : [eq(sidecarAllocation.reconciliationLeaseId, expectedLeaseId)];
62
+ }
63
+ export function createSidecarAllocationStore(db) {
64
+ const workflowRunDispatchStore = createWorkflowRunDispatchStore(db);
65
+ async function insertSidecarIdentity(tx, args) {
66
+ await tx.insert(sidecar).values({
67
+ id: args.sidecarId,
68
+ url: null,
69
+ tokenHashSha256: args.tokenHashSha256,
70
+ credentialScope: "allocated",
71
+ status: "offline",
72
+ createdAt: args.now,
73
+ updatedAt: args.now,
74
+ });
75
+ }
76
+ async function failRunningRuns(tx, anchorRunId, now) {
77
+ // Fail every live run anchored here -- both "running" runs and a "deployed"
78
+ // anchor torn down before its first trigger -- so a release settles them.
79
+ const failedRuns = await tx
80
+ .update(workflowRun)
81
+ .set({ status: "failed", endedAt: now })
82
+ .where(and(eq(workflowRun.anchorRunId, anchorRunId), inArray(workflowRun.status, [...liveWorkflowRunStatuses])))
83
+ .returning({ principalId: workflowRun.principalId });
84
+ const principalIds = failedRuns.flatMap(({ principalId }) => principalId === null ? [] : [principalId]);
85
+ if (principalIds.length > 0) {
86
+ await tx
87
+ .update(principal)
88
+ .set({ status: "deactivated", updatedAt: now })
89
+ .where(inArray(principal.id, principalIds));
90
+ }
91
+ }
92
+ async function beginRelease(args, tx) {
93
+ const now = databaseTimestamp(args.now);
94
+ const [updated] = await (tx ?? db)
95
+ .update(sidecarAllocation)
96
+ .set({
97
+ status: "releasing",
98
+ generation: args.expectedGeneration + 1,
99
+ nextAttemptAt: now,
100
+ reconciliationLeaseId: null,
101
+ reconciliationLeaseExpiresAt: null,
102
+ connectDeadline: null,
103
+ ...(args.failureCode !== undefined
104
+ ? { failureCode: args.failureCode }
105
+ : {}),
106
+ ...(args.failureMessage !== undefined
107
+ ? { failureMessage: args.failureMessage }
108
+ : {}),
109
+ updatedAt: now,
110
+ })
111
+ .where(and(eq(sidecarAllocation.id, args.allocationId), eq(sidecarAllocation.status, args.expectedStatus), eq(sidecarAllocation.generation, args.expectedGeneration), ...leaseCondition(args.expectedLeaseId)))
112
+ .returning();
113
+ return updated === undefined ? null : parseSidecarAllocationRow(updated);
114
+ }
115
+ return {
116
+ async createPending(args, tx) {
117
+ const executor = tx ?? db;
118
+ const [anchor] = await executor
119
+ .select({
120
+ tenantId: workflowRun.tenantId,
121
+ anchorRunId: workflowRun.anchorRunId,
122
+ status: workflowRun.status,
123
+ })
124
+ .from(workflowRun)
125
+ .where(eq(workflowRun.id, args.anchorRunId))
126
+ .limit(1);
127
+ if (anchor === undefined) {
128
+ throw new Error(`sidecarAllocationStore.createPending: anchor run ${args.anchorRunId} does not exist`);
129
+ }
130
+ if (anchor.tenantId !== args.tenantId ||
131
+ anchor.anchorRunId !== args.anchorRunId) {
132
+ throw new Error(`sidecarAllocationStore.createPending: run ${args.anchorRunId} is not an anchor for tenant ${args.tenantId}`);
133
+ }
134
+ if (!isLiveWorkflowRunStatus(anchor.status)) {
135
+ throw new Error(`sidecarAllocationStore.createPending: anchor run ${args.anchorRunId} is ${anchor.status}, expected a live run`);
136
+ }
137
+ const launchSpec = await executor.query.workflowRunLaunchSpec.findFirst({
138
+ columns: { anchorRunId: true },
139
+ where: eq(workflowRunLaunchSpec.anchorRunId, args.anchorRunId),
140
+ });
141
+ if (launchSpec === undefined) {
142
+ throw new Error(`sidecarAllocationStore.createPending: anchor run ${args.anchorRunId} has no launch specification`);
143
+ }
144
+ if (args.provisionerApiVersion !== 1) {
145
+ throw new Error("sidecarAllocationStore.createPending: unsupported API version");
146
+ }
147
+ const placement = SidecarPlacementRequirement.assert(args.placement ?? { sharing: "exclusive", reuse: "never" });
148
+ const now = databaseTimestamp(args.now);
149
+ const [inserted] = await executor
150
+ .insert(sidecarAllocation)
151
+ .values({
152
+ id: args.id,
153
+ anchorRunId: args.anchorRunId,
154
+ tenantId: args.tenantId,
155
+ provisionerId: args.provisionerId,
156
+ provisionerApiVersion: args.provisionerApiVersion,
157
+ provisionerBindingFingerprint: args.provisionerBindingFingerprint,
158
+ placementSharing: placement.sharing,
159
+ sidecarReuse: placement.reuse ?? "never",
160
+ status: "pending",
161
+ generation: 0,
162
+ nextAttemptAt: now,
163
+ createdAt: now,
164
+ updatedAt: now,
165
+ })
166
+ .returning();
167
+ if (inserted === undefined) {
168
+ throw new Error(`sidecarAllocationStore.createPending: insert returned no row for ${args.id}`);
169
+ }
170
+ return parseSidecarAllocationRow(inserted);
171
+ },
172
+ async bindInitialSidecar(args) {
173
+ return db.transaction(async (tx) => {
174
+ const [allocation] = await tx
175
+ .select()
176
+ .from(sidecarAllocation)
177
+ .where(eq(sidecarAllocation.id, args.allocationId))
178
+ .limit(1)
179
+ .for("update");
180
+ if (allocation === undefined ||
181
+ allocation.status !== "pending" ||
182
+ allocation.generation !== args.expectedGeneration ||
183
+ (args.expectedLeaseId !== undefined &&
184
+ allocation.reconciliationLeaseId !== args.expectedLeaseId)) {
185
+ return null;
186
+ }
187
+ const now = databaseTimestamp(args.now);
188
+ await insertSidecarIdentity(tx, {
189
+ sidecarId: args.sidecarId,
190
+ tokenHashSha256: args.tokenHashSha256,
191
+ now,
192
+ });
193
+ const [updated] = await tx
194
+ .update(sidecarAllocation)
195
+ .set({
196
+ sidecarId: args.sidecarId,
197
+ status: "provisioning",
198
+ generation: args.expectedGeneration + 1,
199
+ ensureAcceptedGeneration: null,
200
+ externalRef: null,
201
+ connectDeadline: args.connectDeadline,
202
+ nextAttemptAt: now,
203
+ failureCode: null,
204
+ failureMessage: null,
205
+ updatedAt: now,
206
+ })
207
+ .where(and(eq(sidecarAllocation.id, args.allocationId), eq(sidecarAllocation.status, "pending"), eq(sidecarAllocation.generation, args.expectedGeneration), ...leaseCondition(args.expectedLeaseId)))
208
+ .returning();
209
+ if (updated === undefined) {
210
+ throw new Error(`sidecarAllocationStore.bindInitialSidecar: locked allocation ${args.allocationId} changed before update`);
211
+ }
212
+ return parseSidecarAllocationRow(updated);
213
+ });
214
+ },
215
+ async bindReplacementSidecar(args) {
216
+ return db.transaction(async (tx) => {
217
+ const [allocation] = await tx
218
+ .select()
219
+ .from(sidecarAllocation)
220
+ .where(eq(sidecarAllocation.id, args.allocationId))
221
+ .limit(1)
222
+ .for("update");
223
+ if (allocation === undefined ||
224
+ allocation.status !== "replacing" ||
225
+ allocation.generation !== args.generation ||
226
+ (args.expectedLeaseId !== undefined &&
227
+ allocation.reconciliationLeaseId !== args.expectedLeaseId)) {
228
+ return null;
229
+ }
230
+ const now = databaseTimestamp(args.now);
231
+ await insertSidecarIdentity(tx, {
232
+ sidecarId: args.sidecarId,
233
+ tokenHashSha256: args.tokenHashSha256,
234
+ now,
235
+ });
236
+ const [updated] = await tx
237
+ .update(sidecarAllocation)
238
+ .set({
239
+ sidecarId: args.sidecarId,
240
+ status: "provisioning",
241
+ ensureAcceptedGeneration: null,
242
+ externalRef: null,
243
+ connectDeadline: args.connectDeadline,
244
+ nextAttemptAt: now,
245
+ destroyAttempts: sql `${sidecarAllocation.destroyAttempts} + 1`,
246
+ failureCode: null,
247
+ failureMessage: null,
248
+ updatedAt: now,
249
+ })
250
+ .where(and(eq(sidecarAllocation.id, args.allocationId), eq(sidecarAllocation.status, "replacing"), eq(sidecarAllocation.generation, args.generation), ...leaseCondition(args.expectedLeaseId)))
251
+ .returning();
252
+ if (updated === undefined) {
253
+ throw new Error(`sidecarAllocationStore.bindReplacementSidecar: locked allocation ${args.allocationId} changed before update`);
254
+ }
255
+ return parseSidecarAllocationRow(updated);
256
+ });
257
+ },
258
+ async markAllocated(args) {
259
+ const [updated] = await db
260
+ .update(sidecarAllocation)
261
+ .set({
262
+ status: "allocated",
263
+ ensureAcceptedGeneration: args.generation,
264
+ externalRef: args.externalRef ?? null,
265
+ nextAttemptAt: sidecarAllocation.connectDeadline,
266
+ ensureAttempts: sql `${sidecarAllocation.ensureAttempts} + 1`,
267
+ failureCode: null,
268
+ failureMessage: null,
269
+ updatedAt: databaseTimestamp(args.now),
270
+ })
271
+ .where(and(eq(sidecarAllocation.id, args.allocationId), eq(sidecarAllocation.status, "provisioning"), eq(sidecarAllocation.generation, args.generation), ...leaseCondition(args.expectedLeaseId)))
272
+ .returning();
273
+ return updated === undefined ? null : parseSidecarAllocationRow(updated);
274
+ },
275
+ async scheduleRetry(args) {
276
+ const [updated] = await db
277
+ .update(sidecarAllocation)
278
+ .set({
279
+ nextAttemptAt: args.nextAttemptAt,
280
+ reconciliationLeaseId: null,
281
+ reconciliationLeaseExpiresAt: null,
282
+ ...(args.attempt === "ensure"
283
+ ? { ensureAttempts: sql `${sidecarAllocation.ensureAttempts} + 1` }
284
+ : {}),
285
+ ...(args.attempt === "destroy"
286
+ ? { destroyAttempts: sql `${sidecarAllocation.destroyAttempts} + 1` }
287
+ : {}),
288
+ ...(args.failure !== undefined
289
+ ? {
290
+ failureCode: args.failure.code,
291
+ failureMessage: args.failure.message,
292
+ }
293
+ : {}),
294
+ updatedAt: databaseTimestamp(args.now),
295
+ })
296
+ .where(and(eq(sidecarAllocation.id, args.allocationId), eq(sidecarAllocation.status, args.expectedStatus), eq(sidecarAllocation.generation, args.expectedGeneration), ...leaseCondition(args.expectedLeaseId)))
297
+ .returning();
298
+ return updated === undefined ? null : parseSidecarAllocationRow(updated);
299
+ },
300
+ async beginReplacement(args) {
301
+ const now = databaseTimestamp(args.now);
302
+ return db.transaction(async (tx) => {
303
+ const [updated] = await tx
304
+ .update(sidecarAllocation)
305
+ .set({
306
+ status: "replacing",
307
+ generation: args.expectedGeneration + 1,
308
+ ensureAcceptedGeneration: null,
309
+ nextAttemptAt: args.nextAttemptAt,
310
+ reconciliationLeaseId: null,
311
+ reconciliationLeaseExpiresAt: null,
312
+ connectDeadline: null,
313
+ failureCode: args.failureCode,
314
+ failureMessage: args.failureMessage,
315
+ updatedAt: now,
316
+ })
317
+ .where(and(eq(sidecarAllocation.id, args.allocationId), eq(sidecarAllocation.status, args.expectedStatus), eq(sidecarAllocation.generation, args.expectedGeneration), ...leaseCondition(args.expectedLeaseId)))
318
+ .returning();
319
+ if (updated === undefined)
320
+ return null;
321
+ // The anchor public key is durable proof that this allocation
322
+ // generation completed restore and deployment. Clear it atomically
323
+ // with the generation advance so a replacement cannot mistake stale
324
+ // local sidecar state for a successfully restored workflow.
325
+ await tx
326
+ .update(workflowRun)
327
+ .set({ publicKey: null })
328
+ .where(eq(workflowRun.id, updated.anchorRunId));
329
+ return parseSidecarAllocationRow(updated);
330
+ });
331
+ },
332
+ beginRelease,
333
+ async beginUnrecoverableRelease(args) {
334
+ const now = databaseTimestamp(args.now);
335
+ return db.transaction(async (tx) => {
336
+ const releasing = await beginRelease({
337
+ ...args,
338
+ expectedStatus: "allocated",
339
+ }, tx);
340
+ if (releasing === null)
341
+ return null;
342
+ await failRunningRuns(tx, releasing.anchorRunId, now);
343
+ await workflowRunDispatchStore.failUnsettled(releasing.anchorRunId, args.failureCode, args.failureMessage, now, tx);
344
+ return releasing;
345
+ });
346
+ },
347
+ async markReleased(args) {
348
+ const [updated] = await db
349
+ .update(sidecarAllocation)
350
+ .set({
351
+ status: "released",
352
+ nextAttemptAt: null,
353
+ reconciliationLeaseId: null,
354
+ reconciliationLeaseExpiresAt: null,
355
+ connectDeadline: null,
356
+ destroyAttempts: sql `${sidecarAllocation.destroyAttempts} + 1`,
357
+ updatedAt: databaseTimestamp(args.now),
358
+ })
359
+ .where(and(eq(sidecarAllocation.id, args.allocationId), eq(sidecarAllocation.status, "releasing"), eq(sidecarAllocation.generation, args.generation), ...leaseCondition(args.expectedLeaseId)))
360
+ .returning();
361
+ return updated === undefined ? null : parseSidecarAllocationRow(updated);
362
+ },
363
+ async failWithoutInfrastructure(args, tx) {
364
+ const now = databaseTimestamp(args.now);
365
+ const fail = async (executor) => {
366
+ const [updated] = await executor
367
+ .update(sidecarAllocation)
368
+ .set({
369
+ status: "failed",
370
+ failureCode: args.code,
371
+ failureMessage: args.message,
372
+ nextAttemptAt: null,
373
+ reconciliationLeaseId: null,
374
+ reconciliationLeaseExpiresAt: null,
375
+ connectDeadline: null,
376
+ updatedAt: now,
377
+ })
378
+ .where(and(eq(sidecarAllocation.id, args.allocationId), eq(sidecarAllocation.status, args.expectedStatus), eq(sidecarAllocation.generation, args.expectedGeneration), ...leaseCondition(args.expectedLeaseId)))
379
+ .returning();
380
+ if (updated === undefined)
381
+ return null;
382
+ await failRunningRuns(executor, updated.anchorRunId, now);
383
+ await workflowRunDispatchStore.failUnsettled(updated.anchorRunId, args.code, args.message, now, executor);
384
+ return parseSidecarAllocationRow(updated);
385
+ };
386
+ return tx === undefined ? db.transaction(fail) : fail(tx);
387
+ },
388
+ async findById(id) {
389
+ const row = await db.query.sidecarAllocation.findFirst({
390
+ where: eq(sidecarAllocation.id, id),
391
+ });
392
+ return row === undefined ? null : parseSidecarAllocationRow(row);
393
+ },
394
+ async findByAnchorRunId(anchorRunId) {
395
+ const row = await db.query.sidecarAllocation.findFirst({
396
+ where: eq(sidecarAllocation.anchorRunId, anchorRunId),
397
+ });
398
+ return row === undefined ? null : parseSidecarAllocationRow(row);
399
+ },
400
+ async claimNextReconcilable(args) {
401
+ return db.transaction(async (tx) => {
402
+ const [candidate] = await tx
403
+ .select({ id: sidecarAllocation.id })
404
+ .from(sidecarAllocation)
405
+ .where(and(inArray(sidecarAllocation.status, activeStatuses), lte(sidecarAllocation.nextAttemptAt, sql `now()`), or(isNull(sidecarAllocation.reconciliationLeaseExpiresAt), lte(sidecarAllocation.reconciliationLeaseExpiresAt, sql `now()`))))
406
+ .orderBy(asc(sidecarAllocation.nextAttemptAt), asc(sidecarAllocation.createdAt))
407
+ .limit(1)
408
+ .for("update", { skipLocked: true });
409
+ if (candidate === undefined)
410
+ return null;
411
+ const [claimed] = await tx
412
+ .update(sidecarAllocation)
413
+ .set({
414
+ reconciliationLeaseId: args.leaseId,
415
+ reconciliationLeaseExpiresAt: sql `now() + (${args.leaseDurationMs} * interval '1 millisecond')`,
416
+ })
417
+ .where(eq(sidecarAllocation.id, candidate.id))
418
+ .returning();
419
+ return claimed === undefined
420
+ ? null
421
+ : parseSidecarAllocationRow(claimed);
422
+ });
423
+ },
424
+ async extendReconciliationLease(allocationId, leaseId, leaseDurationMs) {
425
+ const [updated] = await db
426
+ .update(sidecarAllocation)
427
+ .set({
428
+ reconciliationLeaseExpiresAt: sql `now() + (${leaseDurationMs} * interval '1 millisecond')`,
429
+ })
430
+ .where(and(eq(sidecarAllocation.id, allocationId), eq(sidecarAllocation.reconciliationLeaseId, leaseId)))
431
+ .returning({ id: sidecarAllocation.id });
432
+ return updated !== undefined;
433
+ },
434
+ async markConnectionReady(args) {
435
+ const [updated] = await db
436
+ .update(sidecarAllocation)
437
+ .set({
438
+ connectDeadline: null,
439
+ nextAttemptAt: null,
440
+ reconciliationLeaseId: null,
441
+ reconciliationLeaseExpiresAt: null,
442
+ failureCode: null,
443
+ failureMessage: null,
444
+ updatedAt: databaseTimestamp(args.now),
445
+ })
446
+ .where(and(eq(sidecarAllocation.id, args.allocationId), eq(sidecarAllocation.status, "allocated"), eq(sidecarAllocation.generation, args.generation), eq(sidecarAllocation.ensureAcceptedGeneration, args.generation), ...leaseCondition(args.expectedLeaseId)))
447
+ .returning();
448
+ return updated === undefined ? null : parseSidecarAllocationRow(updated);
449
+ },
450
+ async markConnectionLost(args) {
451
+ const [updated] = await db
452
+ .update(sidecarAllocation)
453
+ .set({
454
+ connectDeadline: args.connectDeadline,
455
+ nextAttemptAt: args.connectDeadline,
456
+ reconciliationLeaseId: null,
457
+ reconciliationLeaseExpiresAt: null,
458
+ updatedAt: databaseTimestamp(args.now),
459
+ })
460
+ .where(and(eq(sidecarAllocation.id, args.allocationId), eq(sidecarAllocation.status, "allocated"), eq(sidecarAllocation.generation, args.generation), eq(sidecarAllocation.ensureAcceptedGeneration, args.generation)))
461
+ .returning();
462
+ return updated === undefined ? null : parseSidecarAllocationRow(updated);
463
+ },
464
+ async scheduleReconnectIfUnscheduled(args) {
465
+ const connectDeadline = sql.param(args.connectDeadline, sidecarAllocation.connectDeadline);
466
+ const [updated] = await db
467
+ .update(sidecarAllocation)
468
+ .set({
469
+ connectDeadline: sql `coalesce(${sidecarAllocation.connectDeadline}, ${connectDeadline})`,
470
+ nextAttemptAt: sql `coalesce(${sidecarAllocation.connectDeadline}, ${connectDeadline})`,
471
+ updatedAt: databaseTimestamp(args.now),
472
+ })
473
+ .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)))
474
+ .returning();
475
+ return updated === undefined ? null : parseSidecarAllocationRow(updated);
476
+ },
477
+ async parkReconciliation(allocationId, leaseId, policy) {
478
+ const fallbackNextAttemptAt = policy.kind === "await-connection"
479
+ ? policy.fallbackNextAttemptAt
480
+ : policy.notBefore;
481
+ const fallback = sql.param(fallbackNextAttemptAt, sidecarAllocation.nextAttemptAt);
482
+ const nextAttemptAt = policy.kind === "await-connection"
483
+ ? sql `case when ${sidecarAllocation.connectDeadline} is null then ${fallback} else coalesce(${sidecarAllocation.nextAttemptAt}, ${sidecarAllocation.connectDeadline}) end`
484
+ : sql `greatest(coalesce(${sidecarAllocation.nextAttemptAt}, ${sidecarAllocation.connectDeadline}, ${fallback}), ${fallback})`;
485
+ const [updated] = await db
486
+ .update(sidecarAllocation)
487
+ .set({
488
+ nextAttemptAt,
489
+ reconciliationLeaseId: null,
490
+ reconciliationLeaseExpiresAt: null,
491
+ })
492
+ .where(and(eq(sidecarAllocation.id, allocationId), eq(sidecarAllocation.reconciliationLeaseId, leaseId)))
493
+ .returning({ id: sidecarAllocation.id });
494
+ return updated !== undefined;
495
+ },
496
+ async wakeReconciliation(allocationId, generation) {
497
+ const [updated] = await db
498
+ .update(sidecarAllocation)
499
+ .set({ nextAttemptAt: sql `now()` })
500
+ .where(and(eq(sidecarAllocation.id, allocationId), eq(sidecarAllocation.generation, generation), inArray(sidecarAllocation.status, activeStatuses)))
501
+ .returning({ id: sidecarAllocation.id });
502
+ return updated !== undefined;
503
+ },
504
+ async listActive() {
505
+ const rows = await db
506
+ .select()
507
+ .from(sidecarAllocation)
508
+ .where(inArray(sidecarAllocation.status, activeStatuses));
509
+ return rows.map(parseSidecarAllocationRow);
510
+ },
511
+ };
512
+ }