@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,890 @@
1
+ import {
2
+ AutomationSessionTemplate,
3
+ OPENGENI_PR_REVIEW_PACK_ID,
4
+ OPENGENI_PR_REVIEW_SESSION_ROLE,
5
+ type PrReviewAppRegistration,
6
+ type PrReviewProvider,
7
+ type PrReviewRepositoryBinding,
8
+ } from "@opengeni/contracts";
9
+ import { randomUUID } from "node:crypto";
10
+ import { and, desc, eq, sql } from "drizzle-orm";
11
+ import type { Database } from "./database";
12
+ import { withRlsContext } from "./database";
13
+ import * as schema from "./schema";
14
+
15
+ export type PrReviewAppRegistrationSecret = PrReviewAppRegistration & {
16
+ credentialEncrypted: string | null;
17
+ };
18
+
19
+ export class PrReviewDispatchAuthorityError extends Error {
20
+ constructor(message: string) {
21
+ super(message);
22
+ this.name = "PrReviewDispatchAuthorityError";
23
+ }
24
+ }
25
+
26
+ export async function createPrReviewAppRegistration(
27
+ db: Database,
28
+ input: {
29
+ accountId: string;
30
+ workspaceId: string;
31
+ name: string;
32
+ provider: PrReviewProvider;
33
+ providerBaseUrl: string;
34
+ appId: string | null;
35
+ credentialKind: "github_app" | "provider_token";
36
+ credentialEncrypted: string | null;
37
+ accessTokenExpiresAt: Date | null;
38
+ webhookAuthKind: "hmac_sha256" | "shared_token" | "basic";
39
+ webhookSecretEncrypted: string;
40
+ webhookUsername: string | null;
41
+ createdBySubjectId: string;
42
+ packInstallationId: string;
43
+ packConnectorId: string;
44
+ },
45
+ ): Promise<PrReviewAppRegistration> {
46
+ return await withRlsContext(db, input, async (scopedDb) => {
47
+ return await scopedDb.transaction(async (tx) => {
48
+ const registrationId = randomUUID();
49
+ const [source] = await tx
50
+ .insert(schema.automationSources)
51
+ .values({
52
+ accountId: input.accountId,
53
+ workspaceId: input.workspaceId,
54
+ name: `${input.name} webhooks`,
55
+ adapterId: "source-control.pull-request.v1",
56
+ configuration: {
57
+ provider: input.provider,
58
+ providerBaseUrl: input.providerBaseUrl,
59
+ registrationId,
60
+ webhookUsername: input.webhookUsername,
61
+ },
62
+ webhookSecretEncrypted: input.webhookSecretEncrypted,
63
+ packInstallationId: input.packInstallationId,
64
+ packConnectorId: input.packConnectorId,
65
+ createdBySubjectId: input.createdBySubjectId,
66
+ })
67
+ .returning();
68
+ if (!source) throw new Error("Failed to create PR Review automation source");
69
+ await tx.insert(schema.automationWebhookEndpoints).values({
70
+ endpointId: source.endpointId,
71
+ accountId: input.accountId,
72
+ workspaceId: input.workspaceId,
73
+ sourceId: source.id,
74
+ });
75
+ const [row] = await tx
76
+ .insert(schema.prReviewAppRegistrations)
77
+ .values({
78
+ id: registrationId,
79
+ sourceId: source.id,
80
+ accountId: input.accountId,
81
+ workspaceId: input.workspaceId,
82
+ name: input.name,
83
+ provider: input.provider,
84
+ providerBaseUrl: input.providerBaseUrl,
85
+ appId: input.appId,
86
+ credentialKind: input.credentialKind,
87
+ credentialEncrypted: input.credentialEncrypted,
88
+ accessTokenExpiresAt: input.accessTokenExpiresAt,
89
+ webhookAuthKind: input.webhookAuthKind,
90
+ webhookUsername: input.webhookUsername,
91
+ createdBySubjectId: input.createdBySubjectId,
92
+ })
93
+ .returning();
94
+ if (!row) throw new Error("Failed to create PR Review app registration");
95
+ return mapRegistration(row, source.endpointId);
96
+ });
97
+ });
98
+ }
99
+
100
+ export async function listPrReviewAppRegistrations(
101
+ db: Database,
102
+ accountId: string,
103
+ workspaceId: string,
104
+ ): Promise<PrReviewAppRegistration[]> {
105
+ return await withRlsContext(db, { accountId, workspaceId }, async (scopedDb) => {
106
+ const rows = await scopedDb
107
+ .select({
108
+ registration: schema.prReviewAppRegistrations,
109
+ endpointId: schema.automationSources.endpointId,
110
+ })
111
+ .from(schema.prReviewAppRegistrations)
112
+ .innerJoin(
113
+ schema.automationSources,
114
+ and(
115
+ eq(schema.automationSources.workspaceId, schema.prReviewAppRegistrations.workspaceId),
116
+ eq(schema.automationSources.id, schema.prReviewAppRegistrations.sourceId),
117
+ ),
118
+ )
119
+ .where(eq(schema.prReviewAppRegistrations.workspaceId, workspaceId))
120
+ .orderBy(desc(schema.prReviewAppRegistrations.updatedAt));
121
+ return rows.map(({ registration, endpointId }) => mapRegistration(registration, endpointId));
122
+ });
123
+ }
124
+
125
+ export async function getPrReviewAppRegistrationSecret(
126
+ db: Database,
127
+ input: { accountId: string; workspaceId: string; registrationId: string },
128
+ ): Promise<PrReviewAppRegistrationSecret | null> {
129
+ return await withRlsContext(db, input, async (scopedDb) => {
130
+ const [result] = await scopedDb
131
+ .select({
132
+ registration: schema.prReviewAppRegistrations,
133
+ endpointId: schema.automationSources.endpointId,
134
+ })
135
+ .from(schema.prReviewAppRegistrations)
136
+ .innerJoin(
137
+ schema.automationSources,
138
+ and(
139
+ eq(schema.automationSources.workspaceId, schema.prReviewAppRegistrations.workspaceId),
140
+ eq(schema.automationSources.id, schema.prReviewAppRegistrations.sourceId),
141
+ ),
142
+ )
143
+ .where(
144
+ and(
145
+ eq(schema.prReviewAppRegistrations.workspaceId, input.workspaceId),
146
+ eq(schema.prReviewAppRegistrations.id, input.registrationId),
147
+ ),
148
+ )
149
+ .limit(1);
150
+ const row = result?.registration;
151
+ return row
152
+ ? {
153
+ ...mapRegistration(row, result.endpointId),
154
+ credentialEncrypted: row.credentialEncrypted,
155
+ }
156
+ : null;
157
+ });
158
+ }
159
+
160
+ export async function updatePrReviewAppRegistration(
161
+ db: Database,
162
+ input: {
163
+ accountId: string;
164
+ workspaceId: string;
165
+ registrationId: string;
166
+ name?: string;
167
+ credentialEncrypted?: string;
168
+ accessTokenExpiresAt?: Date | null;
169
+ webhookSecretEncrypted?: string;
170
+ webhookUsername?: string;
171
+ status?: "active" | "disabled";
172
+ },
173
+ ): Promise<PrReviewAppRegistration | null> {
174
+ return await withRlsContext(db, input, async (scopedDb) => {
175
+ return await scopedDb.transaction(async (tx) => {
176
+ const [existing] = await tx
177
+ .select()
178
+ .from(schema.prReviewAppRegistrations)
179
+ .where(
180
+ and(
181
+ eq(schema.prReviewAppRegistrations.workspaceId, input.workspaceId),
182
+ eq(schema.prReviewAppRegistrations.id, input.registrationId),
183
+ ),
184
+ )
185
+ .limit(1)
186
+ .for("update");
187
+ if (!existing) return null;
188
+ const nextConfiguration = {
189
+ provider: existing.provider,
190
+ providerBaseUrl: existing.providerBaseUrl,
191
+ registrationId: existing.id,
192
+ webhookUsername: input.webhookUsername ?? existing.webhookUsername,
193
+ };
194
+ const sourceChanges =
195
+ input.name !== undefined ||
196
+ input.webhookSecretEncrypted !== undefined ||
197
+ input.webhookUsername !== undefined ||
198
+ input.status !== undefined;
199
+ const [source] = sourceChanges
200
+ ? await tx
201
+ .update(schema.automationSources)
202
+ .set({
203
+ ...(input.name !== undefined ? { name: `${input.name} webhooks` } : {}),
204
+ ...(input.webhookSecretEncrypted !== undefined
205
+ ? { webhookSecretEncrypted: input.webhookSecretEncrypted }
206
+ : {}),
207
+ ...(input.webhookUsername !== undefined ? { configuration: nextConfiguration } : {}),
208
+ ...(input.status !== undefined ? { status: input.status } : {}),
209
+ version: sql`${schema.automationSources.version} + 1`,
210
+ updatedAt: new Date(),
211
+ })
212
+ .where(
213
+ and(
214
+ eq(schema.automationSources.workspaceId, input.workspaceId),
215
+ eq(schema.automationSources.id, existing.sourceId),
216
+ ),
217
+ )
218
+ .returning()
219
+ : await tx
220
+ .select()
221
+ .from(schema.automationSources)
222
+ .where(eq(schema.automationSources.id, existing.sourceId))
223
+ .limit(1);
224
+ if (!source)
225
+ throw new PrReviewDispatchAuthorityError("PR Review automation source is unavailable");
226
+ const [row] = await tx
227
+ .update(schema.prReviewAppRegistrations)
228
+ .set({
229
+ ...(input.name !== undefined ? { name: input.name } : {}),
230
+ ...(input.credentialEncrypted !== undefined
231
+ ? { credentialEncrypted: input.credentialEncrypted }
232
+ : {}),
233
+ ...(input.accessTokenExpiresAt !== undefined
234
+ ? { accessTokenExpiresAt: input.accessTokenExpiresAt }
235
+ : {}),
236
+ ...(input.webhookUsername !== undefined
237
+ ? { webhookUsername: input.webhookUsername }
238
+ : {}),
239
+ ...(input.status !== undefined ? { status: input.status } : {}),
240
+ updatedAt: new Date(),
241
+ })
242
+ .where(
243
+ and(
244
+ eq(schema.prReviewAppRegistrations.workspaceId, input.workspaceId),
245
+ eq(schema.prReviewAppRegistrations.id, input.registrationId),
246
+ ),
247
+ )
248
+ .returning();
249
+ return row ? mapRegistration(row, source.endpointId) : null;
250
+ });
251
+ });
252
+ }
253
+
254
+ export async function deletePrReviewAppRegistration(
255
+ db: Database,
256
+ input: { accountId: string; workspaceId: string; registrationId: string },
257
+ ): Promise<boolean> {
258
+ return await withRlsContext(db, input, async (scopedDb) => {
259
+ return await scopedDb.transaction(async (tx) => {
260
+ const [registration] = await tx
261
+ .update(schema.prReviewAppRegistrations)
262
+ .set({ status: "disabled", updatedAt: new Date() })
263
+ .where(
264
+ and(
265
+ eq(schema.prReviewAppRegistrations.workspaceId, input.workspaceId),
266
+ eq(schema.prReviewAppRegistrations.id, input.registrationId),
267
+ ),
268
+ )
269
+ .returning({ sourceId: schema.prReviewAppRegistrations.sourceId });
270
+ if (!registration) return false;
271
+ await tx
272
+ .update(schema.automationSources)
273
+ .set({
274
+ status: "disabled",
275
+ version: sql`${schema.automationSources.version} + 1`,
276
+ updatedAt: new Date(),
277
+ })
278
+ .where(eq(schema.automationSources.id, registration.sourceId));
279
+ return true;
280
+ });
281
+ });
282
+ }
283
+
284
+ export async function createPrReviewRepositoryBinding(
285
+ db: Database,
286
+ input: {
287
+ accountId: string;
288
+ workspaceId: string;
289
+ registrationId: string;
290
+ provider: PrReviewProvider;
291
+ repositoryUri: string;
292
+ repositoryFullName: string;
293
+ providerRepositoryId: string;
294
+ installationId: string | null;
295
+ projectId: string | null;
296
+ model: string | null;
297
+ additionalInstructions: string | null;
298
+ status: "active" | "disabled";
299
+ createdBySubjectId: string;
300
+ packInstallationId: string;
301
+ packTemplateId: string;
302
+ adapterId: string;
303
+ eventTypes: string[];
304
+ configuration: Record<string, unknown>;
305
+ sessionTemplate: AutomationSessionTemplate;
306
+ },
307
+ ): Promise<PrReviewRepositoryBinding> {
308
+ return await withRlsContext(db, input, async (scopedDb) => {
309
+ return await scopedDb.transaction(async (tx) => {
310
+ const [registration] = await tx
311
+ .select({
312
+ provider: schema.prReviewAppRegistrations.provider,
313
+ sourceId: schema.prReviewAppRegistrations.sourceId,
314
+ status: schema.prReviewAppRegistrations.status,
315
+ })
316
+ .from(schema.prReviewAppRegistrations)
317
+ .where(
318
+ and(
319
+ eq(schema.prReviewAppRegistrations.workspaceId, input.workspaceId),
320
+ eq(schema.prReviewAppRegistrations.id, input.registrationId),
321
+ ),
322
+ )
323
+ .limit(1)
324
+ .for("update");
325
+ if (!registration || registration.status !== "active") {
326
+ throw new PrReviewDispatchAuthorityError("PR Review app registration is unavailable");
327
+ }
328
+ if (registration.provider !== input.provider) {
329
+ throw new PrReviewDispatchAuthorityError(
330
+ "PR Review repository provider does not match its app",
331
+ );
332
+ }
333
+ const bindingId = randomUUID();
334
+ const [trigger] = await tx
335
+ .insert(schema.automationTriggers)
336
+ .values({
337
+ accountId: input.accountId,
338
+ workspaceId: input.workspaceId,
339
+ sourceId: registration.sourceId,
340
+ name: `Review ${input.repositoryFullName}`,
341
+ status: input.status,
342
+ packInstallationId: input.packInstallationId,
343
+ packTemplateId: input.packTemplateId,
344
+ createdBySubjectId: input.createdBySubjectId,
345
+ })
346
+ .returning();
347
+ if (!trigger) throw new Error("Failed to create PR Review automation trigger");
348
+ await tx.insert(schema.automationTriggerRevisions).values({
349
+ triggerId: trigger.id,
350
+ revision: 1,
351
+ accountId: input.accountId,
352
+ workspaceId: input.workspaceId,
353
+ adapterId: input.adapterId,
354
+ eventTypes: input.eventTypes,
355
+ configuration: input.configuration,
356
+ parameters: prReviewTriggerParameters(input, bindingId),
357
+ sessionTemplate: input.sessionTemplate,
358
+ createdBySubjectId: input.createdBySubjectId,
359
+ });
360
+ const [row] = await tx
361
+ .insert(schema.prReviewRepositoryBindings)
362
+ .values({
363
+ id: bindingId,
364
+ triggerId: trigger.id,
365
+ accountId: input.accountId,
366
+ workspaceId: input.workspaceId,
367
+ registrationId: input.registrationId,
368
+ provider: input.provider,
369
+ repositoryUri: input.repositoryUri,
370
+ repositoryFullName: input.repositoryFullName,
371
+ providerRepositoryId: input.providerRepositoryId,
372
+ installationId: input.installationId,
373
+ projectId: input.projectId,
374
+ model: input.model,
375
+ additionalInstructions: input.additionalInstructions,
376
+ status: input.status,
377
+ createdBySubjectId: input.createdBySubjectId,
378
+ })
379
+ .returning();
380
+ if (!row) throw new Error("Failed to create PR Review repository binding");
381
+ return mapRepositoryBinding(row);
382
+ });
383
+ });
384
+ }
385
+
386
+ export async function listPrReviewRepositoryBindings(
387
+ db: Database,
388
+ accountId: string,
389
+ workspaceId: string,
390
+ ): Promise<PrReviewRepositoryBinding[]> {
391
+ return await withRlsContext(db, { accountId, workspaceId }, async (scopedDb) => {
392
+ const rows = await scopedDb
393
+ .select()
394
+ .from(schema.prReviewRepositoryBindings)
395
+ .where(eq(schema.prReviewRepositoryBindings.workspaceId, workspaceId))
396
+ .orderBy(desc(schema.prReviewRepositoryBindings.updatedAt));
397
+ return rows.map(mapRepositoryBinding);
398
+ });
399
+ }
400
+
401
+ export async function updatePrReviewRepositoryBinding(
402
+ db: Database,
403
+ input: {
404
+ accountId: string;
405
+ workspaceId: string;
406
+ bindingId: string;
407
+ subjectId: string;
408
+ model?: string | null;
409
+ additionalInstructions?: string | null;
410
+ status?: "active" | "disabled";
411
+ },
412
+ ): Promise<PrReviewRepositoryBinding | null> {
413
+ return await withRlsContext(db, input, async (scopedDb) => {
414
+ return await scopedDb.transaction(async (tx) => {
415
+ const [current] = await tx
416
+ .select({
417
+ binding: schema.prReviewRepositoryBindings,
418
+ trigger: schema.automationTriggers,
419
+ revision: schema.automationTriggerRevisions,
420
+ })
421
+ .from(schema.prReviewRepositoryBindings)
422
+ .innerJoin(
423
+ schema.automationTriggers,
424
+ and(
425
+ eq(
426
+ schema.automationTriggers.workspaceId,
427
+ schema.prReviewRepositoryBindings.workspaceId,
428
+ ),
429
+ eq(schema.automationTriggers.id, schema.prReviewRepositoryBindings.triggerId),
430
+ ),
431
+ )
432
+ .innerJoin(
433
+ schema.automationTriggerRevisions,
434
+ and(
435
+ eq(schema.automationTriggerRevisions.triggerId, schema.automationTriggers.id),
436
+ eq(
437
+ schema.automationTriggerRevisions.revision,
438
+ schema.automationTriggers.currentRevision,
439
+ ),
440
+ ),
441
+ )
442
+ .where(
443
+ and(
444
+ eq(schema.prReviewRepositoryBindings.workspaceId, input.workspaceId),
445
+ eq(schema.prReviewRepositoryBindings.id, input.bindingId),
446
+ ),
447
+ )
448
+ .limit(1)
449
+ .for("update", { of: schema.automationTriggers });
450
+ if (!current) return null;
451
+ const next = {
452
+ ...current.binding,
453
+ model: input.model === undefined ? current.binding.model : input.model,
454
+ additionalInstructions:
455
+ input.additionalInstructions === undefined
456
+ ? current.binding.additionalInstructions
457
+ : input.additionalInstructions,
458
+ status: input.status ?? (current.binding.status as "active" | "disabled"),
459
+ };
460
+ const nextRevision = current.trigger.currentRevision + 1;
461
+ await tx.insert(schema.automationTriggerRevisions).values({
462
+ triggerId: current.trigger.id,
463
+ revision: nextRevision,
464
+ accountId: current.trigger.accountId,
465
+ workspaceId: current.trigger.workspaceId,
466
+ adapterId: current.revision.adapterId,
467
+ eventTypes: current.revision.eventTypes,
468
+ configuration: current.revision.configuration,
469
+ parameters: prReviewTriggerParameters(next, current.binding.id),
470
+ sessionTemplate: current.revision.sessionTemplate,
471
+ createdBySubjectId: input.subjectId,
472
+ });
473
+ await tx
474
+ .update(schema.automationTriggers)
475
+ .set({
476
+ currentRevision: nextRevision,
477
+ status: next.status,
478
+ updatedAt: new Date(),
479
+ })
480
+ .where(eq(schema.automationTriggers.id, current.trigger.id));
481
+ const [row] = await tx
482
+ .update(schema.prReviewRepositoryBindings)
483
+ .set({
484
+ model: next.model,
485
+ additionalInstructions: next.additionalInstructions,
486
+ status: next.status,
487
+ updatedAt: new Date(),
488
+ })
489
+ .where(eq(schema.prReviewRepositoryBindings.id, current.binding.id))
490
+ .returning();
491
+ return row ? mapRepositoryBinding(row) : null;
492
+ });
493
+ });
494
+ }
495
+
496
+ export async function deletePrReviewRepositoryBinding(
497
+ db: Database,
498
+ input: { accountId: string; workspaceId: string; bindingId: string; subjectId: string },
499
+ ): Promise<boolean> {
500
+ return (
501
+ (await updatePrReviewRepositoryBinding(db, {
502
+ accountId: input.accountId,
503
+ workspaceId: input.workspaceId,
504
+ bindingId: input.bindingId,
505
+ subjectId: input.subjectId,
506
+ status: "disabled",
507
+ })) !== null
508
+ );
509
+ }
510
+
511
+ export async function resolvePrReviewGitCredential(
512
+ db: Database,
513
+ input: {
514
+ accountId: string;
515
+ workspaceId: string;
516
+ registrationId: string;
517
+ provider: PrReviewProvider;
518
+ sessionId: string;
519
+ rootSessionId: string;
520
+ turnId: string;
521
+ attemptId: string;
522
+ executionGeneration: number;
523
+ repositoryRefs: Array<{
524
+ uri: string;
525
+ expectedCommitSha?: string;
526
+ repositoryId?: string | number;
527
+ installationId?: string | number;
528
+ projectId?: string | number;
529
+ }>;
530
+ },
531
+ ): Promise<{
532
+ credentialKind: "github_app" | "provider_token";
533
+ appId: string | null;
534
+ credentialEncrypted: string | null;
535
+ expiresAt: string | null;
536
+ }> {
537
+ return await withRlsContext(db, input, async (scopedDb) => {
538
+ const [authority] = await scopedDb
539
+ .select({
540
+ sessionId: schema.sessions.id,
541
+ rootSessionId: schema.sessions.rootSessionId,
542
+ activeTurnId: schema.sessions.activeTurnId,
543
+ sessionMetadata: schema.sessions.metadata,
544
+ sessionResources: schema.sessions.resources,
545
+ policyRole: schema.sessions.policyRole,
546
+ createdByKind: schema.sessions.createdByKind,
547
+ createdBySubjectId: schema.sessions.createdBySubjectId,
548
+ turnId: schema.sessionTurns.id,
549
+ turnGeneration: schema.sessionTurns.executionGeneration,
550
+ activeAttemptId: schema.sessionTurns.activeAttemptId,
551
+ turnInitiatorKind: schema.sessionTurns.initiatorKind,
552
+ turnInitiatorSubjectId: schema.sessionTurns.initiatorSubjectId,
553
+ attemptId: schema.sessionTurnAttempts.id,
554
+ attemptState: schema.sessionTurnAttempts.state,
555
+ attemptGeneration: schema.sessionTurnAttempts.executionGeneration,
556
+ })
557
+ .from(schema.sessions)
558
+ .innerJoin(
559
+ schema.sessionTurns,
560
+ and(
561
+ eq(schema.sessionTurns.workspaceId, schema.sessions.workspaceId),
562
+ eq(schema.sessionTurns.sessionId, schema.sessions.id),
563
+ eq(schema.sessionTurns.id, input.turnId),
564
+ ),
565
+ )
566
+ .innerJoin(
567
+ schema.sessionTurnAttempts,
568
+ and(
569
+ eq(schema.sessionTurnAttempts.workspaceId, schema.sessions.workspaceId),
570
+ eq(schema.sessionTurnAttempts.sessionId, schema.sessions.id),
571
+ eq(schema.sessionTurnAttempts.turnId, schema.sessionTurns.id),
572
+ eq(schema.sessionTurnAttempts.id, input.attemptId),
573
+ ),
574
+ )
575
+ .where(
576
+ and(
577
+ eq(schema.sessions.accountId, input.accountId),
578
+ eq(schema.sessions.workspaceId, input.workspaceId),
579
+ eq(schema.sessions.id, input.sessionId),
580
+ ),
581
+ )
582
+ .limit(1);
583
+ const metadata = authority?.sessionMetadata;
584
+ const bindingId = prReviewMetadataText(metadata, "prReviewRepositoryBindingId");
585
+ const expectedHeadSha = prReviewMetadataText(metadata, "prReviewHeadSha");
586
+ const automationRunId = prReviewMetadataText(metadata, "automationRunId");
587
+ const automationTriggerId = prReviewMetadataText(metadata, "automationTriggerId");
588
+ const exactRepositoryResource = prReviewExactRepositoryResource(
589
+ authority?.sessionResources,
590
+ input.repositoryRefs,
591
+ input.registrationId,
592
+ input.provider,
593
+ );
594
+ if (
595
+ !authority ||
596
+ authority.rootSessionId !== input.rootSessionId ||
597
+ authority.sessionId !== input.rootSessionId ||
598
+ authority.activeTurnId !== input.turnId ||
599
+ authority.turnId !== input.turnId ||
600
+ authority.turnGeneration !== input.executionGeneration ||
601
+ authority.attemptGeneration !== input.executionGeneration ||
602
+ authority.activeAttemptId !== input.attemptId ||
603
+ authority.attemptId !== input.attemptId ||
604
+ !["claimed", "running"].includes(authority.attemptState) ||
605
+ authority.policyRole !== OPENGENI_PR_REVIEW_SESSION_ROLE ||
606
+ authority.createdByKind !== "service" ||
607
+ authority.createdBySubjectId !== `automation:${automationTriggerId}` ||
608
+ authority.turnInitiatorKind !== "service" ||
609
+ authority.turnInitiatorSubjectId !== `automation:${automationTriggerId}` ||
610
+ prReviewMetadataText(metadata, "role") !== OPENGENI_PR_REVIEW_SESSION_ROLE ||
611
+ prReviewMetadataText(metadata, "prReviewRegistrationId") !== input.registrationId ||
612
+ !bindingId ||
613
+ !automationRunId ||
614
+ !automationTriggerId ||
615
+ !expectedHeadSha ||
616
+ !exactRepositoryResource ||
617
+ exactRepositoryResource.expectedCommitSha !== expectedHeadSha
618
+ ) {
619
+ throw new PrReviewDispatchAuthorityError(
620
+ "PR Review credential requires an exact live PR Review attempt",
621
+ );
622
+ }
623
+ const [execution] = await scopedDb
624
+ .select({
625
+ runStatus: schema.automationRuns.status,
626
+ runSessionId: schema.automationRuns.sessionId,
627
+ triggerStatus: schema.automationTriggers.status,
628
+ sourceStatus: schema.automationSources.status,
629
+ sourcePackInstallationId: schema.automationSources.packInstallationId,
630
+ sourcePackConnectorId: schema.automationSources.packConnectorId,
631
+ packInstallationId: schema.packInstallations.id,
632
+ packStatus: schema.packInstallations.status,
633
+ bindingStatus: schema.prReviewRepositoryBindings.status,
634
+ registrationStatus: schema.prReviewAppRegistrations.status,
635
+ })
636
+ .from(schema.automationRuns)
637
+ .innerJoin(
638
+ schema.automationTriggers,
639
+ and(
640
+ eq(schema.automationTriggers.workspaceId, schema.automationRuns.workspaceId),
641
+ eq(schema.automationTriggers.id, schema.automationRuns.triggerId),
642
+ ),
643
+ )
644
+ .innerJoin(
645
+ schema.automationSources,
646
+ and(
647
+ eq(schema.automationSources.workspaceId, schema.automationRuns.workspaceId),
648
+ eq(schema.automationSources.id, schema.automationRuns.sourceId),
649
+ ),
650
+ )
651
+ .innerJoin(
652
+ schema.prReviewRepositoryBindings,
653
+ and(
654
+ eq(schema.prReviewRepositoryBindings.workspaceId, schema.automationRuns.workspaceId),
655
+ eq(schema.prReviewRepositoryBindings.triggerId, schema.automationRuns.triggerId),
656
+ ),
657
+ )
658
+ .innerJoin(
659
+ schema.prReviewAppRegistrations,
660
+ and(
661
+ eq(schema.prReviewAppRegistrations.workspaceId, schema.automationRuns.workspaceId),
662
+ eq(schema.prReviewAppRegistrations.id, schema.prReviewRepositoryBindings.registrationId),
663
+ eq(schema.prReviewAppRegistrations.sourceId, schema.automationRuns.sourceId),
664
+ ),
665
+ )
666
+ .innerJoin(
667
+ schema.packInstallations,
668
+ and(
669
+ eq(schema.packInstallations.workspaceId, schema.automationRuns.workspaceId),
670
+ eq(schema.packInstallations.id, schema.automationTriggers.packInstallationId),
671
+ ),
672
+ )
673
+ .where(
674
+ and(
675
+ eq(schema.automationRuns.workspaceId, input.workspaceId),
676
+ eq(schema.automationRuns.id, automationRunId),
677
+ eq(schema.automationRuns.triggerId, automationTriggerId),
678
+ eq(schema.automationRuns.sessionId, input.sessionId),
679
+ eq(schema.prReviewRepositoryBindings.id, bindingId),
680
+ eq(schema.prReviewAppRegistrations.id, input.registrationId),
681
+ eq(schema.packInstallations.packId, OPENGENI_PR_REVIEW_PACK_ID),
682
+ ),
683
+ )
684
+ .limit(1);
685
+ if (
686
+ !execution ||
687
+ !["dispatching", "dispatched"].includes(execution.runStatus) ||
688
+ execution.runSessionId !== input.sessionId ||
689
+ execution.triggerStatus !== "active" ||
690
+ execution.sourceStatus !== "active" ||
691
+ execution.sourcePackInstallationId !== execution.packInstallationId ||
692
+ execution.sourcePackConnectorId !== prReviewProviderPackConnectorId(input.provider) ||
693
+ execution.packStatus !== "active" ||
694
+ execution.bindingStatus !== "active" ||
695
+ execution.registrationStatus !== "active"
696
+ ) {
697
+ throw new PrReviewDispatchAuthorityError("PR Review automation authority is unavailable");
698
+ }
699
+ const [registration] = await scopedDb
700
+ .select()
701
+ .from(schema.prReviewAppRegistrations)
702
+ .where(
703
+ and(
704
+ eq(schema.prReviewAppRegistrations.workspaceId, input.workspaceId),
705
+ eq(schema.prReviewAppRegistrations.id, input.registrationId),
706
+ eq(schema.prReviewAppRegistrations.provider, input.provider),
707
+ eq(schema.prReviewAppRegistrations.status, "active"),
708
+ ),
709
+ )
710
+ .limit(1);
711
+ if (!registration)
712
+ throw new PrReviewDispatchAuthorityError("PR Review credential registration is unavailable");
713
+ const bindings = await scopedDb
714
+ .select()
715
+ .from(schema.prReviewRepositoryBindings)
716
+ .where(
717
+ and(
718
+ eq(schema.prReviewRepositoryBindings.workspaceId, input.workspaceId),
719
+ eq(schema.prReviewRepositoryBindings.registrationId, input.registrationId),
720
+ eq(schema.prReviewRepositoryBindings.id, bindingId),
721
+ eq(schema.prReviewRepositoryBindings.provider, input.provider),
722
+ eq(schema.prReviewRepositoryBindings.status, "active"),
723
+ ),
724
+ );
725
+ for (const reference of input.repositoryRefs) {
726
+ const repositoryId =
727
+ reference.repositoryId === undefined ? null : String(reference.repositoryId);
728
+ const installationId =
729
+ reference.installationId === undefined ? null : String(reference.installationId);
730
+ const projectId = reference.projectId === undefined ? null : String(reference.projectId);
731
+ const exact = bindings.find(
732
+ (binding) =>
733
+ binding.repositoryUri === reference.uri &&
734
+ repositoryId !== null &&
735
+ binding.providerRepositoryId === repositoryId &&
736
+ (binding.installationId === null || binding.installationId === installationId) &&
737
+ (binding.projectId === null || binding.projectId === projectId),
738
+ );
739
+ if (!exact)
740
+ throw new PrReviewDispatchAuthorityError(
741
+ "PR Review credential is not authorized for this repository",
742
+ );
743
+ }
744
+ const [pack] = await scopedDb
745
+ .select({ status: schema.packInstallations.status })
746
+ .from(schema.packInstallations)
747
+ .where(
748
+ and(
749
+ eq(schema.packInstallations.workspaceId, input.workspaceId),
750
+ eq(schema.packInstallations.packId, OPENGENI_PR_REVIEW_PACK_ID),
751
+ ),
752
+ )
753
+ .limit(1);
754
+ if (pack?.status !== "active")
755
+ throw new PrReviewDispatchAuthorityError("OpenGeni Review Bot Pack is not active");
756
+ return {
757
+ credentialKind: registration.credentialKind as "github_app" | "provider_token",
758
+ appId: registration.appId,
759
+ credentialEncrypted: registration.credentialEncrypted,
760
+ expiresAt: registration.accessTokenExpiresAt?.toISOString() ?? null,
761
+ };
762
+ });
763
+ }
764
+
765
+ function prReviewProviderPackConnectorId(provider: PrReviewProvider): string {
766
+ return provider === "azure_devops" ? "azure-devops" : provider;
767
+ }
768
+
769
+ function prReviewMetadataText(metadata: unknown, key: string): string | null {
770
+ if (typeof metadata !== "object" || metadata === null || Array.isArray(metadata)) return null;
771
+ const value = (metadata as Record<string, unknown>)[key];
772
+ return typeof value === "string" && value.length > 0 ? value : null;
773
+ }
774
+
775
+ function prReviewExactRepositoryResource(
776
+ resources: unknown,
777
+ repositoryRefs: Array<{
778
+ uri: string;
779
+ expectedCommitSha?: string;
780
+ repositoryId?: string | number;
781
+ installationId?: string | number;
782
+ projectId?: string | number;
783
+ }>,
784
+ registrationId: string,
785
+ provider: PrReviewProvider,
786
+ ): Record<string, unknown> | null {
787
+ if (!Array.isArray(resources) || repositoryRefs.length !== 1) return null;
788
+ const repositories = resources.filter(
789
+ (resource): resource is Record<string, unknown> =>
790
+ typeof resource === "object" &&
791
+ resource !== null &&
792
+ !Array.isArray(resource) &&
793
+ (resource as Record<string, unknown>).kind === "repository",
794
+ );
795
+ if (repositories.length !== 1) return null;
796
+ const resource = repositories[0]!;
797
+ const reference = repositoryRefs[0]!;
798
+ const sameOptionalIdentity = (key: "repositoryId" | "installationId" | "projectId") =>
799
+ resource[key] === undefined
800
+ ? reference[key] === undefined
801
+ : String(resource[key]) === String(reference[key]);
802
+ return resource.uri === reference.uri &&
803
+ resource.expectedCommitSha === reference.expectedCommitSha &&
804
+ resource.provider === provider &&
805
+ resource.credentialBindingId === `pr-review:${registrationId}` &&
806
+ sameOptionalIdentity("repositoryId") &&
807
+ sameOptionalIdentity("installationId") &&
808
+ sameOptionalIdentity("projectId")
809
+ ? resource
810
+ : null;
811
+ }
812
+
813
+ function prReviewTriggerParameters(
814
+ input: {
815
+ registrationId: string;
816
+ provider: PrReviewProvider | string;
817
+ repositoryUri: string;
818
+ repositoryFullName: string;
819
+ providerRepositoryId: string;
820
+ installationId: string | null;
821
+ projectId: string | null;
822
+ model: string | null;
823
+ additionalInstructions: string | null;
824
+ },
825
+ repositoryBindingId: string,
826
+ ): Record<string, unknown> {
827
+ return {
828
+ registrationId: input.registrationId,
829
+ repositoryBindingId,
830
+ provider: input.provider,
831
+ repositoryUri: input.repositoryUri,
832
+ repositoryFullName: input.repositoryFullName,
833
+ providerRepositoryId: input.providerRepositoryId,
834
+ installationId: input.installationId,
835
+ projectId: input.projectId,
836
+ model: input.model,
837
+ additionalInstructions: input.additionalInstructions,
838
+ };
839
+ }
840
+
841
+ function mapRegistration(
842
+ row: typeof schema.prReviewAppRegistrations.$inferSelect,
843
+ endpointId: string,
844
+ ): PrReviewAppRegistration {
845
+ return {
846
+ id: row.id,
847
+ sourceId: row.sourceId,
848
+ accountId: row.accountId,
849
+ workspaceId: row.workspaceId,
850
+ name: row.name,
851
+ provider: row.provider as PrReviewProvider,
852
+ providerBaseUrl: row.providerBaseUrl,
853
+ appId: row.appId,
854
+ credentialKind: row.credentialKind as "github_app" | "provider_token",
855
+ hasCredential: row.credentialEncrypted !== null,
856
+ accessTokenExpiresAt: row.accessTokenExpiresAt?.toISOString() ?? null,
857
+ webhookAuthKind: row.webhookAuthKind as "hmac_sha256" | "shared_token" | "basic",
858
+ hasWebhookSecret: true,
859
+ webhookUsername: row.webhookUsername,
860
+ webhookPath: `/v1/webhooks/automations/${endpointId}`,
861
+ status: row.status as "active" | "disabled",
862
+ createdBySubjectId: row.createdBySubjectId,
863
+ createdAt: row.createdAt.toISOString(),
864
+ updatedAt: row.updatedAt.toISOString(),
865
+ };
866
+ }
867
+
868
+ function mapRepositoryBinding(
869
+ row: typeof schema.prReviewRepositoryBindings.$inferSelect,
870
+ ): PrReviewRepositoryBinding {
871
+ return {
872
+ id: row.id,
873
+ triggerId: row.triggerId,
874
+ accountId: row.accountId,
875
+ workspaceId: row.workspaceId,
876
+ registrationId: row.registrationId,
877
+ provider: row.provider as PrReviewProvider,
878
+ repositoryUri: row.repositoryUri,
879
+ repositoryFullName: row.repositoryFullName,
880
+ providerRepositoryId: row.providerRepositoryId,
881
+ installationId: row.installationId,
882
+ projectId: row.projectId,
883
+ model: row.model,
884
+ additionalInstructions: row.additionalInstructions,
885
+ status: row.status as "active" | "disabled",
886
+ createdBySubjectId: row.createdBySubjectId,
887
+ createdAt: row.createdAt.toISOString(),
888
+ updatedAt: row.updatedAt.toISOString(),
889
+ };
890
+ }