@opengeni/db 1.0.1 → 3.2.0-canary.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 (271) hide show
  1. package/dist/attached-browser-devices.d.ts +7 -0
  2. package/dist/automations.d.ts +154 -0
  3. package/dist/canonical-human-identities.js +3 -3
  4. package/dist/capability-integrations.d.ts +13 -0
  5. package/dist/child-lifecycle-notices.d.ts +68 -0
  6. package/dist/{chunk-4SR4HKKA.js → chunk-2EH4WRHL.js} +19 -11
  7. package/dist/chunk-2EH4WRHL.js.map +1 -0
  8. package/dist/{chunk-CUPM3FGB.js → chunk-2PRS4J5Q.js} +3 -3
  9. package/dist/chunk-6HMM2R5T.js +390 -0
  10. package/dist/chunk-6HMM2R5T.js.map +1 -0
  11. package/dist/{chunk-ZWREYEWA.js → chunk-AZHW7R5V.js} +56 -11
  12. package/dist/chunk-AZHW7R5V.js.map +1 -0
  13. package/dist/{chunk-LTG3S6ZY.js → chunk-E6CLMKEE.js} +2 -2
  14. package/dist/{chunk-6ZYKCRFO.js → chunk-KRLPEMLH.js} +7591 -4417
  15. package/dist/chunk-KRLPEMLH.js.map +1 -0
  16. package/dist/chunk-NAB6FKFN.js +516 -0
  17. package/dist/chunk-NAB6FKFN.js.map +1 -0
  18. package/dist/{chunk-SK7YYHBI.js → chunk-SIBYBA2D.js} +46 -1
  19. package/dist/chunk-SIBYBA2D.js.map +1 -0
  20. package/dist/chunk-UZNXG44K.js +4508 -0
  21. package/dist/chunk-UZNXG44K.js.map +1 -0
  22. package/dist/codemode-operations.d.ts +1 -6
  23. package/dist/company-brain-context-selection-schema.d.ts +705 -0
  24. package/dist/company-brain-context-selection.d.ts +94 -0
  25. package/dist/company-brain-governed-writes.d.ts +24 -0
  26. package/dist/company-brain.d.ts +55 -0
  27. package/dist/company-profile-agent-admin.d.ts +15 -0
  28. package/dist/company-profile-schema.d.ts +619 -2
  29. package/dist/company-profile.d.ts +9 -4
  30. package/dist/connection-authority.d.ts +95 -0
  31. package/dist/connection-scopes.d.ts +8 -0
  32. package/dist/connection-tenancy-backfill.d.ts +85 -0
  33. package/dist/connection-token-resolver.d.ts +30 -1
  34. package/dist/database.d.ts +23 -4
  35. package/dist/editable-artifact-durable-export.js +2 -2
  36. package/dist/editable-artifacts.d.ts +2 -2
  37. package/dist/editable-artifacts.js +3 -3
  38. package/dist/governed-learning-activation-schema.d.ts +984 -0
  39. package/dist/governed-learning-activation.d.ts +98 -0
  40. package/dist/governed-learning-evaluator-schema.d.ts +633 -0
  41. package/dist/governed-learning-evaluator.d.ts +28 -0
  42. package/dist/index.d.ts +1606 -81
  43. package/dist/index.js +21898 -8147
  44. package/dist/index.js.map +1 -1
  45. package/dist/insights.d.ts +18 -0
  46. package/dist/integration-bindings.d.ts +1 -0
  47. package/dist/integration-facets.d.ts +30 -0
  48. package/dist/interaction-placement-loss.d.ts +22 -0
  49. package/dist/managed-human-provisioning.d.ts +25 -0
  50. package/dist/memory-domain.d.ts +3 -3
  51. package/dist/migrate.d.ts +11 -1
  52. package/dist/migrate.js +1 -1
  53. package/dist/new-session-drafts.d.ts +43 -5
  54. package/dist/organization-membership-backfill.d.ts +174 -0
  55. package/dist/organization-membership-lifecycle.d.ts +162 -0
  56. package/dist/pr-review.d.ts +107 -0
  57. package/dist/preference-registry-schema.d.ts +2 -0
  58. package/dist/preference-registry.d.ts +2 -0
  59. package/dist/provision-roles.js +1 -1
  60. package/dist/runtime-posture.d.ts +29 -8
  61. package/dist/schema.d.ts +24914 -13703
  62. package/dist/schema.js +99 -1
  63. package/dist/scoped-knowledge-schema.d.ts +52 -1
  64. package/dist/session-background-commands.d.ts +125 -0
  65. package/dist/session-background-commands.js +34 -0
  66. package/dist/session-background-commands.js.map +1 -0
  67. package/dist/session-control.d.ts +156 -3
  68. package/dist/session-goal-pacing.d.ts +68 -0
  69. package/dist/session-queue-commands.d.ts +43 -4
  70. package/dist/session-realtime-ledger.d.ts +2 -0
  71. package/dist/session-tenancy.d.ts +71 -1
  72. package/dist/session-tenancy.js +25 -3
  73. package/dist/session-tool-call-settlement.d.ts +9 -4
  74. package/dist/slack-routing-personal-workspace.d.ts +23 -0
  75. package/dist/slack-routing.d.ts +268 -0
  76. package/dist/slack-task-policy-schema.d.ts +34 -0
  77. package/dist/slack-task-policy.d.ts +8 -0
  78. package/dist/slack-user-link-access.d.ts +28 -0
  79. package/dist/task-notes-schema.d.ts +594 -0
  80. package/dist/task-notes.d.ts +39 -0
  81. package/dist/turn-initiator.d.ts +2 -0
  82. package/dist/user-resource-authority.d.ts +109 -0
  83. package/dist/video-generation.js +3 -3
  84. package/dist/workspace-authority.d.ts +81 -0
  85. package/dist/workspace-instruction-policies-schema.d.ts +279 -0
  86. package/dist/workspace-instruction-policies.d.ts +62 -0
  87. package/dist/workspace-learning-policy.d.ts +1 -0
  88. package/dist/workspace-membership-permissions.d.ts +2 -0
  89. package/drizzle/0240_enrollment_connection_authority.sql +39 -0
  90. package/drizzle/0241_atomic_personal_resource_delegation.sql +1000 -0
  91. package/drizzle/0241_enrollment_agent_runtime.sql +70 -0
  92. package/drizzle/0244_slack_app_home_refresh_queue.sql +120 -0
  93. package/drizzle/0245_model_context_contribution_facts.sql +88 -0
  94. package/drizzle/0246_integration_personal_instance_authority.sql +70 -0
  95. package/drizzle/0247_terraform_stacks_provenance_repair.sql +428 -0
  96. package/drizzle/0248_terraform_stacks_component_resolution_fence.sql +83 -0
  97. package/drizzle/0249_personal_resource_delegation_authority_correction.sql +662 -0
  98. package/drizzle/0250_direct_retained_process_owner_liveness.sql +199 -0
  99. package/drizzle/0251_connected_machine_operation_policy.sql +45 -0
  100. package/drizzle/0252_scheduled_personal_resource_delegation.sql +1867 -0
  101. package/drizzle/0253_common_user_resource_authority_lifecycle.sql +1555 -0
  102. package/drizzle/0254_scoped_variable_set_authority.sql +1255 -0
  103. package/drizzle/0255_company_brain_governed_write_proposals.sql +524 -0
  104. package/drizzle/0256_connection_authority_delegation.sql +703 -0
  105. package/drizzle/0257_goal_revision_decisions_and_root_constraints.sql +436 -0
  106. package/drizzle/0258_three_scope_document_knowledge_authority.sql +1273 -0
  107. package/drizzle/0259_company_brain_context_selection_receipts.sql +869 -0
  108. package/drizzle/0260_task_note_knowledge_promotion.sql +820 -0
  109. package/drizzle/0261_preference_knowledge_proposal_actor_binding.sql +471 -0
  110. package/drizzle/0262_scoped_connected_machines_and_rigs.sql +1700 -0
  111. package/drizzle/0263_organization_membership_lifecycle.sql +3284 -0
  112. package/drizzle/0264_connection_authority_runtime_activation.sql +1084 -0
  113. package/drizzle/0266_company_brain_context_receipt_inspection.sql +156 -0
  114. package/drizzle/0268_governed_learning_decision_receipts.sql +825 -0
  115. package/drizzle/0269_governed_learning_activation_controller.sql +1874 -0
  116. package/drizzle/0270_governed_learning_history_inspection.sql +170 -0
  117. package/drizzle/0271_company_brain_retrieval_only_default.sql +524 -0
  118. package/drizzle/0272_human_confirmed_learning_activation.sql +591 -0
  119. package/drizzle/0273_scheduled_variable_set_materialization.sql +268 -0
  120. package/drizzle/0274_human_confirmed_knowledge_review.sql +310 -0
  121. package/drizzle/0275_scheduled_connection_authority.sql +5527 -0
  122. package/drizzle/0276_onboarding_proposal_initiating_human_guc.sql +198 -0
  123. package/drizzle/0277_workspace_writer_authority_attribution.sql +393 -0
  124. package/drizzle/0278_workspace_membership_removal_fencing.sql +1174 -0
  125. package/drizzle/0279_workspace_connection_use_lane.sql +343 -0
  126. package/drizzle/0280_connection_and_variable_set_audit_attribution.sql +773 -0
  127. package/drizzle/0281_viewer_holder_authority_claims.sql +52 -0
  128. package/drizzle/0282_variable_set_session_attach_attribution.sql +178 -0
  129. package/drizzle/0283_editable_spreadsheet_authored_state.sql +82 -0
  130. package/drizzle/0284_truthful_human_confirmed_review_reason.sql +930 -0
  131. package/drizzle/0285_organization_tenancy_inventory.sql +329 -0
  132. package/drizzle/0286_widen_task_note_expiry_ceiling.sql +391 -0
  133. package/drizzle/0287_open_suffix_pending_tool_calls.sql +18 -0
  134. package/drizzle/0288_attached_browser_reenrollment.sql +64 -0
  135. package/drizzle/0289_session_composer_policy_authority.sql +597 -0
  136. package/drizzle/0290_organization_membership_backfill.sql +268 -0
  137. package/drizzle/0291_resource_authority_classification_assertion.sql +371 -0
  138. package/drizzle/0292_truthful_tenancy_inventory_counters.sql +291 -0
  139. package/drizzle/0293_confirm_time_rule_rebaseline.sql +615 -0
  140. package/drizzle/0294_preference_activation_authority.sql +237 -0
  141. package/drizzle/0295_retire_legacy_standing_memory_mode.sql +476 -0
  142. package/drizzle/0296_force_rls_backfill_noop_repair.sql +215 -0
  143. package/drizzle/0297_session_ownership_classification_and_backfill.sql +752 -0
  144. package/drizzle/0298_organization_tenancy_parity.sql +609 -0
  145. package/drizzle/0299_organization_membership_lock_order.sql +291 -0
  146. package/drizzle/0300_tenancy_backfill_ledger.sql +459 -0
  147. package/drizzle/0301_session_snapshot_and_pin_visibility.sql +181 -0
  148. package/drizzle/0302_personal_workspace_session_ownership.sql +303 -0
  149. package/drizzle/0303_session_tenancy_product_activation.sql +919 -0
  150. package/drizzle/0304_personal_workspace_private_session_reads.sql +97 -0
  151. package/drizzle/0305_personal_resource_grant_management.sql +584 -0
  152. package/drizzle/0306_atomic_personal_resource_attachments.sql +1374 -0
  153. package/drizzle/0307_session_attention_state.sql +51 -0
  154. package/drizzle/0308_session_archives.sql +35 -0
  155. package/drizzle/0309_channel_project_pins.sql +6 -0
  156. package/drizzle/0310_channel_project_order.sql +27 -0
  157. package/drizzle/0311_company_scope_and_private_session_create.sql +656 -0
  158. package/drizzle/0312_quiescent_session_tree_deletion.sql +39 -0
  159. package/drizzle/0313_private_child_session_authority.sql +365 -0
  160. package/drizzle/0314_unregistered_organization_invitations.sql +606 -0
  161. package/drizzle/0315_personal_github_repository_selection.sql +624 -0
  162. package/drizzle/0316_human_confirmed_activation_resumed_generation.sql +869 -0
  163. package/drizzle/0317_session_goal_continuation_hold.sql +64 -0
  164. package/drizzle/0318_session_turn_startup_milestones.sql +115 -0
  165. package/drizzle/0319_event_triggered_automations.sql +254 -0
  166. package/drizzle/0320_pr_review_pack.sql +113 -0
  167. package/drizzle/0321_slack_bot_environment_display_name.sql +15 -0
  168. package/drizzle/0322_session_turns_unclaimed_prompt_trigger_index.sql +5 -0
  169. package/drizzle/0323_organization_private_session_enablement.sql +489 -0
  170. package/drizzle/0324_human_confirmed_company_profile_agent_admin.sql +856 -0
  171. package/drizzle/0325_child_lifecycle_notices.sql +44 -0
  172. package/drizzle/0326_interaction_operation_error_codes.sql +62 -0
  173. package/drizzle/0327_slack_first_task_hint.sql +29 -0
  174. package/drizzle/0328_session_background_commands.sql +590 -0
  175. package/drizzle/0329_slack_orchestration_delivery_events.sql +91 -0
  176. package/drizzle/0330_api_key_descriptions.sql +20 -0
  177. package/drizzle/0331_managed_organization_creation.sql +163 -0
  178. package/drizzle/0332_organization_shared_workspace_control_plane.sql +539 -0
  179. package/drizzle/0333_session_turn_prompt_routing.sql +45 -0
  180. package/drizzle/0334_connected_machine_workspace_root.sql +99 -0
  181. package/drizzle/0335_slack_workspace_routing.sql +460 -0
  182. package/drizzle/0336_atomic_session_fork_visibility.sql +667 -0
  183. package/drizzle/0337_slack_routed_action_handles.sql +189 -0
  184. package/drizzle/0338_atomic_connected_machine_attachments.sql +410 -0
  185. package/drizzle/0339_document_authority_reclassification.sql +1153 -0
  186. package/drizzle/0340_tenancy_backfill_activation_evidence.sql +1347 -0
  187. package/drizzle/0341_slack_routing_probe_organization_fence.sql +109 -0
  188. package/drizzle/0342_slack_route_prompt_single_pending.sql +32 -0
  189. package/drizzle/0343_personal_document_force_rls_lock_repair.sql +146 -0
  190. package/drizzle/0344_private_session_visibility_transition_gate.sql +85 -0
  191. package/drizzle/0345_tenant_scoped_session_tenancy_fence.sql +2839 -0
  192. package/drizzle/0346_document_migration_audit_surface.sql +454 -0
  193. package/drizzle/0347_connection_authority_convergence_evidence.sql +396 -0
  194. package/package.json +10 -6
  195. package/src/attached-browser-devices.ts +233 -5
  196. package/src/automations.ts +911 -0
  197. package/src/browser-sessions.ts +10 -0
  198. package/src/capability-integrations.ts +175 -1
  199. package/src/child-lifecycle-notices.ts +241 -0
  200. package/src/codemode-operations.ts +1 -35
  201. package/src/company-brain-context-selection-schema.ts +100 -0
  202. package/src/company-brain-context-selection.ts +359 -0
  203. package/src/company-brain-governed-writes.ts +1170 -0
  204. package/src/company-brain.ts +309 -0
  205. package/src/company-profile-agent-admin.ts +225 -0
  206. package/src/company-profile-schema.ts +121 -1
  207. package/src/company-profile.ts +21 -0
  208. package/src/computer-sessions.ts +10 -1
  209. package/src/connection-authority.ts +290 -0
  210. package/src/connection-scopes.ts +20 -0
  211. package/src/connection-tenancy-backfill.ts +167 -0
  212. package/src/connection-token-resolver.ts +263 -31
  213. package/src/database.ts +87 -2
  214. package/src/editable-artifacts-schema.ts +10 -5
  215. package/src/editable-artifacts.ts +21 -11
  216. package/src/governed-learning-activation-schema.ts +121 -0
  217. package/src/governed-learning-activation.ts +588 -0
  218. package/src/governed-learning-evaluator-schema.ts +72 -0
  219. package/src/governed-learning-evaluator.ts +217 -0
  220. package/src/index.ts +11676 -1550
  221. package/src/insights.ts +107 -0
  222. package/src/integration-bindings.ts +88 -6
  223. package/src/integration-facets.ts +287 -84
  224. package/src/interaction-placement-loss.ts +234 -0
  225. package/src/managed-human-provisioning.ts +116 -0
  226. package/src/memory-domain.ts +27 -32
  227. package/src/migrate.ts +77 -1
  228. package/src/new-session-drafts.ts +229 -35
  229. package/src/organization-membership-backfill.ts +735 -0
  230. package/src/organization-membership-lifecycle.ts +995 -0
  231. package/src/pr-review.ts +890 -0
  232. package/src/preference-registry.ts +31 -1
  233. package/src/provision-roles.ts +905 -6
  234. package/src/runtime-posture-cli.ts +2 -0
  235. package/src/runtime-posture.ts +1317 -72
  236. package/src/schema.ts +3316 -428
  237. package/src/scoped-knowledge-schema.ts +16 -8
  238. package/src/scoped-knowledge.ts +1 -1
  239. package/src/session-background-commands.ts +759 -0
  240. package/src/session-control.ts +640 -74
  241. package/src/session-goal-pacing.ts +126 -0
  242. package/src/session-queue-commands.ts +441 -85
  243. package/src/session-realtime-context.ts +4 -6
  244. package/src/session-realtime-ledger.ts +28 -14
  245. package/src/session-realtime-mirror.ts +1 -1
  246. package/src/session-tenancy.ts +410 -49
  247. package/src/session-tool-call-settlement.ts +38 -29
  248. package/src/slack-routing-personal-workspace.ts +67 -0
  249. package/src/slack-routing.ts +867 -0
  250. package/src/slack-task-policy-schema.ts +5 -0
  251. package/src/slack-task-policy.ts +18 -0
  252. package/src/slack-user-link-access.ts +138 -11
  253. package/src/task-notes-schema.ts +74 -0
  254. package/src/task-notes.ts +124 -2
  255. package/src/turn-initiator.ts +8 -0
  256. package/src/user-resource-authority.ts +377 -0
  257. package/src/workspace-authority.ts +160 -0
  258. package/src/workspace-instruction-policies-schema.ts +53 -1
  259. package/src/workspace-instruction-policies.ts +528 -77
  260. package/src/workspace-learning-policy.ts +21 -5
  261. package/src/workspace-membership-permissions.ts +9 -0
  262. package/dist/chunk-4SR4HKKA.js.map +0 -1
  263. package/dist/chunk-6ZYKCRFO.js.map +0 -1
  264. package/dist/chunk-NXAFLCQX.js +0 -2468
  265. package/dist/chunk-NXAFLCQX.js.map +0 -1
  266. package/dist/chunk-SK7YYHBI.js.map +0 -1
  267. package/dist/chunk-WD5ROSLL.js +0 -117
  268. package/dist/chunk-WD5ROSLL.js.map +0 -1
  269. package/dist/chunk-ZWREYEWA.js.map +0 -1
  270. /package/dist/{chunk-CUPM3FGB.js.map → chunk-2PRS4J5Q.js.map} +0 -0
  271. /package/dist/{chunk-LTG3S6ZY.js.map → chunk-E6CLMKEE.js.map} +0 -0
