@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,1347 @@
1
+ -- deployment-mode: rolling
2
+ -- Complete the organization-tenancy evidence chain, converge the final
3
+ -- deterministic connection compatibility population, and make activation
4
+ -- consume one fenced evidence snapshot. This migration activates no tenant.
5
+ -- Existing zero-receipt activations stay replayable; new activations require
6
+ -- six exact receipts, including connection convergence. Five was never a
7
+ -- shipped cardinality, so the constraint does not tolerate it.
8
+
9
+ SET LOCAL lock_timeout = '5s';
10
+ SET LOCAL statement_timeout = '10min';
11
+
12
+ ALTER TABLE tenancy_backfill_unresolved_rows
13
+ DROP CONSTRAINT tenancy_backfill_unresolved_reason_chk,
14
+ ADD CONSTRAINT tenancy_backfill_unresolved_reason_chk CHECK (
15
+ reason_code IN (
16
+ 'no_deterministic_evidence', 'ambiguous_candidate_authority',
17
+ 'missing_organization_membership', 'conflicting_authority_rows',
18
+ 'external_lane_owns_row', 'legacy_shape_unrecognized',
19
+ 'missing_login_identity', 'organization_identity_mismatch',
20
+ 'missing_owner_workspace_membership', 'membership_terminal_status',
21
+ 'deterministic_repair_pending'
22
+ )
23
+ );
24
+
25
+ ALTER TABLE session_tenancy_activations
26
+ ADD COLUMN backfill_receipt_ids uuid[] NOT NULL DEFAULT ARRAY[]::uuid[],
27
+ ADD CONSTRAINT session_tenancy_activation_backfill_receipts_check CHECK (
28
+ cardinality(backfill_receipt_ids) IN (0, 6)
29
+ );
30
+
31
+ -- An application-controlled GUC is not a write capability. The exact
32
+ -- backend+transaction+organization receipt allows only the reviewed upgrader
33
+ -- to cross the otherwise immutable connection-authority boundary.
34
+ CREATE TABLE opengeni_private.connection_tenancy_backfill_capabilities (
35
+ backend_pid integer NOT NULL,
36
+ transaction_id xid8 NOT NULL,
37
+ account_id uuid NOT NULL,
38
+ CONSTRAINT connection_tenancy_backfill_capabilities_pk PRIMARY KEY (
39
+ backend_pid, transaction_id, account_id
40
+ )
41
+ );
42
+ REVOKE ALL ON TABLE opengeni_private.connection_tenancy_backfill_capabilities FROM PUBLIC;
43
+
44
+ CREATE FUNCTION opengeni_private.connection_tenancy_backfill_capability_active(
45
+ p_account_id uuid
46
+ ) RETURNS boolean
47
+ LANGUAGE sql STABLE SECURITY DEFINER
48
+ SET search_path = pg_catalog, opengeni_private
49
+ AS $capability$
50
+ SELECT EXISTS (
51
+ SELECT 1 FROM opengeni_private.connection_tenancy_backfill_capabilities capability
52
+ WHERE capability.backend_pid = pg_catalog.pg_backend_pid()
53
+ AND capability.transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
54
+ AND capability.account_id = p_account_id
55
+ )
56
+ $capability$;
57
+ REVOKE ALL ON FUNCTION
58
+ opengeni_private.connection_tenancy_backfill_capability_active(uuid) FROM PUBLIC;
59
+
60
+ -- FORCE-RLS visibility exists only while the SECURITY DEFINER classifier or
61
+ -- upgrader holds its unforgeable transaction capability, and only for the
62
+ -- migration owner the reviewed seams run as (0285/0298's conjunct). Direct
63
+ -- runtime DML cannot create the capability row.
64
+ DO $connection_tenancy_backfill_capability_policies$
65
+ DECLARE
66
+ data_schema text := pg_catalog.current_schema();
67
+ migration_owner text := current_user;
68
+ BEGIN
69
+ EXECUTE pg_catalog.format(
70
+ 'CREATE POLICY connection_tenancy_backfill_read ON %I.organization_memberships '
71
+ || 'FOR SELECT USING (current_user = %L AND '
72
+ || 'opengeni_private.connection_tenancy_backfill_capability_active(account_id))',
73
+ data_schema, migration_owner
74
+ );
75
+ EXECUTE pg_catalog.format(
76
+ 'CREATE POLICY connection_tenancy_backfill_read ON %I.connections '
77
+ || 'FOR SELECT USING (current_user = %L AND '
78
+ || 'opengeni_private.connection_tenancy_backfill_capability_active(account_id))',
79
+ data_schema, migration_owner
80
+ );
81
+ EXECUTE pg_catalog.format(
82
+ 'CREATE POLICY connection_tenancy_backfill_update ON %I.connections '
83
+ || 'FOR UPDATE USING (current_user = %L AND '
84
+ || 'opengeni_private.connection_tenancy_backfill_capability_active(account_id)) '
85
+ || 'WITH CHECK (current_user = %L AND '
86
+ || 'opengeni_private.connection_tenancy_backfill_capability_active(account_id))',
87
+ data_schema, migration_owner, migration_owner
88
+ );
89
+ EXECUTE pg_catalog.format(
90
+ 'CREATE POLICY connection_tenancy_backfill_read '
91
+ || 'ON %I.organization_user_resource_authorities '
92
+ || 'FOR SELECT USING (resource_kind = ''connection'' AND current_user = %L AND '
93
+ || 'opengeni_private.connection_tenancy_backfill_capability_active(account_id))',
94
+ data_schema, migration_owner
95
+ );
96
+ EXECUTE pg_catalog.format(
97
+ 'CREATE POLICY connection_tenancy_backfill_insert '
98
+ || 'ON %I.organization_user_resource_authorities '
99
+ || 'FOR INSERT WITH CHECK (resource_kind = ''connection'' AND current_user = %L AND '
100
+ || 'opengeni_private.connection_tenancy_backfill_capability_active(account_id))',
101
+ data_schema, migration_owner
102
+ );
103
+ END
104
+ $connection_tenancy_backfill_capability_policies$;
105
+
106
+ -- Owner-only marker policies for the connection owner-authority binding below.
107
+ --
108
+ -- `organization_memberships` and `organization_user_resource_authorities` are
109
+ -- FORCE ROW LEVEL SECURITY with ZERO runtime table privileges, so the marker is
110
+ -- unreachable as an application write vector - it is exactly the gate the
111
+ -- existing `organization_tenancy_lifecycle` policies on both tables already
112
+ -- use - and the `current_user` conjunct keeps every branch owner-only besides.
113
+ -- They are separate narrow policies rather than new markers on the shared
114
+ -- lifecycle policy so a later migration restating that policy's list cannot
115
+ -- silently drop connection binding (0305 dropped 0290's marker exactly that
116
+ -- way).
117
+ --
118
+ -- The `FOR UPDATE` policy on `organization_memberships` is not a write grant to
119
+ -- the binding: PostgreSQL evaluates the UPDATE/ALL policy USING clause for
120
+ -- `SELECT ... FOR SHARE` as well as the SELECT one, so a row-locking lookup is
121
+ -- blind without it.
122
+ DO $connection_authority_binding_policies$
123
+ DECLARE
124
+ data_schema text := pg_catalog.current_schema();
125
+ migration_owner text := current_user;
126
+ -- Tenant-fenced in the policy itself, not only in the seam: the window can
127
+ -- never widen into an organization-wide membership or authority read.
128
+ marker constant text :=
129
+ 'current_setting(''opengeni.organization_tenancy_lifecycle'', true) '
130
+ || '= ''connection_authority_binding'' AND account_id = nullif('
131
+ || 'current_setting(''opengeni.account_id'', true), '''')::uuid';
132
+ BEGIN
133
+ EXECUTE pg_catalog.format(
134
+ 'CREATE POLICY connection_authority_binding_read ON %I.organization_memberships '
135
+ || 'FOR SELECT USING (current_user = %L AND %s)',
136
+ data_schema, migration_owner, marker
137
+ );
138
+ EXECUTE pg_catalog.format(
139
+ 'CREATE POLICY connection_authority_binding_lock ON %I.organization_memberships '
140
+ || 'FOR UPDATE USING (current_user = %L AND %s)',
141
+ data_schema, migration_owner, marker
142
+ );
143
+ EXECUTE pg_catalog.format(
144
+ 'CREATE POLICY connection_authority_binding_insert '
145
+ || 'ON %I.organization_user_resource_authorities '
146
+ || 'FOR INSERT WITH CHECK (resource_kind = ''connection'' AND current_user = %L AND %s)',
147
+ data_schema, migration_owner, marker
148
+ );
149
+ END
150
+ $connection_authority_binding_policies$;
151
+
152
+ -- Migration 0290 gave its two read-only membership seams the
153
+ -- `organization_membership_backfill` marker on the shared
154
+ -- `organization_tenancy_lifecycle` policy. Migration 0305 then restated that
155
+ -- policy's marker list to add `personal_resource_grant_management` and dropped
156
+ -- 0290's entry, so `list_organization_membership_backfill_anchors` and
157
+ -- `list_organization_memberships_without_personal_workspace` have returned `[]`
158
+ -- ever since for a NON-superuser migration owner - measured, not inferred, on
159
+ -- `acquireOwnerMigratedTestDatabase`. Both are SECURITY DEFINER owned by that
160
+ -- role, so even a superuser CALLER gets the owner's RLS; only a
161
+ -- superuser-migrated database (every existing test harness) hid it.
162
+ --
163
+ -- The consequence is phase D's membership half: an already-anchored subject is
164
+ -- misread as provisionable, and the memberships that carry no personal
165
+ -- workspace - the actual backfill target population - are invisible, so the
166
+ -- walk can never converge them and its `organization_memberships` receipt
167
+ -- counts are wrong.
168
+ --
169
+ -- Restored as its own narrow read-only policy rather than as a fourth entry in
170
+ -- the shared list, so the next migration to restate that list cannot silently
171
+ -- delete it again. Both seams are plain `SELECT`s, so `FOR SELECT` is exactly
172
+ -- the command they issue.
173
+ DO $organization_membership_backfill_read_policy$
174
+ DECLARE
175
+ data_schema text := pg_catalog.current_schema();
176
+ migration_owner text := current_user;
177
+ BEGIN
178
+ EXECUTE pg_catalog.format(
179
+ 'CREATE POLICY organization_membership_backfill_read '
180
+ || 'ON %I.organization_memberships FOR SELECT USING (current_user = %L AND '
181
+ || 'current_setting(''opengeni.organization_tenancy_lifecycle'', true) '
182
+ || '= ''organization_membership_backfill'' AND account_id = nullif('
183
+ || 'current_setting(''opengeni.account_id'', true), '''')::uuid)',
184
+ data_schema, migration_owner
185
+ );
186
+ END
187
+ $organization_membership_backfill_read_policy$;
188
+
189
+ -- The single owner-authority seam both connection-binding branches use.
190
+ -- `organization_memberships` and `organization_user_resource_authorities` are
191
+ -- FORCE ROW LEVEL SECURITY, and OpenGeni migrates and runs its SECURITY DEFINER
192
+ -- routines as a NON-superuser owner without BYPASSRLS, so the owner is
193
+ -- policy-bound too. 0256's inline `SELECT ... FOR SHARE` plus authority INSERT
194
+ -- therefore could not work on any production deployment: the mint path silently
195
+ -- degraded every personal connection to `legacy_user`, and 0340's backfill
196
+ -- verification would have raised `42501` on every deterministic candidate.
197
+ -- Opening the reviewed marker window for exactly this binding is 0263's
198
+ -- `assert_active_managed_human_organization_membership` pattern, restored on
199
+ -- every exit. The row lock is kept: it serializes the mint and the bounded
200
+ -- upgrade against a concurrent membership revocation.
201
+ --
202
+ -- Tenant-fenced, so it never becomes a cross-organization membership oracle.
203
+ -- It is a resolver, never an authorization: the trigger still owns the decision
204
+ -- that the caller IS this subject. `provision-roles` blanket-grants EXECUTE on
205
+ -- every `opengeni_private` routine to the runtime role, and the four authority
206
+ -- tables must keep ZERO direct application DML, so the seam additionally
207
+ -- refuses to do anything outside a trigger: a direct call by the application
208
+ -- role can neither read a membership nor mint an authority row.
209
+ CREATE FUNCTION opengeni_private.bind_connection_owner_authority(
210
+ p_account_id uuid,
211
+ p_subject_id text,
212
+ p_connection_id uuid DEFAULT NULL,
213
+ p_workspace_id uuid DEFAULT NULL,
214
+ OUT membership_id uuid,
215
+ OUT authority_id uuid
216
+ ) RETURNS record
217
+ LANGUAGE plpgsql SECURITY DEFINER
218
+ SET search_path FROM CURRENT
219
+ AS $owner_authority$
220
+ DECLARE
221
+ previous_lifecycle text := pg_catalog.current_setting(
222
+ 'opengeni.organization_tenancy_lifecycle', true
223
+ );
224
+ BEGIN
225
+ membership_id := NULL;
226
+ authority_id := NULL;
227
+ IF pg_catalog.pg_trigger_depth() = 0
228
+ OR p_account_id IS NULL OR p_subject_id IS NULL
229
+ OR p_account_id IS DISTINCT FROM nullif(
230
+ pg_catalog.current_setting('opengeni.account_id', true), ''
231
+ )::uuid
232
+ THEN
233
+ RETURN;
234
+ END IF;
235
+ PERFORM pg_catalog.set_config(
236
+ 'opengeni.organization_tenancy_lifecycle', 'connection_authority_binding', true
237
+ );
238
+ SELECT membership.id INTO membership_id
239
+ FROM organization_memberships membership
240
+ WHERE membership.account_id = p_account_id
241
+ AND membership.subject_id = p_subject_id
242
+ AND membership.status = 'active' AND membership.revoked_at IS NULL
243
+ FOR SHARE;
244
+ IF membership_id IS NOT NULL AND p_connection_id IS NOT NULL
245
+ AND p_workspace_id IS NOT NULL
246
+ THEN
247
+ authority_id := pg_catalog.gen_random_uuid();
248
+ INSERT INTO organization_user_resource_authorities (
249
+ id, account_id, organization_membership_id, resource_kind, resource_id,
250
+ origin_workspace_id, generation, status
251
+ ) VALUES (
252
+ authority_id, p_account_id, membership_id, 'connection', p_connection_id,
253
+ p_workspace_id, 1, 'active'
254
+ );
255
+ END IF;
256
+ PERFORM pg_catalog.set_config(
257
+ 'opengeni.organization_tenancy_lifecycle', coalesce(previous_lifecycle, ''), true
258
+ );
259
+ RETURN;
260
+ EXCEPTION WHEN OTHERS THEN
261
+ PERFORM pg_catalog.set_config(
262
+ 'opengeni.organization_tenancy_lifecycle', coalesce(previous_lifecycle, ''), true
263
+ );
264
+ RAISE;
265
+ END
266
+ $owner_authority$;
267
+ REVOKE ALL ON FUNCTION
268
+ opengeni_private.bind_connection_owner_authority(uuid, text, uuid, uuid) FROM PUBLIC;
269
+
270
+ -- Exact-scoped activation lookup used by the connection/writer triggers. The
271
+ -- account must already be the transaction tenant: this keeps the helper safe
272
+ -- under the provisioner's reviewed private-helper EXECUTE posture.
273
+ CREATE FUNCTION opengeni_private.session_tenancy_account_activated(p_account_id uuid)
274
+ RETURNS boolean
275
+ LANGUAGE sql STABLE SECURITY DEFINER
276
+ SET search_path FROM CURRENT
277
+ AS $activated$
278
+ SELECT p_account_id IS NOT DISTINCT FROM nullif(
279
+ pg_catalog.current_setting('opengeni.account_id', true), ''
280
+ )::uuid
281
+ AND EXISTS (
282
+ SELECT 1 FROM session_tenancy_activations activation
283
+ WHERE activation.account_id = p_account_id AND activation.activation_version = 1
284
+ )
285
+ $activated$;
286
+ REVOKE ALL ON FUNCTION opengeni_private.session_tenancy_account_activated(uuid) FROM PUBLIC;
287
+
288
+ -- Preserve 0256 byte-for-byte behavior before activation. Afterwards a new
289
+ -- personal connection must have a live membership and may not reopen the
290
+ -- legacy_user lane. The only immutable-authority transition is the exact
291
+ -- capability-held legacy_user -> user backfill below.
292
+ CREATE OR REPLACE FUNCTION opengeni_private.bind_connection_authority()
293
+ RETURNS trigger
294
+ LANGUAGE plpgsql SECURITY DEFINER
295
+ SET search_path FROM CURRENT
296
+ AS $binding$
297
+ DECLARE
298
+ caller_subject text := nullif(pg_catalog.current_setting('opengeni.subject_id', true), '');
299
+ membership_id uuid;
300
+ authority_value uuid;
301
+ identity_changed boolean;
302
+ backfill_transition boolean;
303
+ BEGIN
304
+ IF TG_OP = 'INSERT' THEN
305
+ NEW.origin_workspace_id := NEW.workspace_id;
306
+ NEW.authority_generation := 1;
307
+ IF NEW.subject_id IS NULL THEN
308
+ NEW.authority_scope := 'workspace';
309
+ NEW.authority_id := NULL;
310
+ NEW.owner_organization_membership_id := NULL;
311
+ RETURN NEW;
312
+ END IF;
313
+ IF caller_subject IS DISTINCT FROM NEW.subject_id THEN
314
+ RAISE EXCEPTION 'personal connection owner must be the authenticated subject'
315
+ USING ERRCODE = '42501';
316
+ END IF;
317
+ SELECT binding.membership_id, binding.authority_id
318
+ INTO membership_id, authority_value
319
+ FROM opengeni_private.bind_connection_owner_authority(
320
+ NEW.account_id, caller_subject, NEW.id, NEW.workspace_id
321
+ ) binding;
322
+ IF membership_id IS NULL THEN
323
+ IF opengeni_private.session_tenancy_account_activated(NEW.account_id) THEN
324
+ RAISE EXCEPTION 'activated organization requires connection membership authority'
325
+ USING ERRCODE = '42501';
326
+ END IF;
327
+ NEW.authority_scope := 'legacy_user';
328
+ NEW.authority_id := NULL;
329
+ NEW.owner_organization_membership_id := NULL;
330
+ RETURN NEW;
331
+ END IF;
332
+ NEW.authority_scope := 'user';
333
+ NEW.authority_id := authority_value;
334
+ NEW.owner_organization_membership_id := membership_id;
335
+ RETURN NEW;
336
+ END IF;
337
+
338
+ backfill_transition :=
339
+ opengeni_private.connection_tenancy_backfill_capability_active(NEW.account_id)
340
+ AND OLD.authority_scope = 'legacy_user'
341
+ AND OLD.subject_id IS NOT NULL
342
+ AND OLD.authority_id IS NULL
343
+ AND OLD.owner_organization_membership_id IS NULL
344
+ AND NEW.account_id IS NOT DISTINCT FROM OLD.account_id
345
+ AND NEW.workspace_id IS NOT DISTINCT FROM OLD.workspace_id
346
+ AND NEW.subject_id IS NOT DISTINCT FROM OLD.subject_id
347
+ AND NEW.authority_scope = 'user'
348
+ AND NEW.authority_id IS NOT NULL
349
+ AND NEW.owner_organization_membership_id IS NOT NULL
350
+ AND NEW.origin_workspace_id IS NOT DISTINCT FROM OLD.origin_workspace_id;
351
+
352
+ IF NEW.account_id IS DISTINCT FROM OLD.account_id
353
+ OR NEW.workspace_id IS DISTINCT FROM OLD.workspace_id
354
+ OR NEW.subject_id IS DISTINCT FROM OLD.subject_id
355
+ OR NEW.authority_scope IS DISTINCT FROM OLD.authority_scope
356
+ OR NEW.authority_id IS DISTINCT FROM OLD.authority_id
357
+ OR NEW.owner_organization_membership_id IS DISTINCT FROM OLD.owner_organization_membership_id
358
+ OR NEW.origin_workspace_id IS DISTINCT FROM OLD.origin_workspace_id
359
+ THEN
360
+ IF NOT backfill_transition THEN
361
+ RAISE EXCEPTION 'connection owner authority is immutable' USING ERRCODE = '23514';
362
+ END IF;
363
+ SELECT binding.membership_id INTO membership_id
364
+ FROM opengeni_private.bind_connection_owner_authority(
365
+ NEW.account_id, NEW.subject_id
366
+ ) binding;
367
+ IF membership_id IS DISTINCT FROM NEW.owner_organization_membership_id THEN
368
+ RAISE EXCEPTION 'connection backfill membership authority is unavailable'
369
+ USING ERRCODE = '42501';
370
+ END IF;
371
+ PERFORM 1 FROM organization_user_resource_authorities authority
372
+ WHERE authority.id = NEW.authority_id
373
+ AND authority.account_id = NEW.account_id
374
+ AND authority.organization_membership_id = NEW.owner_organization_membership_id
375
+ AND authority.resource_kind = 'connection' AND authority.resource_id = NEW.id
376
+ AND authority.origin_workspace_id = NEW.origin_workspace_id
377
+ AND authority.generation = 1
378
+ AND authority.status = 'active' AND authority.revoked_at IS NULL;
379
+ IF NOT FOUND THEN
380
+ RAISE EXCEPTION 'connection backfill common authority is unavailable'
381
+ USING ERRCODE = '42501';
382
+ END IF;
383
+ NEW.authority_generation := OLD.authority_generation + 1;
384
+ RETURN NEW;
385
+ END IF;
386
+
387
+ identity_changed := NEW.provider_domain IS DISTINCT FROM OLD.provider_domain
388
+ OR NEW.kind IS DISTINCT FROM OLD.kind OR NEW.status IS DISTINCT FROM OLD.status;
389
+ IF identity_changed THEN
390
+ NEW.authority_generation := OLD.authority_generation + 1;
391
+ ELSIF NEW.authority_generation NOT IN (
392
+ OLD.authority_generation, OLD.authority_generation + 1
393
+ ) THEN
394
+ RAISE EXCEPTION 'connection authority generation must be stable or advance once'
395
+ USING ERRCODE = '23514';
396
+ END IF;
397
+ RETURN NEW;
398
+ END
399
+ $binding$;
400
+ REVOKE ALL ON FUNCTION opengeni_private.bind_connection_authority() FROM PUBLIC;
401
+
402
+ -- Full-population classifier. A legacy row with an exact live same-account
403
+ -- membership is a deterministic repair obligation; without one it is
404
+ -- unresolved. Origin workspace/current access are never ownership evidence.
405
+ CREATE FUNCTION classify_organization_connection_authority(
406
+ p_account_id uuid, p_run_key text DEFAULT NULL
407
+ ) RETURNS jsonb
408
+ LANGUAGE plpgsql SECURITY DEFINER
409
+ SET search_path FROM CURRENT
410
+ SET statement_timeout = '5min'
411
+ AS $classification$
412
+ DECLARE
413
+ summary jsonb;
414
+ receipt_id uuid;
415
+ unresolved_row record;
416
+ BEGIN
417
+ IF p_account_id IS NULL THEN
418
+ RAISE EXCEPTION 'connection authority classification requires an organization id'
419
+ USING ERRCODE = '22004';
420
+ END IF;
421
+ IF p_account_id IS DISTINCT FROM nullif(
422
+ pg_catalog.current_setting('opengeni.account_id', true), ''
423
+ )::uuid
424
+ THEN
425
+ RAISE EXCEPTION 'connection authority classification scope mismatch'
426
+ USING ERRCODE = '42501';
427
+ END IF;
428
+ IF p_run_key IS NOT NULL AND length(btrim(p_run_key)) = 0 THEN
429
+ RAISE EXCEPTION 'connection authority classification run key must not be blank'
430
+ USING ERRCODE = '22023';
431
+ END IF;
432
+ INSERT INTO opengeni_private.connection_tenancy_backfill_capabilities (
433
+ backend_pid, transaction_id, account_id
434
+ ) VALUES (pg_catalog.pg_backend_pid(), pg_catalog.pg_current_xact_id(), p_account_id)
435
+ ON CONFLICT DO NOTHING;
436
+
437
+ WITH classified AS (
438
+ SELECT connection_row.id AS resource_id,
439
+ CASE
440
+ WHEN connection_row.authority_scope = 'workspace'
441
+ AND connection_row.subject_id IS NULL AND connection_row.authority_id IS NULL
442
+ AND connection_row.owner_organization_membership_id IS NULL
443
+ AND connection_row.origin_workspace_id = connection_row.workspace_id
444
+ THEN 'workspace_owned'
445
+ WHEN connection_row.authority_scope = 'user'
446
+ AND connection_row.subject_id IS NOT NULL
447
+ AND authority.id = connection_row.authority_id
448
+ AND authority.resource_kind = 'connection'
449
+ AND authority.resource_id = connection_row.id
450
+ AND authority.organization_membership_id = connection_row.owner_organization_membership_id
451
+ AND authority.origin_workspace_id = connection_row.origin_workspace_id
452
+ AND authority.status = 'active' AND authority.revoked_at IS NULL
453
+ AND membership.id = connection_row.owner_organization_membership_id
454
+ AND membership.subject_id = connection_row.subject_id
455
+ AND membership.status = 'active' AND membership.revoked_at IS NULL
456
+ THEN 'user_owned'
457
+ WHEN connection_row.authority_scope = 'legacy_user'
458
+ AND connection_row.subject_id IS NOT NULL AND connection_row.authority_id IS NULL
459
+ AND connection_row.owner_organization_membership_id IS NULL
460
+ AND membership.id IS NOT NULL
461
+ AND membership.status = 'active' AND membership.revoked_at IS NULL
462
+ THEN 'deterministic_repair_pending'
463
+ WHEN connection_row.authority_scope = 'legacy_user'
464
+ THEN 'missing_organization_membership'
465
+ WHEN connection_row.authority_scope = 'user'
466
+ AND (membership.id IS NULL OR membership.status <> 'active'
467
+ OR membership.revoked_at IS NOT NULL)
468
+ THEN 'missing_organization_membership'
469
+ WHEN connection_row.authority_scope = 'user' THEN 'conflicting_authority_rows'
470
+ ELSE 'legacy_shape_unrecognized'
471
+ END AS verdict
472
+ FROM connections connection_row
473
+ LEFT JOIN organization_memberships membership
474
+ ON membership.account_id = connection_row.account_id
475
+ AND membership.subject_id = connection_row.subject_id
476
+ AND membership.status = 'active' AND membership.revoked_at IS NULL
477
+ LEFT JOIN organization_user_resource_authorities authority
478
+ ON authority.id = connection_row.authority_id
479
+ AND authority.account_id = connection_row.account_id
480
+ WHERE connection_row.account_id = p_account_id
481
+ )
482
+ SELECT pg_catalog.jsonb_build_object(
483
+ 'schemaVersion', 1, 'organizationId', p_account_id, 'runKey', p_run_key,
484
+ 'rewroteConnectionRows', false,
485
+ 'connections', pg_catalog.jsonb_build_object(
486
+ 'total', count(*)::int,
487
+ 'workspaceOwned', count(*) FILTER (WHERE verdict = 'workspace_owned')::int,
488
+ 'userOwned', count(*) FILTER (WHERE verdict = 'user_owned')::int,
489
+ 'deterministicRepairPending', count(*) FILTER (
490
+ WHERE verdict = 'deterministic_repair_pending'
491
+ )::int,
492
+ 'unresolved', count(*) FILTER (
493
+ WHERE verdict NOT IN ('workspace_owned', 'user_owned')
494
+ )::int
495
+ )
496
+ ) INTO summary FROM classified;
497
+
498
+ IF p_run_key IS NOT NULL THEN
499
+ SELECT open_tenancy_backfill_receipt(p_account_id, 'connections', p_run_key)
500
+ INTO receipt_id;
501
+ FOR unresolved_row IN
502
+ WITH classified AS (
503
+ SELECT connection_row.id AS resource_id,
504
+ CASE
505
+ WHEN connection_row.authority_scope = 'workspace'
506
+ AND connection_row.subject_id IS NULL AND connection_row.authority_id IS NULL
507
+ AND connection_row.owner_organization_membership_id IS NULL
508
+ AND connection_row.origin_workspace_id = connection_row.workspace_id
509
+ THEN 'terminal'
510
+ WHEN connection_row.authority_scope = 'user'
511
+ AND connection_row.subject_id IS NOT NULL
512
+ AND authority.id = connection_row.authority_id
513
+ AND authority.resource_kind = 'connection'
514
+ AND authority.resource_id = connection_row.id
515
+ AND authority.organization_membership_id
516
+ = connection_row.owner_organization_membership_id
517
+ AND authority.origin_workspace_id = connection_row.origin_workspace_id
518
+ AND authority.status = 'active' AND authority.revoked_at IS NULL
519
+ AND membership.id = connection_row.owner_organization_membership_id
520
+ AND membership.subject_id = connection_row.subject_id
521
+ AND membership.status = 'active' AND membership.revoked_at IS NULL
522
+ THEN 'terminal'
523
+ WHEN connection_row.authority_scope = 'legacy_user'
524
+ AND membership.id IS NOT NULL
525
+ AND membership.status = 'active' AND membership.revoked_at IS NULL
526
+ THEN 'deterministic_repair_pending'
527
+ WHEN connection_row.authority_scope = 'legacy_user'
528
+ OR (connection_row.authority_scope = 'user'
529
+ AND (membership.id IS NULL OR membership.status <> 'active'
530
+ OR membership.revoked_at IS NOT NULL))
531
+ THEN 'missing_organization_membership'
532
+ WHEN connection_row.authority_scope = 'user' THEN 'conflicting_authority_rows'
533
+ ELSE 'legacy_shape_unrecognized'
534
+ END AS reason_code
535
+ FROM connections connection_row
536
+ LEFT JOIN organization_memberships membership
537
+ ON membership.account_id = connection_row.account_id
538
+ AND membership.subject_id = connection_row.subject_id
539
+ AND membership.status = 'active' AND membership.revoked_at IS NULL
540
+ LEFT JOIN organization_user_resource_authorities authority
541
+ ON authority.id = connection_row.authority_id
542
+ AND authority.account_id = connection_row.account_id
543
+ WHERE connection_row.account_id = p_account_id
544
+ )
545
+ SELECT resource_id, reason_code FROM classified
546
+ WHERE reason_code <> 'terminal' ORDER BY resource_id
547
+ LOOP
548
+ PERFORM record_tenancy_backfill_unresolved(
549
+ receipt_id, unresolved_row.resource_id, unresolved_row.reason_code
550
+ );
551
+ END LOOP;
552
+ PERFORM complete_tenancy_backfill_receipt(
553
+ receipt_id,
554
+ (summary #>> '{connections,workspaceOwned}')::bigint
555
+ + (summary #>> '{connections,userOwned}')::bigint,
556
+ 0, 'completed'
557
+ );
558
+ summary := summary || pg_catalog.jsonb_build_object('receiptId', receipt_id);
559
+ END IF;
560
+
561
+ DELETE FROM opengeni_private.connection_tenancy_backfill_capabilities
562
+ WHERE backend_pid = pg_catalog.pg_backend_pid()
563
+ AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
564
+ AND account_id = p_account_id;
565
+ RETURN summary;
566
+ EXCEPTION WHEN OTHERS THEN
567
+ DELETE FROM opengeni_private.connection_tenancy_backfill_capabilities
568
+ WHERE backend_pid = pg_catalog.pg_backend_pid()
569
+ AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
570
+ AND account_id = p_account_id;
571
+ RAISE;
572
+ END
573
+ $classification$;
574
+ REVOKE ALL ON FUNCTION classify_organization_connection_authority(uuid, text) FROM PUBLIC;
575
+
576
+ -- Bounded, resumable, SKIP-LOCKED upgrader. It changes only legacy_user rows
577
+ -- whose exact subject has one live same-organization membership.
578
+ CREATE FUNCTION backfill_organization_connection_authority(
579
+ p_account_id uuid, p_limit integer DEFAULT 500, p_dry_run boolean DEFAULT true
580
+ ) RETURNS jsonb
581
+ LANGUAGE plpgsql SECURITY DEFINER
582
+ SET search_path FROM CURRENT
583
+ SET statement_timeout = '5min'
584
+ AS $backfill$
585
+ DECLARE
586
+ candidate_count integer := 0;
587
+ upgraded_count integer := 0;
588
+ BEGIN
589
+ IF p_account_id IS NULL THEN
590
+ RAISE EXCEPTION 'connection authority backfill requires an organization id'
591
+ USING ERRCODE = '22004';
592
+ END IF;
593
+ IF p_account_id IS DISTINCT FROM nullif(
594
+ pg_catalog.current_setting('opengeni.account_id', true), ''
595
+ )::uuid
596
+ THEN
597
+ RAISE EXCEPTION 'connection authority backfill scope mismatch' USING ERRCODE = '42501';
598
+ END IF;
599
+ IF p_limit IS NULL OR p_limit NOT BETWEEN 1 AND 5000 OR p_dry_run IS NULL THEN
600
+ RAISE EXCEPTION 'connection authority backfill request is invalid' USING ERRCODE = '22023';
601
+ END IF;
602
+ INSERT INTO opengeni_private.connection_tenancy_backfill_capabilities (
603
+ backend_pid, transaction_id, account_id
604
+ ) VALUES (pg_catalog.pg_backend_pid(), pg_catalog.pg_current_xact_id(), p_account_id)
605
+ ON CONFLICT DO NOTHING;
606
+
607
+ IF p_dry_run THEN
608
+ SELECT count(*)::int INTO candidate_count FROM (
609
+ SELECT connection_row.id
610
+ FROM connections connection_row
611
+ JOIN organization_memberships membership
612
+ ON membership.account_id = connection_row.account_id
613
+ AND membership.subject_id = connection_row.subject_id
614
+ AND membership.status = 'active' AND membership.revoked_at IS NULL
615
+ WHERE connection_row.account_id = p_account_id
616
+ AND connection_row.authority_scope = 'legacy_user'
617
+ ORDER BY connection_row.id LIMIT p_limit
618
+ ) candidate;
619
+ ELSE
620
+ WITH candidates AS MATERIALIZED (
621
+ SELECT connection_row.id, connection_row.account_id,
622
+ connection_row.origin_workspace_id, membership.id AS membership_id
623
+ FROM connections connection_row
624
+ JOIN organization_memberships membership
625
+ ON membership.account_id = connection_row.account_id
626
+ AND membership.subject_id = connection_row.subject_id
627
+ AND membership.status = 'active' AND membership.revoked_at IS NULL
628
+ WHERE connection_row.account_id = p_account_id
629
+ AND connection_row.authority_scope = 'legacy_user'
630
+ ORDER BY connection_row.id LIMIT p_limit
631
+ FOR UPDATE OF connection_row SKIP LOCKED
632
+ ), authorities AS (
633
+ INSERT INTO organization_user_resource_authorities (
634
+ id, account_id, organization_membership_id, resource_kind, resource_id,
635
+ origin_workspace_id, generation, status
636
+ )
637
+ SELECT pg_catalog.gen_random_uuid(), candidate.account_id, candidate.membership_id,
638
+ 'connection', candidate.id, candidate.origin_workspace_id, 1, 'active'
639
+ FROM candidates candidate
640
+ RETURNING id, resource_id, organization_membership_id
641
+ ), upgraded AS (
642
+ UPDATE connections connection_row SET authority_scope = 'user',
643
+ authority_id = authority.id,
644
+ owner_organization_membership_id = authority.organization_membership_id
645
+ FROM authorities authority
646
+ WHERE connection_row.id = authority.resource_id
647
+ AND connection_row.account_id = p_account_id
648
+ AND connection_row.authority_scope = 'legacy_user'
649
+ RETURNING connection_row.id
650
+ )
651
+ SELECT (SELECT count(*)::int FROM candidates),
652
+ (SELECT count(*)::int FROM upgraded)
653
+ INTO candidate_count, upgraded_count;
654
+ END IF;
655
+
656
+ DELETE FROM opengeni_private.connection_tenancy_backfill_capabilities
657
+ WHERE backend_pid = pg_catalog.pg_backend_pid()
658
+ AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
659
+ AND account_id = p_account_id;
660
+ RETURN pg_catalog.jsonb_build_object(
661
+ 'schemaVersion', 1, 'organizationId', p_account_id,
662
+ 'dryRun', p_dry_run, 'limit', p_limit,
663
+ 'candidates', candidate_count, 'upgraded', upgraded_count,
664
+ 'moreLikely', NOT p_dry_run AND candidate_count >= p_limit
665
+ );
666
+ EXCEPTION WHEN OTHERS THEN
667
+ DELETE FROM opengeni_private.connection_tenancy_backfill_capabilities
668
+ WHERE backend_pid = pg_catalog.pg_backend_pid()
669
+ AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
670
+ AND account_id = p_account_id;
671
+ RAISE;
672
+ END
673
+ $backfill$;
674
+ REVOKE ALL ON FUNCTION backfill_organization_connection_authority(uuid, integer, boolean)
675
+ FROM PUBLIC;
676
+
677
+ -- Once activated, surviving legacy_user rows are invisible to runtime reads.
678
+ -- This also fences an old worker that bypasses accepted-use resolution. The
679
+ -- audited inventory/parity/backfill capabilities retain recovery visibility.
680
+ --
681
+ -- Two constraints shape how this policy is created:
682
+ --
683
+ -- * `connections.authority_scope` arrives in 0256, and a partial-history
684
+ -- replay can legitimately defer that migration past this point (0249's
685
+ -- upgrade fixture does exactly that). Creating a policy over an absent
686
+ -- column aborts the whole chain there, so the creation is existence-guarded
687
+ -- exactly like 0298's parity capability policies. Such a database simply
688
+ -- cannot execute the connection lane, which is already true of every other
689
+ -- reader of that column.
690
+ -- * Every predicate a `connections` policy calls must live in
691
+ -- `opengeni_private` (`workspace_rls_visible` is the established example).
692
+ -- EXECUTE on the data-schema `session_tenancy_product_activated` is revoked
693
+ -- from PUBLIC and granted only to the runtime role, so calling it here
694
+ -- would make an unrelated SECURITY DEFINER owner of `connections` fail with
695
+ -- `42501` on an ordinary read. `opengeni_private.session_tenancy_account_activated`
696
+ -- is the byte-identical private predicate (same account-GUC fence, version 1).
697
+ DO $connection_tenancy_legacy_retirement$
698
+ DECLARE
699
+ data_schema text := pg_catalog.current_schema();
700
+ BEGIN
701
+ IF EXISTS (
702
+ SELECT 1 FROM pg_catalog.pg_attribute attribute
703
+ WHERE attribute.attrelid = pg_catalog.to_regclass(
704
+ pg_catalog.format('%I.%I', data_schema, 'connections')
705
+ )
706
+ AND attribute.attname = 'authority_scope'
707
+ AND attribute.attnum > 0
708
+ AND NOT attribute.attisdropped
709
+ ) THEN
710
+ EXECUTE pg_catalog.format(
711
+ 'CREATE POLICY connection_tenancy_legacy_retirement ON %I.connections '
712
+ || 'AS RESTRICTIVE FOR SELECT USING ('
713
+ || 'authority_scope <> ''legacy_user'''
714
+ || ' OR NOT opengeni_private.session_tenancy_account_activated(account_id)'
715
+ || ' OR opengeni_private.connection_tenancy_backfill_capability_active(account_id)'
716
+ || ' OR opengeni_private.organization_tenancy_inventory_capability_active()'
717
+ || ' OR opengeni_private.organization_tenancy_parity_capability_active())',
718
+ data_schema
719
+ );
720
+ END IF;
721
+ END
722
+ $connection_tenancy_legacy_retirement$;
723
+
724
+ -- Pre-0277 binaries/turns can omit attribution and land on the explicit
725
+ -- legacy default before activation. Afterwards a new legacy admission or
726
+ -- retained process is rejected instead of reopening the observation lane.
727
+ CREATE FUNCTION opengeni_private.guard_activated_tenancy_writer()
728
+ RETURNS trigger
729
+ LANGUAGE plpgsql SECURITY DEFINER
730
+ SET search_path FROM CURRENT
731
+ AS $writer_fence$
732
+ BEGIN
733
+ IF NEW.initiator_kind = 'legacy_unattributed'
734
+ AND opengeni_private.session_tenancy_account_activated(NEW.account_id)
735
+ THEN
736
+ RAISE EXCEPTION 'activated organization refuses unattributed workspace writers'
737
+ USING ERRCODE = '42501';
738
+ END IF;
739
+ RETURN NEW;
740
+ END
741
+ $writer_fence$;
742
+ REVOKE ALL ON FUNCTION opengeni_private.guard_activated_tenancy_writer() FROM PUBLIC;
743
+
744
+ CREATE TRIGGER sandbox_workspace_mutation_admissions_tenancy_writer_fence
745
+ BEFORE INSERT ON sandbox_workspace_mutation_admissions
746
+ FOR EACH ROW EXECUTE FUNCTION opengeni_private.guard_activated_tenancy_writer();
747
+ CREATE TRIGGER sandbox_retained_processes_tenancy_writer_fence
748
+ BEFORE INSERT ON sandbox_retained_processes
749
+ FOR EACH ROW EXECUTE FUNCTION opengeni_private.guard_activated_tenancy_writer();
750
+
751
+ -- Preserve the reviewed 0298 report and replace only its known undercount:
752
+ -- stated session authority is an ordinary workspace-membership row OR the
753
+ -- active membership's own personal_workspace_id pointer (0302).
754
+ ALTER FUNCTION check_organization_tenancy_parity(uuid, integer, integer)
755
+ RENAME TO check_organization_tenancy_parity_pre_0340;
756
+ ALTER FUNCTION check_organization_tenancy_parity_pre_0340(uuid, integer, integer)
757
+ SET SCHEMA opengeni_private;
758
+ REVOKE ALL ON FUNCTION
759
+ opengeni_private.check_organization_tenancy_parity_pre_0340(uuid, integer, integer)
760
+ FROM PUBLIC;
761
+
762
+ CREATE FUNCTION check_organization_tenancy_parity(
763
+ p_organization_id uuid,
764
+ p_evidence_limit integer DEFAULT 10,
765
+ p_observation_window_days integer DEFAULT 30
766
+ ) RETURNS jsonb
767
+ LANGUAGE plpgsql SECURITY DEFINER
768
+ SET search_path FROM CURRENT
769
+ SET statement_timeout = '60s'
770
+ AS $parity$
771
+ DECLARE
772
+ result jsonb;
773
+ attributable_count integer;
774
+ BEGIN
775
+ result := opengeni_private.check_organization_tenancy_parity_pre_0340(
776
+ p_organization_id, p_evidence_limit, p_observation_window_days
777
+ );
778
+ INSERT INTO opengeni_private.organization_tenancy_parity_capabilities (
779
+ backend_pid, transaction_id
780
+ ) VALUES (pg_catalog.pg_backend_pid(), pg_catalog.pg_current_xact_id())
781
+ ON CONFLICT DO NOTHING;
782
+ SELECT count(*)::int INTO attributable_count
783
+ FROM sessions session_row
784
+ WHERE session_row.account_id = p_organization_id
785
+ AND session_row.owner_organization_membership_id IS NULL
786
+ AND session_row.created_by_kind = 'subject'
787
+ AND session_row.created_by_subject_id IS NOT NULL
788
+ AND EXISTS (
789
+ SELECT 1 FROM organization_memberships membership
790
+ WHERE membership.account_id = p_organization_id
791
+ AND membership.subject_id = session_row.created_by_subject_id
792
+ AND membership.status = 'active' AND membership.revoked_at IS NULL
793
+ AND (
794
+ membership.personal_workspace_id = session_row.workspace_id
795
+ OR EXISTS (
796
+ SELECT 1 FROM workspace_memberships workspace_membership
797
+ WHERE workspace_membership.account_id = p_organization_id
798
+ AND workspace_membership.workspace_id = session_row.workspace_id
799
+ AND workspace_membership.subject_id = session_row.created_by_subject_id
800
+ )
801
+ )
802
+ );
803
+ DELETE FROM opengeni_private.organization_tenancy_parity_capabilities
804
+ WHERE backend_pid = pg_catalog.pg_backend_pid()
805
+ AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned();
806
+ RETURN pg_catalog.jsonb_set(
807
+ result, '{lanes,sessionsAttributableButUnattributed}',
808
+ pg_catalog.to_jsonb(attributable_count), false
809
+ );
810
+ EXCEPTION WHEN OTHERS THEN
811
+ DELETE FROM opengeni_private.organization_tenancy_parity_capabilities
812
+ WHERE backend_pid = pg_catalog.pg_backend_pid()
813
+ AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned();
814
+ RAISE;
815
+ END
816
+ $parity$;
817
+ REVOKE ALL ON FUNCTION check_organization_tenancy_parity(uuid, integer, integer)
818
+ FROM PUBLIC;
819
+
820
+ -- Same sorted UTF-8/C-locale canonical JSON algorithm as the operator command.
821
+ CREATE FUNCTION opengeni_private.tenancy_activation_canonical_json(value jsonb)
822
+ RETURNS text
823
+ LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE
824
+ SET search_path = pg_catalog, opengeni_private
825
+ AS $canonical_json$
826
+ DECLARE
827
+ rendered text;
828
+ BEGIN
829
+ CASE pg_catalog.jsonb_typeof(value)
830
+ WHEN 'object' THEN
831
+ SELECT coalesce(
832
+ '{' || pg_catalog.string_agg(
833
+ pg_catalog.to_jsonb(entry.key)::text || ':' ||
834
+ opengeni_private.tenancy_activation_canonical_json(entry.value),
835
+ ',' ORDER BY entry.key COLLATE "C"
836
+ ) || '}', '{}'
837
+ ) INTO rendered
838
+ FROM pg_catalog.jsonb_each(value) AS entry(key, value);
839
+ RETURN rendered;
840
+ WHEN 'array' THEN
841
+ SELECT coalesce(
842
+ '[' || pg_catalog.string_agg(
843
+ opengeni_private.tenancy_activation_canonical_json(entry.value),
844
+ ',' ORDER BY entry.ordinality
845
+ ) || ']', '[]'
846
+ ) INTO rendered
847
+ FROM pg_catalog.jsonb_array_elements(value)
848
+ WITH ORDINALITY AS entry(value, ordinality);
849
+ RETURN rendered;
850
+ ELSE
851
+ RETURN value::text;
852
+ END CASE;
853
+ END
854
+ $canonical_json$;
855
+ REVOKE ALL ON FUNCTION opengeni_private.tenancy_activation_canonical_json(jsonb) FROM PUBLIC;
856
+
857
+ -- The newest receipt per family is authoritative. Full-population classifiers
858
+ -- must settle; connection/resource families must carry zero unresolved rows.
859
+ CREATE OR REPLACE FUNCTION check_tenancy_backfill_activation_evidence(
860
+ p_account_id uuid
861
+ ) RETURNS jsonb
862
+ LANGUAGE plpgsql SECURITY DEFINER
863
+ SET search_path FROM CURRENT
864
+ SET statement_timeout = '5min'
865
+ AS $evidence$
866
+ DECLARE
867
+ previous_lifecycle text := pg_catalog.current_setting(
868
+ 'opengeni.organization_tenancy_lifecycle', true
869
+ );
870
+ resource_report jsonb;
871
+ session_report jsonb;
872
+ connection_report jsonb;
873
+ result jsonb;
874
+ BEGIN
875
+ IF p_account_id IS NULL THEN
876
+ RAISE EXCEPTION 'tenancy backfill activation evidence requires an organization id'
877
+ USING ERRCODE = '22004';
878
+ END IF;
879
+ IF p_account_id IS DISTINCT FROM nullif(
880
+ pg_catalog.current_setting('opengeni.account_id', true), ''
881
+ )::uuid
882
+ THEN
883
+ RAISE EXCEPTION 'tenancy backfill activation evidence scope mismatch'
884
+ USING ERRCODE = '42501';
885
+ END IF;
886
+ resource_report := verify_organization_resource_classification(p_account_id, NULL);
887
+ session_report := classify_organization_session_ownership(p_account_id, NULL);
888
+ connection_report := classify_organization_connection_authority(p_account_id, NULL);
889
+ PERFORM pg_catalog.set_config(
890
+ 'opengeni.organization_tenancy_lifecycle', 'tenancy_backfill_ledger', true
891
+ );
892
+
893
+ WITH required(resource_family, expected_total, unresolved_must_be_zero, family_order) AS (
894
+ VALUES
895
+ ('organization_memberships'::text, NULL::bigint, false, 1),
896
+ ('sessions'::text, (session_report #>> '{sessions,total}')::bigint, false, 2),
897
+ ('variable_sets'::text,
898
+ (resource_report #>> '{families,variable_sets,total}')::bigint, true, 3),
899
+ ('rigs'::text, (resource_report #>> '{families,rigs,total}')::bigint, true, 4),
900
+ ('machines'::text, (resource_report #>> '{families,machines,total}')::bigint, true, 5),
901
+ ('connections'::text,
902
+ (connection_report #>> '{connections,total}')::bigint, true, 6)
903
+ ), latest_receipt AS (
904
+ SELECT DISTINCT ON (receipt.resource_family)
905
+ receipt.resource_family, receipt.id, receipt.run_key, receipt.status,
906
+ receipt.classified_count, receipt.skipped_count, receipt.unresolved_count,
907
+ receipt.started_at
908
+ FROM tenancy_backfill_receipts receipt
909
+ WHERE receipt.account_id = p_account_id
910
+ AND receipt.resource_family IN (SELECT resource_family FROM required)
911
+ ORDER BY receipt.resource_family, receipt.started_at DESC, receipt.id DESC
912
+ ), evaluated AS (
913
+ SELECT required.*,
914
+ latest_receipt.id AS receipt_id, latest_receipt.run_key,
915
+ latest_receipt.status, latest_receipt.classified_count,
916
+ latest_receipt.skipped_count, latest_receipt.unresolved_count,
917
+ CASE
918
+ WHEN latest_receipt.id IS NULL THEN 'missing_receipt'
919
+ WHEN latest_receipt.status <> 'completed' THEN 'receipt_not_completed'
920
+ WHEN required.expected_total IS NOT NULL
921
+ AND latest_receipt.classified_count + latest_receipt.skipped_count
922
+ + latest_receipt.unresolved_count <> required.expected_total
923
+ THEN 'population_mismatch'
924
+ WHEN required.unresolved_must_be_zero AND latest_receipt.unresolved_count <> 0
925
+ THEN 'unresolved_rows'
926
+ ELSE NULL
927
+ END AS blocker
928
+ FROM required LEFT JOIN latest_receipt USING (resource_family)
929
+ )
930
+ SELECT pg_catalog.jsonb_build_object(
931
+ 'schemaVersion', 1, 'organizationId', p_account_id,
932
+ 'ready', pg_catalog.bool_and(blocker IS NULL),
933
+ 'receiptIds', coalesce(
934
+ pg_catalog.jsonb_agg(receipt_id ORDER BY family_order)
935
+ FILTER (WHERE receipt_id IS NOT NULL), '[]'::jsonb
936
+ ),
937
+ 'blockers', coalesce(
938
+ pg_catalog.jsonb_agg(pg_catalog.jsonb_build_object(
939
+ 'resourceFamily', resource_family, 'code', blocker
940
+ ) ORDER BY family_order) FILTER (WHERE blocker IS NOT NULL), '[]'::jsonb
941
+ ),
942
+ 'families', pg_catalog.jsonb_object_agg(
943
+ resource_family, pg_catalog.jsonb_build_object(
944
+ 'receiptId', receipt_id, 'runKey', run_key, 'status', status,
945
+ 'classifiedCount', classified_count, 'skippedCount', skipped_count,
946
+ 'unresolvedCount', unresolved_count, 'expectedTotal', expected_total,
947
+ 'blocker', blocker
948
+ ) ORDER BY family_order
949
+ )
950
+ ) INTO result FROM evaluated;
951
+
952
+ PERFORM pg_catalog.set_config(
953
+ 'opengeni.organization_tenancy_lifecycle', coalesce(previous_lifecycle, ''), true
954
+ );
955
+ RETURN result;
956
+ EXCEPTION WHEN OTHERS THEN
957
+ PERFORM pg_catalog.set_config(
958
+ 'opengeni.organization_tenancy_lifecycle', coalesce(previous_lifecycle, ''), true
959
+ );
960
+ RAISE;
961
+ END
962
+ $evidence$;
963
+ REVOKE ALL ON FUNCTION check_tenancy_backfill_activation_evidence(uuid) FROM PUBLIC;
964
+
965
+ -- This deployment-wide transaction fence is the serialization boundary between
966
+ -- the first irreversible operator activation and later greenfield organization
967
+ -- provisioning. A provisioning transaction must acquire it before observing
968
+ -- session_tenancy_any_product_activation(): if it wins, it commits before the
969
+ -- boundary; if activation wins, it observes the committed boundary and can
970
+ -- create its organization-scoped receipt atomically. Keep the key and signature
971
+ -- stable for that forward lane. The function is owner-only so an application
972
+ -- session cannot hold the deployment boundary open.
973
+ CREATE FUNCTION lock_session_tenancy_activation_boundary()
974
+ RETURNS void
975
+ LANGUAGE sql VOLATILE
976
+ SET search_path FROM CURRENT
977
+ AS $boundary$
978
+ SELECT pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(
979
+ 'session-tenancy-canonical-boundary:v1', 0
980
+ ))
981
+ $boundary$;
982
+ REVOKE ALL ON FUNCTION lock_session_tenancy_activation_boundary() FROM PUBLIC;
983
+ COMMENT ON FUNCTION lock_session_tenancy_activation_boundary() IS
984
+ 'Owner-only deployment boundary fence shared by operator activation and atomic greenfield activation.';
985
+
986
+ -- Migration 0303 gave `session_tenancy_activations` FORCE ROW LEVEL SECURITY
987
+ -- and a `FOR SELECT`-only policy, and no INSERT policy at all. Under the
988
+ -- documented production posture - a NON-superuser owner without BYPASSRLS -
989
+ -- the activation's own receipt write was therefore denied `42501` AFTER every
990
+ -- gate had already passed, which made the whole cutover unexecutable. The
991
+ -- runtime role holds only `GRANT SELECT` on this table and is not the owner, so
992
+ -- an owner-only marker policy re-opens exactly the one command
993
+ -- `activate_session_tenancy_product` issues and nothing else. INSERT is the
994
+ -- complete write set: the table is append-only, has no UPDATE or DELETE writer
995
+ -- anywhere in the tree, and activation is one-way with no rollback path.
996
+ DO $session_tenancy_activation_receipt_policy$
997
+ DECLARE
998
+ data_schema text := pg_catalog.current_schema();
999
+ migration_owner text := current_user;
1000
+ BEGIN
1001
+ EXECUTE pg_catalog.format(
1002
+ 'CREATE POLICY session_tenancy_activation_receipt_insert '
1003
+ || 'ON %I.session_tenancy_activations FOR INSERT WITH CHECK ('
1004
+ || 'current_user = %L AND '
1005
+ || 'current_setting(''opengeni.organization_tenancy_lifecycle'', true) '
1006
+ || '= ''session_tenancy_activation'' AND account_id = nullif('
1007
+ || 'current_setting(''opengeni.account_id'', true), '''')::uuid)',
1008
+ data_schema, migration_owner
1009
+ );
1010
+ END
1011
+ $session_tenancy_activation_receipt_policy$;
1012
+
1013
+ -- Existing receipts replay before new evidence requirements. A new activation
1014
+ -- takes the organization lifecycle advisory prefix, verifies the app drain,
1015
+ -- write-locks every report source, and only then takes the deployment boundary.
1016
+ -- Under those locks it recomputes all reports, validates the exact parity
1017
+ -- catalogs, compares canonical digests, and stores six receipt ids.
1018
+ CREATE OR REPLACE FUNCTION activate_session_tenancy_product(
1019
+ p_account_id uuid,
1020
+ p_inventory_digest text,
1021
+ p_parity_digest text,
1022
+ p_activated_by text,
1023
+ p_application_roles text[]
1024
+ ) RETURNS TABLE (
1025
+ account_id uuid, activation_version integer,
1026
+ activated_at timestamptz, replay boolean
1027
+ )
1028
+ LANGUAGE plpgsql SECURITY DEFINER
1029
+ SET search_path FROM CURRENT
1030
+ AS $activation$
1031
+ DECLARE
1032
+ existing session_tenancy_activations%ROWTYPE;
1033
+ inserted session_tenancy_activations%ROWTYPE;
1034
+ previous_lifecycle text := pg_catalog.current_setting(
1035
+ 'opengeni.organization_tenancy_lifecycle', true
1036
+ );
1037
+ inventory_report jsonb;
1038
+ parity_report jsonb;
1039
+ backfill_evidence jsonb;
1040
+ computed_inventory_digest text;
1041
+ computed_parity_digest text;
1042
+ evidence_receipt_ids uuid[];
1043
+ required_gate text;
1044
+ required_lane text;
1045
+ required_gates constant text[] := ARRAY[
1046
+ 'membership_personal_workspace_pointer',
1047
+ 'membership_personal_workspace_exclusive',
1048
+ 'membership_personal_workspace_same_organization',
1049
+ 'personal_workspace_has_no_membership_row',
1050
+ 'authority_resource_single_owner',
1051
+ 'grant_delegation_fence_complete',
1052
+ 'grant_owner_membership_active',
1053
+ 'grant_authority_live',
1054
+ 'grant_session_fence_not_ahead',
1055
+ 'session_owner_provenance_paired',
1056
+ 'session_owner_subject_matches_membership',
1057
+ 'session_owner_membership_same_organization',
1058
+ 'login_binding_dispute_propagated',
1059
+ 'identity_active_binding_owned',
1060
+ 'user_scoped_resource_live_anchor'
1061
+ ];
1062
+ required_lanes constant text[] := ARRAY[
1063
+ 'connectionsLegacyUser',
1064
+ 'workspaceWriterAdmissionsLegacyUnattributedInWindow',
1065
+ 'workspaceWriterProcessesLegacyUnattributedInWindow',
1066
+ 'documentsLegacyPersonalNullAuthority',
1067
+ 'codexCredentialsUnattributedConnector',
1068
+ 'workspaceMemberSubjectsWithoutMembershipAnchor',
1069
+ 'sessionsAttributableButUnattributed',
1070
+ 'connectionUseLegacyResolutionsInWindow'
1071
+ ];
1072
+ BEGIN
1073
+ IF p_account_id IS NULL
1074
+ OR p_inventory_digest !~ '^[0-9a-f]{64}$'
1075
+ OR p_parity_digest !~ '^[0-9a-f]{64}$'
1076
+ OR pg_catalog.octet_length(pg_catalog.btrim(p_activated_by)) NOT BETWEEN 1 AND 256
1077
+ OR p_application_roles IS NULL
1078
+ OR pg_catalog.cardinality(p_application_roles) NOT BETWEEN 1 AND 16
1079
+ OR EXISTS (
1080
+ SELECT 1 FROM pg_catalog.unnest(p_application_roles) role_name
1081
+ WHERE role_name IS NULL OR role_name <> pg_catalog.btrim(role_name)
1082
+ OR pg_catalog.octet_length(role_name) NOT BETWEEN 1 AND 63
1083
+ )
1084
+ OR pg_catalog.cardinality(ARRAY(
1085
+ SELECT DISTINCT role_name FROM pg_catalog.unnest(p_application_roles) role_name
1086
+ )) <> pg_catalog.cardinality(p_application_roles)
1087
+ THEN
1088
+ RAISE EXCEPTION 'session tenancy activation request is invalid'
1089
+ USING ERRCODE = '22023';
1090
+ END IF;
1091
+ IF EXISTS (
1092
+ SELECT 1 FROM pg_catalog.pg_stat_activity activity
1093
+ WHERE activity.datname = pg_catalog.current_database()
1094
+ AND activity.usename = ANY(p_application_roles)
1095
+ AND activity.pid <> pg_catalog.pg_backend_pid()
1096
+ ) THEN
1097
+ RAISE EXCEPTION 'session tenancy activation requires every application role session to be stopped'
1098
+ USING ERRCODE = '55000';
1099
+ END IF;
1100
+
1101
+ PERFORM pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(
1102
+ 'organization-membership:' || p_account_id::text, 0
1103
+ ));
1104
+ LOCK TABLE
1105
+ workspaces, workspace_memberships, organization_memberships,
1106
+ canonical_human_identities, canonical_human_identity_subjects,
1107
+ canonical_human_login_bindings, sessions, session_turns,
1108
+ session_turn_attempts, session_attempt_interruptions,
1109
+ session_system_updates, session_human_input_requests,
1110
+ session_pending_tool_calls, agent_run_states, session_goals,
1111
+ codex_capacity_waiters, xai_capacity_waiters, session_realtime_modes,
1112
+ session_realtime_connections, scheduled_tasks,
1113
+ workspace_variable_sets, rigs, enrollments, connections, documents,
1114
+ codex_subscription_credentials, sandbox_workspace_mutation_admissions,
1115
+ sandbox_retained_processes, sandbox_lease_holders,
1116
+ organization_user_resource_authorities, organization_user_resource_grants,
1117
+ connection_use_audit_facts, tenancy_backfill_receipts,
1118
+ tenancy_backfill_unresolved_rows, session_tenancy_activations
1119
+ IN ACCESS EXCLUSIVE MODE;
1120
+
1121
+ IF EXISTS (
1122
+ SELECT 1 FROM pg_catalog.pg_stat_activity activity
1123
+ WHERE activity.datname = pg_catalog.current_database()
1124
+ AND activity.usename = ANY(p_application_roles)
1125
+ AND activity.pid <> pg_catalog.pg_backend_pid()
1126
+ ) THEN
1127
+ RAISE EXCEPTION 'session tenancy activation requires every application role session to be stopped'
1128
+ USING ERRCODE = '55000';
1129
+ END IF;
1130
+
1131
+ -- Lock every provisioning source before the shared deployment fence. The
1132
+ -- greenfield path writes its graph first and takes the fence last; reversing
1133
+ -- this order would deadlock RowExclusive -> boundary against boundary ->
1134
+ -- AccessExclusive during the first cutover.
1135
+ PERFORM lock_session_tenancy_activation_boundary();
1136
+ PERFORM pg_catalog.set_config('opengeni.account_id', p_account_id::text, true);
1137
+ SELECT * INTO existing FROM session_tenancy_activations activation
1138
+ WHERE activation.account_id = p_account_id;
1139
+ IF FOUND THEN
1140
+ IF existing.inventory_digest <> p_inventory_digest
1141
+ OR existing.parity_digest <> p_parity_digest
1142
+ THEN
1143
+ RAISE EXCEPTION 'session tenancy activation evidence conflicts with the durable receipt'
1144
+ USING ERRCODE = '23505';
1145
+ END IF;
1146
+ account_id := existing.account_id;
1147
+ activation_version := existing.activation_version;
1148
+ activated_at := existing.activated_at;
1149
+ replay := true;
1150
+ RETURN NEXT;
1151
+ RETURN;
1152
+ END IF;
1153
+
1154
+ inventory_report := inventory_organization_tenancy(p_account_id);
1155
+ parity_report := check_organization_tenancy_parity(p_account_id, 10, 30);
1156
+ backfill_evidence := check_tenancy_backfill_activation_evidence(p_account_id);
1157
+ IF inventory_report #>> '{schemaVersion}' <> '2'
1158
+ OR inventory_report ->> 'organizationId' <> p_account_id::text
1159
+ OR parity_report #>> '{schemaVersion}' <> '1'
1160
+ OR parity_report ->> 'organizationId' <> p_account_id::text
1161
+ OR pg_catalog.jsonb_typeof(parity_report -> 'gates') <> 'object'
1162
+ OR pg_catalog.jsonb_typeof(parity_report -> 'lanes') <> 'object'
1163
+ THEN
1164
+ RAISE EXCEPTION 'session tenancy activation evidence is structurally invalid'
1165
+ USING ERRCODE = '55000';
1166
+ END IF;
1167
+
1168
+ FOREACH required_gate IN ARRAY required_gates LOOP
1169
+ IF NOT (parity_report -> 'gates' ? required_gate)
1170
+ OR pg_catalog.jsonb_typeof(parity_report -> 'gates' -> required_gate -> 'violations')
1171
+ <> 'number'
1172
+ OR (parity_report #>> ARRAY['gates', required_gate, 'violations'])::bigint <> 0
1173
+ THEN
1174
+ RAISE EXCEPTION 'session tenancy activation parity gate is not clean: %', required_gate
1175
+ USING ERRCODE = '55000';
1176
+ END IF;
1177
+ END LOOP;
1178
+ IF EXISTS (
1179
+ SELECT 1 FROM pg_catalog.jsonb_each(parity_report -> 'gates') gate
1180
+ WHERE pg_catalog.jsonb_typeof(gate.value -> 'violations') <> 'number'
1181
+ OR (gate.value ->> 'violations')::bigint <> 0
1182
+ ) THEN
1183
+ RAISE EXCEPTION 'session tenancy activation parity has a non-zero gate'
1184
+ USING ERRCODE = '55000';
1185
+ END IF;
1186
+ FOREACH required_lane IN ARRAY required_lanes LOOP
1187
+ IF NOT (parity_report -> 'lanes' ? required_lane)
1188
+ OR pg_catalog.jsonb_typeof(parity_report -> 'lanes' -> required_lane) <> 'number'
1189
+ OR (parity_report #>> ARRAY['lanes', required_lane])::bigint <> 0
1190
+ THEN
1191
+ RAISE EXCEPTION 'session tenancy activation compatibility lane is not drained: %',
1192
+ required_lane USING ERRCODE = '55000';
1193
+ END IF;
1194
+ END LOOP;
1195
+ IF EXISTS (
1196
+ SELECT 1 FROM pg_catalog.jsonb_each(parity_report -> 'lanes') lane
1197
+ WHERE pg_catalog.jsonb_typeof(lane.value) <> 'number'
1198
+ OR (lane.value #>> '{}')::bigint <> 0
1199
+ ) THEN
1200
+ RAISE EXCEPTION 'session tenancy activation has an undrained compatibility lane'
1201
+ USING ERRCODE = '55000';
1202
+ END IF;
1203
+ IF coalesce((backfill_evidence ->> 'ready')::boolean, false) IS NOT TRUE THEN
1204
+ RAISE EXCEPTION 'session tenancy activation requires settled backfill evidence'
1205
+ USING ERRCODE = '55000', DETAIL = (backfill_evidence -> 'blockers')::text;
1206
+ END IF;
1207
+
1208
+ computed_inventory_digest := pg_catalog.encode(public.digest(pg_catalog.convert_to(
1209
+ opengeni_private.tenancy_activation_canonical_json(inventory_report), 'UTF8'
1210
+ ), 'sha256'), 'hex');
1211
+ computed_parity_digest := pg_catalog.encode(public.digest(pg_catalog.convert_to(
1212
+ opengeni_private.tenancy_activation_canonical_json(parity_report), 'UTF8'
1213
+ ), 'sha256'), 'hex');
1214
+ IF computed_inventory_digest <> p_inventory_digest
1215
+ OR computed_parity_digest <> p_parity_digest
1216
+ THEN
1217
+ RAISE EXCEPTION 'session tenancy activation evidence changed before the cutover lock'
1218
+ USING ERRCODE = '40001';
1219
+ END IF;
1220
+
1221
+ SELECT pg_catalog.array_agg(receipt_id::uuid ORDER BY ordinal)
1222
+ INTO evidence_receipt_ids
1223
+ FROM pg_catalog.jsonb_array_elements_text(backfill_evidence -> 'receiptIds')
1224
+ WITH ORDINALITY AS evidence(receipt_id, ordinal);
1225
+ IF pg_catalog.cardinality(evidence_receipt_ids) <> 6 THEN
1226
+ RAISE EXCEPTION 'session tenancy activation backfill evidence is structurally invalid'
1227
+ USING ERRCODE = '55000';
1228
+ END IF;
1229
+ -- The narrow marker window for the one append this function performs. The
1230
+ -- `RETURNING` read is covered by 0303's existing organization-isolation
1231
+ -- SELECT policy, whose account GUC this function already set above.
1232
+ BEGIN
1233
+ PERFORM pg_catalog.set_config(
1234
+ 'opengeni.organization_tenancy_lifecycle', 'session_tenancy_activation', true
1235
+ );
1236
+ INSERT INTO session_tenancy_activations (
1237
+ account_id, activation_version, inventory_digest, parity_digest,
1238
+ activated_by, backfill_receipt_ids
1239
+ ) VALUES (
1240
+ p_account_id, 1, p_inventory_digest, p_parity_digest,
1241
+ pg_catalog.btrim(p_activated_by), evidence_receipt_ids
1242
+ ) RETURNING * INTO inserted;
1243
+ PERFORM pg_catalog.set_config(
1244
+ 'opengeni.organization_tenancy_lifecycle', coalesce(previous_lifecycle, ''), true
1245
+ );
1246
+ EXCEPTION WHEN OTHERS THEN
1247
+ PERFORM pg_catalog.set_config(
1248
+ 'opengeni.organization_tenancy_lifecycle', coalesce(previous_lifecycle, ''), true
1249
+ );
1250
+ RAISE;
1251
+ END;
1252
+ account_id := inserted.account_id;
1253
+ activation_version := inserted.activation_version;
1254
+ activated_at := inserted.activated_at;
1255
+ replay := false;
1256
+ RETURN NEXT;
1257
+ END
1258
+ $activation$;
1259
+ REVOKE ALL ON FUNCTION
1260
+ activate_session_tenancy_product(uuid, text, text, text, text[]) FROM PUBLIC;
1261
+
1262
+ DO $posture$
1263
+ DECLARE
1264
+ data_schema text := pg_catalog.current_schema();
1265
+ BEGIN
1266
+ EXECUTE pg_catalog.format(
1267
+ 'ALTER FUNCTION opengeni_private.session_tenancy_account_activated(uuid) '
1268
+ || 'SET search_path = pg_catalog, %I, opengeni_private, pg_temp', data_schema
1269
+ );
1270
+ EXECUTE pg_catalog.format(
1271
+ 'ALTER FUNCTION opengeni_private.bind_connection_owner_authority(uuid,text,uuid,uuid) '
1272
+ || 'SET search_path = pg_catalog, %I, opengeni_private, pg_temp', data_schema
1273
+ );
1274
+ EXECUTE pg_catalog.format(
1275
+ 'ALTER FUNCTION opengeni_private.bind_connection_authority() '
1276
+ || 'SET search_path = pg_catalog, %I, opengeni_private, pg_temp', data_schema
1277
+ );
1278
+ EXECUTE pg_catalog.format(
1279
+ 'ALTER FUNCTION %I.classify_organization_connection_authority(uuid,text) '
1280
+ || 'SET search_path = pg_catalog, %I, opengeni_private, pg_temp',
1281
+ data_schema, data_schema
1282
+ );
1283
+ EXECUTE pg_catalog.format(
1284
+ 'ALTER FUNCTION %I.backfill_organization_connection_authority(uuid,integer,boolean) '
1285
+ || 'SET search_path = pg_catalog, %I, opengeni_private, pg_temp',
1286
+ data_schema, data_schema
1287
+ );
1288
+ EXECUTE pg_catalog.format(
1289
+ 'ALTER FUNCTION opengeni_private.guard_activated_tenancy_writer() '
1290
+ || 'SET search_path = pg_catalog, %I, opengeni_private, pg_temp', data_schema
1291
+ );
1292
+ EXECUTE pg_catalog.format(
1293
+ 'ALTER FUNCTION %I.check_organization_tenancy_parity(uuid,integer,integer) '
1294
+ || 'SET search_path = pg_catalog, %I, opengeni_private, pg_temp',
1295
+ data_schema, data_schema
1296
+ );
1297
+ EXECUTE pg_catalog.format(
1298
+ 'ALTER FUNCTION %I.check_tenancy_backfill_activation_evidence(uuid) '
1299
+ || 'SET search_path = pg_catalog, %I, opengeni_private, pg_temp',
1300
+ data_schema, data_schema
1301
+ );
1302
+ EXECUTE pg_catalog.format(
1303
+ 'ALTER FUNCTION %I.lock_session_tenancy_activation_boundary() '
1304
+ || 'SET search_path = pg_catalog, %I, pg_temp',
1305
+ data_schema, data_schema
1306
+ );
1307
+ EXECUTE pg_catalog.format(
1308
+ 'ALTER FUNCTION %I.activate_session_tenancy_product(uuid,text,text,text,text[]) '
1309
+ || 'SET search_path = pg_catalog, %I, opengeni_private, public, pg_temp',
1310
+ data_schema, data_schema
1311
+ );
1312
+
1313
+ IF EXISTS (SELECT 1 FROM pg_catalog.pg_roles WHERE rolname = 'opengeni_app') THEN
1314
+ GRANT EXECUTE ON FUNCTION
1315
+ opengeni_private.connection_tenancy_backfill_capability_active(uuid)
1316
+ TO opengeni_app;
1317
+ -- Referenced by the RESTRICTIVE `connections` retirement policy, so the
1318
+ -- runtime role must be able to evaluate it on every ordinary read even in
1319
+ -- the migrate-without-reprovision order.
1320
+ GRANT EXECUTE ON FUNCTION
1321
+ opengeni_private.session_tenancy_account_activated(uuid) TO opengeni_app;
1322
+ GRANT EXECUTE ON FUNCTION
1323
+ opengeni_private.bind_connection_owner_authority(uuid, text, uuid, uuid)
1324
+ TO opengeni_app;
1325
+ EXECUTE pg_catalog.format(
1326
+ 'GRANT EXECUTE ON FUNCTION %I.classify_organization_connection_authority(uuid,text) TO opengeni_app',
1327
+ data_schema
1328
+ );
1329
+ EXECUTE pg_catalog.format(
1330
+ 'GRANT EXECUTE ON FUNCTION %I.backfill_organization_connection_authority(uuid,integer,boolean) TO opengeni_app',
1331
+ data_schema
1332
+ );
1333
+ EXECUTE pg_catalog.format(
1334
+ 'GRANT EXECUTE ON FUNCTION %I.check_organization_tenancy_parity(uuid,integer,integer) TO opengeni_app',
1335
+ data_schema
1336
+ );
1337
+ REVOKE ALL ON FUNCTION
1338
+ opengeni_private.check_organization_tenancy_parity_pre_0340(uuid,integer,integer)
1339
+ FROM opengeni_app;
1340
+ END IF;
1341
+ END
1342
+ $posture$;
1343
+
1344
+ COMMENT ON FUNCTION classify_organization_connection_authority(uuid, text) IS
1345
+ 'Full-population connection authority classifier and migration-0300 receipt writer. Deterministic legacy_user repairs remain unresolved until the bounded upgrader converges them.';
1346
+ COMMENT ON FUNCTION backfill_organization_connection_authority(uuid, integer, boolean) IS
1347
+ 'Bounded SKIP-LOCKED legacy_user upgrader using only the connection subject and one exact live same-organization membership; origin workspace and current access are never ownership evidence.';