@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,624 @@
1
+ -- deployment-mode: rolling
2
+ -- Personal GitHub repository discovery is live. Persist only the owner's
3
+ -- explicit selected set and monotonic authority facts. Runtime Git and GitHub
4
+ -- API execution remain disabled until their separately reviewed consumers land.
5
+
6
+ SET LOCAL lock_timeout = '5s';
7
+ SET LOCAL statement_timeout = '10min';
8
+
9
+ CREATE TABLE personal_github_repository_selection_heads (
10
+ connection_id uuid PRIMARY KEY REFERENCES connections(id) ON DELETE CASCADE,
11
+ account_id uuid NOT NULL REFERENCES managed_accounts(id) ON DELETE CASCADE,
12
+ origin_workspace_id uuid NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
13
+ owner_subject_id text NOT NULL,
14
+ provider_principal_id text NOT NULL,
15
+ credential_binding_id uuid NOT NULL,
16
+ connection_authority_generation bigint NOT NULL,
17
+ generation bigint NOT NULL,
18
+ updated_by_subject_id text NOT NULL,
19
+ created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
20
+ updated_at timestamptz NOT NULL DEFAULT clock_timestamp(),
21
+ CONSTRAINT personal_github_repository_selection_heads_tenant_uq
22
+ UNIQUE (account_id, origin_workspace_id, owner_subject_id, connection_id),
23
+ CONSTRAINT personal_github_repository_selection_heads_generation_chk CHECK (
24
+ connection_authority_generation > 0 AND generation > 0
25
+ AND octet_length(owner_subject_id) BETWEEN 1 AND 512
26
+ AND provider_principal_id ~ '^[1-9][0-9]*$'
27
+ )
28
+ );
29
+ CREATE INDEX personal_github_repository_selection_heads_owner_idx
30
+ ON personal_github_repository_selection_heads(account_id, owner_subject_id, connection_id);
31
+
32
+ CREATE TABLE personal_github_repository_selections (
33
+ connection_id uuid NOT NULL,
34
+ repository_id bigint NOT NULL,
35
+ account_id uuid NOT NULL REFERENCES managed_accounts(id) ON DELETE CASCADE,
36
+ origin_workspace_id uuid NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
37
+ owner_subject_id text NOT NULL,
38
+ canonical_full_name text NOT NULL,
39
+ canonical_https_uri text NOT NULL,
40
+ default_branch text NOT NULL,
41
+ visibility text NOT NULL,
42
+ private boolean NOT NULL,
43
+ archived boolean NOT NULL,
44
+ disabled boolean NOT NULL,
45
+ permissions jsonb NOT NULL,
46
+ selected_access text NOT NULL,
47
+ selection_generation bigint NOT NULL,
48
+ selected_by_subject_id text NOT NULL,
49
+ selected_at timestamptz NOT NULL DEFAULT clock_timestamp(),
50
+ verified_at timestamptz NOT NULL DEFAULT clock_timestamp(),
51
+ updated_at timestamptz NOT NULL DEFAULT clock_timestamp(),
52
+ PRIMARY KEY (connection_id, repository_id),
53
+ CONSTRAINT personal_github_repository_selections_head_fk FOREIGN KEY (
54
+ account_id, origin_workspace_id, owner_subject_id, connection_id
55
+ ) REFERENCES personal_github_repository_selection_heads (
56
+ account_id, origin_workspace_id, owner_subject_id, connection_id
57
+ ) ON DELETE CASCADE,
58
+ CONSTRAINT personal_github_repository_selections_shape_chk CHECK (
59
+ repository_id > 0 AND selection_generation > 0
60
+ AND selected_access IN ('read', 'write')
61
+ AND visibility IN ('public', 'private', 'internal')
62
+ AND private = (visibility = 'private')
63
+ AND octet_length(owner_subject_id) BETWEEN 1 AND 512
64
+ AND canonical_full_name ~ '^[A-Za-z0-9][A-Za-z0-9-]{0,38}/[A-Za-z0-9_.-]{1,100}$'
65
+ AND octet_length(canonical_full_name) BETWEEN 3 AND 140
66
+ AND octet_length(default_branch) BETWEEN 1 AND 255
67
+ AND canonical_https_uri = 'https://github.com/' || canonical_full_name
68
+ AND jsonb_typeof(permissions) = 'object'
69
+ AND permissions ?& ARRAY['pull','push','admin','maintain','triage']
70
+ AND permissions - ARRAY['pull','push','admin','maintain','triage']::text[] = '{}'::jsonb
71
+ AND jsonb_typeof(permissions -> 'pull') = 'boolean'
72
+ AND jsonb_typeof(permissions -> 'push') = 'boolean'
73
+ AND jsonb_typeof(permissions -> 'admin') = 'boolean'
74
+ AND jsonb_typeof(permissions -> 'maintain') = 'boolean'
75
+ AND jsonb_typeof(permissions -> 'triage') = 'boolean'
76
+ AND (
77
+ permissions -> 'pull' = 'true'::jsonb
78
+ OR permissions -> 'push' = 'true'::jsonb
79
+ OR permissions -> 'admin' = 'true'::jsonb
80
+ OR permissions -> 'maintain' = 'true'::jsonb
81
+ OR permissions -> 'triage' = 'true'::jsonb
82
+ )
83
+ AND (selected_access <> 'write' OR (
84
+ NOT archived AND NOT disabled
85
+ AND (
86
+ permissions -> 'push' = 'true'::jsonb
87
+ OR permissions -> 'admin' = 'true'::jsonb
88
+ OR permissions -> 'maintain' = 'true'::jsonb
89
+ )
90
+ ))
91
+ )
92
+ );
93
+ CREATE INDEX personal_github_repository_selections_tenant_idx
94
+ ON personal_github_repository_selections(account_id, origin_workspace_id, connection_id);
95
+
96
+ CREATE TABLE personal_github_repository_selection_operations (
97
+ connection_id uuid NOT NULL,
98
+ idempotency_key text NOT NULL,
99
+ account_id uuid NOT NULL,
100
+ origin_workspace_id uuid NOT NULL,
101
+ owner_subject_id text NOT NULL,
102
+ request_digest bytea NOT NULL,
103
+ resulting_generation bigint NOT NULL,
104
+ created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
105
+ PRIMARY KEY (connection_id, idempotency_key),
106
+ CONSTRAINT personal_github_repository_selection_operations_head_fk FOREIGN KEY (
107
+ account_id, origin_workspace_id, owner_subject_id, connection_id
108
+ ) REFERENCES personal_github_repository_selection_heads (
109
+ account_id, origin_workspace_id, owner_subject_id, connection_id
110
+ ) ON DELETE CASCADE,
111
+ CONSTRAINT personal_github_repository_selection_operations_shape_chk CHECK (
112
+ resulting_generation > 0
113
+ AND octet_length(idempotency_key) BETWEEN 1 AND 200
114
+ AND octet_length(owner_subject_id) BETWEEN 1 AND 512
115
+ AND octet_length(request_digest) = 32
116
+ )
117
+ );
118
+
119
+ ALTER TABLE personal_github_repository_selection_heads ENABLE ROW LEVEL SECURITY;
120
+ ALTER TABLE personal_github_repository_selection_heads FORCE ROW LEVEL SECURITY;
121
+ ALTER TABLE personal_github_repository_selections ENABLE ROW LEVEL SECURITY;
122
+ ALTER TABLE personal_github_repository_selections FORCE ROW LEVEL SECURITY;
123
+ ALTER TABLE personal_github_repository_selection_operations ENABLE ROW LEVEL SECURITY;
124
+ ALTER TABLE personal_github_repository_selection_operations FORCE ROW LEVEL SECURITY;
125
+
126
+ CREATE POLICY personal_github_repository_selection_heads_owner_isolation
127
+ ON personal_github_repository_selection_heads
128
+ USING (
129
+ account_id = nullif(current_setting('opengeni.account_id', true), '')::uuid
130
+ AND origin_workspace_id = nullif(current_setting('opengeni.workspace_id', true), '')::uuid
131
+ AND owner_subject_id = nullif(current_setting('opengeni.subject_id', true), '')
132
+ ) WITH CHECK (
133
+ account_id = nullif(current_setting('opengeni.account_id', true), '')::uuid
134
+ AND origin_workspace_id = nullif(current_setting('opengeni.workspace_id', true), '')::uuid
135
+ AND owner_subject_id = nullif(current_setting('opengeni.subject_id', true), '')
136
+ );
137
+ CREATE POLICY personal_github_repository_selections_owner_isolation
138
+ ON personal_github_repository_selections
139
+ USING (
140
+ account_id = nullif(current_setting('opengeni.account_id', true), '')::uuid
141
+ AND origin_workspace_id = nullif(current_setting('opengeni.workspace_id', true), '')::uuid
142
+ AND owner_subject_id = nullif(current_setting('opengeni.subject_id', true), '')
143
+ ) WITH CHECK (
144
+ account_id = nullif(current_setting('opengeni.account_id', true), '')::uuid
145
+ AND origin_workspace_id = nullif(current_setting('opengeni.workspace_id', true), '')::uuid
146
+ AND owner_subject_id = nullif(current_setting('opengeni.subject_id', true), '')
147
+ );
148
+ CREATE POLICY personal_github_repository_selection_operations_owner_isolation
149
+ ON personal_github_repository_selection_operations
150
+ USING (
151
+ account_id = nullif(current_setting('opengeni.account_id', true), '')::uuid
152
+ AND origin_workspace_id = nullif(current_setting('opengeni.workspace_id', true), '')::uuid
153
+ AND owner_subject_id = nullif(current_setting('opengeni.subject_id', true), '')
154
+ ) WITH CHECK (
155
+ account_id = nullif(current_setting('opengeni.account_id', true), '')::uuid
156
+ AND origin_workspace_id = nullif(current_setting('opengeni.workspace_id', true), '')::uuid
157
+ AND owner_subject_id = nullif(current_setting('opengeni.subject_id', true), '')
158
+ );
159
+
160
+ DO $personal_github_repository_functions$
161
+ DECLARE
162
+ data_schema text := current_schema();
163
+ BEGIN
164
+ EXECUTE format($ddl$
165
+ CREATE FUNCTION %1$I.get_self_personal_github_repository_selection(
166
+ p_account_id uuid,
167
+ p_origin_workspace_id uuid,
168
+ p_owner_subject_id text,
169
+ p_connection_id uuid
170
+ ) RETURNS jsonb
171
+ LANGUAGE plpgsql SECURITY DEFINER
172
+ SET search_path = %1$I, pg_catalog, pg_temp
173
+ AS $body$
174
+ DECLARE
175
+ connection_row connections%%ROWTYPE;
176
+ head_row personal_github_repository_selection_heads%%ROWTYPE;
177
+ selected_rows jsonb;
178
+ BEGIN
179
+ IF p_account_id IS DISTINCT FROM
180
+ nullif(current_setting('opengeni.account_id', true), '')::uuid
181
+ OR p_origin_workspace_id IS DISTINCT FROM
182
+ nullif(current_setting('opengeni.workspace_id', true), '')::uuid
183
+ OR p_owner_subject_id IS DISTINCT FROM
184
+ nullif(current_setting('opengeni.subject_id', true), '')
185
+ THEN
186
+ RETURN NULL;
187
+ END IF;
188
+ SELECT connection_value.* INTO connection_row
189
+ FROM connections connection_value
190
+ WHERE connection_value.id = p_connection_id
191
+ AND connection_value.account_id = p_account_id
192
+ AND connection_value.origin_workspace_id = p_origin_workspace_id
193
+ AND connection_value.workspace_id = p_origin_workspace_id
194
+ AND connection_value.subject_id = p_owner_subject_id
195
+ AND connection_value.status = 'active'
196
+ AND connection_value.authority_scope = 'user'
197
+ AND connection_value.provider_domain = 'github.com'
198
+ AND connection_value.kind = 'oauth2'
199
+ AND connection_value.granted_scopes = '["repo"]'::jsonb
200
+ AND connection_value.metadata ->> 'credentialRole' = 'opengeni_github_personal'
201
+ AND connection_value.metadata ->> 'providerFamily' = 'github'
202
+ AND connection_value.metadata ->> 'providerPrincipalId' ~ '^[1-9][0-9]*$'
203
+ AND connection_value.metadata ->> 'credentialBindingId'
204
+ ~* '^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$';
205
+ IF NOT FOUND THEN RETURN NULL; END IF;
206
+ SELECT head.* INTO head_row
207
+ FROM personal_github_repository_selection_heads head
208
+ WHERE head.connection_id = p_connection_id
209
+ AND head.account_id = p_account_id
210
+ AND head.origin_workspace_id = p_origin_workspace_id
211
+ AND head.owner_subject_id = p_owner_subject_id;
212
+ IF NOT FOUND THEN
213
+ RETURN jsonb_build_object(
214
+ 'connectionAuthorityGeneration', connection_row.authority_generation,
215
+ 'credentialBindingId', connection_row.metadata ->> 'credentialBindingId',
216
+ 'providerPrincipalId', connection_row.metadata ->> 'providerPrincipalId',
217
+ 'selectionGeneration', 0,
218
+ 'repositories', '[]'::jsonb
219
+ );
220
+ END IF;
221
+ SELECT coalesce(jsonb_agg(jsonb_build_object(
222
+ 'repositoryId', selected.repository_id::text,
223
+ 'fullName', selected.canonical_full_name,
224
+ 'canonicalUrl', selected.canonical_https_uri,
225
+ 'defaultBranch', selected.default_branch,
226
+ 'visibility', selected.visibility,
227
+ 'private', selected.private,
228
+ 'archived', selected.archived,
229
+ 'disabled', selected.disabled,
230
+ 'permissions', selected.permissions,
231
+ 'selectedAccess', selected.selected_access,
232
+ 'selectionGeneration', selected.selection_generation,
233
+ 'selectedAt', selected.selected_at,
234
+ 'lastVerifiedAt', selected.verified_at
235
+ ) ORDER BY selected.canonical_full_name, selected.repository_id), '[]'::jsonb)
236
+ INTO selected_rows
237
+ FROM personal_github_repository_selections selected
238
+ WHERE selected.connection_id = p_connection_id
239
+ AND selected.account_id = p_account_id
240
+ AND selected.origin_workspace_id = p_origin_workspace_id
241
+ AND selected.owner_subject_id = p_owner_subject_id;
242
+ RETURN jsonb_build_object(
243
+ 'connectionAuthorityGeneration', connection_row.authority_generation,
244
+ 'credentialBindingId', head_row.credential_binding_id,
245
+ 'providerPrincipalId', head_row.provider_principal_id,
246
+ 'selectionGeneration', head_row.generation,
247
+ 'repositories', selected_rows
248
+ );
249
+ END
250
+ $body$
251
+ $ddl$, data_schema);
252
+
253
+ EXECUTE format($ddl$
254
+ CREATE FUNCTION %1$I.mutate_self_personal_github_repository_selection(
255
+ p_account_id uuid,
256
+ p_origin_workspace_id uuid,
257
+ p_owner_subject_id text,
258
+ p_connection_id uuid,
259
+ p_expected_connection_authority_generation bigint,
260
+ p_expected_selection_generation bigint,
261
+ p_idempotency_key text,
262
+ p_repositories jsonb,
263
+ p_verify_only boolean DEFAULT false
264
+ ) RETURNS jsonb
265
+ LANGUAGE plpgsql SECURITY DEFINER
266
+ SET search_path = %1$I, pg_catalog, pg_temp
267
+ AS $body$
268
+ DECLARE
269
+ connection_row connections%%ROWTYPE;
270
+ head_row personal_github_repository_selection_heads%%ROWTYPE;
271
+ receipt_row personal_github_repository_selection_operations%%ROWTYPE;
272
+ item jsonb;
273
+ request_digest bytea;
274
+ current_generation bigint;
275
+ next_generation bigint;
276
+ authority_changed boolean;
277
+ BEGIN
278
+ IF p_account_id IS DISTINCT FROM
279
+ nullif(current_setting('opengeni.account_id', true), '')::uuid
280
+ OR p_origin_workspace_id IS DISTINCT FROM
281
+ nullif(current_setting('opengeni.workspace_id', true), '')::uuid
282
+ OR p_owner_subject_id IS DISTINCT FROM
283
+ nullif(current_setting('opengeni.subject_id', true), '')
284
+ THEN RAISE EXCEPTION 'personal GitHub repository selection is unavailable'
285
+ USING ERRCODE = '42501';
286
+ END IF;
287
+ IF p_expected_connection_authority_generation <= 0
288
+ OR p_expected_selection_generation < 0
289
+ OR octet_length(p_idempotency_key) NOT BETWEEN 1 AND 200
290
+ OR jsonb_typeof(p_repositories) <> 'array'
291
+ OR jsonb_array_length(p_repositories) > 100
292
+ THEN RAISE EXCEPTION 'invalid personal GitHub repository selection'
293
+ USING ERRCODE = '22023';
294
+ END IF;
295
+ IF EXISTS (
296
+ SELECT 1 FROM jsonb_array_elements(p_repositories) selected(value)
297
+ WHERE jsonb_typeof(value) <> 'object'
298
+ OR value - ARRAY[
299
+ 'repositoryId','fullName','canonicalUrl','defaultBranch','visibility',
300
+ 'private','archived','disabled','permissions','selectedAccess','lastVerifiedAt'
301
+ ]::text[] <> '{}'::jsonb
302
+ OR NOT (value ?& ARRAY[
303
+ 'repositoryId','fullName','canonicalUrl','defaultBranch','visibility',
304
+ 'private','archived','disabled','permissions','selectedAccess','lastVerifiedAt'
305
+ ])
306
+ OR value ->> 'repositoryId' !~ '^[0-9]+$'
307
+ OR (value ->> 'repositoryId')::numeric <= 0
308
+ OR value ->> 'fullName'
309
+ !~ '^[A-Za-z0-9][A-Za-z0-9-]{0,38}/[A-Za-z0-9_.-]{1,100}$'
310
+ OR value ->> 'canonicalUrl' IS DISTINCT FROM
311
+ 'https://github.com/' || (value ->> 'fullName')
312
+ OR octet_length(value ->> 'defaultBranch') NOT BETWEEN 1 AND 255
313
+ OR value ->> 'visibility' NOT IN ('public','private','internal')
314
+ OR jsonb_typeof(value -> 'private') <> 'boolean'
315
+ OR jsonb_typeof(value -> 'archived') <> 'boolean'
316
+ OR jsonb_typeof(value -> 'disabled') <> 'boolean'
317
+ OR value -> 'private' IS DISTINCT FROM
318
+ to_jsonb((value ->> 'visibility') = 'private')
319
+ OR value ->> 'selectedAccess' NOT IN ('read','write')
320
+ OR jsonb_typeof(value -> 'permissions') <> 'object'
321
+ OR NOT (value -> 'permissions' ?& ARRAY['pull','push','admin','maintain','triage'])
322
+ OR EXISTS (
323
+ SELECT 1 FROM jsonb_each(value -> 'permissions') permission(key, permission_value)
324
+ WHERE key NOT IN ('pull','push','admin','maintain','triage')
325
+ OR jsonb_typeof(permission_value) <> 'boolean'
326
+ )
327
+ OR NOT (
328
+ value -> 'permissions' -> 'pull' = 'true'::jsonb
329
+ OR value -> 'permissions' -> 'push' = 'true'::jsonb
330
+ OR value -> 'permissions' -> 'admin' = 'true'::jsonb
331
+ OR value -> 'permissions' -> 'maintain' = 'true'::jsonb
332
+ OR value -> 'permissions' -> 'triage' = 'true'::jsonb
333
+ )
334
+ OR (
335
+ value ->> 'selectedAccess' = 'write'
336
+ AND (
337
+ value -> 'archived' = 'true'::jsonb
338
+ OR value -> 'disabled' = 'true'::jsonb
339
+ OR NOT (
340
+ value -> 'permissions' -> 'push' = 'true'::jsonb
341
+ OR value -> 'permissions' -> 'admin' = 'true'::jsonb
342
+ OR value -> 'permissions' -> 'maintain' = 'true'::jsonb
343
+ )
344
+ )
345
+ )
346
+ OR (value ->> 'lastVerifiedAt')::timestamptz IS NULL
347
+ ) OR (
348
+ SELECT count(*) FROM jsonb_array_elements(p_repositories)
349
+ ) <> (
350
+ SELECT count(DISTINCT value ->> 'repositoryId')
351
+ FROM jsonb_array_elements(p_repositories) selected(value)
352
+ ) OR (
353
+ SELECT count(*) FROM jsonb_array_elements(p_repositories)
354
+ ) <> (
355
+ SELECT count(DISTINCT lower(value ->> 'fullName'))
356
+ FROM jsonb_array_elements(p_repositories) selected(value)
357
+ )
358
+ THEN RAISE EXCEPTION 'invalid personal GitHub repository selection'
359
+ USING ERRCODE = '22023';
360
+ END IF;
361
+
362
+ PERFORM pg_advisory_xact_lock(hashtextextended(
363
+ 'personal-github-repositories:' || p_connection_id::text, 0
364
+ ));
365
+ SELECT connection_value.* INTO connection_row
366
+ FROM connections connection_value
367
+ WHERE connection_value.id = p_connection_id
368
+ AND connection_value.account_id = p_account_id
369
+ AND connection_value.origin_workspace_id = p_origin_workspace_id
370
+ AND connection_value.workspace_id = p_origin_workspace_id
371
+ AND connection_value.subject_id = p_owner_subject_id
372
+ AND connection_value.status = 'active'
373
+ AND connection_value.authority_scope = 'user'
374
+ AND connection_value.provider_domain = 'github.com'
375
+ AND connection_value.kind = 'oauth2'
376
+ AND connection_value.granted_scopes = '["repo"]'::jsonb
377
+ AND connection_value.authority_generation = p_expected_connection_authority_generation
378
+ AND connection_value.metadata ->> 'credentialRole' = 'opengeni_github_personal'
379
+ AND connection_value.metadata ->> 'providerFamily' = 'github'
380
+ AND connection_value.metadata ->> 'providerPrincipalId' ~ '^[1-9][0-9]*$'
381
+ AND connection_value.metadata ->> 'credentialBindingId'
382
+ ~* '^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'
383
+ FOR SHARE;
384
+ IF NOT FOUND THEN RAISE EXCEPTION 'personal GitHub repository selection is unavailable'
385
+ USING ERRCODE = '42501'; END IF;
386
+
387
+ SELECT head.* INTO head_row
388
+ FROM personal_github_repository_selection_heads head
389
+ WHERE head.connection_id = p_connection_id
390
+ FOR UPDATE;
391
+ current_generation := CASE WHEN FOUND THEN head_row.generation ELSE 0 END;
392
+ request_digest := digest(convert_to(jsonb_build_object(
393
+ 'expectedConnectionAuthorityGeneration', p_expected_connection_authority_generation,
394
+ 'expectedSelectionGeneration', p_expected_selection_generation,
395
+ 'verifyOnly', p_verify_only,
396
+ 'repositories', coalesce((
397
+ SELECT jsonb_agg(
398
+ jsonb_build_array(
399
+ value ->> 'repositoryId',
400
+ lower(value ->> 'fullName'),
401
+ value ->> 'selectedAccess'
402
+ ) ORDER BY (value ->> 'repositoryId')::numeric
403
+ )
404
+ FROM jsonb_array_elements(p_repositories) incoming(value)
405
+ ), '[]'::jsonb)
406
+ )::text, 'UTF8'), 'sha256');
407
+ IF FOUND THEN
408
+ SELECT operation.* INTO receipt_row
409
+ FROM personal_github_repository_selection_operations operation
410
+ WHERE operation.connection_id = p_connection_id
411
+ AND operation.idempotency_key = p_idempotency_key;
412
+ IF FOUND THEN
413
+ IF receipt_row.request_digest IS DISTINCT FROM request_digest THEN
414
+ RAISE EXCEPTION 'personal GitHub repository idempotency key was reused'
415
+ USING ERRCODE = '23505';
416
+ END IF;
417
+ IF current_generation IS DISTINCT FROM receipt_row.resulting_generation THEN
418
+ RAISE EXCEPTION 'personal GitHub repository selection changed'
419
+ USING ERRCODE = '40001';
420
+ END IF;
421
+ RETURN get_self_personal_github_repository_selection(
422
+ p_account_id, p_origin_workspace_id, p_owner_subject_id, p_connection_id
423
+ );
424
+ END IF;
425
+ END IF;
426
+ IF current_generation IS DISTINCT FROM p_expected_selection_generation THEN
427
+ RAISE EXCEPTION 'personal GitHub repository selection changed'
428
+ USING ERRCODE = '40001';
429
+ END IF;
430
+ IF p_verify_only AND current_generation = 0
431
+ AND jsonb_array_length(p_repositories) = 0
432
+ THEN
433
+ RETURN get_self_personal_github_repository_selection(
434
+ p_account_id, p_origin_workspace_id, p_owner_subject_id, p_connection_id
435
+ );
436
+ END IF;
437
+ IF p_verify_only AND (
438
+ (SELECT count(*) FROM personal_github_repository_selections selected
439
+ WHERE selected.connection_id = p_connection_id)
440
+ <> jsonb_array_length(p_repositories)
441
+ OR EXISTS (
442
+ SELECT 1 FROM personal_github_repository_selections selected
443
+ WHERE selected.connection_id = p_connection_id
444
+ AND NOT EXISTS (
445
+ SELECT 1 FROM jsonb_array_elements(p_repositories) incoming(value)
446
+ WHERE incoming.value ->> 'repositoryId' = selected.repository_id::text
447
+ AND incoming.value ->> 'selectedAccess' = selected.selected_access
448
+ )
449
+ )
450
+ ) THEN RAISE EXCEPTION 'personal GitHub verify set changed'
451
+ USING ERRCODE = '40001'; END IF;
452
+
453
+ SELECT current_generation = 0 OR EXISTS (
454
+ (SELECT selected.repository_id::text, selected.canonical_https_uri,
455
+ selected.selected_access
456
+ FROM personal_github_repository_selections selected
457
+ WHERE selected.connection_id = p_connection_id
458
+ EXCEPT
459
+ SELECT value ->> 'repositoryId', value ->> 'canonicalUrl', value ->> 'selectedAccess'
460
+ FROM jsonb_array_elements(p_repositories) incoming(value))
461
+ UNION ALL
462
+ (SELECT value ->> 'repositoryId', value ->> 'canonicalUrl', value ->> 'selectedAccess'
463
+ FROM jsonb_array_elements(p_repositories) incoming(value)
464
+ EXCEPT
465
+ SELECT selected.repository_id::text, selected.canonical_https_uri,
466
+ selected.selected_access
467
+ FROM personal_github_repository_selections selected
468
+ WHERE selected.connection_id = p_connection_id)
469
+ ) INTO authority_changed;
470
+ next_generation := CASE
471
+ WHEN authority_changed THEN current_generation + 1 ELSE current_generation END;
472
+
473
+ INSERT INTO personal_github_repository_selection_heads (
474
+ connection_id, account_id, origin_workspace_id, owner_subject_id,
475
+ provider_principal_id, credential_binding_id,
476
+ connection_authority_generation, generation, updated_by_subject_id
477
+ ) VALUES (
478
+ p_connection_id, p_account_id, p_origin_workspace_id, p_owner_subject_id,
479
+ connection_row.metadata ->> 'providerPrincipalId',
480
+ (connection_row.metadata ->> 'credentialBindingId')::uuid,
481
+ connection_row.authority_generation, next_generation, p_owner_subject_id
482
+ ) ON CONFLICT (connection_id) DO UPDATE SET
483
+ provider_principal_id = EXCLUDED.provider_principal_id,
484
+ credential_binding_id = EXCLUDED.credential_binding_id,
485
+ connection_authority_generation = EXCLUDED.connection_authority_generation,
486
+ generation = EXCLUDED.generation,
487
+ updated_by_subject_id = EXCLUDED.updated_by_subject_id,
488
+ updated_at = clock_timestamp();
489
+
490
+ FOR item IN SELECT value FROM jsonb_array_elements(p_repositories) incoming(value)
491
+ LOOP
492
+ INSERT INTO personal_github_repository_selections (
493
+ connection_id, repository_id, account_id, origin_workspace_id,
494
+ owner_subject_id, canonical_full_name, canonical_https_uri,
495
+ default_branch, visibility, private, archived, disabled, permissions,
496
+ selected_access, selection_generation, selected_by_subject_id,
497
+ selected_at, verified_at, updated_at
498
+ ) VALUES (
499
+ p_connection_id, (item ->> 'repositoryId')::bigint, p_account_id,
500
+ p_origin_workspace_id, p_owner_subject_id, item ->> 'fullName',
501
+ item ->> 'canonicalUrl', item ->> 'defaultBranch', item ->> 'visibility',
502
+ (item ->> 'private')::boolean, (item ->> 'archived')::boolean,
503
+ (item ->> 'disabled')::boolean, item -> 'permissions',
504
+ item ->> 'selectedAccess', next_generation, p_owner_subject_id,
505
+ clock_timestamp(), (item ->> 'lastVerifiedAt')::timestamptz, clock_timestamp()
506
+ ) ON CONFLICT (connection_id, repository_id) DO UPDATE SET
507
+ canonical_full_name = EXCLUDED.canonical_full_name,
508
+ canonical_https_uri = EXCLUDED.canonical_https_uri,
509
+ default_branch = EXCLUDED.default_branch,
510
+ visibility = EXCLUDED.visibility,
511
+ private = EXCLUDED.private,
512
+ archived = EXCLUDED.archived,
513
+ disabled = EXCLUDED.disabled,
514
+ permissions = EXCLUDED.permissions,
515
+ selected_access = EXCLUDED.selected_access,
516
+ selection_generation = CASE
517
+ WHEN personal_github_repository_selections.canonical_https_uri
518
+ IS DISTINCT FROM EXCLUDED.canonical_https_uri
519
+ OR personal_github_repository_selections.selected_access
520
+ IS DISTINCT FROM EXCLUDED.selected_access
521
+ THEN next_generation
522
+ ELSE personal_github_repository_selections.selection_generation
523
+ END,
524
+ selected_at = CASE
525
+ WHEN personal_github_repository_selections.canonical_https_uri
526
+ IS DISTINCT FROM EXCLUDED.canonical_https_uri
527
+ OR personal_github_repository_selections.selected_access
528
+ IS DISTINCT FROM EXCLUDED.selected_access
529
+ THEN EXCLUDED.selected_at
530
+ ELSE personal_github_repository_selections.selected_at
531
+ END,
532
+ verified_at = EXCLUDED.verified_at,
533
+ updated_at = clock_timestamp();
534
+ END LOOP;
535
+ IF NOT p_verify_only THEN
536
+ DELETE FROM personal_github_repository_selections selected
537
+ WHERE selected.connection_id = p_connection_id
538
+ AND NOT EXISTS (
539
+ SELECT 1 FROM jsonb_array_elements(p_repositories) incoming(value)
540
+ WHERE incoming.value ->> 'repositoryId' = selected.repository_id::text
541
+ );
542
+ END IF;
543
+ INSERT INTO personal_github_repository_selection_operations (
544
+ connection_id, idempotency_key, account_id, origin_workspace_id,
545
+ owner_subject_id, request_digest, resulting_generation
546
+ ) VALUES (
547
+ p_connection_id, p_idempotency_key, p_account_id, p_origin_workspace_id,
548
+ p_owner_subject_id, request_digest, next_generation
549
+ );
550
+ RETURN get_self_personal_github_repository_selection(
551
+ p_account_id, p_origin_workspace_id, p_owner_subject_id, p_connection_id
552
+ );
553
+ END
554
+ $body$
555
+ $ddl$, data_schema);
556
+ END
557
+ $personal_github_repository_functions$;
558
+
559
+ DO $personal_github_repository_privileges$
560
+ DECLARE
561
+ data_schema text := current_schema();
562
+ BEGIN
563
+ EXECUTE format(
564
+ 'REVOKE ALL ON FUNCTION %I.get_self_personal_github_repository_selection('
565
+ || 'uuid,uuid,text,uuid) FROM PUBLIC', data_schema
566
+ );
567
+ EXECUTE format(
568
+ 'REVOKE ALL ON FUNCTION %I.mutate_self_personal_github_repository_selection('
569
+ || 'uuid,uuid,text,uuid,bigint,bigint,text,jsonb,boolean) FROM PUBLIC', data_schema
570
+ );
571
+ IF to_regrole('opengeni_app') IS NOT NULL THEN
572
+ EXECUTE format(
573
+ 'REVOKE ALL ON TABLE %I.personal_github_repository_selection_heads, '
574
+ || '%I.personal_github_repository_selections, '
575
+ || '%I.personal_github_repository_selection_operations FROM opengeni_app',
576
+ data_schema, data_schema, data_schema
577
+ );
578
+ EXECUTE format(
579
+ 'GRANT EXECUTE ON FUNCTION %I.get_self_personal_github_repository_selection('
580
+ || 'uuid,uuid,text,uuid) TO opengeni_app', data_schema
581
+ );
582
+ EXECUTE format(
583
+ 'GRANT EXECUTE ON FUNCTION %I.mutate_self_personal_github_repository_selection('
584
+ || 'uuid,uuid,text,uuid,bigint,bigint,text,jsonb,boolean) TO opengeni_app', data_schema
585
+ );
586
+ END IF;
587
+ END
588
+ $personal_github_repository_privileges$;
589
+
590
+ COMMENT ON TABLE personal_github_repository_selection_heads IS
591
+ 'Owner-only monotonic authority head for an explicit personal GitHub repository set.';
592
+ COMMENT ON TABLE personal_github_repository_selections IS
593
+ 'Only explicitly selected personal GitHub repositories; never a private discovery catalog.';
594
+
595
+ -- Scheduled tasks already freeze user-owned connection.use authority in a
596
+ -- separate immutable ledger. Admit the dedicated personal-GitHub discriminator
597
+ -- into that existing lane; the exact repository set remains in the task's
598
+ -- accepted personal_connection_delegations snapshot and execution digest.
599
+ ALTER TABLE scheduled_task_connection_authority_snapshots
600
+ DROP CONSTRAINT scheduled_task_connection_authority_shape_chk,
601
+ ADD CONSTRAINT scheduled_task_connection_authority_shape_chk CHECK (
602
+ task_authority_revision > 0
603
+ AND execution_digest ~ '^[0-9a-f]{64}$'
604
+ AND octet_length(server_id) BETWEEN 1 AND 256
605
+ AND connection_generation > 0
606
+ AND (selected_kind IS NULL OR selected_kind IN ('oauth2','api_key','app_install','delegated'))
607
+ AND (connection_type IS NULL OR connection_type IN ('mcp', 'github_personal'))
608
+ AND membership_authorization_revision > 0
609
+ AND authority_generation > 0
610
+ AND grant_generation > 0
611
+ AND grant_mode IN ('once', 'session', 'always')
612
+ AND grant_context IN ('user_private', 'workspace_shared')
613
+ AND grant_context = session_visibility
614
+ AND session_visibility IN ('user_private', 'workspace_shared')
615
+ AND cardinality(selection_sources) > 0
616
+ ) NOT VALID;
617
+
618
+ ALTER TABLE scheduled_task_connection_authority_snapshots
619
+ VALIDATE CONSTRAINT scheduled_task_connection_authority_shape_chk;
620
+
621
+ -- Per-occurrence repository-generation revalidation belongs to the scheduled
622
+ -- lifecycle phase. Keep the existing run-ledger discriminator unchanged so a
623
+ -- task definition can retain explicit personal GitHub authority while every
624
+ -- occurrence still fails closed before a provider consumer can run.