@@ -0,0 +1,911 @@
1
+ import {
2
+ AutomationAcceptedExecution,
3
+ AutomationNormalizedEvent,
4
+ AutomationSessionTemplate,
5
+ CapabilityPack,
6
+ stableJson,
7
+ type AutomationRun,
8
+ type AutomationSource,
9
+ type AutomationTrigger,
10
+ type CreateAutomationSourceRequest,
11
+ type CreateAutomationTriggerRequest,
12
+ type UpdateAutomationSourceRequest,
13
+ type UpdateAutomationTriggerRequest,
14
+ } from "@opengeni/contracts";
15
+ import { and, desc, eq, inArray, isNull, sql } from "drizzle-orm";
16
+ import type { Database } from "./database";
17
+ import { withWorkspaceRls } from "./database";
18
+ import * as schema from "./schema";
19
+
20
+ type SourceRow = typeof schema.automationSources.$inferSelect;
21
+ type TriggerRow = typeof schema.automationTriggers.$inferSelect;
22
+ type RevisionRow = typeof schema.automationTriggerRevisions.$inferSelect;
23
+ type EventRow = typeof schema.automationTriggerEvents.$inferSelect;
24
+ type RunRow = typeof schema.automationRuns.$inferSelect;
25
+
26
+ export class AutomationDeliveryConflictError extends Error {
27
+ readonly name = "AutomationDeliveryConflictError";
28
+ constructor() {
29
+ super("Automation delivery identity was reused with different authenticated bytes");
30
+ }
31
+ }
32
+
33
+ export class AutomationRevisionConflictError extends Error {
34
+ readonly name = "AutomationRevisionConflictError";
35
+ constructor() {
36
+ super("Automation trigger changed since it was read");
37
+ }
38
+ }
39
+
40
+ export class AutomationAuthorityRevokedError extends Error {
41
+ readonly name = "AutomationAuthorityRevokedError";
42
+ constructor(message = "Automation dispatch authority is no longer active") {
43
+ super(message);
44
+ }
45
+ }
46
+
47
+ export type AutomationSourceSecret = AutomationSource & {
48
+ webhookSecretEncrypted: string;
49
+ };
50
+
51
+ export type AutomationTriggerWithRevision = AutomationTrigger & {
52
+ sourceStatus: "active" | "disabled";
53
+ sourceVersion: number;
54
+ sourceConfiguration: Record<string, unknown>;
55
+ };
56
+
57
+ export type AutomationStoredEvent = {
58
+ id: string;
59
+ accountId: string;
60
+ workspaceId: string;
61
+ sourceId: string;
62
+ sourceVersion: number;
63
+ sourceConfiguration: Record<string, unknown>;
64
+ matchedTriggerRevisions: Array<{ triggerId: string; revision: number }>;
65
+ deliveryKey: string;
66
+ requestDigest: string;
67
+ normalizedEvent: AutomationNormalizedEvent;
68
+ status: "accepted" | "ignored" | "failed";
69
+ ignoredReason: string | null;
70
+ errorCode: string | null;
71
+ createdAt: string;
72
+ updatedAt: string;
73
+ };
74
+
75
+ export type AutomationRunExecution = AutomationRun & {
76
+ acceptedExecution: AutomationAcceptedExecution;
77
+ };
78
+
79
+ export async function createAutomationSource(
80
+ db: Database,
81
+ input: {
82
+ accountId: string;
83
+ workspaceId: string;
84
+ createdBySubjectId: string;
85
+ request: CreateAutomationSourceRequest;
86
+ webhookSecretEncrypted: string;
87
+ packInstallationId?: string | null;
88
+ packConnectorId?: string | null;
89
+ },
90
+ ): Promise<AutomationSource> {
91
+ return await withWorkspaceRls(
92
+ db,
93
+ input.workspaceId,
94
+ async (scoped) =>
95
+ await scoped.transaction(async (tx) => {
96
+ const [row] = await tx
97
+ .insert(schema.automationSources)
98
+ .values({
99
+ accountId: input.accountId,
100
+ workspaceId: input.workspaceId,
101
+ name: input.request.name,
102
+ adapterId: input.request.adapterId,
103
+ configuration: input.request.configuration,
104
+ webhookSecretEncrypted: input.webhookSecretEncrypted,
105
+ packInstallationId: input.packInstallationId ?? null,
106
+ packConnectorId: input.packConnectorId ?? null,
107
+ createdBySubjectId: input.createdBySubjectId,
108
+ })
109
+ .returning();
110
+ if (!row) throw new Error("Failed to create automation source");
111
+ await tx.insert(schema.automationWebhookEndpoints).values({
112
+ endpointId: row.endpointId,
113
+ accountId: row.accountId,
114
+ workspaceId: row.workspaceId,
115
+ sourceId: row.id,
116
+ });
117
+ return mapSource(row);
118
+ }),
119
+ );
120
+ }
121
+
122
+ export async function resolveAutomationWebhookEndpoint(
123
+ db: Database,
124
+ endpointId: string,
125
+ ): Promise<{
126
+ accountId: string;
127
+ workspaceId: string;
128
+ sourceId: string;
129
+ } | null> {
130
+ const [row] = await db
131
+ .select({
132
+ accountId: schema.automationWebhookEndpoints.accountId,
133
+ workspaceId: schema.automationWebhookEndpoints.workspaceId,
134
+ sourceId: schema.automationWebhookEndpoints.sourceId,
135
+ })
136
+ .from(schema.automationWebhookEndpoints)
137
+ .where(eq(schema.automationWebhookEndpoints.endpointId, endpointId))
138
+ .limit(1);
139
+ return row ?? null;
140
+ }
141
+
142
+ export async function getAutomationSourceSecret(
143
+ db: Database,
144
+ input: { accountId: string; workspaceId: string; sourceId: string },
145
+ ): Promise<AutomationSourceSecret | null> {
146
+ return await withWorkspaceRls(db, input.workspaceId, async (scoped) => {
147
+ const [row] = await scoped
148
+ .select()
149
+ .from(schema.automationSources)
150
+ .where(
151
+ and(
152
+ eq(schema.automationSources.accountId, input.accountId),
153
+ eq(schema.automationSources.workspaceId, input.workspaceId),
154
+ eq(schema.automationSources.id, input.sourceId),
155
+ ),
156
+ )
157
+ .limit(1);
158
+ return row
159
+ ? {
160
+ ...mapSource(row),
161
+ webhookSecretEncrypted: row.webhookSecretEncrypted,
162
+ }
163
+ : null;
164
+ });
165
+ }
166
+
167
+ export async function listAutomationSources(
168
+ db: Database,
169
+ workspaceId: string,
170
+ ): Promise<AutomationSource[]> {
171
+ return await withWorkspaceRls(db, workspaceId, async (scoped) =>
172
+ (
173
+ await scoped
174
+ .select()
175
+ .from(schema.automationSources)
176
+ .where(eq(schema.automationSources.workspaceId, workspaceId))
177
+ .orderBy(desc(schema.automationSources.updatedAt))
178
+ ).map(mapSource),
179
+ );
180
+ }
181
+
182
+ export async function updateAutomationSource(
183
+ db: Database,
184
+ input: {
185
+ workspaceId: string;
186
+ sourceId: string;
187
+ request: UpdateAutomationSourceRequest;
188
+ webhookSecretEncrypted?: string;
189
+ },
190
+ ): Promise<AutomationSource | null> {
191
+ return await withWorkspaceRls(db, input.workspaceId, async (scoped) => {
192
+ const [row] = await scoped
193
+ .update(schema.automationSources)
194
+ .set({
195
+ ...(input.request.name !== undefined ? { name: input.request.name } : {}),
196
+ ...(input.request.configuration !== undefined
197
+ ? { configuration: input.request.configuration }
198
+ : {}),
199
+ ...(input.request.status !== undefined ? { status: input.request.status } : {}),
200
+ ...(input.webhookSecretEncrypted !== undefined
201
+ ? { webhookSecretEncrypted: input.webhookSecretEncrypted }
202
+ : {}),
203
+ version: sql`${schema.automationSources.version} + 1`,
204
+ updatedAt: new Date(),
205
+ })
206
+ .where(
207
+ and(
208
+ eq(schema.automationSources.workspaceId, input.workspaceId),
209
+ eq(schema.automationSources.id, input.sourceId),
210
+ isNull(schema.automationSources.packInstallationId),
211
+ ),
212
+ )
213
+ .returning();
214
+ return row ? mapSource(row) : null;
215
+ });
216
+ }
217
+
218
+ export async function createAutomationTrigger(
219
+ db: Database,
220
+ input: {
221
+ accountId: string;
222
+ workspaceId: string;
223
+ createdBySubjectId: string;
224
+ request: CreateAutomationTriggerRequest;
225
+ adapterId: string;
226
+ },
227
+ ): Promise<AutomationTrigger> {
228
+ return await withWorkspaceRls(
229
+ db,
230
+ input.workspaceId,
231
+ async (scoped) =>
232
+ await scoped.transaction(async (tx) => {
233
+ const [source] = await tx
234
+ .select({
235
+ id: schema.automationSources.id,
236
+ adapterId: schema.automationSources.adapterId,
237
+ status: schema.automationSources.status,
238
+ packInstallationId: schema.automationSources.packInstallationId,
239
+ })
240
+ .from(schema.automationSources)
241
+ .where(
242
+ and(
243
+ eq(schema.automationSources.accountId, input.accountId),
244
+ eq(schema.automationSources.workspaceId, input.workspaceId),
245
+ eq(schema.automationSources.id, input.request.sourceId),
246
+ ),
247
+ )
248
+ .limit(1);
249
+ if (!source) throw new Error("Automation source not found");
250
+ if (source.status !== "active") throw new Error("Automation source is disabled");
251
+ if (source.adapterId !== input.adapterId) {
252
+ throw new Error("Automation source and trigger adapter do not match");
253
+ }
254
+ if (source.packInstallationId !== input.request.packInstallationId) {
255
+ throw new Error("Automation source and trigger Pack ownership do not match");
256
+ }
257
+ if (input.request.packInstallationId) {
258
+ const [installation] = await tx
259
+ .select({
260
+ status: schema.packInstallations.status,
261
+ manifestSnapshot: schema.packInstallations.manifestSnapshot,
262
+ })
263
+ .from(schema.packInstallations)
264
+ .where(
265
+ and(
266
+ eq(schema.packInstallations.workspaceId, input.workspaceId),
267
+ eq(schema.packInstallations.id, input.request.packInstallationId),
268
+ ),
269
+ )
270
+ .limit(1);
271
+ const manifest = installation?.manifestSnapshot
272
+ ? CapabilityPack.safeParse(installation.manifestSnapshot)
273
+ : null;
274
+ if (!installation || installation.status !== "active" || !manifest?.success) {
275
+ throw new Error("Automation Pack installation is not active");
276
+ }
277
+ const template = manifest.data.automationTemplates?.find(
278
+ (candidate) => candidate.id === input.request.packTemplateId,
279
+ );
280
+ if (!template || template.adapterId !== input.adapterId) {
281
+ throw new Error("Automation Pack template does not match the source adapter");
282
+ }
283
+ if (
284
+ stableJson([...new Set(input.request.eventTypes)].sort()) !==
285
+ stableJson([...new Set(template.eventTypes)].sort()) ||
286
+ stableJson(input.request.configuration) !== stableJson(template.configuration) ||
287
+ stableJson(input.request.sessionTemplate) !== stableJson(template.sessionTemplate)
288
+ ) {
289
+ throw new Error("Automation Pack trigger must use its frozen manifest template");
290
+ }
291
+ }
292
+ const [head] = await tx
293
+ .insert(schema.automationTriggers)
294
+ .values({
295
+ accountId: input.accountId,
296
+ workspaceId: input.workspaceId,
297
+ sourceId: input.request.sourceId,
298
+ name: input.request.name,
299
+ status: input.request.status,
300
+ packInstallationId: input.request.packInstallationId,
301
+ packTemplateId: input.request.packTemplateId,
302
+ createdBySubjectId: input.createdBySubjectId,
303
+ })
304
+ .returning();
305
+ if (!head) throw new Error("Failed to create automation trigger");
306
+ const [revision] = await tx
307
+ .insert(schema.automationTriggerRevisions)
308
+ .values({
309
+ triggerId: head.id,
310
+ revision: 1,
311
+ accountId: input.accountId,
312
+ workspaceId: input.workspaceId,
313
+ adapterId: input.adapterId,
314
+ eventTypes: [...new Set(input.request.eventTypes)],
315
+ configuration: input.request.configuration,
316
+ parameters: input.request.parameters,
317
+ sessionTemplate: input.request.sessionTemplate,
318
+ createdBySubjectId: input.createdBySubjectId,
319
+ })
320
+ .returning();
321
+ if (!revision) throw new Error("Failed to create automation trigger revision");
322
+ return mapTrigger(head, revision);
323
+ }),
324
+ );
325
+ }
326
+
327
+ export async function listAutomationTriggers(
328
+ db: Database,
329
+ workspaceId: string,
330
+ ): Promise<AutomationTrigger[]> {
331
+ return await withWorkspaceRls(db, workspaceId, async (scoped) => {
332
+ const rows = await scoped
333
+ .select({
334
+ head: schema.automationTriggers,
335
+ revision: schema.automationTriggerRevisions,
336
+ })
337
+ .from(schema.automationTriggers)
338
+ .innerJoin(
339
+ schema.automationTriggerRevisions,
340
+ and(
341
+ eq(schema.automationTriggerRevisions.triggerId, schema.automationTriggers.id),
342
+ eq(schema.automationTriggerRevisions.revision, schema.automationTriggers.currentRevision),
343
+ ),
344
+ )
345
+ .where(eq(schema.automationTriggers.workspaceId, workspaceId))
346
+ .orderBy(desc(schema.automationTriggers.updatedAt));
347
+ return rows.map(({ head, revision }) => mapTrigger(head, revision));
348
+ });
349
+ }
350
+
351
+ export async function listActiveAutomationTriggersForSource(
352
+ db: Database,
353
+ input: { accountId: string; workspaceId: string; sourceId: string },
354
+ ): Promise<AutomationTriggerWithRevision[]> {
355
+ return await withWorkspaceRls(db, input.workspaceId, async (scoped) => {
356
+ const rows = await scoped
357
+ .select({
358
+ head: schema.automationTriggers,
359
+ revision: schema.automationTriggerRevisions,
360
+ sourceStatus: schema.automationSources.status,
361
+ sourceVersion: schema.automationSources.version,
362
+ sourceConfiguration: schema.automationSources.configuration,
363
+ })
364
+ .from(schema.automationTriggers)
365
+ .innerJoin(
366
+ schema.automationTriggerRevisions,
367
+ and(
368
+ eq(schema.automationTriggerRevisions.triggerId, schema.automationTriggers.id),
369
+ eq(schema.automationTriggerRevisions.revision, schema.automationTriggers.currentRevision),
370
+ ),
371
+ )
372
+ .innerJoin(
373
+ schema.automationSources,
374
+ eq(schema.automationSources.id, schema.automationTriggers.sourceId),
375
+ )
376
+ .where(
377
+ and(
378
+ eq(schema.automationTriggers.accountId, input.accountId),
379
+ eq(schema.automationTriggers.workspaceId, input.workspaceId),
380
+ eq(schema.automationTriggers.sourceId, input.sourceId),
381
+ eq(schema.automationTriggers.status, "active"),
382
+ eq(schema.automationSources.status, "active"),
383
+ ),
384
+ );
385
+ return rows.map((row) => ({
386
+ ...mapTrigger(row.head, row.revision),
387
+ sourceStatus: row.sourceStatus as "active" | "disabled",
388
+ sourceVersion: row.sourceVersion,
389
+ sourceConfiguration: row.sourceConfiguration,
390
+ }));
391
+ });
392
+ }
393
+
394
+ export async function getAutomationTriggerRevisions(
395
+ db: Database,
396
+ input: {
397
+ workspaceId: string;
398
+ refs: Array<{ triggerId: string; revision: number }>;
399
+ },
400
+ ): Promise<AutomationTrigger[]> {
401
+ if (input.refs.length === 0) return [];
402
+ return await withWorkspaceRls(db, input.workspaceId, async (scoped) => {
403
+ const rows = await scoped
404
+ .select({
405
+ head: schema.automationTriggers,
406
+ revision: schema.automationTriggerRevisions,
407
+ })
408
+ .from(schema.automationTriggers)
409
+ .innerJoin(
410
+ schema.automationTriggerRevisions,
411
+ eq(schema.automationTriggerRevisions.triggerId, schema.automationTriggers.id),
412
+ )
413
+ .where(
414
+ and(
415
+ eq(schema.automationTriggers.workspaceId, input.workspaceId),
416
+ inArray(
417
+ schema.automationTriggers.id,
418
+ input.refs.map((ref) => ref.triggerId),
419
+ ),
420
+ ),
421
+ );
422
+ const byIdentity = new Map(
423
+ rows.map(({ head, revision }) => [
424
+ `${head.id}:${revision.revision}`,
425
+ mapTrigger(head, revision, revision.revision),
426
+ ]),
427
+ );
428
+ return input.refs
429
+ .map((ref) => byIdentity.get(`${ref.triggerId}:${ref.revision}`))
430
+ .filter((trigger): trigger is AutomationTrigger => Boolean(trigger));
431
+ });
432
+ }
433
+
434
+ export async function updateAutomationTrigger(
435
+ db: Database,
436
+ input: {
437
+ workspaceId: string;
438
+ triggerId: string;
439
+ subjectId: string;
440
+ request: UpdateAutomationTriggerRequest;
441
+ },
442
+ ): Promise<AutomationTrigger | null> {
443
+ return await withWorkspaceRls(
444
+ db,
445
+ input.workspaceId,
446
+ async (scoped) =>
447
+ await scoped.transaction(async (tx) => {
448
+ const [existing] = await tx
449
+ .select({
450
+ head: schema.automationTriggers,
451
+ revision: schema.automationTriggerRevisions,
452
+ })
453
+ .from(schema.automationTriggers)
454
+ .innerJoin(
455
+ schema.automationTriggerRevisions,
456
+ and(
457
+ eq(schema.automationTriggerRevisions.triggerId, schema.automationTriggers.id),
458
+ eq(
459
+ schema.automationTriggerRevisions.revision,
460
+ schema.automationTriggers.currentRevision,
461
+ ),
462
+ ),
463
+ )
464
+ .where(
465
+ and(
466
+ eq(schema.automationTriggers.workspaceId, input.workspaceId),
467
+ eq(schema.automationTriggers.id, input.triggerId),
468
+ ),
469
+ )
470
+ .for("update")
471
+ .limit(1);
472
+ if (!existing) return null;
473
+ if (existing.head.currentRevision !== input.request.expectedRevision) {
474
+ throw new AutomationRevisionConflictError();
475
+ }
476
+ if (existing.head.packInstallationId) {
477
+ throw new Error(
478
+ "Pack-owned automation triggers must be managed through their Pack setup API",
479
+ );
480
+ }
481
+ const revisionNumber = existing.head.currentRevision + 1;
482
+ const [head] = await tx
483
+ .update(schema.automationTriggers)
484
+ .set({
485
+ ...(input.request.name !== undefined ? { name: input.request.name } : {}),
486
+ ...(input.request.status !== undefined ? { status: input.request.status } : {}),
487
+ currentRevision: revisionNumber,
488
+ updatedAt: new Date(),
489
+ })
490
+ .where(eq(schema.automationTriggers.id, input.triggerId))
491
+ .returning();
492
+ if (!head) throw new Error("Failed to update automation trigger");
493
+ const [revision] = await tx
494
+ .insert(schema.automationTriggerRevisions)
495
+ .values({
496
+ triggerId: head.id,
497
+ revision: revisionNumber,
498
+ accountId: head.accountId,
499
+ workspaceId: head.workspaceId,
500
+ adapterId: existing.revision.adapterId,
501
+ eventTypes: input.request.eventTypes
502
+ ? [...new Set(input.request.eventTypes)]
503
+ : existing.revision.eventTypes,
504
+ configuration: input.request.configuration ?? existing.revision.configuration,
505
+ parameters: input.request.parameters ?? existing.revision.parameters,
506
+ sessionTemplate: input.request.sessionTemplate ?? existing.revision.sessionTemplate,
507
+ createdBySubjectId: input.subjectId,
508
+ })
509
+ .returning();
510
+ if (!revision) throw new Error("Failed to write automation trigger revision");
511
+ return mapTrigger(head, revision);
512
+ }),
513
+ );
514
+ }
515
+
516
+ export async function recordAutomationEvent(
517
+ db: Database,
518
+ input: {
519
+ accountId: string;
520
+ workspaceId: string;
521
+ sourceId: string;
522
+ sourceVersion: number;
523
+ sourceConfiguration: Record<string, unknown>;
524
+ matchedTriggerRevisions: Array<{ triggerId: string; revision: number }>;
525
+ deliveryKey: string;
526
+ requestDigest: string;
527
+ normalizedEvent: AutomationNormalizedEvent;
528
+ ignoredReason?: string | null;
529
+ },
530
+ ): Promise<{ event: AutomationStoredEvent; duplicate: boolean }> {
531
+ return await withWorkspaceRls(db, input.workspaceId, async (scoped) => {
532
+ const [inserted] = await scoped
533
+ .insert(schema.automationTriggerEvents)
534
+ .values({
535
+ accountId: input.accountId,
536
+ workspaceId: input.workspaceId,
537
+ sourceId: input.sourceId,
538
+ sourceVersion: input.sourceVersion,
539
+ sourceConfiguration: input.sourceConfiguration,
540
+ matchedTriggerRevisions: input.matchedTriggerRevisions,
541
+ deliveryKey: input.deliveryKey,
542
+ requestDigest: input.requestDigest,
543
+ adapterId: input.normalizedEvent.adapterId,
544
+ eventType: input.normalizedEvent.eventType,
545
+ occurrenceKey: input.normalizedEvent.occurrenceKey,
546
+ normalizedEvent: input.normalizedEvent,
547
+ status: input.ignoredReason ? "ignored" : "accepted",
548
+ ignoredReason: input.ignoredReason ?? null,
549
+ })
550
+ .onConflictDoNothing({
551
+ target: [
552
+ schema.automationTriggerEvents.sourceId,
553
+ schema.automationTriggerEvents.deliveryKey,
554
+ ],
555
+ })
556
+ .returning();
557
+ if (inserted) return { event: mapEvent(inserted), duplicate: false };
558
+ const [existing] = await scoped
559
+ .select()
560
+ .from(schema.automationTriggerEvents)
561
+ .where(
562
+ and(
563
+ eq(schema.automationTriggerEvents.sourceId, input.sourceId),
564
+ eq(schema.automationTriggerEvents.deliveryKey, input.deliveryKey),
565
+ ),
566
+ )
567
+ .limit(1);
568
+ if (!existing) throw new Error("Automation delivery conflict could not be resolved");
569
+ if (existing.requestDigest !== input.requestDigest) {
570
+ throw new AutomationDeliveryConflictError();
571
+ }
572
+ return { event: mapEvent(existing), duplicate: true };
573
+ });
574
+ }
575
+
576
+ export async function createAutomationRun(
577
+ db: Database,
578
+ input: {
579
+ accountId: string;
580
+ workspaceId: string;
581
+ sourceId: string;
582
+ triggerId: string;
583
+ triggerRevision: number;
584
+ eventId: string;
585
+ occurrenceKey: string;
586
+ acceptedExecution: AutomationAcceptedExecution;
587
+ },
588
+ ): Promise<{ run: AutomationRunExecution; duplicate: boolean }> {
589
+ return await withWorkspaceRls(
590
+ db,
591
+ input.workspaceId,
592
+ async (scoped) =>
593
+ await scoped.transaction(async (tx) => {
594
+ const [inserted] = await tx
595
+ .insert(schema.automationRuns)
596
+ .values(input)
597
+ .onConflictDoNothing({
598
+ target: [schema.automationRuns.triggerId, schema.automationRuns.occurrenceKey],
599
+ })
600
+ .returning();
601
+ const row =
602
+ inserted ??
603
+ (
604
+ await tx
605
+ .select()
606
+ .from(schema.automationRuns)
607
+ .where(
608
+ and(
609
+ eq(schema.automationRuns.triggerId, input.triggerId),
610
+ eq(schema.automationRuns.occurrenceKey, input.occurrenceKey),
611
+ ),
612
+ )
613
+ .limit(1)
614
+ )[0];
615
+ if (!row) throw new Error("Automation run conflict could not be resolved");
616
+ if (
617
+ row.accountId !== input.accountId ||
618
+ row.workspaceId !== input.workspaceId ||
619
+ row.sourceId !== input.sourceId ||
620
+ row.triggerId !== input.triggerId
621
+ ) {
622
+ throw new Error("Automation occurrence key was reused with different provenance");
623
+ }
624
+ await tx
625
+ .insert(schema.automationRunEventLinks)
626
+ .values({
627
+ runId: row.id,
628
+ eventId: input.eventId,
629
+ accountId: input.accountId,
630
+ workspaceId: input.workspaceId,
631
+ sourceId: input.sourceId,
632
+ triggerId: input.triggerId,
633
+ })
634
+ .onConflictDoNothing();
635
+ return { run: mapRunExecution(row), duplicate: !inserted };
636
+ }),
637
+ );
638
+ }
639
+
640
+ export async function listAutomationRuns(
641
+ db: Database,
642
+ workspaceId: string,
643
+ limit = 100,
644
+ ): Promise<AutomationRun[]> {
645
+ return await withWorkspaceRls(db, workspaceId, async (scoped) =>
646
+ (
647
+ await scoped
648
+ .select()
649
+ .from(schema.automationRuns)
650
+ .where(eq(schema.automationRuns.workspaceId, workspaceId))
651
+ .orderBy(desc(schema.automationRuns.createdAt))
652
+ .limit(Math.max(1, Math.min(limit, 200)))
653
+ ).map(mapRun),
654
+ );
655
+ }
656
+
657
+ export async function getAutomationRunExecution(
658
+ db: Database,
659
+ input: { accountId: string; workspaceId: string; runId: string },
660
+ ): Promise<AutomationRunExecution | null> {
661
+ return await withWorkspaceRls(db, input.workspaceId, async (scoped) => {
662
+ const [row] = await scoped
663
+ .select()
664
+ .from(schema.automationRuns)
665
+ .where(
666
+ and(
667
+ eq(schema.automationRuns.accountId, input.accountId),
668
+ eq(schema.automationRuns.workspaceId, input.workspaceId),
669
+ eq(schema.automationRuns.id, input.runId),
670
+ ),
671
+ )
672
+ .limit(1);
673
+ return row ? mapRunExecution(row) : null;
674
+ });
675
+ }
676
+
677
+ export async function claimAutomationRun(
678
+ db: Database,
679
+ input: { accountId: string; workspaceId: string; runId: string },
680
+ ): Promise<AutomationRunExecution | null> {
681
+ return await withWorkspaceRls(
682
+ db,
683
+ input.workspaceId,
684
+ async (scoped) =>
685
+ await scoped.transaction(async (tx) => {
686
+ const [row] = await tx
687
+ .select()
688
+ .from(schema.automationRuns)
689
+ .where(
690
+ and(
691
+ eq(schema.automationRuns.accountId, input.accountId),
692
+ eq(schema.automationRuns.workspaceId, input.workspaceId),
693
+ eq(schema.automationRuns.id, input.runId),
694
+ ),
695
+ )
696
+ .for("update")
697
+ .limit(1);
698
+ if (!row) return null;
699
+ if (row.status === "dispatched" || row.status === "skipped") return mapRunExecution(row);
700
+ const [updated] = await tx
701
+ .update(schema.automationRuns)
702
+ .set({
703
+ status: "dispatching",
704
+ errorCode: null,
705
+ updatedAt: new Date(),
706
+ })
707
+ .where(eq(schema.automationRuns.id, row.id))
708
+ .returning();
709
+ return updated ? mapRunExecution(updated) : null;
710
+ }),
711
+ );
712
+ }
713
+
714
+ export async function assertAutomationRunAuthorityInTransaction(
715
+ db: Database,
716
+ input: {
717
+ workspaceId: string;
718
+ runId: string;
719
+ triggerId: string;
720
+ triggerRevision: number;
721
+ sourceId: string;
722
+ sourceVersion: number;
723
+ sessionId: string;
724
+ },
725
+ ): Promise<void> {
726
+ const [row] = await db
727
+ .select({
728
+ runStatus: schema.automationRuns.status,
729
+ sourceStatus: schema.automationSources.status,
730
+ sourceVersion: schema.automationSources.version,
731
+ triggerStatus: schema.automationTriggers.status,
732
+ currentRevision: schema.automationTriggers.currentRevision,
733
+ packInstallationId: schema.automationTriggers.packInstallationId,
734
+ })
735
+ .from(schema.automationRuns)
736
+ .innerJoin(
737
+ schema.automationSources,
738
+ eq(schema.automationSources.id, schema.automationRuns.sourceId),
739
+ )
740
+ .innerJoin(
741
+ schema.automationTriggers,
742
+ eq(schema.automationTriggers.id, schema.automationRuns.triggerId),
743
+ )
744
+ .where(
745
+ and(
746
+ eq(schema.automationRuns.workspaceId, input.workspaceId),
747
+ eq(schema.automationRuns.id, input.runId),
748
+ eq(schema.automationRuns.triggerId, input.triggerId),
749
+ eq(schema.automationRuns.triggerRevision, input.triggerRevision),
750
+ eq(schema.automationRuns.sourceId, input.sourceId),
751
+ ),
752
+ )
753
+ .for("update")
754
+ .limit(1);
755
+ if (
756
+ !row ||
757
+ row.runStatus !== "dispatching" ||
758
+ row.sourceStatus !== "active" ||
759
+ row.triggerStatus !== "active"
760
+ ) {
761
+ throw new AutomationAuthorityRevokedError();
762
+ }
763
+ if (row.sourceVersion !== input.sourceVersion) {
764
+ throw new AutomationAuthorityRevokedError("Automation source changed before dispatch");
765
+ }
766
+ if (row.currentRevision !== input.triggerRevision) {
767
+ throw new AutomationAuthorityRevokedError("Automation trigger changed before dispatch");
768
+ }
769
+ if (row.packInstallationId) {
770
+ const [installation] = await db
771
+ .select({ status: schema.packInstallations.status })
772
+ .from(schema.packInstallations)
773
+ .where(
774
+ and(
775
+ eq(schema.packInstallations.workspaceId, input.workspaceId),
776
+ eq(schema.packInstallations.id, row.packInstallationId),
777
+ ),
778
+ )
779
+ .limit(1);
780
+ if (!installation || installation.status !== "active") {
781
+ throw new AutomationAuthorityRevokedError("Automation Pack is no longer active");
782
+ }
783
+ }
784
+ await db
785
+ .update(schema.automationRuns)
786
+ .set({
787
+ status: "dispatched",
788
+ sessionId: input.sessionId,
789
+ errorCode: null,
790
+ updatedAt: new Date(),
791
+ })
792
+ .where(eq(schema.automationRuns.id, input.runId));
793
+ }
794
+
795
+ export async function settleAutomationRun(
796
+ db: Database,
797
+ input: {
798
+ workspaceId: string;
799
+ runId: string;
800
+ status: "skipped" | "failed";
801
+ errorCode: string;
802
+ },
803
+ ): Promise<void> {
804
+ await withWorkspaceRls(db, input.workspaceId, async (scoped) => {
805
+ await scoped
806
+ .update(schema.automationRuns)
807
+ .set({
808
+ status: input.status,
809
+ errorCode: input.errorCode.slice(0, 512),
810
+ updatedAt: new Date(),
811
+ })
812
+ .where(
813
+ and(
814
+ eq(schema.automationRuns.workspaceId, input.workspaceId),
815
+ eq(schema.automationRuns.id, input.runId),
816
+ inArray(schema.automationRuns.status, ["queued", "dispatching", "failed"]),
817
+ ),
818
+ );
819
+ });
820
+ }
821
+
822
+ function mapSource(row: SourceRow): AutomationSource {
823
+ return {
824
+ id: row.id,
825
+ accountId: row.accountId,
826
+ workspaceId: row.workspaceId,
827
+ name: row.name,
828
+ adapterId: row.adapterId,
829
+ configuration: row.configuration,
830
+ status: row.status as AutomationSource["status"],
831
+ version: row.version,
832
+ packInstallationId: row.packInstallationId,
833
+ packConnectorId: row.packConnectorId,
834
+ hasWebhookSecret: Boolean(row.webhookSecretEncrypted),
835
+ webhookPath: `/v1/webhooks/automations/${row.endpointId}`,
836
+ createdBySubjectId: row.createdBySubjectId,
837
+ createdAt: row.createdAt.toISOString(),
838
+ updatedAt: row.updatedAt.toISOString(),
839
+ };
840
+ }
841
+
842
+ function mapTrigger(
843
+ head: TriggerRow,
844
+ revision: RevisionRow,
845
+ revisionNumber = head.currentRevision,
846
+ ): AutomationTrigger {
847
+ return {
848
+ id: head.id,
849
+ accountId: head.accountId,
850
+ workspaceId: head.workspaceId,
851
+ sourceId: head.sourceId,
852
+ name: head.name,
853
+ adapterId: revision.adapterId,
854
+ eventTypes: revision.eventTypes,
855
+ configuration: revision.configuration,
856
+ parameters: revision.parameters,
857
+ sessionTemplate: AutomationSessionTemplate.parse(revision.sessionTemplate),
858
+ status: head.status as AutomationTrigger["status"],
859
+ revision: revisionNumber,
860
+ packInstallationId: head.packInstallationId,
861
+ packTemplateId: head.packTemplateId,
862
+ createdBySubjectId: head.createdBySubjectId,
863
+ createdAt: head.createdAt.toISOString(),
864
+ updatedAt: head.updatedAt.toISOString(),
865
+ };
866
+ }
867
+
868
+ function mapEvent(row: EventRow): AutomationStoredEvent {
869
+ return {
870
+ id: row.id,
871
+ accountId: row.accountId,
872
+ workspaceId: row.workspaceId,
873
+ sourceId: row.sourceId,
874
+ sourceVersion: row.sourceVersion,
875
+ sourceConfiguration: row.sourceConfiguration,
876
+ matchedTriggerRevisions: row.matchedTriggerRevisions,
877
+ deliveryKey: row.deliveryKey,
878
+ requestDigest: row.requestDigest,
879
+ normalizedEvent: AutomationNormalizedEvent.parse(row.normalizedEvent),
880
+ status: row.status as AutomationStoredEvent["status"],
881
+ ignoredReason: row.ignoredReason,
882
+ errorCode: row.errorCode,
883
+ createdAt: row.createdAt.toISOString(),
884
+ updatedAt: row.updatedAt.toISOString(),
885
+ };
886
+ }
887
+
888
+ function mapRun(row: RunRow): AutomationRun {
889
+ return {
890
+ id: row.id,
891
+ accountId: row.accountId,
892
+ workspaceId: row.workspaceId,
893
+ sourceId: row.sourceId,
894
+ triggerId: row.triggerId,
895
+ triggerRevision: row.triggerRevision,
896
+ eventId: row.eventId,
897
+ occurrenceKey: row.occurrenceKey,
898
+ status: row.status as AutomationRun["status"],
899
+ sessionId: row.sessionId,
900
+ errorCode: row.errorCode,
901
+ createdAt: row.createdAt.toISOString(),
902
+ updatedAt: row.updatedAt.toISOString(),
903
+ };
904
+ }
905
+
906
+ function mapRunExecution(row: RunRow): AutomationRunExecution {
907
+ return {
908
+ ...mapRun(row),
909
+ acceptedExecution: AutomationAcceptedExecution.parse(row.acceptedExecution),
910
+ };
911
+ }