@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.
- package/README.md +13 -2
- package/dist/approval-store.d.ts +59 -0
- package/dist/approval-store.js +92 -0
- package/dist/asset-resolution.d.ts +2 -2
- package/dist/backfill-principal-keys.d.ts +9 -0
- package/dist/backfill-principal-keys.js +51 -0
- package/dist/client.d.ts +37 -2
- package/dist/client.js +6 -0
- package/dist/config.d.ts +1 -0
- package/dist/config.js +2 -0
- package/dist/connection.js +12 -8
- package/dist/credential-resolution.d.ts +181 -20
- package/dist/credential-resolution.js +251 -3
- package/dist/grant-store.js +35 -13
- package/dist/index.d.ts +20 -5
- package/dist/index.js +19 -5
- package/dist/model-source-resolution.d.ts +54 -7
- package/dist/model-source-resolution.js +177 -22
- package/dist/parse-row.d.ts +245 -34
- package/dist/parse-row.js +117 -18
- package/dist/principal-key-store.d.ts +51 -0
- package/dist/principal-key-store.js +82 -0
- package/dist/principal-store.d.ts +44 -0
- package/dist/principal-store.js +67 -0
- package/dist/rekey-credential-secrets.d.ts +13 -0
- package/dist/rekey-credential-secrets.js +61 -0
- package/dist/schema/approvals.d.ts +245 -0
- package/dist/schema/approvals.js +57 -0
- package/dist/schema/catalog.d.ts +17 -0
- package/dist/schema/catalog.js +7 -1
- package/dist/schema/credentials.d.ts +1 -1
- package/dist/schema/git-tokens.js +1 -1
- package/dist/schema/grants.js +8 -2
- package/dist/schema/index.d.ts +10 -4
- package/dist/schema/index.js +10 -4
- package/dist/schema/messages.d.ts +5 -5
- package/dist/schema/messages.js +20 -9
- package/dist/schema/offerings.js +8 -2
- package/dist/schema/{workflow-deployments.d.ts → principal-keys.d.ts} +29 -32
- package/dist/schema/principal-keys.js +28 -0
- package/dist/schema/principals.d.ts +2 -2
- package/dist/schema/principals.js +7 -5
- package/dist/schema/providers.d.ts +17 -0
- package/dist/schema/providers.js +5 -0
- package/dist/schema/roles.js +6 -2
- package/dist/schema/session-assets.d.ts +1 -38
- package/dist/schema/session-assets.js +36 -19
- package/dist/schema/sessions.js +7 -2
- package/dist/schema/sidecar-allocation.d.ts +385 -0
- package/dist/schema/sidecar-allocation.js +53 -0
- package/dist/schema/sidecar.d.ts +2 -2
- package/dist/schema/sidecar.js +3 -1
- package/dist/schema/signal-correlations.d.ts +177 -0
- package/dist/schema/signal-correlations.js +31 -0
- package/dist/schema/tenants.js +13 -3
- package/dist/schema/wallets.d.ts +3 -3
- package/dist/schema/wallets.js +4 -2
- package/dist/schema/{agents.d.ts → workflow-definitions.d.ts} +79 -113
- package/dist/schema/workflow-definitions.js +97 -0
- package/dist/schema/workflow-probe.d.ts +358 -0
- package/dist/schema/workflow-probe.js +51 -0
- package/dist/schema/workflow-run-dispatch.d.ts +345 -0
- package/dist/schema/workflow-run-dispatch.js +63 -0
- package/dist/schema/{agent-assets.d.ts → workflow-run-execution.d.ts} +44 -27
- package/dist/schema/workflow-run-execution.js +48 -0
- package/dist/schema/workflow-run-launch-spec.d.ts +200 -0
- package/dist/schema/workflow-run-launch-spec.js +26 -0
- package/dist/schema/{instances.d.ts → workflow-run.d.ts} +61 -66
- package/dist/schema/workflow-run.js +110 -0
- package/dist/sender-key-resolver.d.ts +91 -0
- package/dist/sender-key-resolver.js +184 -0
- package/dist/sidecar-allocation-store.d.ts +206 -0
- package/dist/sidecar-allocation-store.js +714 -0
- package/dist/signal-correlation-store.d.ts +36 -0
- package/dist/signal-correlation-store.js +63 -0
- package/dist/signer-identity.d.ts +12 -0
- package/dist/signer-identity.js +15 -0
- package/dist/tenant-hierarchy.d.ts +4 -1
- package/dist/tenant-hierarchy.js +37 -5
- package/dist/workflow-definition-store.d.ts +55 -0
- package/dist/workflow-definition-store.js +93 -0
- package/dist/workflow-probe-store.d.ts +47 -0
- package/dist/workflow-probe-store.js +124 -0
- package/dist/workflow-run-dispatch-store.d.ts +83 -0
- package/dist/workflow-run-dispatch-store.js +308 -0
- package/dist/workflow-run-launch-spec-store.d.ts +13 -0
- package/dist/workflow-run-launch-spec-store.js +30 -0
- package/dist/workflow-run-store.d.ts +54 -0
- package/dist/workflow-run-store.js +85 -0
- package/migrations/0037_credential_use_backfill.sql +39 -0
- package/migrations/0038_chilly_blacklash.sql +37 -0
- package/migrations/0039_quick_carnage.sql +2 -0
- package/migrations/0040_reshape_approval_origin.sql +24 -0
- package/migrations/0041_make_approval_timeout_at_nullable.sql +1 -0
- package/migrations/0042_youthful_mantis.sql +2 -0
- package/migrations/0043_signal_correlation_deployment_fk.sql +1 -0
- package/migrations/0044_model_offering_quirks.sql +1 -0
- package/migrations/0045_create_workflow_run.sql +13 -0
- package/migrations/0046_approval_signal_correlation_run_fk.sql +2 -0
- package/migrations/0047_create_workflow_definition.sql +30 -0
- package/migrations/0048_workflow_definition_origin_agent_id.sql +2 -0
- package/migrations/0049_curly_omega_flight.sql +2 -0
- package/migrations/0050_late_crystal.sql +10 -0
- package/migrations/0051_funny_madelyne_pryor.sql +1 -0
- package/migrations/0052_drop_inference_turn_instance_fk.sql +1 -0
- package/migrations/0053_session_mail_session_id_index.sql +1 -0
- package/migrations/0054_rekey_instance_grants_to_workflow_run.sql +10 -0
- package/migrations/0055_backfill_anchor_workflow_runs.sql +69 -0
- package/migrations/0056_repoint_deployment_fks_to_anchor_run.sql +32 -0
- package/migrations/0057_drop_workflow_deployment_projection.sql +22 -0
- package/migrations/0058_repoint_offering_fk_to_definition.sql +41 -0
- package/migrations/0059_repoint_transaction_fk_to_run.sql +10 -0
- package/migrations/0060_drop_session_mail_instance_fk.sql +6 -0
- package/migrations/0061_drop_session_asset_instance_fk.sql +7 -0
- package/migrations/0062_drop_agent_asset_table.sql +11 -0
- package/migrations/0063_repoint_agent_role_fk_to_definition.sql +39 -0
- package/migrations/0064_repoint_agent_session_fk_to_definition.sql +47 -0
- package/migrations/0065_add_workflow_definition_model_requirements.sql +37 -0
- package/migrations/0066_rekey_agent_definition_principals_to_workflow.sql +27 -0
- package/migrations/0067_add_workflow_definition_kind.sql +10 -0
- package/migrations/0068_drop_agent_tables_and_origin_agent_id.sql +31 -0
- package/migrations/0069_drop_workflow_definition_kind.sql +10 -0
- package/migrations/0070_flashy_proteus.sql +13 -0
- package/migrations/0072_add_workflow_definition_credential_bindings.sql +1 -0
- package/migrations/0073_grant_target_exactly_one_check.sql +13 -0
- package/migrations/0074_add_provider_api_base_url.sql +1 -0
- package/migrations/0075_workflow_run_launch_spec.sql +17 -0
- package/migrations/0076_sidecar_allocation.sql +40 -0
- package/migrations/0077_workflow_run_dispatch.sql +29 -0
- package/migrations/0078_workflow_run_dispatch_kind.sql +2 -0
- package/migrations/0079_rename_workflow_run_deployment_id_to_anchor_run_id.sql +4 -0
- package/migrations/0080_rename_correlation_approval_deployment_id_to_anchor_run_id.sql +9 -0
- package/migrations/0081_workflow_definition_content_hash_and_approved_wire_hash.sql +4 -0
- package/migrations/0082_blue_black_queen.sql +1 -0
- package/migrations/0083_replace_launch_spec_snapshot_with_frozen_bundle.sql +3 -0
- package/migrations/0084_delete_orphaned_credential_grants.sql +22 -0
- package/migrations/0085_add_approval_run_idx.sql +1 -0
- package/migrations/0086_cool_human_cannonball.sql +1 -0
- package/migrations/0087_drop_sidecar_placement.sql +3 -0
- package/migrations/0088_thick_sprite.sql +32 -0
- package/migrations/0089_tense_selene.sql +13 -0
- package/migrations/0090_tenant_domain_lower_unique.sql +2 -0
- package/migrations/0091_workflow_run_dispatch_sender_address.sql +21 -0
- package/migrations/0092_sidecar_destroy_failed.sql +4 -0
- package/migrations/0093_sidecar_initialization.sql +2 -0
- package/migrations/meta/0037_snapshot.json +3237 -0
- package/migrations/meta/0038_snapshot.json +3470 -0
- package/migrations/meta/0039_snapshot.json +3470 -0
- package/migrations/meta/0040_snapshot.json +3489 -0
- package/migrations/meta/0041_snapshot.json +3489 -0
- package/migrations/meta/0042_snapshot.json +3489 -0
- package/migrations/meta/0043_snapshot.json +3498 -0
- package/migrations/meta/0044_snapshot.json +3504 -0
- package/migrations/meta/0045_snapshot.json +3589 -0
- package/migrations/meta/0046_snapshot.json +3607 -0
- package/migrations/meta/0047_snapshot.json +3832 -0
- package/migrations/meta/0048_snapshot.json +3853 -0
- package/migrations/meta/0049_snapshot.json +3854 -0
- package/migrations/meta/0050_snapshot.json +3924 -0
- package/migrations/meta/0051_snapshot.json +3940 -0
- package/migrations/meta/0052_snapshot.json +3931 -0
- package/migrations/meta/0053_snapshot.json +3952 -0
- package/migrations/meta/0054_snapshot.json +3952 -0
- package/migrations/meta/0055_snapshot.json +3952 -0
- package/migrations/meta/0056_snapshot.json +3952 -0
- package/migrations/meta/0057_snapshot.json +3839 -0
- package/migrations/meta/0058_snapshot.json +3839 -0
- package/migrations/meta/0059_snapshot.json +3839 -0
- package/migrations/meta/0060_snapshot.json +3830 -0
- package/migrations/meta/0061_snapshot.json +3821 -0
- package/migrations/meta/0062_snapshot.json +3723 -0
- package/migrations/meta/0063_snapshot.json +3723 -0
- package/migrations/meta/0064_snapshot.json +3723 -0
- package/migrations/meta/0065_snapshot.json +3729 -0
- package/migrations/meta/0066_snapshot.json +3729 -0
- package/migrations/meta/0067_snapshot.json +3736 -0
- package/migrations/meta/0068_snapshot.json +3347 -0
- package/migrations/meta/0069_snapshot.json +3340 -0
- package/migrations/meta/0070_snapshot.json +3444 -0
- package/migrations/meta/0072_snapshot.json +3450 -0
- package/migrations/meta/0073_snapshot.json +3455 -0
- package/migrations/meta/0074_snapshot.json +3461 -0
- package/migrations/meta/0075_snapshot.json +3567 -0
- package/migrations/meta/0076_snapshot.json +3851 -0
- package/migrations/meta/0077_snapshot.json +4067 -0
- package/migrations/meta/0078_snapshot.json +4078 -0
- package/migrations/meta/0079_snapshot.json +4078 -0
- package/migrations/meta/0080_snapshot.json +4078 -0
- package/migrations/meta/0081_snapshot.json +4096 -0
- package/migrations/meta/0082_snapshot.json +4102 -0
- package/migrations/meta/0083_snapshot.json +4096 -0
- package/migrations/meta/0084_snapshot.json +4096 -0
- package/migrations/meta/0085_snapshot.json +4111 -0
- package/migrations/meta/0086_snapshot.json +4117 -0
- package/migrations/meta/0087_snapshot.json +4100 -0
- package/migrations/meta/0088_snapshot.json +4286 -0
- package/migrations/meta/0089_snapshot.json +4376 -0
- package/migrations/meta/0090_snapshot.json +4387 -0
- package/migrations/meta/0091_snapshot.json +4397 -0
- package/migrations/meta/0092_snapshot.json +4397 -0
- package/migrations/meta/0093_snapshot.json +4407 -0
- package/migrations/meta/_journal.json +392 -0
- package/package.json +7 -3
- package/dist/schema/agent-assets.js +0 -15
- package/dist/schema/agents.js +0 -57
- package/dist/schema/instances.js +0 -39
- 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
|
+
}
|