@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,308 @@
1
+ import { isDeepStrictEqual } from "node:util";
2
+ import { and, asc, eq, inArray, isNull, lte, notInArray, or, sql, } from "drizzle-orm";
3
+ import { RunGrantsFrame, SignalDeliverFrame, } from "@intx/types/sidecar";
4
+ import { parseWorkflowRunDispatchRow } from "./parse-row.js";
5
+ import { sidecarAllocation, workflowRun, workflowRunDispatch } from "./schema/index.js";
6
+ export class WorkflowRunDispatchPayloadConflictError extends Error {
7
+ messageId;
8
+ payloadKind;
9
+ constructor(messageId, payloadKind, operation) {
10
+ super(`workflowRunDispatchStore.${operation}: ${payloadKind} ${messageId} conflicts with its durable payload`);
11
+ this.messageId = messageId;
12
+ this.payloadKind = payloadKind;
13
+ this.name = "WorkflowRunDispatchPayloadConflictError";
14
+ }
15
+ }
16
+ function databaseTimestamp(override) {
17
+ return override ?? sql `now()`;
18
+ }
19
+ function leaseCondition(expectedLeaseId) {
20
+ return expectedLeaseId === undefined
21
+ ? []
22
+ : [eq(workflowRunDispatch.deliveryLeaseId, expectedLeaseId)];
23
+ }
24
+ function byteArraysEqual(left, right) {
25
+ if (left.byteLength !== right.byteLength)
26
+ return false;
27
+ return left.every((value, index) => value === right[index]);
28
+ }
29
+ function grantsEqual(left, right) {
30
+ return isDeepStrictEqual(left, right);
31
+ }
32
+ function validateStepGrants(anchorRunId, stepGrants) {
33
+ return RunGrantsFrame.assert({
34
+ type: "run.grants",
35
+ agentAddress: "persisted@validation.invalid",
36
+ runId: anchorRunId,
37
+ stepGrants,
38
+ }).stepGrants;
39
+ }
40
+ async function insertOrReconcileDispatch(executor, args) {
41
+ const [anchor] = await executor
42
+ .select({ anchorRunId: workflowRun.anchorRunId })
43
+ .from(workflowRun)
44
+ .where(eq(workflowRun.id, args.anchorRunId))
45
+ .limit(1);
46
+ if (anchor?.anchorRunId !== args.anchorRunId) {
47
+ throw new Error(`workflowRunDispatchStore.${args.operation}: run ${args.anchorRunId} is not a deployment anchor`);
48
+ }
49
+ const now = databaseTimestamp(args.now);
50
+ const [inserted] = await executor
51
+ .insert(workflowRunDispatch)
52
+ .values({
53
+ id: args.id,
54
+ anchorRunId: args.anchorRunId,
55
+ messageId: args.messageId,
56
+ kind: args.kind,
57
+ senderAddress: args.senderAddress,
58
+ rawMessage: args.rawMessage,
59
+ stepGrants: args.stepGrants,
60
+ status: "pending",
61
+ nextAttemptAt: now,
62
+ createdAt: now,
63
+ updatedAt: now,
64
+ })
65
+ .onConflictDoNothing({
66
+ target: [workflowRunDispatch.anchorRunId, workflowRunDispatch.messageId],
67
+ })
68
+ .returning();
69
+ if (inserted !== undefined) {
70
+ return {
71
+ dispatch: parseWorkflowRunDispatchRow(inserted),
72
+ created: true,
73
+ };
74
+ }
75
+ const existing = await executor.query.workflowRunDispatch.findFirst({
76
+ where: and(eq(workflowRunDispatch.anchorRunId, args.anchorRunId), eq(workflowRunDispatch.messageId, args.messageId)),
77
+ });
78
+ if (existing === undefined) {
79
+ throw new Error(`workflowRunDispatchStore.${args.operation}: conflicting row disappeared for ${args.anchorRunId}/${args.messageId}`);
80
+ }
81
+ const parsed = parseWorkflowRunDispatchRow(existing);
82
+ if (parsed.kind !== args.kind ||
83
+ !byteArraysEqual(parsed.rawMessage, args.rawMessage) ||
84
+ !grantsEqual(parsed.stepGrants, args.stepGrants)) {
85
+ const payloadKind = args.kind === "mail" ? "message" : "signal";
86
+ throw new WorkflowRunDispatchPayloadConflictError(args.messageId, payloadKind, args.operation);
87
+ }
88
+ return { dispatch: parsed, created: false };
89
+ }
90
+ async function acknowledgeMatchingDispatch(executor, generation, conditions, nowOverride) {
91
+ const now = databaseTimestamp(nowOverride);
92
+ const [updated] = await executor
93
+ .update(workflowRunDispatch)
94
+ .set({
95
+ status: "acknowledged",
96
+ acknowledgedGeneration: generation,
97
+ acknowledgedAt: now,
98
+ attemptCount: sql `${workflowRunDispatch.attemptCount} + 1`,
99
+ nextAttemptAt: null,
100
+ deliveryLeaseId: null,
101
+ deliveryLeaseExpiresAt: null,
102
+ failureCode: null,
103
+ failureMessage: null,
104
+ updatedAt: now,
105
+ })
106
+ .where(and(...conditions))
107
+ .returning();
108
+ return updated === undefined ? null : parseWorkflowRunDispatchRow(updated);
109
+ }
110
+ export function createWorkflowRunDispatchStore(db) {
111
+ return {
112
+ async enqueue(args, tx) {
113
+ const stepGrants = validateStepGrants(args.anchorRunId, args.stepGrants);
114
+ return insertOrReconcileDispatch(tx ?? db, {
115
+ operation: "enqueue",
116
+ id: args.id,
117
+ anchorRunId: args.anchorRunId,
118
+ messageId: args.messageId,
119
+ kind: "mail",
120
+ senderAddress: args.senderAddress,
121
+ rawMessage: args.rawMessage,
122
+ stepGrants,
123
+ ...(args.now !== undefined ? { now: args.now } : {}),
124
+ });
125
+ },
126
+ async enqueueSignal(args, tx) {
127
+ const signal = SignalDeliverFrame.assert({
128
+ type: "signal.deliver",
129
+ ...args.signal,
130
+ });
131
+ const encoded = new TextEncoder().encode(JSON.stringify(signal));
132
+ return insertOrReconcileDispatch(tx ?? db, {
133
+ operation: "enqueueSignal",
134
+ id: args.id,
135
+ anchorRunId: args.anchorRunId,
136
+ messageId: signal.signalId,
137
+ kind: "signal",
138
+ senderAddress: null,
139
+ rawMessage: encoded,
140
+ stepGrants: [],
141
+ ...(args.now !== undefined ? { now: args.now } : {}),
142
+ });
143
+ },
144
+ async claimNextPending(args) {
145
+ return db.transaction(async (tx) => {
146
+ const [candidate] = await tx
147
+ .select({ id: workflowRunDispatch.id })
148
+ .from(workflowRunDispatch)
149
+ .where(and(eq(workflowRunDispatch.status, "pending"), lte(workflowRunDispatch.nextAttemptAt, sql `now()`), ...(args.excludedDispatchIds !== undefined &&
150
+ args.excludedDispatchIds.length > 0
151
+ ? [
152
+ notInArray(workflowRunDispatch.id, [
153
+ ...args.excludedDispatchIds,
154
+ ]),
155
+ ]
156
+ : []), or(isNull(workflowRunDispatch.deliveryLeaseExpiresAt), lte(workflowRunDispatch.deliveryLeaseExpiresAt, sql `now()`))))
157
+ .orderBy(asc(workflowRunDispatch.nextAttemptAt), asc(workflowRunDispatch.createdAt))
158
+ .limit(1)
159
+ .for("update", { skipLocked: true });
160
+ if (candidate === undefined)
161
+ return null;
162
+ const [claimed] = await tx
163
+ .update(workflowRunDispatch)
164
+ .set({
165
+ deliveryLeaseId: args.leaseId,
166
+ deliveryLeaseExpiresAt: sql `now() + (${args.leaseDurationMs} * interval '1 millisecond')`,
167
+ })
168
+ .where(eq(workflowRunDispatch.id, candidate.id))
169
+ .returning();
170
+ return claimed === undefined
171
+ ? null
172
+ : parseWorkflowRunDispatchRow(claimed);
173
+ });
174
+ },
175
+ async acknowledge(args) {
176
+ return db.transaction(async (tx) => {
177
+ const [allocation] = await tx
178
+ .select({
179
+ anchorRunId: sidecarAllocation.anchorRunId,
180
+ status: sidecarAllocation.status,
181
+ generation: sidecarAllocation.generation,
182
+ ensureAcceptedGeneration: sidecarAllocation.ensureAcceptedGeneration,
183
+ })
184
+ .from(sidecarAllocation)
185
+ .where(eq(sidecarAllocation.id, args.allocationId))
186
+ .limit(1)
187
+ .for("update");
188
+ if (allocation === undefined ||
189
+ allocation.anchorRunId !== args.anchorRunId ||
190
+ allocation.status !== "allocated" ||
191
+ allocation.generation !== args.generation ||
192
+ allocation.ensureAcceptedGeneration !== args.generation) {
193
+ return null;
194
+ }
195
+ return acknowledgeMatchingDispatch(tx, args.generation, [
196
+ eq(workflowRunDispatch.anchorRunId, args.anchorRunId),
197
+ eq(workflowRunDispatch.messageId, args.messageId),
198
+ eq(workflowRunDispatch.kind, "mail"),
199
+ eq(workflowRunDispatch.status, "pending"),
200
+ ], args.now);
201
+ });
202
+ },
203
+ async scheduleRetry(args) {
204
+ const [updated] = await db
205
+ .update(workflowRunDispatch)
206
+ .set({
207
+ nextAttemptAt: args.nextAttemptAt,
208
+ attemptCount: sql `${workflowRunDispatch.attemptCount} + 1`,
209
+ deliveryLeaseId: null,
210
+ deliveryLeaseExpiresAt: null,
211
+ failureCode: args.code,
212
+ failureMessage: args.message,
213
+ updatedAt: databaseTimestamp(args.now),
214
+ })
215
+ .where(and(eq(workflowRunDispatch.id, args.dispatchId), eq(workflowRunDispatch.status, "pending"), ...leaseCondition(args.expectedLeaseId)))
216
+ .returning();
217
+ return updated === undefined
218
+ ? null
219
+ : parseWorkflowRunDispatchRow(updated);
220
+ },
221
+ async settle(anchorRunId, messageId, now = new Date()) {
222
+ const [updated] = await db
223
+ .update(workflowRunDispatch)
224
+ .set({
225
+ status: "settled",
226
+ nextAttemptAt: null,
227
+ deliveryLeaseId: null,
228
+ deliveryLeaseExpiresAt: null,
229
+ failureCode: null,
230
+ failureMessage: null,
231
+ settledAt: now,
232
+ updatedAt: now,
233
+ })
234
+ .where(and(eq(workflowRunDispatch.anchorRunId, anchorRunId), eq(workflowRunDispatch.messageId, messageId), inArray(workflowRunDispatch.status, ["pending", "acknowledged"])))
235
+ .returning();
236
+ return updated === undefined
237
+ ? null
238
+ : parseWorkflowRunDispatchRow(updated);
239
+ },
240
+ async fail(args) {
241
+ const now = args.now ?? new Date();
242
+ const [updated] = await db
243
+ .update(workflowRunDispatch)
244
+ .set({
245
+ status: "failed",
246
+ nextAttemptAt: null,
247
+ deliveryLeaseId: null,
248
+ deliveryLeaseExpiresAt: null,
249
+ failureCode: args.code,
250
+ failureMessage: args.message,
251
+ updatedAt: now,
252
+ })
253
+ .where(and(eq(workflowRunDispatch.anchorRunId, args.anchorRunId), eq(workflowRunDispatch.messageId, args.messageId), inArray(workflowRunDispatch.status, ["pending", "acknowledged"])))
254
+ .returning();
255
+ return updated === undefined
256
+ ? null
257
+ : parseWorkflowRunDispatchRow(updated);
258
+ },
259
+ async failUnsettled(anchorRunId, code, message, now = new Date(), tx) {
260
+ const rows = await (tx ?? db)
261
+ .update(workflowRunDispatch)
262
+ .set({
263
+ status: "failed",
264
+ nextAttemptAt: null,
265
+ deliveryLeaseId: null,
266
+ deliveryLeaseExpiresAt: null,
267
+ failureCode: code,
268
+ failureMessage: message,
269
+ updatedAt: now,
270
+ })
271
+ .where(and(eq(workflowRunDispatch.anchorRunId, anchorRunId), inArray(workflowRunDispatch.status, ["pending", "acknowledged"])))
272
+ .returning({ id: workflowRunDispatch.id });
273
+ return rows.length;
274
+ },
275
+ async requeueUnsettled(anchorRunId) {
276
+ const rows = await db
277
+ .update(workflowRunDispatch)
278
+ .set({
279
+ status: "pending",
280
+ acknowledgedGeneration: null,
281
+ acknowledgedAt: null,
282
+ nextAttemptAt: sql `now()`,
283
+ deliveryLeaseId: null,
284
+ deliveryLeaseExpiresAt: null,
285
+ failureCode: null,
286
+ failureMessage: null,
287
+ updatedAt: sql `now()`,
288
+ })
289
+ .where(and(eq(workflowRunDispatch.anchorRunId, anchorRunId), inArray(workflowRunDispatch.status, ["pending", "acknowledged"])))
290
+ .returning({ id: workflowRunDispatch.id });
291
+ return rows.length;
292
+ },
293
+ async findById(id) {
294
+ const row = await db.query.workflowRunDispatch.findFirst({
295
+ where: eq(workflowRunDispatch.id, id),
296
+ });
297
+ return row === undefined ? null : parseWorkflowRunDispatchRow(row);
298
+ },
299
+ async listUnsettled(anchorRunId) {
300
+ const rows = await db
301
+ .select()
302
+ .from(workflowRunDispatch)
303
+ .where(and(eq(workflowRunDispatch.anchorRunId, anchorRunId), inArray(workflowRunDispatch.status, ["pending", "acknowledged"])))
304
+ .orderBy(asc(workflowRunDispatch.createdAt));
305
+ return rows.map(parseWorkflowRunDispatchRow);
306
+ },
307
+ };
308
+ }
@@ -0,0 +1,13 @@
1
+ import type { DB, DBExecutor } from "./client.js";
2
+ import { parseWorkflowRunLaunchSpecRow } from "./parse-row.js";
3
+ import { workflowRunLaunchSpec } from "./schema/workflow-run-launch-spec.js";
4
+ type DBHandle = DB["db"];
5
+ type LaunchSpecInsert = typeof workflowRunLaunchSpec.$inferInsert;
6
+ type ParsedLaunchSpec = ReturnType<typeof parseWorkflowRunLaunchSpecRow>;
7
+ /** Store for immutable workflow recovery inputs keyed by anchor run. */
8
+ export declare function createWorkflowRunLaunchSpecStore(db: DBHandle): {
9
+ create(row: LaunchSpecInsert, tx?: DBExecutor): Promise<ParsedLaunchSpec>;
10
+ get(anchorRunId: string, tx?: DBExecutor): Promise<ParsedLaunchSpec | null>;
11
+ };
12
+ export type WorkflowRunLaunchSpecStore = ReturnType<typeof createWorkflowRunLaunchSpecStore>;
13
+ export {};
@@ -0,0 +1,30 @@
1
+ import { eq } from "drizzle-orm";
2
+ import { parseWorkflowRunLaunchSpecRow } from "./parse-row.js";
3
+ import { workflowRunLaunchSpec } from "./schema/workflow-run-launch-spec.js";
4
+ /** Store for immutable workflow recovery inputs keyed by anchor run. */
5
+ export function createWorkflowRunLaunchSpecStore(db) {
6
+ return {
7
+ async create(row, tx) {
8
+ parseWorkflowRunLaunchSpecRow({
9
+ ...row,
10
+ schemaVersion: row.schemaVersion ?? 1,
11
+ toolPackagePins: row.toolPackagePins ?? null,
12
+ createdAt: row.createdAt ?? new Date(),
13
+ });
14
+ const [inserted] = await (tx ?? db)
15
+ .insert(workflowRunLaunchSpec)
16
+ .values(row)
17
+ .returning();
18
+ if (inserted === undefined) {
19
+ throw new Error(`workflowRunLaunchSpecStore.create: insert returned no row for ${row.anchorRunId}`);
20
+ }
21
+ return parseWorkflowRunLaunchSpecRow(inserted);
22
+ },
23
+ async get(anchorRunId, tx) {
24
+ const row = await (tx ?? db).query.workflowRunLaunchSpec.findFirst({
25
+ where: eq(workflowRunLaunchSpec.anchorRunId, anchorRunId),
26
+ });
27
+ return row === undefined ? null : parseWorkflowRunLaunchSpecRow(row);
28
+ },
29
+ };
30
+ }
@@ -0,0 +1,54 @@
1
+ import type { DB, DBExecutor } from "./client.js";
2
+ import { workflowRun } from "./schema/workflow-run.js";
3
+ import { parseWorkflowRunRow } from "./parse-row.js";
4
+ type DBHandle = DB["db"];
5
+ type WorkflowRunInsert = typeof workflowRun.$inferInsert;
6
+ type ParsedWorkflowRun = ReturnType<typeof parseWorkflowRunRow>;
7
+ /**
8
+ * Store for the `workflow_run` table, the per-run authorization subject that
9
+ * approvals and signal correlations anchor to. Each method accepts an optional
10
+ * transaction handle so the run row can be written in the same transaction that
11
+ * co-writes the run's principal or its approval.
12
+ */
13
+ export declare function createWorkflowRunStore(db: DBHandle): {
14
+ create(row: WorkflowRunInsert, tx?: DBExecutor): Promise<ParsedWorkflowRun>;
15
+ /**
16
+ * Idempotent variant of `create`. On an `id` primary-key conflict the
17
+ * insert is a no-op and this returns `null` rather than throwing, so a
18
+ * redelivered createIfAbsent (workflow-log replay, supervisor restart
19
+ * re-emitting) does not fail the run's co-write. Returns the parsed row
20
+ * only when this call performed the insert.
21
+ */
22
+ createIfAbsent(row: WorkflowRunInsert, tx?: DBExecutor): Promise<ParsedWorkflowRun | null>;
23
+ /**
24
+ * Anchor an externally-triggered run onto its deploy-time anchor row. The
25
+ * anchor is created at deploy (born "deployed" with a null principal); the
26
+ * first trigger reconciles it here, attaching `principalId` and flipping
27
+ * "deployed" -> "running" in one UPDATE. The insert is a conflict-tolerant
28
+ * safety net: the anchor provably exists by the time a trigger runs, so it
29
+ * normally no-ops. The three-part guard makes the reconcile single-shot and
30
+ * safe: `principalId IS NULL` never overwrites a principal a concurrent
31
+ * winner already set, and `status = 'deployed'` never resurrects a run a
32
+ * concurrent teardown already settled terminal (the null-principal guard
33
+ * alone would). `principalId` must be non-null: only the externally-
34
+ * triggered path anchors through here, and it always mints one.
35
+ */
36
+ anchorWithPrincipal(row: WorkflowRunInsert & {
37
+ principalId: string;
38
+ }, tx?: DBExecutor): Promise<void>;
39
+ /**
40
+ * Atomically settle a live run into a terminal state. The live-status guard
41
+ * (`status IN ('deployed','running')`) makes the flip single-shot: the first
42
+ * caller stamps the terminal status and `endedAt` and gets the row back; any
43
+ * later caller matches no row and receives null, so the run is not
44
+ * re-terminated and its `endedAt` is not overwritten. Accepting "deployed"
45
+ * settles a deployment torn down before its first trigger. This is a safety
46
+ * property, not a recovery path -- it makes a second call (a manual replay
47
+ * against an already-settled run) a harmless no-op; it does not by itself
48
+ * re-drive a flip that failed. Returns the parsed row only on the winning
49
+ * flip.
50
+ */
51
+ markTerminal(runId: string, status: "completed" | "failed" | "cancelled", endedAt: Date, tx?: DBExecutor): Promise<ParsedWorkflowRun | null>;
52
+ };
53
+ export type WorkflowRunStore = ReturnType<typeof createWorkflowRunStore>;
54
+ export {};
@@ -0,0 +1,85 @@
1
+ import { and, eq, inArray, isNull } from "drizzle-orm";
2
+ import { liveWorkflowRunStatuses, workflowRun } from "./schema/workflow-run.js";
3
+ import { parseWorkflowRunRow } from "./parse-row.js";
4
+ /**
5
+ * Store for the `workflow_run` table, the per-run authorization subject that
6
+ * approvals and signal correlations anchor to. Each method accepts an optional
7
+ * transaction handle so the run row can be written in the same transaction that
8
+ * co-writes the run's principal or its approval.
9
+ */
10
+ export function createWorkflowRunStore(db) {
11
+ return {
12
+ async create(row, tx) {
13
+ const [inserted] = await (tx ?? db)
14
+ .insert(workflowRun)
15
+ .values(row)
16
+ .returning();
17
+ if (inserted === undefined) {
18
+ throw new Error(`workflowRunStore.create: insert returned no row for ${row.id}`);
19
+ }
20
+ return parseWorkflowRunRow(inserted);
21
+ },
22
+ /**
23
+ * Idempotent variant of `create`. On an `id` primary-key conflict the
24
+ * insert is a no-op and this returns `null` rather than throwing, so a
25
+ * redelivered createIfAbsent (workflow-log replay, supervisor restart
26
+ * re-emitting) does not fail the run's co-write. Returns the parsed row
27
+ * only when this call performed the insert.
28
+ */
29
+ async createIfAbsent(row, tx) {
30
+ const [inserted] = await (tx ?? db)
31
+ .insert(workflowRun)
32
+ .values(row)
33
+ .onConflictDoNothing({ target: workflowRun.id })
34
+ .returning();
35
+ return inserted === undefined ? null : parseWorkflowRunRow(inserted);
36
+ },
37
+ /**
38
+ * Anchor an externally-triggered run onto its deploy-time anchor row. The
39
+ * anchor is created at deploy (born "deployed" with a null principal); the
40
+ * first trigger reconciles it here, attaching `principalId` and flipping
41
+ * "deployed" -> "running" in one UPDATE. The insert is a conflict-tolerant
42
+ * safety net: the anchor provably exists by the time a trigger runs, so it
43
+ * normally no-ops. The three-part guard makes the reconcile single-shot and
44
+ * safe: `principalId IS NULL` never overwrites a principal a concurrent
45
+ * winner already set, and `status = 'deployed'` never resurrects a run a
46
+ * concurrent teardown already settled terminal (the null-principal guard
47
+ * alone would). `principalId` must be non-null: only the externally-
48
+ * triggered path anchors through here, and it always mints one.
49
+ */
50
+ async anchorWithPrincipal(row, tx) {
51
+ const executor = tx ?? db;
52
+ const [inserted] = await executor
53
+ .insert(workflowRun)
54
+ .values(row)
55
+ .onConflictDoNothing({ target: workflowRun.id })
56
+ .returning();
57
+ if (inserted !== undefined)
58
+ return;
59
+ await executor
60
+ .update(workflowRun)
61
+ .set({ principalId: row.principalId, status: "running" })
62
+ .where(and(eq(workflowRun.id, row.id), isNull(workflowRun.principalId), eq(workflowRun.status, "deployed")));
63
+ },
64
+ /**
65
+ * Atomically settle a live run into a terminal state. The live-status guard
66
+ * (`status IN ('deployed','running')`) makes the flip single-shot: the first
67
+ * caller stamps the terminal status and `endedAt` and gets the row back; any
68
+ * later caller matches no row and receives null, so the run is not
69
+ * re-terminated and its `endedAt` is not overwritten. Accepting "deployed"
70
+ * settles a deployment torn down before its first trigger. This is a safety
71
+ * property, not a recovery path -- it makes a second call (a manual replay
72
+ * against an already-settled run) a harmless no-op; it does not by itself
73
+ * re-drive a flip that failed. Returns the parsed row only on the winning
74
+ * flip.
75
+ */
76
+ async markTerminal(runId, status, endedAt, tx) {
77
+ const [updated] = await (tx ?? db)
78
+ .update(workflowRun)
79
+ .set({ status, endedAt })
80
+ .where(and(eq(workflowRun.id, runId), inArray(workflowRun.status, [...liveWorkflowRunStatuses])))
81
+ .returning();
82
+ return updated === undefined ? null : parseWorkflowRunRow(updated);
83
+ },
84
+ };
85
+ }
@@ -0,0 +1,39 @@
1
+ -- Backfill `credential:{id}` / `use` grants for the owner of every existing
2
+ -- personal credential (rows where `principal_id` is set) that does not already
3
+ -- have one. This keeps existing personal-credential owners working once
4
+ -- launch enforcement fails closed. Organizational credentials
5
+ -- (`principal_id` IS NULL) are intentionally excluded: they remain gated
6
+ -- behind tenant-owner role inheritance and explicit administrative grants.
7
+ --
8
+ -- Idempotent: the `WHERE NOT EXISTS` guard skips any credential whose owner
9
+ -- already holds a matching grant, so re-application inserts nothing and does
10
+ -- not fail. Grant ids reproduce the app convention (`grt_` prefix followed by
11
+ -- 32 hex characters) via `gen_random_uuid()`.
12
+ INSERT INTO "grant" (
13
+ "id",
14
+ "tenant_id",
15
+ "principal_id",
16
+ "resource",
17
+ "action",
18
+ "effect",
19
+ "origin",
20
+ "expires_at"
21
+ )
22
+ SELECT
23
+ 'grt_' || replace(gen_random_uuid()::text, '-', ''),
24
+ c."tenant_id",
25
+ c."principal_id",
26
+ 'credential:' || c."id",
27
+ 'use',
28
+ 'allow',
29
+ 'creator',
30
+ NULL
31
+ FROM "credential" c
32
+ WHERE c."principal_id" IS NOT NULL
33
+ AND NOT EXISTS (
34
+ SELECT 1
35
+ FROM "grant" g
36
+ WHERE g."principal_id" = c."principal_id"
37
+ AND g."resource" = 'credential:' || c."id"
38
+ AND g."action" = 'use'
39
+ );
@@ -0,0 +1,37 @@
1
+ CREATE TABLE "approval" (
2
+ "id" text PRIMARY KEY NOT NULL,
3
+ "tenant_id" text NOT NULL,
4
+ "instance_id" text NOT NULL,
5
+ "agent_id" text NOT NULL,
6
+ "origin_principal_id" text NOT NULL,
7
+ "correlation_id" text NOT NULL,
8
+ "tool_definition" jsonb NOT NULL,
9
+ "tool_arguments" jsonb NOT NULL,
10
+ "scope" text,
11
+ "status" text DEFAULT 'pending' NOT NULL,
12
+ "origin_kind" text NOT NULL,
13
+ "timeout_at" timestamp NOT NULL,
14
+ "resolved_at" timestamp,
15
+ "created_at" timestamp DEFAULT now() NOT NULL,
16
+ "updated_at" timestamp DEFAULT now() NOT NULL,
17
+ CONSTRAINT "approval_correlation_id_unique" UNIQUE("correlation_id")
18
+ );
19
+ --> statement-breakpoint
20
+ CREATE TABLE "signal_correlation" (
21
+ "correlation_id" text PRIMARY KEY NOT NULL,
22
+ "tenant_id" text NOT NULL,
23
+ "deployment_id" text NOT NULL,
24
+ "agent_address" text NOT NULL,
25
+ "run_id" text NOT NULL,
26
+ "signal_name" text NOT NULL,
27
+ "kind" text NOT NULL,
28
+ "signal_id" text,
29
+ "resolved_at" timestamp,
30
+ "created_at" timestamp DEFAULT now() NOT NULL
31
+ );
32
+ --> statement-breakpoint
33
+ ALTER TABLE "approval" ADD CONSTRAINT "approval_tenant_id_tenant_id_fk" FOREIGN KEY ("tenant_id") REFERENCES "public"."tenant"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
34
+ ALTER TABLE "approval" ADD CONSTRAINT "approval_instance_id_agent_instance_id_fk" FOREIGN KEY ("instance_id") REFERENCES "public"."agent_instance"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
35
+ ALTER TABLE "approval" ADD CONSTRAINT "approval_agent_id_agent_id_fk" FOREIGN KEY ("agent_id") REFERENCES "public"."agent"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
36
+ ALTER TABLE "approval" ADD CONSTRAINT "approval_origin_principal_id_principal_id_fk" FOREIGN KEY ("origin_principal_id") REFERENCES "public"."principal"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
37
+ ALTER TABLE "signal_correlation" ADD CONSTRAINT "signal_correlation_tenant_id_tenant_id_fk" FOREIGN KEY ("tenant_id") REFERENCES "public"."tenant"("id") ON DELETE cascade ON UPDATE no action;
@@ -0,0 +1,2 @@
1
+ ALTER TABLE "approval" ALTER COLUMN "tool_definition" DROP NOT NULL;--> statement-breakpoint
2
+ ALTER TABLE "approval" ALTER COLUMN "tool_arguments" DROP NOT NULL;
@@ -0,0 +1,24 @@
1
+ -- Reshape `approval` so its origin is the workflow deployment it came from.
2
+ -- The dropped instance/agent/origin-principal FKs had no valid referent: a
3
+ -- workflow deployment has no agent_instance/agent row and its run principal is
4
+ -- a substrate principal, not a `principal`-table row. The three added columns
5
+ -- are NOT NULL with no default and no backfill; like the repo's other such
6
+ -- adds (e.g. 0014, `workflow_deployment.address`), this relies on the table
7
+ -- being empty when the migration runs. The table is unreleased -- the approval
8
+ -- routes are 501 stubs -- so no populated row predates these columns.
9
+ ALTER TABLE "approval" DROP CONSTRAINT "approval_instance_id_agent_instance_id_fk";
10
+ --> statement-breakpoint
11
+ ALTER TABLE "approval" DROP CONSTRAINT "approval_agent_id_agent_id_fk";
12
+ --> statement-breakpoint
13
+ ALTER TABLE "approval" DROP CONSTRAINT "approval_origin_principal_id_principal_id_fk";
14
+ --> statement-breakpoint
15
+ ALTER TABLE "approval" ADD COLUMN "deployment_id" text NOT NULL;--> statement-breakpoint
16
+ ALTER TABLE "approval" ADD COLUMN "run_id" text NOT NULL;--> statement-breakpoint
17
+ ALTER TABLE "approval" ADD COLUMN "agent_address" text NOT NULL;--> statement-breakpoint
18
+ ALTER TABLE "approval" ADD CONSTRAINT "approval_deployment_id_workflow_deployment_id_fk" FOREIGN KEY ("deployment_id") REFERENCES "public"."workflow_deployment"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
19
+ CREATE INDEX "approval_tenant_status_idx" ON "approval" USING btree ("tenant_id","status","created_at");--> statement-breakpoint
20
+ CREATE INDEX "approval_deployment_idx" ON "approval" USING btree ("deployment_id");--> statement-breakpoint
21
+ ALTER TABLE "approval" DROP COLUMN "instance_id";--> statement-breakpoint
22
+ ALTER TABLE "approval" DROP COLUMN "agent_id";--> statement-breakpoint
23
+ ALTER TABLE "approval" DROP COLUMN "origin_principal_id";--> statement-breakpoint
24
+ ALTER TABLE "approval" DROP COLUMN "origin_kind";
@@ -0,0 +1 @@
1
+ ALTER TABLE "approval" ALTER COLUMN "timeout_at" DROP NOT NULL;
@@ -0,0 +1,2 @@
1
+ ALTER TABLE "approval" ALTER COLUMN "tool_definition" SET NOT NULL;--> statement-breakpoint
2
+ ALTER TABLE "approval" ALTER COLUMN "tool_arguments" SET NOT NULL;
@@ -0,0 +1 @@
1
+ ALTER TABLE "signal_correlation" ADD CONSTRAINT "signal_correlation_deployment_id_workflow_deployment_id_fk" FOREIGN KEY ("deployment_id") REFERENCES "public"."workflow_deployment"("id") ON DELETE cascade ON UPDATE no action;
@@ -0,0 +1 @@
1
+ ALTER TABLE "model_offering" ADD COLUMN "quirks" jsonb;
@@ -0,0 +1,13 @@
1
+ CREATE TABLE "workflow_run" (
2
+ "id" text PRIMARY KEY NOT NULL,
3
+ "deployment_id" text NOT NULL,
4
+ "tenant_id" text NOT NULL,
5
+ "principal_id" text,
6
+ "status" text DEFAULT 'running' NOT NULL,
7
+ "created_at" timestamp DEFAULT now() NOT NULL,
8
+ "ended_at" timestamp
9
+ );
10
+ --> statement-breakpoint
11
+ ALTER TABLE "workflow_run" ADD CONSTRAINT "workflow_run_deployment_id_workflow_deployment_id_fk" FOREIGN KEY ("deployment_id") REFERENCES "public"."workflow_deployment"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
12
+ ALTER TABLE "workflow_run" ADD CONSTRAINT "workflow_run_tenant_id_tenant_id_fk" FOREIGN KEY ("tenant_id") REFERENCES "public"."tenant"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
13
+ ALTER TABLE "workflow_run" ADD CONSTRAINT "workflow_run_principal_id_principal_id_fk" FOREIGN KEY ("principal_id") REFERENCES "public"."principal"("id") ON DELETE set null ON UPDATE no action;
@@ -0,0 +1,2 @@
1
+ ALTER TABLE "approval" ADD CONSTRAINT "approval_run_id_workflow_run_id_fk" FOREIGN KEY ("run_id") REFERENCES "public"."workflow_run"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
2
+ ALTER TABLE "signal_correlation" ADD CONSTRAINT "signal_correlation_run_id_workflow_run_id_fk" FOREIGN KEY ("run_id") REFERENCES "public"."workflow_run"("id") ON DELETE cascade ON UPDATE no action;