@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,298 @@
1
+ import { isDeepStrictEqual } from "node:util";
2
+ import { and, asc, eq, inArray, isNull, lte, 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
+ rawMessage: args.rawMessage,
58
+ stepGrants: args.stepGrants,
59
+ status: "pending",
60
+ nextAttemptAt: now,
61
+ createdAt: now,
62
+ updatedAt: now,
63
+ })
64
+ .onConflictDoNothing({
65
+ target: [workflowRunDispatch.anchorRunId, workflowRunDispatch.messageId],
66
+ })
67
+ .returning();
68
+ if (inserted !== undefined) {
69
+ return {
70
+ dispatch: parseWorkflowRunDispatchRow(inserted),
71
+ created: true,
72
+ };
73
+ }
74
+ const existing = await executor.query.workflowRunDispatch.findFirst({
75
+ where: and(eq(workflowRunDispatch.anchorRunId, args.anchorRunId), eq(workflowRunDispatch.messageId, args.messageId)),
76
+ });
77
+ if (existing === undefined) {
78
+ throw new Error(`workflowRunDispatchStore.${args.operation}: conflicting row disappeared for ${args.anchorRunId}/${args.messageId}`);
79
+ }
80
+ const parsed = parseWorkflowRunDispatchRow(existing);
81
+ if (parsed.kind !== args.kind ||
82
+ !byteArraysEqual(parsed.rawMessage, args.rawMessage) ||
83
+ !grantsEqual(parsed.stepGrants, args.stepGrants)) {
84
+ const payloadKind = args.kind === "mail" ? "message" : "signal";
85
+ throw new WorkflowRunDispatchPayloadConflictError(args.messageId, payloadKind, args.operation);
86
+ }
87
+ return { dispatch: parsed, created: false };
88
+ }
89
+ async function acknowledgeMatchingDispatch(executor, generation, conditions, nowOverride) {
90
+ const now = databaseTimestamp(nowOverride);
91
+ const [updated] = await executor
92
+ .update(workflowRunDispatch)
93
+ .set({
94
+ status: "acknowledged",
95
+ acknowledgedGeneration: generation,
96
+ acknowledgedAt: now,
97
+ attemptCount: sql `${workflowRunDispatch.attemptCount} + 1`,
98
+ nextAttemptAt: null,
99
+ deliveryLeaseId: null,
100
+ deliveryLeaseExpiresAt: null,
101
+ failureCode: null,
102
+ failureMessage: null,
103
+ updatedAt: now,
104
+ })
105
+ .where(and(...conditions))
106
+ .returning();
107
+ return updated === undefined ? null : parseWorkflowRunDispatchRow(updated);
108
+ }
109
+ export function createWorkflowRunDispatchStore(db) {
110
+ return {
111
+ async enqueue(args, tx) {
112
+ const stepGrants = validateStepGrants(args.anchorRunId, args.stepGrants);
113
+ return insertOrReconcileDispatch(tx ?? db, {
114
+ operation: "enqueue",
115
+ id: args.id,
116
+ anchorRunId: args.anchorRunId,
117
+ messageId: args.messageId,
118
+ kind: "mail",
119
+ rawMessage: args.rawMessage,
120
+ stepGrants,
121
+ ...(args.now !== undefined ? { now: args.now } : {}),
122
+ });
123
+ },
124
+ async enqueueSignal(args, tx) {
125
+ const signal = SignalDeliverFrame.assert({
126
+ type: "signal.deliver",
127
+ ...args.signal,
128
+ });
129
+ const encoded = new TextEncoder().encode(JSON.stringify(signal));
130
+ return insertOrReconcileDispatch(tx ?? db, {
131
+ operation: "enqueueSignal",
132
+ id: args.id,
133
+ anchorRunId: args.anchorRunId,
134
+ messageId: signal.signalId,
135
+ kind: "signal",
136
+ rawMessage: encoded,
137
+ stepGrants: [],
138
+ ...(args.now !== undefined ? { now: args.now } : {}),
139
+ });
140
+ },
141
+ async claimNextPending(args) {
142
+ return db.transaction(async (tx) => {
143
+ const [candidate] = await tx
144
+ .select({ id: workflowRunDispatch.id })
145
+ .from(workflowRunDispatch)
146
+ .where(and(eq(workflowRunDispatch.status, "pending"), lte(workflowRunDispatch.nextAttemptAt, sql `now()`), or(isNull(workflowRunDispatch.deliveryLeaseExpiresAt), lte(workflowRunDispatch.deliveryLeaseExpiresAt, sql `now()`))))
147
+ .orderBy(asc(workflowRunDispatch.nextAttemptAt), asc(workflowRunDispatch.createdAt))
148
+ .limit(1)
149
+ .for("update", { skipLocked: true });
150
+ if (candidate === undefined)
151
+ return null;
152
+ const [claimed] = await tx
153
+ .update(workflowRunDispatch)
154
+ .set({
155
+ deliveryLeaseId: args.leaseId,
156
+ deliveryLeaseExpiresAt: sql `now() + (${args.leaseDurationMs} * interval '1 millisecond')`,
157
+ })
158
+ .where(eq(workflowRunDispatch.id, candidate.id))
159
+ .returning();
160
+ return claimed === undefined
161
+ ? null
162
+ : parseWorkflowRunDispatchRow(claimed);
163
+ });
164
+ },
165
+ async acknowledge(args) {
166
+ return db.transaction(async (tx) => {
167
+ const [allocation] = await tx
168
+ .select({
169
+ anchorRunId: sidecarAllocation.anchorRunId,
170
+ status: sidecarAllocation.status,
171
+ generation: sidecarAllocation.generation,
172
+ ensureAcceptedGeneration: sidecarAllocation.ensureAcceptedGeneration,
173
+ })
174
+ .from(sidecarAllocation)
175
+ .where(eq(sidecarAllocation.id, args.allocationId))
176
+ .limit(1)
177
+ .for("update");
178
+ if (allocation === undefined ||
179
+ allocation.anchorRunId !== args.anchorRunId ||
180
+ allocation.status !== "allocated" ||
181
+ allocation.generation !== args.generation ||
182
+ allocation.ensureAcceptedGeneration !== args.generation) {
183
+ return null;
184
+ }
185
+ return acknowledgeMatchingDispatch(tx, args.generation, [
186
+ eq(workflowRunDispatch.anchorRunId, args.anchorRunId),
187
+ eq(workflowRunDispatch.messageId, args.messageId),
188
+ eq(workflowRunDispatch.kind, "mail"),
189
+ eq(workflowRunDispatch.status, "pending"),
190
+ ], args.now);
191
+ });
192
+ },
193
+ async scheduleRetry(args) {
194
+ const [updated] = await db
195
+ .update(workflowRunDispatch)
196
+ .set({
197
+ nextAttemptAt: args.nextAttemptAt,
198
+ attemptCount: sql `${workflowRunDispatch.attemptCount} + 1`,
199
+ deliveryLeaseId: null,
200
+ deliveryLeaseExpiresAt: null,
201
+ failureCode: args.code,
202
+ failureMessage: args.message,
203
+ updatedAt: databaseTimestamp(args.now),
204
+ })
205
+ .where(and(eq(workflowRunDispatch.id, args.dispatchId), eq(workflowRunDispatch.status, "pending"), ...leaseCondition(args.expectedLeaseId)))
206
+ .returning();
207
+ return updated === undefined
208
+ ? null
209
+ : parseWorkflowRunDispatchRow(updated);
210
+ },
211
+ async settle(anchorRunId, messageId, now = new Date()) {
212
+ const [updated] = await db
213
+ .update(workflowRunDispatch)
214
+ .set({
215
+ status: "settled",
216
+ nextAttemptAt: null,
217
+ deliveryLeaseId: null,
218
+ deliveryLeaseExpiresAt: null,
219
+ failureCode: null,
220
+ failureMessage: null,
221
+ settledAt: now,
222
+ updatedAt: now,
223
+ })
224
+ .where(and(eq(workflowRunDispatch.anchorRunId, anchorRunId), eq(workflowRunDispatch.messageId, messageId), inArray(workflowRunDispatch.status, ["pending", "acknowledged"])))
225
+ .returning();
226
+ return updated === undefined
227
+ ? null
228
+ : parseWorkflowRunDispatchRow(updated);
229
+ },
230
+ async fail(args) {
231
+ const now = args.now ?? new Date();
232
+ const [updated] = await db
233
+ .update(workflowRunDispatch)
234
+ .set({
235
+ status: "failed",
236
+ nextAttemptAt: null,
237
+ deliveryLeaseId: null,
238
+ deliveryLeaseExpiresAt: null,
239
+ failureCode: args.code,
240
+ failureMessage: args.message,
241
+ updatedAt: now,
242
+ })
243
+ .where(and(eq(workflowRunDispatch.anchorRunId, args.anchorRunId), eq(workflowRunDispatch.messageId, args.messageId), inArray(workflowRunDispatch.status, ["pending", "acknowledged"])))
244
+ .returning();
245
+ return updated === undefined
246
+ ? null
247
+ : parseWorkflowRunDispatchRow(updated);
248
+ },
249
+ async failUnsettled(anchorRunId, code, message, now = new Date(), tx) {
250
+ const rows = await (tx ?? db)
251
+ .update(workflowRunDispatch)
252
+ .set({
253
+ status: "failed",
254
+ nextAttemptAt: null,
255
+ deliveryLeaseId: null,
256
+ deliveryLeaseExpiresAt: null,
257
+ failureCode: code,
258
+ failureMessage: message,
259
+ updatedAt: now,
260
+ })
261
+ .where(and(eq(workflowRunDispatch.anchorRunId, anchorRunId), inArray(workflowRunDispatch.status, ["pending", "acknowledged"])))
262
+ .returning({ id: workflowRunDispatch.id });
263
+ return rows.length;
264
+ },
265
+ async requeueUnsettled(anchorRunId) {
266
+ const rows = await db
267
+ .update(workflowRunDispatch)
268
+ .set({
269
+ status: "pending",
270
+ acknowledgedGeneration: null,
271
+ acknowledgedAt: null,
272
+ nextAttemptAt: sql `now()`,
273
+ deliveryLeaseId: null,
274
+ deliveryLeaseExpiresAt: null,
275
+ failureCode: null,
276
+ failureMessage: null,
277
+ updatedAt: sql `now()`,
278
+ })
279
+ .where(and(eq(workflowRunDispatch.anchorRunId, anchorRunId), inArray(workflowRunDispatch.status, ["pending", "acknowledged"])))
280
+ .returning({ id: workflowRunDispatch.id });
281
+ return rows.length;
282
+ },
283
+ async findById(id) {
284
+ const row = await db.query.workflowRunDispatch.findFirst({
285
+ where: eq(workflowRunDispatch.id, id),
286
+ });
287
+ return row === undefined ? null : parseWorkflowRunDispatchRow(row);
288
+ },
289
+ async listUnsettled(anchorRunId) {
290
+ const rows = await db
291
+ .select()
292
+ .from(workflowRunDispatch)
293
+ .where(and(eq(workflowRunDispatch.anchorRunId, anchorRunId), inArray(workflowRunDispatch.status, ["pending", "acknowledged"])))
294
+ .orderBy(asc(workflowRunDispatch.createdAt));
295
+ return rows.map(parseWorkflowRunDispatchRow);
296
+ },
297
+ };
298
+ }
@@ -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 @@
1
+ ALTER TABLE "agent" ADD COLUMN "tool_packages" jsonb DEFAULT '[]'::jsonb NOT NULL;
@@ -0,0 +1,5 @@
1
+ ALTER TABLE "session_asset" DROP CONSTRAINT "session_asset_instance_id_agent_asset_id_pk";--> statement-breakpoint
2
+ ALTER TABLE "session_asset" ALTER COLUMN "agent_asset_id" DROP NOT NULL;--> statement-breakpoint
3
+ ALTER TABLE "session_asset" ADD CONSTRAINT "session_asset_instance_id_mount_path_pk" PRIMARY KEY("instance_id","mount_path");--> statement-breakpoint
4
+ ALTER TABLE "session_asset" ADD COLUMN "source" text NOT NULL DEFAULT 'direct';--> statement-breakpoint
5
+ ALTER TABLE "session_asset" ALTER COLUMN "source" DROP DEFAULT;
@@ -0,0 +1,68 @@
1
+ CREATE TABLE "model" (
2
+ "id" text PRIMARY KEY NOT NULL,
3
+ "tenant_id" text NOT NULL,
4
+ "canonical_name" text NOT NULL,
5
+ "display_name" text,
6
+ "description" text,
7
+ "disabled" boolean DEFAULT false NOT NULL,
8
+ "created_at" timestamp DEFAULT now() NOT NULL,
9
+ "updated_at" timestamp DEFAULT now() NOT NULL,
10
+ CONSTRAINT "model_tenant_canonical_name" UNIQUE("tenant_id","canonical_name")
11
+ );
12
+ --> statement-breakpoint
13
+ CREATE TABLE "model_offering" (
14
+ "id" text PRIMARY KEY NOT NULL,
15
+ "tenant_id" text NOT NULL,
16
+ "model_id" text NOT NULL,
17
+ "provider_id" text NOT NULL,
18
+ "priority" integer NOT NULL,
19
+ "deployment_tags" text[] DEFAULT '{}' NOT NULL,
20
+ "capabilities" text[] DEFAULT '{}' NOT NULL,
21
+ "disabled" boolean DEFAULT false NOT NULL,
22
+ "created_at" timestamp DEFAULT now() NOT NULL,
23
+ "updated_at" timestamp DEFAULT now() NOT NULL,
24
+ CONSTRAINT "model_offering_tenant_model_provider" UNIQUE("tenant_id","model_id","provider_id")
25
+ );
26
+ --> statement-breakpoint
27
+ CREATE TABLE "model_pricing" (
28
+ "id" text PRIMARY KEY NOT NULL,
29
+ "tenant_id" text NOT NULL,
30
+ "offering_id" text NOT NULL,
31
+ "currency" text NOT NULL,
32
+ "input_token_price" text,
33
+ "output_token_price" text,
34
+ "cache_read_token_price" text,
35
+ "cache_write_token_price" text,
36
+ "thinking_token_price" text,
37
+ "per_request_fee" text,
38
+ "per_image_fee" text,
39
+ "per_audio_fee" text,
40
+ "effective_from" timestamp with time zone NOT NULL,
41
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
42
+ CONSTRAINT "model_pricing_offering_currency_effective_from" UNIQUE("offering_id","currency","effective_from")
43
+ );
44
+ --> statement-breakpoint
45
+ CREATE TABLE "model_provider" (
46
+ "id" text PRIMARY KEY NOT NULL,
47
+ "tenant_id" text NOT NULL,
48
+ "name" text NOT NULL,
49
+ "plugin" text NOT NULL,
50
+ "base_url" text NOT NULL,
51
+ "credential_id" text,
52
+ "wallet_id" text,
53
+ "disabled" boolean DEFAULT false NOT NULL,
54
+ "created_at" timestamp DEFAULT now() NOT NULL,
55
+ "updated_at" timestamp DEFAULT now() NOT NULL,
56
+ CONSTRAINT "model_provider_tenant_name" UNIQUE("tenant_id","name"),
57
+ CONSTRAINT "model_provider_auth_xor" CHECK (("model_provider"."credential_id" is not null) <> ("model_provider"."wallet_id" is not null))
58
+ );
59
+ --> statement-breakpoint
60
+ ALTER TABLE "model" ADD CONSTRAINT "model_tenant_id_tenant_id_fk" FOREIGN KEY ("tenant_id") REFERENCES "public"."tenant"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
61
+ ALTER TABLE "model_offering" ADD CONSTRAINT "model_offering_tenant_id_tenant_id_fk" FOREIGN KEY ("tenant_id") REFERENCES "public"."tenant"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
62
+ ALTER TABLE "model_offering" ADD CONSTRAINT "model_offering_model_id_model_id_fk" FOREIGN KEY ("model_id") REFERENCES "public"."model"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
63
+ ALTER TABLE "model_offering" ADD CONSTRAINT "model_offering_provider_id_model_provider_id_fk" FOREIGN KEY ("provider_id") REFERENCES "public"."model_provider"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
64
+ ALTER TABLE "model_pricing" ADD CONSTRAINT "model_pricing_tenant_id_tenant_id_fk" FOREIGN KEY ("tenant_id") REFERENCES "public"."tenant"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
65
+ ALTER TABLE "model_pricing" ADD CONSTRAINT "model_pricing_offering_id_model_offering_id_fk" FOREIGN KEY ("offering_id") REFERENCES "public"."model_offering"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
66
+ ALTER TABLE "model_provider" ADD CONSTRAINT "model_provider_tenant_id_tenant_id_fk" FOREIGN KEY ("tenant_id") REFERENCES "public"."tenant"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
67
+ ALTER TABLE "model_provider" ADD CONSTRAINT "model_provider_credential_id_credential_id_fk" FOREIGN KEY ("credential_id") REFERENCES "public"."credential"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
68
+ ALTER TABLE "model_provider" ADD CONSTRAINT "model_provider_wallet_id_wallet_id_fk" FOREIGN KEY ("wallet_id") REFERENCES "public"."wallet"("id") ON DELETE restrict ON UPDATE no action;
@@ -0,0 +1 @@
1
+ ALTER TABLE "agent" ADD COLUMN "model_requirements" jsonb;
@@ -0,0 +1 @@
1
+ ALTER TABLE "agent_instance" ADD COLUMN "model_preferences" jsonb;
@@ -0,0 +1,11 @@
1
+ CREATE TABLE "workflow_deployment" (
2
+ "id" text PRIMARY KEY NOT NULL,
3
+ "tenant_id" text NOT NULL,
4
+ "definition_asset_id" text NOT NULL,
5
+ "status" text DEFAULT 'deployed' NOT NULL,
6
+ "created_at" timestamp DEFAULT now() NOT NULL
7
+ );
8
+ --> statement-breakpoint
9
+ ALTER TABLE "workflow_deployment" ADD CONSTRAINT "workflow_deployment_tenant_id_tenant_id_fk" FOREIGN KEY ("tenant_id") REFERENCES "public"."tenant"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
10
+ ALTER TABLE "workflow_deployment" ADD CONSTRAINT "workflow_deployment_definition_asset_id_asset_id_fk" FOREIGN KEY ("definition_asset_id") REFERENCES "public"."asset"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
11
+ CREATE INDEX "workflow_deployment_tenant_idx" ON "workflow_deployment" USING btree ("tenant_id","created_at");
@@ -0,0 +1,3 @@
1
+ ALTER TABLE "workflow_deployment" ADD COLUMN "address" text NOT NULL;--> statement-breakpoint
2
+ ALTER TABLE "workflow_deployment" ADD COLUMN "public_key" text;--> statement-breakpoint
3
+ CREATE UNIQUE INDEX "workflow_deployment_address_idx" ON "workflow_deployment" USING btree ("address");
@@ -0,0 +1,2 @@
1
+ ALTER TABLE "sidecar" ADD COLUMN "token_hash_sha256" "bytea" NOT NULL;--> statement-breakpoint
2
+ ALTER TABLE "sidecar" ADD CONSTRAINT "sidecar_token_hash_sha256_unique" UNIQUE("token_hash_sha256");
@@ -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
+ );