@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,703 @@
1
+ -- deployment-mode: rolling
2
+ -- Activate explicit workspace and organization+user authority for provider
3
+ -- connections. Credential material remains in connections. This migration owns
4
+ -- only metadata authority, immutable generations, grants, and pre-use fencing.
5
+
6
+ SET LOCAL lock_timeout = '5s';
7
+ SET LOCAL statement_timeout = '10min';
8
+
9
+ ALTER TABLE "connections"
10
+ ADD COLUMN "authority_scope" text NOT NULL DEFAULT 'workspace',
11
+ ADD COLUMN "authority_id" uuid,
12
+ ADD COLUMN "owner_organization_membership_id" uuid,
13
+ ADD COLUMN "origin_workspace_id" uuid,
14
+ ADD COLUMN "authority_generation" bigint NOT NULL DEFAULT 1;
15
+
16
+ UPDATE "connections"
17
+ SET "origin_workspace_id" = "workspace_id"
18
+ WHERE "origin_workspace_id" IS NULL;
19
+
20
+ DO $backfill_personal_connection_authority$
21
+ DECLARE
22
+ connection_row record;
23
+ membership_id uuid;
24
+ authority_value uuid;
25
+ BEGIN
26
+ FOR connection_row IN
27
+ SELECT connection_value.id, connection_value.account_id,
28
+ connection_value.workspace_id, connection_value.subject_id
29
+ FROM connections connection_value
30
+ WHERE connection_value.subject_id IS NOT NULL
31
+ ORDER BY connection_value.account_id, connection_value.id
32
+ FOR UPDATE
33
+ LOOP
34
+ SELECT membership.id INTO membership_id
35
+ FROM organization_memberships membership
36
+ WHERE membership.account_id = connection_row.account_id
37
+ AND membership.subject_id = connection_row.subject_id
38
+ AND membership.status = 'active'
39
+ AND membership.revoked_at IS NULL;
40
+
41
+ IF membership_id IS NULL THEN
42
+ UPDATE connections
43
+ SET authority_scope = 'legacy_user', authority_id = NULL,
44
+ owner_organization_membership_id = NULL,
45
+ origin_workspace_id = connection_row.workspace_id,
46
+ authority_generation = 1
47
+ WHERE id = connection_row.id;
48
+ CONTINUE;
49
+ END IF;
50
+
51
+ authority_value := pg_catalog.gen_random_uuid();
52
+ INSERT INTO organization_user_resource_authorities (
53
+ id, account_id, organization_membership_id, resource_kind, resource_id,
54
+ origin_workspace_id, generation, status
55
+ ) VALUES (
56
+ authority_value, connection_row.account_id, membership_id, 'connection',
57
+ connection_row.id, connection_row.workspace_id, 1, 'active'
58
+ );
59
+
60
+ UPDATE connections
61
+ SET authority_scope = 'user', authority_id = authority_value,
62
+ owner_organization_membership_id = membership_id,
63
+ origin_workspace_id = connection_row.workspace_id,
64
+ authority_generation = 1
65
+ WHERE id = connection_row.id;
66
+ END LOOP;
67
+ END
68
+ $backfill_personal_connection_authority$;
69
+
70
+ ALTER TABLE "connections"
71
+ ADD CONSTRAINT "connections_authority_scope_check"
72
+ CHECK ("authority_scope" IN ('workspace', 'user', 'legacy_user')) NOT VALID,
73
+ ADD CONSTRAINT "connections_authority_shape_check" CHECK (
74
+ (
75
+ "authority_scope" = 'workspace'
76
+ AND "subject_id" IS NULL
77
+ AND "authority_id" IS NULL
78
+ AND "owner_organization_membership_id" IS NULL
79
+ AND "origin_workspace_id" = "workspace_id"
80
+ ) OR (
81
+ "authority_scope" = 'user'
82
+ AND "subject_id" IS NOT NULL
83
+ AND "authority_id" IS NOT NULL
84
+ AND "owner_organization_membership_id" IS NOT NULL
85
+ AND "origin_workspace_id" IS NOT NULL
86
+ ) OR (
87
+ "authority_scope" = 'legacy_user'
88
+ AND "subject_id" IS NOT NULL
89
+ AND "authority_id" IS NULL
90
+ AND "owner_organization_membership_id" IS NULL
91
+ AND "origin_workspace_id" IS NOT NULL
92
+ )
93
+ ) NOT VALID,
94
+ ADD CONSTRAINT "connections_authority_generation_check"
95
+ CHECK ("authority_generation" > 0) NOT VALID,
96
+ ADD CONSTRAINT "connections_authority_fk" FOREIGN KEY (
97
+ "authority_id", "account_id", "owner_organization_membership_id"
98
+ ) REFERENCES "organization_user_resource_authorities"(
99
+ "id", "account_id", "organization_membership_id"
100
+ ) ON DELETE RESTRICT NOT VALID,
101
+ ADD CONSTRAINT "connections_origin_workspace_fk" FOREIGN KEY (
102
+ "origin_workspace_id", "account_id"
103
+ ) REFERENCES "workspaces"("id", "account_id")
104
+ ON DELETE SET NULL ("origin_workspace_id") NOT VALID;
105
+
106
+ ALTER TABLE "connections"
107
+ VALIDATE CONSTRAINT "connections_authority_scope_check",
108
+ VALIDATE CONSTRAINT "connections_authority_shape_check",
109
+ VALIDATE CONSTRAINT "connections_authority_generation_check",
110
+ VALIDATE CONSTRAINT "connections_authority_fk",
111
+ VALIDATE CONSTRAINT "connections_origin_workspace_fk";
112
+
113
+ CREATE UNIQUE INDEX "connections_authority_identity_uq"
114
+ ON "connections" ("account_id", "authority_id")
115
+ WHERE "authority_id" IS NOT NULL;
116
+ CREATE INDEX "connections_owner_authority_idx"
117
+ ON "connections" (
118
+ "account_id", "owner_organization_membership_id", "status", "updated_at", "id"
119
+ ) WHERE "authority_scope" = 'user';
120
+
121
+ CREATE TABLE "connection_use_once_consumption_receipts" (
122
+ "grant_id" uuid PRIMARY KEY,
123
+ "account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
124
+ "authority_id" uuid NOT NULL,
125
+ "authority_generation" bigint NOT NULL,
126
+ "grant_generation" bigint NOT NULL,
127
+ "accepted_work_kind" text NOT NULL,
128
+ "accepted_work_id" uuid NOT NULL,
129
+ "created_at" timestamptz NOT NULL DEFAULT now(),
130
+ CONSTRAINT "connection_once_receipts_authority_fk" FOREIGN KEY (
131
+ "authority_id", "account_id"
132
+ ) REFERENCES "organization_user_resource_authorities"("id", "account_id")
133
+ ON DELETE CASCADE,
134
+ CONSTRAINT "connection_once_receipts_grant_fk" FOREIGN KEY (
135
+ "grant_id", "account_id"
136
+ ) REFERENCES "organization_user_resource_grants"("id", "account_id")
137
+ ON DELETE CASCADE,
138
+ CONSTRAINT "connection_once_receipts_generation_check" CHECK (
139
+ "authority_generation" > 0 AND "grant_generation" > 0
140
+ ),
141
+ CONSTRAINT "connection_once_receipts_work_kind_check" CHECK (
142
+ "accepted_work_kind" IN ('turn', 'scheduled_task')
143
+ )
144
+ );
145
+
146
+ ALTER TABLE "connection_use_once_consumption_receipts" ENABLE ROW LEVEL SECURITY;
147
+ ALTER TABLE "connection_use_once_consumption_receipts" FORCE ROW LEVEL SECURITY;
148
+ CREATE POLICY "organization_isolation" ON "connection_use_once_consumption_receipts"
149
+ USING (
150
+ account_id = nullif(current_setting('opengeni.account_id', true), '')::uuid
151
+ ) WITH CHECK (
152
+ account_id = nullif(current_setting('opengeni.account_id', true), '')::uuid
153
+ );
154
+
155
+ DO $connection_authority_routines$
156
+ DECLARE
157
+ data_schema text := current_schema();
158
+ BEGIN
159
+ EXECUTE format($ddl$
160
+ CREATE OR REPLACE FUNCTION opengeni_private.bind_connection_authority()
161
+ RETURNS trigger
162
+ LANGUAGE plpgsql
163
+ SECURITY DEFINER
164
+ SET search_path = %1$I, pg_catalog, pg_temp
165
+ AS $body$
166
+ DECLARE
167
+ caller_subject text := nullif(current_setting('opengeni.subject_id', true), '');
168
+ membership_id uuid;
169
+ authority_value uuid;
170
+ identity_changed boolean;
171
+ BEGIN
172
+ IF TG_OP = 'INSERT' THEN
173
+ NEW.origin_workspace_id := NEW.workspace_id;
174
+ NEW.authority_generation := 1;
175
+ IF NEW.subject_id IS NULL THEN
176
+ NEW.authority_scope := 'workspace';
177
+ NEW.authority_id := NULL;
178
+ NEW.owner_organization_membership_id := NULL;
179
+ RETURN NEW;
180
+ END IF;
181
+ IF caller_subject IS DISTINCT FROM NEW.subject_id THEN
182
+ RAISE EXCEPTION 'personal connection owner must be the authenticated subject'
183
+ USING ERRCODE = '42501';
184
+ END IF;
185
+ SELECT membership.id INTO membership_id
186
+ FROM organization_memberships membership
187
+ WHERE membership.account_id = NEW.account_id
188
+ AND membership.subject_id = caller_subject
189
+ AND membership.status = 'active'
190
+ AND membership.revoked_at IS NULL
191
+ FOR SHARE;
192
+ IF membership_id IS NULL THEN
193
+ NEW.authority_scope := 'legacy_user';
194
+ NEW.authority_id := NULL;
195
+ NEW.owner_organization_membership_id := NULL;
196
+ RETURN NEW;
197
+ END IF;
198
+ authority_value := gen_random_uuid();
199
+ INSERT INTO organization_user_resource_authorities (
200
+ id, account_id, organization_membership_id, resource_kind, resource_id,
201
+ origin_workspace_id, generation, status
202
+ ) VALUES (
203
+ authority_value, NEW.account_id, membership_id, 'connection', NEW.id,
204
+ NEW.workspace_id, 1, 'active'
205
+ );
206
+ NEW.authority_scope := 'user';
207
+ NEW.authority_id := authority_value;
208
+ NEW.owner_organization_membership_id := membership_id;
209
+ RETURN NEW;
210
+ END IF;
211
+
212
+ IF NEW.account_id IS DISTINCT FROM OLD.account_id
213
+ OR NEW.workspace_id IS DISTINCT FROM OLD.workspace_id
214
+ OR NEW.subject_id IS DISTINCT FROM OLD.subject_id
215
+ OR NEW.authority_scope IS DISTINCT FROM OLD.authority_scope
216
+ OR NEW.authority_id IS DISTINCT FROM OLD.authority_id
217
+ OR NEW.owner_organization_membership_id
218
+ IS DISTINCT FROM OLD.owner_organization_membership_id
219
+ OR NEW.origin_workspace_id IS DISTINCT FROM OLD.origin_workspace_id
220
+ THEN
221
+ RAISE EXCEPTION 'connection owner authority is immutable' USING ERRCODE = '23514';
222
+ END IF;
223
+
224
+ identity_changed := NEW.provider_domain IS DISTINCT FROM OLD.provider_domain
225
+ OR NEW.kind IS DISTINCT FROM OLD.kind
226
+ OR NEW.status IS DISTINCT FROM OLD.status;
227
+ IF identity_changed THEN
228
+ NEW.authority_generation := OLD.authority_generation + 1;
229
+ ELSIF NEW.authority_generation NOT IN (
230
+ OLD.authority_generation, OLD.authority_generation + 1
231
+ ) THEN
232
+ RAISE EXCEPTION 'connection authority generation must be stable or advance once'
233
+ USING ERRCODE = '23514';
234
+ END IF;
235
+ RETURN NEW;
236
+ END
237
+ $body$
238
+ $ddl$, data_schema);
239
+
240
+ EXECUTE format($ddl$
241
+ CREATE TRIGGER connections_authority_binding
242
+ BEFORE INSERT OR UPDATE ON %1$I.connections
243
+ FOR EACH ROW EXECUTE FUNCTION opengeni_private.bind_connection_authority()
244
+ $ddl$, data_schema);
245
+
246
+ EXECUTE format($ddl$
247
+ CREATE OR REPLACE FUNCTION opengeni_private.validate_user_resource_grant_action()
248
+ RETURNS trigger
249
+ LANGUAGE plpgsql
250
+ SECURITY DEFINER
251
+ SET search_path = %1$I, pg_catalog, pg_temp
252
+ AS $body$
253
+ BEGIN
254
+ IF NEW.action <> 'connection.use' AND EXISTS (
255
+ SELECT 1
256
+ FROM organization_user_resource_authorities authority
257
+ WHERE authority.id = NEW.authority_id
258
+ AND authority.account_id = NEW.account_id
259
+ AND authority.resource_kind = 'connection'
260
+ ) THEN
261
+ RAISE EXCEPTION 'connection grants require connection.use'
262
+ USING ERRCODE = '22023';
263
+ END IF;
264
+ RETURN NEW;
265
+ END
266
+ $body$
267
+ $ddl$, data_schema);
268
+
269
+ EXECUTE format($ddl$
270
+ CREATE TRIGGER organization_user_resource_grants_action_contract
271
+ BEFORE INSERT OR UPDATE OF account_id, authority_id, action
272
+ ON %1$I.organization_user_resource_grants
273
+ FOR EACH ROW
274
+ EXECUTE FUNCTION opengeni_private.validate_user_resource_grant_action()
275
+ $ddl$, data_schema);
276
+
277
+ EXECUTE format($ddl$
278
+ CREATE OR REPLACE FUNCTION %1$I.list_self_connection_authorities(
279
+ p_account_id uuid
280
+ ) RETURNS TABLE (
281
+ authority_id uuid, authority_generation bigint, authority_status text,
282
+ grant_id uuid, target_workspace_id uuid, target_session_id uuid,
283
+ grant_mode text, grant_context text, grant_generation bigint,
284
+ grant_status text, expires_at timestamptz
285
+ )
286
+ LANGUAGE sql
287
+ SECURITY DEFINER
288
+ SET search_path = %1$I, pg_catalog, pg_temp
289
+ AS $body$
290
+ SELECT authority.authority_id, authority.authority_generation,
291
+ authority.authority_status, authority.grant_id,
292
+ authority.target_workspace_id, authority.target_session_id,
293
+ authority.grant_mode, authority.grant_context,
294
+ authority.grant_generation, authority.grant_status, authority.expires_at
295
+ FROM list_self_user_resource_authorities(p_account_id) authority
296
+ WHERE authority.resource_kind = 'connection'
297
+ AND (authority.grant_id IS NULL OR authority.action = 'connection.use')
298
+ $body$
299
+ $ddl$, data_schema);
300
+
301
+ EXECUTE format($ddl$
302
+ CREATE OR REPLACE FUNCTION %1$I.issue_self_connection_use_grant(
303
+ p_account_id uuid,
304
+ p_authority_id uuid,
305
+ p_workspace_id uuid,
306
+ p_mode text,
307
+ p_context text,
308
+ p_session_id uuid DEFAULT NULL,
309
+ p_workspace_shared_acknowledged boolean DEFAULT false
310
+ ) RETURNS TABLE (
311
+ grant_id uuid, target_workspace_id uuid, target_session_id uuid,
312
+ action text, grant_mode text, grant_context text, grant_generation bigint,
313
+ grant_status text, expires_at timestamptz
314
+ )
315
+ LANGUAGE plpgsql
316
+ SECURITY DEFINER
317
+ SET search_path = %1$I, pg_catalog, pg_temp
318
+ AS $body$
319
+ BEGIN
320
+ PERFORM 1
321
+ FROM organization_user_resource_authorities authority
322
+ WHERE authority.id = p_authority_id
323
+ AND authority.account_id = p_account_id
324
+ AND authority.resource_kind = 'connection';
325
+ IF NOT FOUND THEN
326
+ RAISE EXCEPTION 'self-owned connection authority required' USING ERRCODE = '42501';
327
+ END IF;
328
+ RETURN QUERY SELECT * FROM issue_self_user_resource_grant(
329
+ p_account_id, p_authority_id, p_workspace_id, 'connection.use', p_mode,
330
+ p_context, p_session_id, p_workspace_shared_acknowledged
331
+ );
332
+ END
333
+ $body$
334
+ $ddl$, data_schema);
335
+
336
+ EXECUTE format($ddl$
337
+ CREATE OR REPLACE FUNCTION %1$I.revoke_self_connection_use_grant(
338
+ p_account_id uuid,
339
+ p_grant_id uuid
340
+ ) RETURNS TABLE (
341
+ grant_id uuid, grant_generation bigint, grant_status text, revoked_at timestamptz
342
+ )
343
+ LANGUAGE plpgsql
344
+ SECURITY DEFINER
345
+ SET search_path = %1$I, pg_catalog, pg_temp
346
+ AS $body$
347
+ BEGIN
348
+ PERFORM 1
349
+ FROM organization_user_resource_grants grant_value
350
+ JOIN organization_user_resource_authorities authority
351
+ ON authority.id = grant_value.authority_id
352
+ AND authority.account_id = grant_value.account_id
353
+ WHERE grant_value.id = p_grant_id
354
+ AND grant_value.account_id = p_account_id
355
+ AND grant_value.action = 'connection.use'
356
+ AND authority.resource_kind = 'connection';
357
+ IF NOT FOUND THEN
358
+ RAISE EXCEPTION 'self-owned connection use grant required' USING ERRCODE = '42501';
359
+ END IF;
360
+ RETURN QUERY SELECT * FROM revoke_self_user_resource_grant(p_account_id, p_grant_id);
361
+ END
362
+ $body$
363
+ $ddl$, data_schema);
364
+
365
+ EXECUTE format($ddl$
366
+ CREATE OR REPLACE FUNCTION %1$I.resolve_connection_use_authority(
367
+ p_account_id uuid,
368
+ p_workspace_id uuid,
369
+ p_session_id uuid,
370
+ p_snapshot jsonb
371
+ ) RETURNS TABLE (
372
+ authorization_status text,
373
+ denial_reason text,
374
+ connection_id uuid,
375
+ connection_generation bigint,
376
+ authority_scope text,
377
+ owner_subject_id text,
378
+ authority_id uuid,
379
+ grant_id uuid
380
+ )
381
+ LANGUAGE plpgsql
382
+ SECURITY DEFINER
383
+ SET search_path = %1$I, pg_catalog, pg_temp
384
+ AS $body$
385
+ #variable_conflict use_column
386
+ DECLARE
387
+ session_row record;
388
+ connection_row record;
389
+ membership_row record;
390
+ authority_row record;
391
+ grant_row record;
392
+ receipt_row record;
393
+ accepted_kind text := p_snapshot #>> '{acceptedWork,kind}';
394
+ accepted_id uuid := coalesce(
395
+ nullif(p_snapshot #>> '{acceptedWork,turnId}', '')::uuid,
396
+ nullif(p_snapshot #>> '{acceptedWork,taskId}', '')::uuid
397
+ );
398
+ snapshot_scope text := p_snapshot ->> 'scope';
399
+ snapshot_owner text := nullif(p_snapshot ->> 'ownerSubjectId', '');
400
+ snapshot_membership uuid := nullif(
401
+ p_snapshot ->> 'ownerOrganizationMembershipId', ''
402
+ )::uuid;
403
+ snapshot_authority uuid := nullif(
404
+ p_snapshot #>> '{userDelegation,authorityId}', ''
405
+ )::uuid;
406
+ snapshot_grant uuid := nullif(p_snapshot #>> '{userDelegation,grantId}', '')::uuid;
407
+ BEGIN
408
+ IF p_account_id IS DISTINCT FROM nullif(
409
+ current_setting('opengeni.account_id', true), ''
410
+ )::uuid
411
+ OR p_workspace_id IS DISTINCT FROM nullif(
412
+ current_setting('opengeni.workspace_id', true), ''
413
+ )::uuid
414
+ OR p_snapshot ->> 'organizationId' IS DISTINCT FROM p_account_id::text
415
+ OR p_snapshot ->> 'targetWorkspaceId' IS DISTINCT FROM p_workspace_id::text
416
+ OR p_snapshot ->> 'targetSessionId' IS DISTINCT FROM p_session_id::text
417
+ THEN
418
+ RAISE EXCEPTION 'connection use scope mismatch' USING ERRCODE = '42501';
419
+ END IF;
420
+
421
+ SELECT session_value.id, session_value.account_id, session_value.workspace_id,
422
+ session_value.visibility, session_value.authority_epoch, session_value.status
423
+ INTO session_row
424
+ FROM sessions session_value
425
+ WHERE session_value.id = p_session_id
426
+ AND session_value.account_id = p_account_id
427
+ AND session_value.workspace_id = p_workspace_id;
428
+ IF NOT FOUND OR session_row.status = 'cancelled' THEN
429
+ authorization_status := 'denied'; denial_reason := 'session_inactive';
430
+ RETURN NEXT; RETURN;
431
+ END IF;
432
+ IF session_row.visibility IS DISTINCT FROM p_snapshot ->> 'targetSessionVisibility' THEN
433
+ authorization_status := 'denied'; denial_reason := 'session_visibility_changed';
434
+ RETURN NEXT; RETURN;
435
+ END IF;
436
+ IF session_row.authority_epoch IS DISTINCT FROM (
437
+ p_snapshot ->> 'targetSessionAuthorityEpoch'
438
+ )::integer THEN
439
+ authorization_status := 'denied'; denial_reason := 'session_authority_epoch_changed';
440
+ RETURN NEXT; RETURN;
441
+ END IF;
442
+ IF accepted_kind = 'turn' AND NOT EXISTS (
443
+ SELECT 1 FROM session_turns turn_value
444
+ WHERE turn_value.id = accepted_id
445
+ AND turn_value.account_id = p_account_id
446
+ AND turn_value.workspace_id = p_workspace_id
447
+ AND turn_value.session_id = p_session_id
448
+ AND turn_value.status <> 'cancelled'
449
+ ) THEN
450
+ authorization_status := 'denied'; denial_reason := 'session_identity_changed';
451
+ RETURN NEXT; RETURN;
452
+ ELSIF accepted_kind = 'scheduled_task' AND NOT EXISTS (
453
+ SELECT 1 FROM scheduled_tasks task
454
+ WHERE task.id = accepted_id
455
+ AND task.account_id = p_account_id
456
+ AND task.workspace_id = p_workspace_id
457
+ AND task.authority_revision = (
458
+ p_snapshot #>> '{acceptedWork,taskAuthorityRevision}'
459
+ )::bigint
460
+ AND task.status = 'active'
461
+ ) THEN
462
+ authorization_status := 'denied'; denial_reason := 'session_identity_changed';
463
+ RETURN NEXT; RETURN;
464
+ END IF;
465
+
466
+ SELECT connection_value.id, connection_value.account_id,
467
+ connection_value.workspace_id, connection_value.origin_workspace_id,
468
+ connection_value.subject_id, connection_value.provider_domain,
469
+ connection_value.kind, connection_value.status,
470
+ connection_value.authority_scope, connection_value.authority_id,
471
+ connection_value.owner_organization_membership_id,
472
+ connection_value.authority_generation
473
+ INTO connection_row
474
+ FROM connections connection_value
475
+ WHERE connection_value.id = (p_snapshot ->> 'connectionId')::uuid
476
+ AND connection_value.account_id = p_account_id;
477
+ IF NOT FOUND THEN
478
+ authorization_status := 'denied'; denial_reason := 'connection_missing';
479
+ RETURN NEXT; RETURN;
480
+ END IF;
481
+ IF connection_row.origin_workspace_id::text IS DISTINCT FROM
482
+ p_snapshot ->> 'originWorkspaceId'
483
+ OR lower(connection_row.provider_domain) IS DISTINCT FROM lower(
484
+ p_snapshot ->> 'providerDomain'
485
+ )
486
+ OR connection_row.kind IS DISTINCT FROM p_snapshot ->> 'connectionKind'
487
+ OR connection_row.authority_scope IS DISTINCT FROM snapshot_scope
488
+ THEN
489
+ authorization_status := 'denied'; denial_reason := 'connection_identity_changed';
490
+ RETURN NEXT; RETURN;
491
+ END IF;
492
+ IF connection_row.authority_generation IS DISTINCT FROM (
493
+ p_snapshot ->> 'connectionGeneration'
494
+ )::bigint THEN
495
+ authorization_status := 'denied'; denial_reason := 'connection_generation_changed';
496
+ RETURN NEXT; RETURN;
497
+ END IF;
498
+ IF connection_row.status <> 'active' THEN
499
+ authorization_status := 'denied'; denial_reason := 'connection_status_inactive';
500
+ RETURN NEXT; RETURN;
501
+ END IF;
502
+ IF connection_row.subject_id IS DISTINCT FROM snapshot_owner
503
+ OR connection_row.owner_organization_membership_id
504
+ IS DISTINCT FROM snapshot_membership
505
+ THEN
506
+ authorization_status := 'denied'; denial_reason := 'connection_owner_changed';
507
+ RETURN NEXT; RETURN;
508
+ END IF;
509
+
510
+ connection_id := connection_row.id;
511
+ connection_generation := connection_row.authority_generation;
512
+ authority_scope := snapshot_scope;
513
+ owner_subject_id := snapshot_owner;
514
+ authority_id := snapshot_authority;
515
+ grant_id := snapshot_grant;
516
+
517
+ IF snapshot_scope = 'workspace' THEN
518
+ IF connection_row.workspace_id <> p_workspace_id
519
+ OR connection_row.origin_workspace_id <> p_workspace_id
520
+ OR connection_row.subject_id IS NOT NULL
521
+ OR snapshot_authority IS NOT NULL
522
+ OR snapshot_grant IS NOT NULL
523
+ THEN
524
+ authorization_status := 'denied'; denial_reason := 'connection_owner_changed';
525
+ RETURN NEXT; RETURN;
526
+ END IF;
527
+ authorization_status := 'authorized'; denial_reason := NULL;
528
+ RETURN NEXT; RETURN;
529
+ END IF;
530
+
531
+ SELECT membership.id, membership.account_id, membership.subject_id,
532
+ membership.status, membership.revoked_at
533
+ INTO membership_row
534
+ FROM organization_memberships membership
535
+ WHERE membership.id = snapshot_membership
536
+ AND membership.account_id = p_account_id
537
+ AND membership.subject_id = snapshot_owner;
538
+ IF NOT FOUND OR membership_row.status <> 'active' OR membership_row.revoked_at IS NOT NULL
539
+ OR NOT EXISTS (
540
+ SELECT 1 FROM organization_memberships membership
541
+ WHERE membership.id = snapshot_membership
542
+ AND (
543
+ membership.personal_workspace_id = p_workspace_id
544
+ OR EXISTS (
545
+ SELECT 1 FROM workspace_memberships workspace_membership
546
+ WHERE workspace_membership.account_id = p_account_id
547
+ AND workspace_membership.workspace_id = p_workspace_id
548
+ AND workspace_membership.subject_id = snapshot_owner
549
+ )
550
+ )
551
+ )
552
+ THEN
553
+ authorization_status := 'denied'; denial_reason := 'owner_membership_inactive';
554
+ RETURN NEXT; RETURN;
555
+ END IF;
556
+
557
+ SELECT authority.id, authority.account_id, authority.organization_membership_id,
558
+ authority.resource_kind, authority.resource_id, authority.origin_workspace_id,
559
+ authority.generation, authority.status, authority.revoked_at
560
+ INTO authority_row
561
+ FROM organization_user_resource_authorities authority
562
+ WHERE authority.id = snapshot_authority
563
+ AND authority.account_id = p_account_id;
564
+ IF NOT FOUND THEN
565
+ authorization_status := 'denied'; denial_reason := 'authority_missing';
566
+ RETURN NEXT; RETURN;
567
+ END IF;
568
+ IF authority_row.organization_membership_id <> snapshot_membership
569
+ OR authority_row.resource_kind <> 'connection'
570
+ OR authority_row.resource_id <> connection_row.id
571
+ OR authority_row.origin_workspace_id <> connection_row.origin_workspace_id
572
+ THEN
573
+ authorization_status := 'denied'; denial_reason := 'authority_identity_changed';
574
+ RETURN NEXT; RETURN;
575
+ END IF;
576
+ IF authority_row.generation IS DISTINCT FROM (
577
+ p_snapshot #>> '{userDelegation,authorityGeneration}'
578
+ )::bigint THEN
579
+ authorization_status := 'denied'; denial_reason := 'authority_generation_changed';
580
+ RETURN NEXT; RETURN;
581
+ END IF;
582
+ IF authority_row.status <> 'active' OR authority_row.revoked_at IS NOT NULL THEN
583
+ authorization_status := 'denied'; denial_reason := 'authority_status_inactive';
584
+ RETURN NEXT; RETURN;
585
+ END IF;
586
+
587
+ SELECT grant_value.id, grant_value.account_id, grant_value.authority_id,
588
+ grant_value.owner_organization_membership_id, grant_value.workspace_id,
589
+ grant_value.session_id, grant_value.action, grant_value.mode,
590
+ grant_value.context, grant_value.authority_epoch, grant_value.generation,
591
+ grant_value.status, grant_value.expires_at
592
+ INTO grant_row
593
+ FROM organization_user_resource_grants grant_value
594
+ WHERE grant_value.id = snapshot_grant
595
+ AND grant_value.account_id = p_account_id
596
+ FOR UPDATE;
597
+ IF NOT FOUND THEN
598
+ authorization_status := 'denied'; denial_reason := 'grant_missing';
599
+ RETURN NEXT; RETURN;
600
+ END IF;
601
+ IF grant_row.authority_id <> snapshot_authority
602
+ OR grant_row.owner_organization_membership_id <> snapshot_membership
603
+ OR grant_row.workspace_id <> p_workspace_id
604
+ OR grant_row.action <> 'connection.use'
605
+ OR grant_row.mode IS DISTINCT FROM p_snapshot #>> '{userDelegation,mode}'
606
+ OR grant_row.context IS DISTINCT FROM p_snapshot ->> 'targetSessionVisibility'
607
+ OR grant_row.session_id IS DISTINCT FROM nullif(
608
+ p_snapshot #>> '{userDelegation,sessionId}', ''
609
+ )::uuid
610
+ OR grant_row.authority_epoch IS DISTINCT FROM nullif(
611
+ p_snapshot #>> '{userDelegation,authorityEpoch}', ''
612
+ )::integer
613
+ THEN
614
+ authorization_status := 'denied'; denial_reason := 'grant_identity_changed';
615
+ RETURN NEXT; RETURN;
616
+ END IF;
617
+ IF grant_row.generation IS DISTINCT FROM (
618
+ p_snapshot #>> '{userDelegation,grantGeneration}'
619
+ )::bigint THEN
620
+ authorization_status := 'denied'; denial_reason := 'grant_generation_changed';
621
+ RETURN NEXT; RETURN;
622
+ END IF;
623
+ IF grant_row.expires_at IS NOT NULL AND grant_row.expires_at <= clock_timestamp() THEN
624
+ authorization_status := 'denied'; denial_reason := 'grant_expired';
625
+ RETURN NEXT; RETURN;
626
+ END IF;
627
+
628
+ IF grant_row.mode = 'once' THEN
629
+ SELECT receipt.accepted_work_kind, receipt.accepted_work_id
630
+ INTO receipt_row
631
+ FROM connection_use_once_consumption_receipts receipt
632
+ WHERE receipt.grant_id = grant_row.id;
633
+ IF FOUND THEN
634
+ IF receipt_row.accepted_work_kind IS DISTINCT FROM accepted_kind
635
+ OR receipt_row.accepted_work_id IS DISTINCT FROM accepted_id
636
+ THEN
637
+ authorization_status := 'denied'; denial_reason := 'grant_already_consumed';
638
+ RETURN NEXT; RETURN;
639
+ END IF;
640
+ ELSIF grant_row.status = 'active' THEN
641
+ UPDATE organization_user_resource_grants
642
+ SET status = 'consumed', updated_at = clock_timestamp()
643
+ WHERE id = grant_row.id AND account_id = p_account_id
644
+ AND generation = grant_row.generation AND status = 'active';
645
+ IF NOT FOUND THEN
646
+ authorization_status := 'denied'; denial_reason := 'grant_already_consumed';
647
+ RETURN NEXT; RETURN;
648
+ END IF;
649
+ INSERT INTO connection_use_once_consumption_receipts (
650
+ grant_id, account_id, authority_id, authority_generation,
651
+ grant_generation, accepted_work_kind, accepted_work_id
652
+ ) VALUES (
653
+ grant_row.id, p_account_id, snapshot_authority, authority_row.generation,
654
+ grant_row.generation, accepted_kind, accepted_id
655
+ );
656
+ ELSE
657
+ authorization_status := 'denied'; denial_reason := 'grant_status_inactive';
658
+ RETURN NEXT; RETURN;
659
+ END IF;
660
+ ELSIF grant_row.status <> 'active' THEN
661
+ authorization_status := 'denied'; denial_reason := 'grant_status_inactive';
662
+ RETURN NEXT; RETURN;
663
+ END IF;
664
+
665
+ authorization_status := 'authorized'; denial_reason := NULL;
666
+ RETURN NEXT;
667
+ END
668
+ $body$
669
+ $ddl$, data_schema);
670
+ END
671
+ $connection_authority_routines$;
672
+
673
+ REVOKE ALL ON FUNCTION opengeni_private.bind_connection_authority() FROM PUBLIC;
674
+ REVOKE ALL ON FUNCTION opengeni_private.validate_user_resource_grant_action() FROM PUBLIC;
675
+ REVOKE ALL ON FUNCTION list_self_connection_authorities(uuid) FROM PUBLIC;
676
+ REVOKE ALL ON FUNCTION issue_self_connection_use_grant(
677
+ uuid, uuid, uuid, text, text, uuid, boolean
678
+ ) FROM PUBLIC;
679
+ REVOKE ALL ON FUNCTION revoke_self_connection_use_grant(uuid, uuid) FROM PUBLIC;
680
+ REVOKE ALL ON FUNCTION resolve_connection_use_authority(uuid, uuid, uuid, jsonb) FROM PUBLIC;
681
+
682
+ DO $grants$
683
+ BEGIN
684
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
685
+ GRANT EXECUTE ON FUNCTION list_self_connection_authorities(uuid) TO opengeni_app;
686
+ GRANT EXECUTE ON FUNCTION issue_self_connection_use_grant(
687
+ uuid, uuid, uuid, text, text, uuid, boolean
688
+ ) TO opengeni_app;
689
+ GRANT EXECUTE ON FUNCTION revoke_self_connection_use_grant(uuid, uuid) TO opengeni_app;
690
+ GRANT EXECUTE ON FUNCTION resolve_connection_use_authority(
691
+ uuid, uuid, uuid, jsonb
692
+ ) TO opengeni_app;
693
+ REVOKE ALL ON TABLE connection_use_once_consumption_receipts FROM opengeni_app;
694
+ REVOKE ALL ON TABLE organization_user_resource_authorities FROM opengeni_app;
695
+ REVOKE ALL ON TABLE organization_user_resource_grants FROM opengeni_app;
696
+ END IF;
697
+ END
698
+ $grants$;
699
+
700
+ COMMENT ON COLUMN "connections"."authority_generation" IS
701
+ 'Monotonic execution-authority generation. Token refresh does not advance it; reconnect, disconnect, provider/kind, and status transitions do.';
702
+ COMMENT ON TABLE "connection_use_once_consumption_receipts" IS
703
+ 'Exact accepted turn/task that consumed one connection.use grant; contains no credential, quota, provider response, or usage value.';