@opengeni/db 0.36.1 → 3.1.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 (274) 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-GYO7ED2Q.js → chunk-EULAN2NP.js} +2 -2
  7. package/dist/{chunk-VTPRIXKG.js → chunk-NJVZPWXY.js} +3 -3
  8. package/dist/{chunk-HSOWSYOX.js → chunk-NMUBV7Z5.js} +19 -11
  9. package/dist/chunk-NMUBV7Z5.js.map +1 -0
  10. package/dist/chunk-OF5OBB76.js +4457 -0
  11. package/dist/chunk-OF5OBB76.js.map +1 -0
  12. package/dist/{chunk-5KTIEDX7.js → chunk-PR2WMLOQ.js} +7714 -4443
  13. package/dist/chunk-PR2WMLOQ.js.map +1 -0
  14. package/dist/{chunk-Z7VYMNPZ.js → chunk-SCPUJS53.js} +41 -4
  15. package/dist/chunk-SCPUJS53.js.map +1 -0
  16. package/dist/{chunk-SK7YYHBI.js → chunk-SIBYBA2D.js} +46 -1
  17. package/dist/chunk-SIBYBA2D.js.map +1 -0
  18. package/dist/chunk-TIKF3KTI.js +383 -0
  19. package/dist/chunk-TIKF3KTI.js.map +1 -0
  20. package/dist/chunk-ZZ5LJEFX.js +516 -0
  21. package/dist/chunk-ZZ5LJEFX.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 +41 -0
  33. package/dist/connection-token-resolver.d.ts +30 -1
  34. package/dist/database.d.ts +19 -0
  35. package/dist/editable-artifact-durable-export.d.ts +10 -0
  36. package/dist/editable-artifact-durable-export.js +18 -3
  37. package/dist/editable-artifact-durable-export.js.map +1 -1
  38. package/dist/editable-artifacts.d.ts +2 -2
  39. package/dist/editable-artifacts.js +3 -3
  40. package/dist/governed-learning-activation-schema.d.ts +984 -0
  41. package/dist/governed-learning-activation.d.ts +98 -0
  42. package/dist/governed-learning-evaluator-schema.d.ts +633 -0
  43. package/dist/governed-learning-evaluator.d.ts +28 -0
  44. package/dist/index.d.ts +1633 -87
  45. package/dist/index.js +22004 -8109
  46. package/dist/index.js.map +1 -1
  47. package/dist/insights.d.ts +18 -0
  48. package/dist/integration-bindings.d.ts +1 -0
  49. package/dist/integration-facets.d.ts +30 -0
  50. package/dist/interaction-placement-loss.d.ts +22 -0
  51. package/dist/knowledge-source-sync-schema.d.ts +730 -0
  52. package/dist/knowledge-source-sync.d.ts +60 -0
  53. package/dist/managed-human-provisioning.d.ts +25 -0
  54. package/dist/memory-domain.d.ts +3 -3
  55. package/dist/migrate.d.ts +11 -1
  56. package/dist/migrate.js +1 -1
  57. package/dist/new-session-drafts.d.ts +43 -5
  58. package/dist/organization-membership-backfill.d.ts +174 -0
  59. package/dist/organization-membership-lifecycle.d.ts +162 -0
  60. package/dist/pr-review.d.ts +107 -0
  61. package/dist/preference-registry-schema.d.ts +2 -0
  62. package/dist/preference-registry.d.ts +2 -0
  63. package/dist/provision-roles.js +1 -1
  64. package/dist/runtime-posture.d.ts +23 -9
  65. package/dist/schema.d.ts +24925 -13697
  66. package/dist/schema.js +103 -1
  67. package/dist/scoped-knowledge-schema.d.ts +52 -1
  68. package/dist/session-background-commands.d.ts +125 -0
  69. package/dist/session-background-commands.js +34 -0
  70. package/dist/session-background-commands.js.map +1 -0
  71. package/dist/session-control.d.ts +156 -3
  72. package/dist/session-goal-pacing.d.ts +68 -0
  73. package/dist/session-queue-commands.d.ts +42 -5
  74. package/dist/session-realtime-ledger.d.ts +3 -0
  75. package/dist/session-tenancy.d.ts +71 -1
  76. package/dist/session-tenancy.js +25 -3
  77. package/dist/session-tool-call-settlement.d.ts +9 -4
  78. package/dist/slack-routing-personal-workspace.d.ts +23 -0
  79. package/dist/slack-routing.d.ts +153 -0
  80. package/dist/slack-task-policy-schema.d.ts +34 -0
  81. package/dist/slack-task-policy.d.ts +8 -0
  82. package/dist/slack-user-link-access.d.ts +1 -0
  83. package/dist/task-notes-schema.d.ts +594 -0
  84. package/dist/task-notes.d.ts +39 -0
  85. package/dist/turn-initiator.d.ts +2 -0
  86. package/dist/user-resource-authority.d.ts +109 -0
  87. package/dist/video-generation.js +3 -3
  88. package/dist/workspace-authority.d.ts +81 -0
  89. package/dist/workspace-instruction-policies-schema.d.ts +279 -0
  90. package/dist/workspace-instruction-policies.d.ts +62 -0
  91. package/dist/workspace-learning-policy.d.ts +1 -0
  92. package/dist/workspace-membership-permissions.d.ts +2 -0
  93. package/drizzle/0240_enrollment_connection_authority.sql +39 -0
  94. package/drizzle/0240_model_context_user_messages.sql +204 -0
  95. package/drizzle/0241_atomic_personal_resource_delegation.sql +1000 -0
  96. package/drizzle/0241_enrollment_agent_runtime.sql +70 -0
  97. package/drizzle/0243_google_drive_object_acl_authority.sql +554 -0
  98. package/drizzle/0244_slack_app_home_refresh_queue.sql +120 -0
  99. package/drizzle/0245_model_context_contribution_facts.sql +88 -0
  100. package/drizzle/0246_integration_personal_instance_authority.sql +70 -0
  101. package/drizzle/0247_terraform_stacks_provenance_repair.sql +428 -0
  102. package/drizzle/0248_terraform_stacks_component_resolution_fence.sql +83 -0
  103. package/drizzle/0249_personal_resource_delegation_authority_correction.sql +662 -0
  104. package/drizzle/0250_direct_retained_process_owner_liveness.sql +199 -0
  105. package/drizzle/0251_connected_machine_operation_policy.sql +45 -0
  106. package/drizzle/0252_scheduled_personal_resource_delegation.sql +1867 -0
  107. package/drizzle/0253_common_user_resource_authority_lifecycle.sql +1555 -0
  108. package/drizzle/0254_scoped_variable_set_authority.sql +1255 -0
  109. package/drizzle/0255_company_brain_governed_write_proposals.sql +524 -0
  110. package/drizzle/0256_connection_authority_delegation.sql +703 -0
  111. package/drizzle/0257_goal_revision_decisions_and_root_constraints.sql +436 -0
  112. package/drizzle/0258_three_scope_document_knowledge_authority.sql +1273 -0
  113. package/drizzle/0259_company_brain_context_selection_receipts.sql +869 -0
  114. package/drizzle/0260_task_note_knowledge_promotion.sql +820 -0
  115. package/drizzle/0261_preference_knowledge_proposal_actor_binding.sql +471 -0
  116. package/drizzle/0262_scoped_connected_machines_and_rigs.sql +1700 -0
  117. package/drizzle/0263_organization_membership_lifecycle.sql +3284 -0
  118. package/drizzle/0264_connection_authority_runtime_activation.sql +1084 -0
  119. package/drizzle/0266_company_brain_context_receipt_inspection.sql +156 -0
  120. package/drizzle/0268_governed_learning_decision_receipts.sql +825 -0
  121. package/drizzle/0269_governed_learning_activation_controller.sql +1874 -0
  122. package/drizzle/0270_governed_learning_history_inspection.sql +170 -0
  123. package/drizzle/0271_company_brain_retrieval_only_default.sql +524 -0
  124. package/drizzle/0272_human_confirmed_learning_activation.sql +591 -0
  125. package/drizzle/0273_scheduled_variable_set_materialization.sql +268 -0
  126. package/drizzle/0274_human_confirmed_knowledge_review.sql +310 -0
  127. package/drizzle/0275_scheduled_connection_authority.sql +5527 -0
  128. package/drizzle/0276_onboarding_proposal_initiating_human_guc.sql +198 -0
  129. package/drizzle/0277_workspace_writer_authority_attribution.sql +393 -0
  130. package/drizzle/0278_workspace_membership_removal_fencing.sql +1174 -0
  131. package/drizzle/0279_workspace_connection_use_lane.sql +343 -0
  132. package/drizzle/0280_connection_and_variable_set_audit_attribution.sql +773 -0
  133. package/drizzle/0281_viewer_holder_authority_claims.sql +52 -0
  134. package/drizzle/0282_variable_set_session_attach_attribution.sql +178 -0
  135. package/drizzle/0283_editable_spreadsheet_authored_state.sql +82 -0
  136. package/drizzle/0284_truthful_human_confirmed_review_reason.sql +930 -0
  137. package/drizzle/0285_organization_tenancy_inventory.sql +329 -0
  138. package/drizzle/0286_widen_task_note_expiry_ceiling.sql +391 -0
  139. package/drizzle/0287_open_suffix_pending_tool_calls.sql +18 -0
  140. package/drizzle/0288_attached_browser_reenrollment.sql +64 -0
  141. package/drizzle/0289_session_composer_policy_authority.sql +597 -0
  142. package/drizzle/0290_organization_membership_backfill.sql +268 -0
  143. package/drizzle/0291_resource_authority_classification_assertion.sql +371 -0
  144. package/drizzle/0292_truthful_tenancy_inventory_counters.sql +291 -0
  145. package/drizzle/0293_confirm_time_rule_rebaseline.sql +615 -0
  146. package/drizzle/0294_preference_activation_authority.sql +237 -0
  147. package/drizzle/0295_retire_legacy_standing_memory_mode.sql +476 -0
  148. package/drizzle/0296_force_rls_backfill_noop_repair.sql +215 -0
  149. package/drizzle/0297_session_ownership_classification_and_backfill.sql +752 -0
  150. package/drizzle/0298_organization_tenancy_parity.sql +609 -0
  151. package/drizzle/0299_organization_membership_lock_order.sql +291 -0
  152. package/drizzle/0300_tenancy_backfill_ledger.sql +459 -0
  153. package/drizzle/0301_session_snapshot_and_pin_visibility.sql +181 -0
  154. package/drizzle/0302_personal_workspace_session_ownership.sql +303 -0
  155. package/drizzle/0303_session_tenancy_product_activation.sql +919 -0
  156. package/drizzle/0304_personal_workspace_private_session_reads.sql +97 -0
  157. package/drizzle/0305_personal_resource_grant_management.sql +584 -0
  158. package/drizzle/0306_atomic_personal_resource_attachments.sql +1374 -0
  159. package/drizzle/0307_session_attention_state.sql +51 -0
  160. package/drizzle/0308_session_archives.sql +35 -0
  161. package/drizzle/0309_channel_project_pins.sql +6 -0
  162. package/drizzle/0310_channel_project_order.sql +27 -0
  163. package/drizzle/0311_company_scope_and_private_session_create.sql +656 -0
  164. package/drizzle/0312_quiescent_session_tree_deletion.sql +39 -0
  165. package/drizzle/0313_private_child_session_authority.sql +365 -0
  166. package/drizzle/0314_unregistered_organization_invitations.sql +606 -0
  167. package/drizzle/0315_personal_github_repository_selection.sql +624 -0
  168. package/drizzle/0316_human_confirmed_activation_resumed_generation.sql +869 -0
  169. package/drizzle/0317_session_goal_continuation_hold.sql +64 -0
  170. package/drizzle/0318_session_turn_startup_milestones.sql +115 -0
  171. package/drizzle/0319_event_triggered_automations.sql +254 -0
  172. package/drizzle/0320_pr_review_pack.sql +113 -0
  173. package/drizzle/0321_slack_bot_environment_display_name.sql +15 -0
  174. package/drizzle/0322_session_turns_unclaimed_prompt_trigger_index.sql +5 -0
  175. package/drizzle/0323_organization_private_session_enablement.sql +489 -0
  176. package/drizzle/0324_human_confirmed_company_profile_agent_admin.sql +856 -0
  177. package/drizzle/0325_child_lifecycle_notices.sql +44 -0
  178. package/drizzle/0326_interaction_operation_error_codes.sql +62 -0
  179. package/drizzle/0327_slack_first_task_hint.sql +29 -0
  180. package/drizzle/0328_session_background_commands.sql +590 -0
  181. package/drizzle/0329_slack_orchestration_delivery_events.sql +91 -0
  182. package/drizzle/0330_api_key_descriptions.sql +20 -0
  183. package/drizzle/0331_managed_organization_creation.sql +163 -0
  184. package/drizzle/0332_organization_shared_workspace_control_plane.sql +539 -0
  185. package/drizzle/0333_session_turn_prompt_routing.sql +45 -0
  186. package/drizzle/0334_connected_machine_workspace_root.sql +99 -0
  187. package/drizzle/0335_slack_workspace_routing.sql +460 -0
  188. package/drizzle/0336_atomic_session_fork_visibility.sql +667 -0
  189. package/drizzle/0337_slack_routed_action_handles.sql +189 -0
  190. package/drizzle/0338_atomic_connected_machine_attachments.sql +410 -0
  191. package/drizzle/0339_document_authority_reclassification.sql +1153 -0
  192. package/drizzle/0340_tenancy_backfill_activation_evidence.sql +1347 -0
  193. package/drizzle/0341_slack_routing_probe_organization_fence.sql +109 -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 +81 -0
  212. package/src/connection-token-resolver.ts +263 -31
  213. package/src/database.ts +75 -2
  214. package/src/editable-artifact-durable-export.ts +27 -0
  215. package/src/editable-artifacts-schema.ts +10 -5
  216. package/src/editable-artifacts.ts +21 -11
  217. package/src/governed-learning-activation-schema.ts +121 -0
  218. package/src/governed-learning-activation.ts +588 -0
  219. package/src/governed-learning-evaluator-schema.ts +72 -0
  220. package/src/governed-learning-evaluator.ts +217 -0
  221. package/src/index.ts +12001 -1699
  222. package/src/insights.ts +107 -0
  223. package/src/integration-bindings.ts +88 -6
  224. package/src/integration-facets.ts +287 -84
  225. package/src/interaction-placement-loss.ts +234 -0
  226. package/src/knowledge-source-sync-schema.ts +83 -0
  227. package/src/knowledge-source-sync.ts +530 -0
  228. package/src/managed-human-provisioning.ts +116 -0
  229. package/src/memory-domain.ts +27 -32
  230. package/src/migrate.ts +77 -1
  231. package/src/new-session-drafts.ts +229 -35
  232. package/src/organization-membership-backfill.ts +735 -0
  233. package/src/organization-membership-lifecycle.ts +985 -0
  234. package/src/pr-review.ts +890 -0
  235. package/src/preference-registry.ts +31 -1
  236. package/src/provision-roles.ts +931 -24
  237. package/src/runtime-posture-cli.ts +2 -0
  238. package/src/runtime-posture.ts +1315 -52
  239. package/src/schema.ts +3336 -438
  240. package/src/scoped-knowledge-schema.ts +16 -8
  241. package/src/scoped-knowledge.ts +1 -1
  242. package/src/session-background-commands.ts +759 -0
  243. package/src/session-control.ts +640 -74
  244. package/src/session-goal-pacing.ts +126 -0
  245. package/src/session-queue-commands.ts +450 -98
  246. package/src/session-realtime-context.ts +19 -3
  247. package/src/session-realtime-ledger.ts +75 -12
  248. package/src/session-realtime-mirror.ts +1 -1
  249. package/src/session-tenancy.ts +403 -49
  250. package/src/session-tool-call-settlement.ts +38 -29
  251. package/src/slack-routing-personal-workspace.ts +67 -0
  252. package/src/slack-routing.ts +469 -0
  253. package/src/slack-task-policy-schema.ts +5 -0
  254. package/src/slack-task-policy.ts +18 -0
  255. package/src/slack-user-link-access.ts +17 -1
  256. package/src/task-notes-schema.ts +74 -0
  257. package/src/task-notes.ts +124 -2
  258. package/src/turn-initiator.ts +8 -0
  259. package/src/user-resource-authority.ts +377 -0
  260. package/src/workspace-authority.ts +160 -0
  261. package/src/workspace-instruction-policies-schema.ts +53 -1
  262. package/src/workspace-instruction-policies.ts +528 -77
  263. package/src/workspace-learning-policy.ts +21 -5
  264. package/src/workspace-membership-permissions.ts +9 -0
  265. package/dist/chunk-5KTIEDX7.js.map +0 -1
  266. package/dist/chunk-FPVAD4CO.js +0 -117
  267. package/dist/chunk-FPVAD4CO.js.map +0 -1
  268. package/dist/chunk-HSOWSYOX.js.map +0 -1
  269. package/dist/chunk-SK7YYHBI.js.map +0 -1
  270. package/dist/chunk-VKLB2ZGI.js +0 -2389
  271. package/dist/chunk-VKLB2ZGI.js.map +0 -1
  272. package/dist/chunk-Z7VYMNPZ.js.map +0 -1
  273. /package/dist/{chunk-GYO7ED2Q.js.map → chunk-EULAN2NP.js.map} +0 -0
  274. /package/dist/{chunk-VTPRIXKG.js.map → chunk-NJVZPWXY.js.map} +0 -0
@@ -0,0 +1,919 @@
1
+ -- deployment-mode: rolling
2
+ -- Installs the forward-only, per-organization activation receipt and the
3
+ -- hardened database contract for the still-inert session-tenancy adapters.
4
+ -- Ordinary migration is intentionally safe with activation disabled: no
5
+ -- organization is activated by this file. The separately drained operator
6
+ -- command calls activate_session_tenancy_product only after inventory/parity
7
+ -- evidence and the canonical deployment opt-in have been verified.
8
+
9
+ SET LOCAL lock_timeout = '5s';
10
+ SET LOCAL statement_timeout = '10min';
11
+
12
+ CREATE TABLE session_tenancy_activations (
13
+ account_id uuid PRIMARY KEY REFERENCES managed_accounts(id) ON DELETE RESTRICT,
14
+ activation_version integer NOT NULL,
15
+ inventory_digest text NOT NULL,
16
+ parity_digest text NOT NULL,
17
+ activated_by text NOT NULL,
18
+ activated_at timestamptz NOT NULL DEFAULT clock_timestamp(),
19
+ CONSTRAINT session_tenancy_activations_version_check CHECK (activation_version = 1),
20
+ CONSTRAINT session_tenancy_activations_digests_check CHECK (
21
+ inventory_digest ~ '^[0-9a-f]{64}$' AND parity_digest ~ '^[0-9a-f]{64}$'
22
+ ),
23
+ CONSTRAINT session_tenancy_activations_actor_check CHECK (
24
+ octet_length(activated_by) BETWEEN 1 AND 256
25
+ )
26
+ );
27
+ ALTER TABLE session_tenancy_activations ENABLE ROW LEVEL SECURITY;
28
+ ALTER TABLE session_tenancy_activations FORCE ROW LEVEL SECURITY;
29
+ CREATE POLICY session_tenancy_activations_organization_isolation
30
+ ON session_tenancy_activations
31
+ FOR SELECT USING (
32
+ account_id = nullif(current_setting('opengeni.account_id', true), '')::uuid
33
+ );
34
+ REVOKE ALL ON TABLE session_tenancy_activations FROM PUBLIC;
35
+
36
+ CREATE FUNCTION session_tenancy_product_activated(
37
+ p_account_id uuid,
38
+ p_activation_version integer
39
+ ) RETURNS boolean
40
+ LANGUAGE sql STABLE SECURITY DEFINER
41
+ SET search_path FROM CURRENT
42
+ AS $$
43
+ SELECT p_account_id IS NOT DISTINCT FROM
44
+ nullif(current_setting('opengeni.account_id', true), '')::uuid
45
+ AND EXISTS (
46
+ SELECT 1 FROM session_tenancy_activations activation
47
+ WHERE activation.account_id = p_account_id
48
+ AND activation.activation_version = p_activation_version
49
+ )
50
+ $$;
51
+ REVOKE ALL ON FUNCTION session_tenancy_product_activated(uuid, integer) FROM PUBLIC;
52
+
53
+ -- Startup/readiness cannot select tenant rows without a tenant GUC. This
54
+ -- deliberately value-free predicate lets the exact new image enforce the
55
+ -- forward-only deployment switch after the first organization is activated.
56
+ CREATE FUNCTION session_tenancy_any_product_activation()
57
+ RETURNS boolean
58
+ LANGUAGE sql STABLE SECURITY DEFINER
59
+ SET search_path FROM CURRENT
60
+ AS $$
61
+ SELECT EXISTS (SELECT 1 FROM session_tenancy_activations)
62
+ $$;
63
+ REVOKE ALL ON FUNCTION session_tenancy_any_product_activation() FROM PUBLIC;
64
+
65
+ -- Owner-only activation seam. The caller supplies the exact application login
66
+ -- inventory; it is checked both before and after taking the write-blocking
67
+ -- locks so a late old process cannot slip through the cutover.
68
+ CREATE FUNCTION activate_session_tenancy_product(
69
+ p_account_id uuid,
70
+ p_inventory_digest text,
71
+ p_parity_digest text,
72
+ p_activated_by text,
73
+ p_application_roles text[]
74
+ ) RETURNS TABLE (
75
+ account_id uuid,
76
+ activation_version integer,
77
+ activated_at timestamptz,
78
+ replay boolean
79
+ )
80
+ LANGUAGE plpgsql SECURITY DEFINER
81
+ SET search_path FROM CURRENT
82
+ AS $$
83
+ DECLARE
84
+ existing session_tenancy_activations%ROWTYPE;
85
+ inserted session_tenancy_activations%ROWTYPE;
86
+ BEGIN
87
+ IF p_account_id IS NULL
88
+ OR p_inventory_digest !~ '^[0-9a-f]{64}$'
89
+ OR p_parity_digest !~ '^[0-9a-f]{64}$'
90
+ OR octet_length(btrim(p_activated_by)) NOT BETWEEN 1 AND 256
91
+ OR p_application_roles IS NULL
92
+ OR cardinality(p_application_roles) NOT BETWEEN 1 AND 16
93
+ OR EXISTS (
94
+ SELECT 1 FROM unnest(p_application_roles) role_name
95
+ WHERE role_name IS NULL OR role_name <> btrim(role_name)
96
+ OR octet_length(role_name) NOT BETWEEN 1 AND 63
97
+ )
98
+ OR cardinality(ARRAY(SELECT DISTINCT role_name FROM unnest(p_application_roles) role_name))
99
+ <> cardinality(p_application_roles)
100
+ THEN
101
+ RAISE EXCEPTION 'session tenancy activation request is invalid'
102
+ USING ERRCODE = '22023';
103
+ END IF;
104
+
105
+ IF EXISTS (
106
+ SELECT 1 FROM pg_stat_activity activity
107
+ WHERE activity.datname = current_database()
108
+ AND activity.usename = ANY(p_application_roles)
109
+ AND activity.pid <> pg_backend_pid()
110
+ ) THEN
111
+ RAISE EXCEPTION 'session tenancy activation requires every application role session to be stopped'
112
+ USING ERRCODE = '55000';
113
+ END IF;
114
+
115
+ LOCK TABLE sessions, session_turns, session_turn_attempts,
116
+ session_attempt_interruptions, session_system_updates,
117
+ session_human_input_requests, session_pending_tool_calls, agent_run_states,
118
+ session_goals, codex_capacity_waiters, xai_capacity_waiters,
119
+ session_realtime_modes, session_realtime_connections, scheduled_tasks,
120
+ sandbox_workspace_mutation_admissions, sandbox_retained_processes,
121
+ sandbox_lease_holders, organization_user_resource_grants,
122
+ session_tenancy_activations IN ACCESS EXCLUSIVE MODE;
123
+
124
+ IF EXISTS (
125
+ SELECT 1 FROM pg_stat_activity activity
126
+ WHERE activity.datname = current_database()
127
+ AND activity.usename = ANY(p_application_roles)
128
+ AND activity.pid <> pg_backend_pid()
129
+ ) THEN
130
+ RAISE EXCEPTION 'session tenancy activation requires every application role session to be stopped'
131
+ USING ERRCODE = '55000';
132
+ END IF;
133
+
134
+ -- Re-read only after the global activation lock. Concurrent same-evidence
135
+ -- activations converge on one receipt; changed evidence remains a conflict.
136
+ SELECT * INTO existing FROM session_tenancy_activations activation
137
+ WHERE activation.account_id = p_account_id;
138
+ IF FOUND THEN
139
+ IF existing.inventory_digest <> p_inventory_digest
140
+ OR existing.parity_digest <> p_parity_digest
141
+ THEN
142
+ RAISE EXCEPTION 'session tenancy activation evidence conflicts with the durable receipt'
143
+ USING ERRCODE = '23505';
144
+ END IF;
145
+ account_id := existing.account_id;
146
+ activation_version := existing.activation_version;
147
+ activated_at := existing.activated_at;
148
+ replay := true;
149
+ RETURN NEXT;
150
+ RETURN;
151
+ END IF;
152
+
153
+ INSERT INTO session_tenancy_activations (
154
+ account_id, activation_version, inventory_digest, parity_digest, activated_by
155
+ ) VALUES (
156
+ p_account_id, 1, p_inventory_digest, p_parity_digest, btrim(p_activated_by)
157
+ ) RETURNING * INTO inserted;
158
+
159
+ account_id := inserted.account_id;
160
+ activation_version := inserted.activation_version;
161
+ activated_at := inserted.activated_at;
162
+ replay := false;
163
+ RETURN NEXT;
164
+ END
165
+ $$;
166
+ REVOKE ALL ON FUNCTION
167
+ activate_session_tenancy_product(uuid, text, text, text, text[]) FROM PUBLIC;
168
+
169
+ -- Nested implementation helper only. The outer lifecycle functions call this
170
+ -- after their complete tenant, membership, owner, activation, and operation
171
+ -- checks. It is deliberately ungranted to the application role: by itself it
172
+ -- has no authority boundary and would otherwise expose a cross-tenant activity
173
+ -- oracle while taking global table locks. All mutation-bearing tables are
174
+ -- write-locked for the short transition/fork transaction so the zero
175
+ -- observation cannot race a new arrival. The stable blocker code is carried in
176
+ -- DETAIL; the adapter maps SQLSTATE 55P03 to a typed 409-ready error.
177
+ CREATE FUNCTION assert_session_tenancy_quiescent(
178
+ p_account_id uuid,
179
+ p_workspace_id uuid,
180
+ p_session_id uuid,
181
+ p_require_singleton_group boolean
182
+ ) RETURNS void
183
+ LANGUAGE plpgsql SECURITY DEFINER
184
+ SET search_path FROM CURRENT
185
+ AS $$
186
+ DECLARE
187
+ session_group_id uuid;
188
+ previous_subject text := current_setting('opengeni.subject_id', true);
189
+ blocker text;
190
+ BEGIN
191
+ LOCK TABLE session_turns, session_turn_attempts, session_attempt_interruptions,
192
+ session_system_updates, session_human_input_requests,
193
+ session_pending_tool_calls, agent_run_states, session_goals,
194
+ codex_capacity_waiters, xai_capacity_waiters, session_realtime_modes,
195
+ session_realtime_connections, scheduled_tasks,
196
+ sandbox_workspace_mutation_admissions, sandbox_retained_processes,
197
+ sandbox_lease_holders IN SHARE ROW EXCLUSIVE MODE;
198
+
199
+ SELECT sandbox_group_id INTO session_group_id FROM sessions
200
+ WHERE account_id = p_account_id AND workspace_id = p_workspace_id
201
+ AND id = p_session_id;
202
+
203
+ IF EXISTS (SELECT 1 FROM session_turns WHERE workspace_id = p_workspace_id
204
+ AND session_id = p_session_id
205
+ AND status IN ('queued','running','requires_action','recovering','waiting_capacity'))
206
+ THEN blocker := 'nonterminal_turn';
207
+ ELSIF EXISTS (SELECT 1 FROM session_turn_attempts WHERE workspace_id = p_workspace_id
208
+ AND session_id = p_session_id AND state <> 'closed')
209
+ THEN blocker := 'nonterminal_attempt';
210
+ ELSIF EXISTS (SELECT 1 FROM session_attempt_interruptions WHERE workspace_id = p_workspace_id
211
+ AND session_id = p_session_id AND state IN ('pending','delivered','acknowledged'))
212
+ THEN blocker := 'unsettled_interruption';
213
+ ELSIF EXISTS (SELECT 1 FROM session_system_updates WHERE workspace_id = p_workspace_id
214
+ AND session_id = p_session_id AND state = 'pending')
215
+ THEN blocker := 'pending_system_update';
216
+ ELSIF EXISTS (SELECT 1 FROM session_human_input_requests WHERE workspace_id = p_workspace_id
217
+ AND session_id = p_session_id AND status = 'pending')
218
+ THEN blocker := 'pending_human_input';
219
+ ELSIF EXISTS (SELECT 1 FROM session_pending_tool_calls WHERE workspace_id = p_workspace_id
220
+ AND session_id = p_session_id)
221
+ THEN blocker := 'pending_tool_receipt';
222
+ ELSIF EXISTS (SELECT 1 FROM agent_run_states WHERE workspace_id = p_workspace_id
223
+ AND session_id = p_session_id)
224
+ THEN blocker := 'run_state';
225
+ ELSIF EXISTS (SELECT 1 FROM session_goals WHERE workspace_id = p_workspace_id
226
+ AND session_id = p_session_id AND status = 'active')
227
+ THEN blocker := 'active_goal';
228
+ ELSIF EXISTS (SELECT 1 FROM codex_capacity_waiters WHERE workspace_id = p_workspace_id
229
+ AND session_id = p_session_id AND status = 'waiting')
230
+ OR EXISTS (SELECT 1 FROM xai_capacity_waiters WHERE workspace_id = p_workspace_id
231
+ AND session_id = p_session_id AND status = 'waiting')
232
+ THEN blocker := 'capacity_waiter';
233
+ ELSIF EXISTS (SELECT 1 FROM session_realtime_modes WHERE workspace_id = p_workspace_id
234
+ AND session_id = p_session_id AND state = 'active')
235
+ OR EXISTS (SELECT 1 FROM session_realtime_connections WHERE workspace_id = p_workspace_id
236
+ AND session_id = p_session_id
237
+ AND state IN ('negotiating','ready','active'))
238
+ THEN blocker := 'active_realtime';
239
+ ELSIF EXISTS (SELECT 1 FROM scheduled_tasks WHERE workspace_id = p_workspace_id
240
+ AND reusable_session_id = p_session_id AND status = 'active')
241
+ THEN blocker := 'active_scheduled_task';
242
+ ELSIF EXISTS (SELECT 1 FROM sandbox_workspace_mutation_admissions
243
+ WHERE workspace_id = p_workspace_id AND session_id = p_session_id
244
+ AND settled_at IS NULL)
245
+ THEN blocker := 'workspace_mutation_admission';
246
+ ELSIF EXISTS (SELECT 1 FROM sandbox_retained_processes
247
+ WHERE workspace_id = p_workspace_id AND session_id = p_session_id
248
+ AND state = 'active')
249
+ THEN blocker := 'retained_process';
250
+ ELSIF EXISTS (
251
+ SELECT 1 FROM sandbox_lease_holders holder
252
+ JOIN sandbox_leases lease ON lease.id = holder.lease_id
253
+ WHERE lease.workspace_id = p_workspace_id
254
+ AND lease.sandbox_group_id = session_group_id
255
+ AND holder.kind IN ('viewer', 'interaction')
256
+ )
257
+ THEN blocker := 'active_sandbox_access';
258
+ END IF;
259
+
260
+ IF blocker IS NULL AND p_require_singleton_group THEN
261
+ -- The caller's own actor-scoped RLS must not hide a sibling. The function
262
+ -- has already locked and identified the authorized source session.
263
+ PERFORM set_config('opengeni.subject_id', '', true);
264
+ IF EXISTS (SELECT 1 FROM sessions sibling
265
+ WHERE sibling.account_id = p_account_id
266
+ AND sibling.workspace_id = p_workspace_id
267
+ AND sibling.sandbox_group_id = session_group_id
268
+ AND sibling.id <> p_session_id)
269
+ THEN blocker := 'shared_sandbox_group'; END IF;
270
+ PERFORM set_config(
271
+ 'opengeni.subject_id', CASE WHEN previous_subject IS NULL THEN '' ELSE previous_subject END, true
272
+ );
273
+ END IF;
274
+
275
+ IF blocker IS NOT NULL THEN
276
+ RAISE EXCEPTION 'session tenancy mutation requires a quiescent session'
277
+ USING ERRCODE = '55P03', DETAIL = blocker;
278
+ END IF;
279
+ EXCEPTION WHEN OTHERS THEN
280
+ PERFORM set_config(
281
+ 'opengeni.subject_id', CASE WHEN previous_subject IS NULL THEN '' ELSE previous_subject END, true
282
+ );
283
+ RAISE;
284
+ END
285
+ $$;
286
+ REVOKE ALL ON FUNCTION assert_session_tenancy_quiescent(uuid, uuid, uuid, boolean)
287
+ FROM PUBLIC;
288
+
289
+ DROP FUNCTION transition_session_visibility(uuid, uuid, uuid, text, text, integer, text, text);
290
+ CREATE FUNCTION transition_session_visibility(
291
+ p_account_id uuid,
292
+ p_workspace_id uuid,
293
+ p_session_id uuid,
294
+ p_actor_subject_id text,
295
+ p_target_visibility text,
296
+ p_expected_authority_epoch integer,
297
+ p_operation_key text,
298
+ p_canonical_request_hash text,
299
+ p_activation_version integer
300
+ ) RETURNS TABLE (
301
+ operation_id uuid,
302
+ event_id uuid,
303
+ event_sequence integer,
304
+ visibility text,
305
+ authority_epoch integer,
306
+ owner_organization_membership_id uuid,
307
+ changed boolean,
308
+ replay boolean,
309
+ interrupted_attempt_count integer,
310
+ cancelled_turn_count integer,
311
+ cancelled_update_count integer,
312
+ paused_goal_count integer,
313
+ revoked_grant_count integer
314
+ )
315
+ LANGUAGE plpgsql SECURITY DEFINER
316
+ SET search_path FROM CURRENT
317
+ AS $$
318
+ DECLARE
319
+ actor_membership organization_memberships%ROWTYPE;
320
+ session_row sessions%ROWTYPE;
321
+ receipt_row session_command_receipts%ROWTYPE;
322
+ new_epoch integer;
323
+ grant_count integer := 0;
324
+ event_row_id uuid;
325
+ event_row_sequence integer;
326
+ visibility_write_capability_id uuid := gen_random_uuid();
327
+ previous_visibility_capability text := current_setting(
328
+ 'opengeni.session_visibility_write_capability', true
329
+ );
330
+ previous_lifecycle text := current_setting('opengeni.organization_tenancy_lifecycle', true);
331
+ BEGIN
332
+ IF p_account_id IS NULL OR p_workspace_id IS NULL OR p_session_id IS NULL
333
+ OR p_actor_subject_id IS NULL OR p_target_visibility IS NULL
334
+ OR p_expected_authority_epoch IS NULL OR p_operation_key IS NULL
335
+ OR p_canonical_request_hash IS NULL OR p_activation_version IS NULL
336
+ THEN RAISE EXCEPTION 'session visibility transition requires complete authority'
337
+ USING ERRCODE = '42501'; END IF;
338
+ IF p_account_id IS DISTINCT FROM opengeni_private.current_account_id()
339
+ OR p_workspace_id IS DISTINCT FROM opengeni_private.current_workspace_id()
340
+ OR p_actor_subject_id IS DISTINCT FROM opengeni_private.current_subject_id()
341
+ THEN RAISE EXCEPTION 'session visibility transition authority is invalid'
342
+ USING ERRCODE = '42501'; END IF;
343
+ IF p_target_visibility NOT IN ('user_private', 'workspace_shared')
344
+ OR p_expected_authority_epoch < 1
345
+ OR p_actor_subject_id <> btrim(p_actor_subject_id)
346
+ OR length(p_actor_subject_id) NOT BETWEEN 1 AND 1024
347
+ OR p_operation_key <> btrim(p_operation_key)
348
+ OR length(p_operation_key) NOT BETWEEN 1 AND 1024
349
+ OR p_canonical_request_hash !~ '^[0-9a-f]{64}$'
350
+ OR p_activation_version <> 1
351
+ THEN RAISE EXCEPTION 'session visibility transition request is invalid'
352
+ USING ERRCODE = '22023'; END IF;
353
+ IF NOT session_tenancy_product_activated(p_account_id, p_activation_version) THEN
354
+ RAISE EXCEPTION 'session tenancy product surface is not activated for this organization'
355
+ USING ERRCODE = '55000';
356
+ END IF;
357
+
358
+ -- Match the canonical organization-membership lifecycle prefix before any
359
+ -- table/row lock. This keeps visibility changes from reintroducing the
360
+ -- workspace/account lock cycle repaired by migration 0299.
361
+ PERFORM pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(
362
+ 'organization-membership:' || p_account_id::text, 0
363
+ ));
364
+
365
+ LOCK TABLE sessions, session_turns, session_turn_attempts,
366
+ session_attempt_interruptions, session_system_updates,
367
+ session_human_input_requests, session_pending_tool_calls, agent_run_states,
368
+ session_goals, codex_capacity_waiters, xai_capacity_waiters,
369
+ session_realtime_modes, session_realtime_connections, scheduled_tasks,
370
+ sandbox_workspace_mutation_admissions, sandbox_retained_processes,
371
+ sandbox_lease_holders IN SHARE ROW EXCLUSIVE MODE;
372
+
373
+ PERFORM set_config('opengeni.organization_tenancy_lifecycle',
374
+ 'session_visibility_activation', true);
375
+ PERFORM 1 FROM workspaces workspace_row
376
+ WHERE workspace_row.id = p_workspace_id AND workspace_row.account_id = p_account_id
377
+ FOR UPDATE;
378
+ IF NOT FOUND THEN RAISE EXCEPTION 'session visibility transition workspace is unavailable'
379
+ USING ERRCODE = '42501'; END IF;
380
+
381
+ SELECT membership.* INTO actor_membership
382
+ FROM organization_memberships membership
383
+ WHERE membership.account_id = p_account_id
384
+ AND membership.subject_id = p_actor_subject_id
385
+ AND membership.status = 'active'
386
+ FOR UPDATE;
387
+ IF NOT FOUND OR NOT (
388
+ actor_membership.personal_workspace_id = p_workspace_id
389
+ OR EXISTS (
390
+ SELECT 1 FROM workspace_memberships workspace_membership
391
+ WHERE workspace_membership.account_id = p_account_id
392
+ AND workspace_membership.workspace_id = p_workspace_id
393
+ AND workspace_membership.subject_id = p_actor_subject_id
394
+ )
395
+ ) THEN RAISE EXCEPTION 'session visibility transition requires active membership'
396
+ USING ERRCODE = '42501'; END IF;
397
+
398
+ SELECT session.* INTO session_row FROM sessions session
399
+ WHERE session.account_id = p_account_id AND session.workspace_id = p_workspace_id
400
+ AND session.id = p_session_id
401
+ FOR UPDATE;
402
+ IF NOT FOUND THEN RAISE EXCEPTION 'session visibility transition session is unavailable'
403
+ USING ERRCODE = 'P0002'; END IF;
404
+ IF session_row.owner_organization_membership_id IS DISTINCT FROM actor_membership.id
405
+ OR session_row.owner_subject_id IS DISTINCT FROM actor_membership.subject_id
406
+ THEN RAISE EXCEPTION 'session visibility transition is owner-only'
407
+ USING ERRCODE = '42501'; END IF;
408
+
409
+ INSERT INTO session_command_receipts (
410
+ account_id, workspace_id, actor_type, actor_subject_id, action,
411
+ target_session_id, operation_key, canonical_request_hash
412
+ ) VALUES (
413
+ p_account_id, p_workspace_id, 'human', p_actor_subject_id,
414
+ 'session.visibility.change', p_session_id, p_operation_key, p_canonical_request_hash
415
+ ) ON CONFLICT DO NOTHING;
416
+ SELECT receipt.* INTO receipt_row FROM session_command_receipts receipt
417
+ WHERE receipt.workspace_id = p_workspace_id AND receipt.actor_type = 'human'
418
+ AND receipt.actor_subject_id = p_actor_subject_id
419
+ AND receipt.actor_attempt_id IS NULL
420
+ AND receipt.action = 'session.visibility.change'
421
+ AND receipt.target_session_id = p_session_id AND receipt.target_turn_id IS NULL
422
+ AND receipt.operation_key = p_operation_key
423
+ FOR UPDATE;
424
+ IF receipt_row.canonical_request_hash <> p_canonical_request_hash THEN
425
+ RAISE EXCEPTION 'session visibility transition idempotency conflict'
426
+ USING ERRCODE = '23505';
427
+ END IF;
428
+ IF receipt_row.result ->> 'status' = 'applied' THEN
429
+ operation_id := receipt_row.id;
430
+ event_id := nullif(receipt_row.result ->> 'eventId', '')::uuid;
431
+ event_sequence := nullif(receipt_row.result ->> 'eventSequence', '')::integer;
432
+ visibility := receipt_row.result ->> 'visibility';
433
+ authority_epoch := (receipt_row.result ->> 'authorityEpoch')::integer;
434
+ owner_organization_membership_id := actor_membership.id;
435
+ changed := (receipt_row.result ->> 'changed')::boolean;
436
+ replay := true;
437
+ interrupted_attempt_count := 0; cancelled_turn_count := 0;
438
+ cancelled_update_count := 0; paused_goal_count := 0;
439
+ revoked_grant_count := (receipt_row.result ->> 'revokedGrantCount')::integer;
440
+ RETURN NEXT; RETURN;
441
+ END IF;
442
+ IF session_row.authority_epoch <> p_expected_authority_epoch THEN
443
+ RAISE EXCEPTION 'session visibility transition authority epoch conflict'
444
+ USING ERRCODE = '40001';
445
+ END IF;
446
+
447
+ new_epoch := session_row.authority_epoch;
448
+ IF session_row.visibility <> p_target_visibility THEN
449
+ PERFORM assert_session_tenancy_quiescent(
450
+ p_account_id, p_workspace_id, p_session_id, p_target_visibility = 'user_private'
451
+ );
452
+ new_epoch := session_row.authority_epoch + 1;
453
+ IF new_epoch < 2 THEN RAISE EXCEPTION 'session authority epoch exhausted'
454
+ USING ERRCODE = '22003'; END IF;
455
+
456
+ UPDATE organization_user_resource_grants grant_row
457
+ SET status = 'revoked', revoked_at = clock_timestamp(),
458
+ generation = grant_row.generation + 1, updated_at = clock_timestamp()
459
+ WHERE grant_row.account_id = p_account_id
460
+ AND grant_row.workspace_id = p_workspace_id
461
+ AND grant_row.session_id = p_session_id
462
+ AND grant_row.authority_epoch = session_row.authority_epoch
463
+ AND grant_row.status = 'active';
464
+ GET DIAGNOSTICS grant_count = ROW_COUNT;
465
+
466
+ INSERT INTO session_visibility_write_capabilities (
467
+ backend_pid, transaction_id, capability_id
468
+ ) VALUES (pg_backend_pid(), pg_current_xact_id(), visibility_write_capability_id);
469
+ PERFORM set_config('opengeni.session_visibility_write_capability',
470
+ visibility_write_capability_id::text, true);
471
+ UPDATE sessions transition_target SET
472
+ visibility = p_target_visibility,
473
+ authority_epoch = new_epoch,
474
+ initial_personal_connection_delegations = '[]'::jsonb,
475
+ last_sequence = session_row.last_sequence + 1,
476
+ updated_at = clock_timestamp()
477
+ WHERE transition_target.id = p_session_id
478
+ AND transition_target.authority_epoch = session_row.authority_epoch;
479
+ IF NOT FOUND THEN RAISE EXCEPTION 'session visibility transition lost authority epoch CAS'
480
+ USING ERRCODE = '40001'; END IF;
481
+
482
+ INSERT INTO session_events (
483
+ account_id, workspace_id, session_id, sequence, type, payload, occurred_at
484
+ ) VALUES (
485
+ p_account_id, p_workspace_id, p_session_id, session_row.last_sequence + 1,
486
+ 'session.visibility.changed',
487
+ jsonb_build_object(
488
+ 'operationId', receipt_row.id,
489
+ 'fromVisibility', CASE session_row.visibility WHEN 'user_private' THEN 'private' ELSE 'workspace' END,
490
+ 'toVisibility', CASE p_target_visibility WHEN 'user_private' THEN 'private' ELSE 'workspace' END,
491
+ 'previousAuthorityEpoch', session_row.authority_epoch,
492
+ 'authorityEpoch', new_epoch,
493
+ 'interruptedAttemptCount', 0, 'cancelledTurnCount', 0,
494
+ 'cancelledUpdateCount', 0, 'pausedGoalCount', 0,
495
+ 'revokedGrantCount', grant_count
496
+ ), clock_timestamp()
497
+ ) RETURNING id, sequence INTO event_row_id, event_row_sequence;
498
+ DELETE FROM session_visibility_write_capabilities capability
499
+ WHERE capability.backend_pid = pg_backend_pid()
500
+ AND capability.transaction_id = pg_current_xact_id()
501
+ AND capability.capability_id = visibility_write_capability_id;
502
+ PERFORM set_config('opengeni.session_visibility_write_capability',
503
+ CASE WHEN previous_visibility_capability IS NULL THEN '' ELSE previous_visibility_capability END,
504
+ true);
505
+ END IF;
506
+
507
+ UPDATE session_command_receipts SET result = jsonb_build_object(
508
+ 'status', 'applied', 'eventId', event_row_id,
509
+ 'eventSequence', event_row_sequence, 'visibility', p_target_visibility,
510
+ 'authorityEpoch', new_epoch, 'changed', session_row.visibility <> p_target_visibility,
511
+ 'revokedGrantCount', grant_count
512
+ ), updated_at = clock_timestamp() WHERE id = receipt_row.id;
513
+
514
+ operation_id := receipt_row.id; event_id := event_row_id;
515
+ event_sequence := event_row_sequence; visibility := p_target_visibility;
516
+ authority_epoch := new_epoch;
517
+ owner_organization_membership_id := actor_membership.id;
518
+ changed := session_row.visibility <> p_target_visibility; replay := false;
519
+ interrupted_attempt_count := 0; cancelled_turn_count := 0;
520
+ cancelled_update_count := 0; paused_goal_count := 0;
521
+ revoked_grant_count := grant_count;
522
+ PERFORM set_config('opengeni.organization_tenancy_lifecycle',
523
+ CASE WHEN previous_lifecycle IS NULL THEN '' ELSE previous_lifecycle END, true);
524
+ RETURN NEXT;
525
+ EXCEPTION WHEN OTHERS THEN
526
+ PERFORM set_config('opengeni.organization_tenancy_lifecycle',
527
+ CASE WHEN previous_lifecycle IS NULL THEN '' ELSE previous_lifecycle END, true);
528
+ PERFORM set_config('opengeni.session_visibility_write_capability',
529
+ CASE WHEN previous_visibility_capability IS NULL THEN '' ELSE previous_visibility_capability END,
530
+ true);
531
+ RAISE;
532
+ END
533
+ $$;
534
+ REVOKE ALL ON FUNCTION transition_session_visibility(
535
+ uuid, uuid, uuid, text, text, integer, text, text, integer
536
+ ) FROM PUBLIC;
537
+
538
+ DROP FUNCTION fork_session_content(uuid, uuid, uuid, text, uuid, text, text, text);
539
+ CREATE FUNCTION fork_session_content(
540
+ p_account_id uuid,
541
+ p_source_workspace_id uuid,
542
+ p_source_session_id uuid,
543
+ p_actor_subject_id text,
544
+ p_destination_workspace_id uuid,
545
+ p_destination_visibility text,
546
+ p_operation_key text,
547
+ p_canonical_request_hash text,
548
+ p_activation_version integer
549
+ ) RETURNS TABLE (
550
+ operation_id uuid,
551
+ event_id uuid,
552
+ event_sequence integer,
553
+ session_id uuid,
554
+ workspace_id uuid,
555
+ visibility text,
556
+ authority_epoch integer,
557
+ copied_history_item_count integer,
558
+ replay boolean
559
+ )
560
+ LANGUAGE plpgsql SECURITY DEFINER
561
+ SET search_path FROM CURRENT
562
+ AS $$
563
+ DECLARE
564
+ actor_membership organization_memberships%ROWTYPE;
565
+ source_session sessions%ROWTYPE;
566
+ destination_workspace workspaces%ROWTYPE;
567
+ receipt_row session_command_receipts%ROWTYPE;
568
+ destination_session_id uuid;
569
+ history_count integer := 0;
570
+ destination_activity_revision bigint;
571
+ destination_depth integer;
572
+ destination_depth_source text;
573
+ destination_resources jsonb := '[]'::jsonb;
574
+ event_row_id uuid;
575
+ visibility_write_capability_id uuid := gen_random_uuid();
576
+ previous_lifecycle text := current_setting('opengeni.organization_tenancy_lifecycle', true);
577
+ previous_gate_state text := current_setting('opengeni.session_activity_gate_state', true);
578
+ previous_gate_workspace text := current_setting(
579
+ 'opengeni.session_activity_gate_workspace_id', true
580
+ );
581
+ previous_visibility_capability text := current_setting(
582
+ 'opengeni.session_visibility_write_capability', true
583
+ );
584
+ BEGIN
585
+ IF p_account_id IS NULL OR p_source_workspace_id IS NULL
586
+ OR p_source_session_id IS NULL OR p_actor_subject_id IS NULL
587
+ OR p_destination_workspace_id IS NULL OR p_destination_visibility IS NULL
588
+ OR p_operation_key IS NULL OR p_canonical_request_hash IS NULL
589
+ OR p_activation_version IS NULL
590
+ THEN RAISE EXCEPTION 'session fork requires complete authority'
591
+ USING ERRCODE = '42501'; END IF;
592
+ IF p_account_id IS DISTINCT FROM opengeni_private.current_account_id()
593
+ OR p_source_workspace_id IS DISTINCT FROM opengeni_private.current_workspace_id()
594
+ OR p_actor_subject_id IS DISTINCT FROM opengeni_private.current_subject_id()
595
+ THEN RAISE EXCEPTION 'session fork authority is invalid'
596
+ USING ERRCODE = '42501'; END IF;
597
+ -- First product contract: independent fork is deliberately same-workspace
598
+ -- and private-only. Cross-workspace/public expansion needs a later authority
599
+ -- design, not a permissive interpretation of this seam.
600
+ IF p_destination_workspace_id IS DISTINCT FROM p_source_workspace_id
601
+ OR p_destination_visibility <> 'user_private'
602
+ OR p_actor_subject_id <> btrim(p_actor_subject_id)
603
+ OR length(p_actor_subject_id) NOT BETWEEN 1 AND 1024
604
+ OR p_operation_key <> btrim(p_operation_key)
605
+ OR length(p_operation_key) NOT BETWEEN 1 AND 1024
606
+ OR p_canonical_request_hash !~ '^[0-9a-f]{64}$'
607
+ OR p_activation_version <> 1
608
+ THEN RAISE EXCEPTION 'session fork request is invalid'
609
+ USING ERRCODE = '22023'; END IF;
610
+ IF nullif(previous_gate_state, '') IS NOT NULL
611
+ OR nullif(previous_gate_workspace, '') IS NOT NULL
612
+ THEN RAISE EXCEPTION 'session fork requires ownership of the activity gate'
613
+ USING ERRCODE = '55000'; END IF;
614
+ IF NOT session_tenancy_product_activated(p_account_id, p_activation_version) THEN
615
+ RAISE EXCEPTION 'session tenancy product surface is not activated for this organization'
616
+ USING ERRCODE = '55000';
617
+ END IF;
618
+
619
+
620
+ PERFORM pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(
621
+ 'organization-membership:' || p_account_id::text, 0
622
+ ));
623
+
624
+ LOCK TABLE sessions, session_turns, session_turn_attempts,
625
+ session_attempt_interruptions, session_system_updates,
626
+ session_human_input_requests, session_pending_tool_calls, agent_run_states,
627
+ session_goals, codex_capacity_waiters, xai_capacity_waiters,
628
+ session_realtime_modes, session_realtime_connections, scheduled_tasks,
629
+ sandbox_workspace_mutation_admissions, sandbox_retained_processes,
630
+ sandbox_lease_holders IN SHARE ROW EXCLUSIVE MODE;
631
+
632
+ PERFORM set_config('opengeni.organization_tenancy_lifecycle',
633
+ 'session_visibility_activation', true);
634
+ SELECT * INTO destination_workspace FROM workspaces workspace_row
635
+ WHERE workspace_row.account_id = p_account_id
636
+ AND workspace_row.id = p_source_workspace_id
637
+ FOR UPDATE;
638
+ IF NOT FOUND THEN RAISE EXCEPTION 'session fork workspace is unavailable'
639
+ USING ERRCODE = '42501'; END IF;
640
+
641
+ SELECT membership.* INTO actor_membership
642
+ FROM organization_memberships membership
643
+ WHERE membership.account_id = p_account_id
644
+ AND membership.subject_id = p_actor_subject_id
645
+ AND membership.status = 'active'
646
+ FOR UPDATE;
647
+ IF NOT FOUND OR NOT (
648
+ actor_membership.personal_workspace_id = p_source_workspace_id
649
+ OR EXISTS (
650
+ SELECT 1 FROM workspace_memberships workspace_membership
651
+ WHERE workspace_membership.account_id = p_account_id
652
+ AND workspace_membership.workspace_id = p_source_workspace_id
653
+ AND workspace_membership.subject_id = p_actor_subject_id
654
+ )
655
+ ) THEN RAISE EXCEPTION 'session fork requires active workspace authority'
656
+ USING ERRCODE = '42501'; END IF;
657
+
658
+ SELECT session.* INTO source_session FROM sessions session
659
+ WHERE session.account_id = p_account_id
660
+ AND session.workspace_id = p_source_workspace_id
661
+ AND session.id = p_source_session_id
662
+ FOR UPDATE;
663
+ IF NOT FOUND THEN RAISE EXCEPTION 'session fork source session is unavailable'
664
+ USING ERRCODE = 'P0002'; END IF;
665
+ IF source_session.owner_organization_membership_id IS DISTINCT FROM actor_membership.id
666
+ OR source_session.owner_subject_id IS DISTINCT FROM actor_membership.subject_id
667
+ THEN RAISE EXCEPTION 'session fork is owner-only'
668
+ USING ERRCODE = '42501'; END IF;
669
+
670
+ -- Resolve the durable operation receipt after current caller authorization
671
+ -- but before inspecting mutable source state. An identical retry returns the
672
+ -- already-committed destination even if new work began on the source after
673
+ -- the first fork. A reused key with different input remains a conflict.
674
+ INSERT INTO session_command_receipts (
675
+ account_id, workspace_id, actor_type, actor_subject_id, action,
676
+ target_session_id, operation_key, canonical_request_hash
677
+ ) VALUES (
678
+ p_account_id, p_source_workspace_id, 'human', p_actor_subject_id,
679
+ 'session.fork', p_source_session_id, p_operation_key, p_canonical_request_hash
680
+ ) ON CONFLICT DO NOTHING;
681
+ SELECT receipt.* INTO receipt_row FROM session_command_receipts receipt
682
+ WHERE receipt.workspace_id = p_source_workspace_id
683
+ AND receipt.actor_type = 'human' AND receipt.actor_subject_id = p_actor_subject_id
684
+ AND receipt.actor_attempt_id IS NULL AND receipt.action = 'session.fork'
685
+ AND receipt.target_session_id = p_source_session_id
686
+ AND receipt.target_turn_id IS NULL AND receipt.operation_key = p_operation_key
687
+ FOR UPDATE;
688
+ IF receipt_row.canonical_request_hash <> p_canonical_request_hash THEN
689
+ RAISE EXCEPTION 'session fork idempotency conflict' USING ERRCODE = '23505';
690
+ END IF;
691
+ IF receipt_row.result ->> 'status' = 'applied' THEN
692
+ operation_id := receipt_row.id;
693
+ event_id := nullif(receipt_row.result ->> 'eventId', '')::uuid;
694
+ event_sequence := (receipt_row.result ->> 'eventSequence')::integer;
695
+ session_id := (receipt_row.result ->> 'sessionId')::uuid;
696
+ workspace_id := (receipt_row.result ->> 'workspaceId')::uuid;
697
+ visibility := receipt_row.result ->> 'visibility'; authority_epoch := 1;
698
+ copied_history_item_count :=
699
+ (receipt_row.result ->> 'copiedHistoryItemCount')::integer;
700
+ replay := true; RETURN NEXT; RETURN;
701
+ END IF;
702
+
703
+ PERFORM assert_session_tenancy_quiescent(
704
+ p_account_id, p_source_workspace_id, p_source_session_id, true
705
+ );
706
+
707
+ SELECT coalesce(jsonb_agg(
708
+ resource.value
709
+ - 'credentialBindingId' - 'connectionId' - 'installationId'
710
+ - 'projectId' - 'githubInstallationId'
711
+ ORDER BY resource.ordinality
712
+ ), '[]'::jsonb) INTO destination_resources
713
+ FROM jsonb_array_elements(source_session.resources)
714
+ WITH ORDINALITY AS resource(value, ordinality);
715
+
716
+ DROP TABLE IF EXISTS pg_temp.opengeni_session_fork_history_spool;
717
+ CREATE TEMP TABLE opengeni_session_fork_history_spool (
718
+ position numeric NOT NULL,
719
+ item jsonb NOT NULL,
720
+ item_codec_version integer,
721
+ active boolean NOT NULL,
722
+ created_at timestamptz NOT NULL
723
+ ) ON COMMIT DROP;
724
+ INSERT INTO opengeni_session_fork_history_spool (
725
+ position, item, item_codec_version, active, created_at
726
+ ) SELECT source_item.position, source_item.item,
727
+ source_item.item_codec_version, source_item.active, source_item.created_at
728
+ FROM session_history_items source_item
729
+ WHERE source_item.account_id = p_account_id
730
+ AND source_item.workspace_id = p_source_workspace_id
731
+ AND source_item.session_id = p_source_session_id
732
+ ORDER BY source_item.position;
733
+ GET DIAGNOSTICS history_count = ROW_COUNT;
734
+
735
+ SELECT coalesce(
736
+ CASE WHEN (destination_workspace.settings ->> 'maxNestedAgentDepth') ~ '^\d+$'
737
+ THEN (destination_workspace.settings ->> 'maxNestedAgentDepth')::integer END,
738
+ configuration.max_nested_agent_depth
739
+ ),
740
+ CASE WHEN (destination_workspace.settings ->> 'maxNestedAgentDepth') ~ '^\d+$'
741
+ THEN 'workspace' ELSE configuration.policy_source END
742
+ INTO destination_depth, destination_depth_source
743
+ FROM nested_agent_depth_configuration configuration
744
+ WHERE configuration.singleton = true;
745
+ IF destination_depth IS NULL OR destination_depth_source IS NULL THEN
746
+ RAISE EXCEPTION 'session fork destination depth policy is unavailable'
747
+ USING ERRCODE = '55000';
748
+ END IF;
749
+
750
+ destination_session_id := gen_random_uuid();
751
+ INSERT INTO session_visibility_write_capabilities (
752
+ backend_pid, transaction_id, capability_id
753
+ ) VALUES (pg_backend_pid(), pg_current_xact_id(), visibility_write_capability_id);
754
+ PERFORM set_config('opengeni.session_visibility_write_capability',
755
+ visibility_write_capability_id::text, true);
756
+ PERFORM set_config('opengeni.session_activity_gate_state', 'open', true);
757
+ PERFORM set_config('opengeni.session_activity_gate_workspace_id',
758
+ p_source_workspace_id::text, true);
759
+
760
+ INSERT INTO sessions (
761
+ id, account_id, workspace_id, status,
762
+ initial_message, initial_message_codec_version,
763
+ title, title_source, instructions, policy_role,
764
+ resources, skills, tools, metadata,
765
+ created_by_kind, created_by_subject_id, created_by_context,
766
+ owner_organization_membership_id, owner_subject_id,
767
+ visibility, authority_epoch,
768
+ forked_from_session_id, forked_from_authority_epoch,
769
+ forked_from_visibility, forked_at, forked_by_organization_membership_id,
770
+ model, reasoning_effort, latency_mode, sandbox_backend, sandbox_os, sandbox_group_id,
771
+ first_party_mcp_permissions, first_party_mcp_tools,
772
+ initial_personal_connection_delegations, tool_policy,
773
+ root_session_id, nested_agent_depth,
774
+ max_nested_agent_depth_override, effective_max_nested_agent_depth,
775
+ nested_agent_depth_policy_source, nested_agent_depth_policy_session_id,
776
+ temporal_workflow_id, active_turn_id, variable_set_id,
777
+ rig_id, rig_version_id, active_sandbox_id, active_epoch,
778
+ working_dir, codex_pinned_credential_id, codex_last_credential_id,
779
+ codex_pin_source, codex_compaction_mode,
780
+ queue_version, queue_head_position, queue_tail_position, last_sequence
781
+ ) VALUES (
782
+ destination_session_id, p_account_id, p_source_workspace_id, 'idle',
783
+ source_session.initial_message, source_session.initial_message_codec_version,
784
+ source_session.title, source_session.title_source,
785
+ source_session.instructions, source_session.policy_role,
786
+ destination_resources, '[]'::jsonb, '[]'::jsonb, '{}'::jsonb,
787
+ 'subject', p_actor_subject_id, '{"fork":true}'::jsonb,
788
+ actor_membership.id, actor_membership.subject_id,
789
+ 'user_private', 1,
790
+ p_source_session_id, source_session.authority_epoch,
791
+ source_session.visibility, clock_timestamp(), actor_membership.id,
792
+ source_session.model, source_session.reasoning_effort, source_session.latency_mode,
793
+ source_session.sandbox_backend, source_session.sandbox_os, destination_session_id,
794
+ NULL, '[]'::jsonb, '[]'::jsonb,
795
+ '{"mode":"explicit","inheritedFromSessionId":null}'::jsonb,
796
+ destination_session_id, 0, NULL, destination_depth,
797
+ destination_depth_source, NULL,
798
+ NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL,
799
+ source_session.codex_compaction_mode,
800
+ 0, 0, 0, 1
801
+ );
802
+
803
+ INSERT INTO session_history_items (
804
+ account_id, workspace_id, session_id, turn_id,
805
+ position, item, item_codec_version, active,
806
+ provider_artifact_invalidated_at, provider_artifact_invalidation_reason,
807
+ provider_artifact_invalidated_by_attempt_id, created_at
808
+ ) SELECT p_account_id, p_source_workspace_id, destination_session_id, NULL,
809
+ source_item.position, source_item.item, source_item.item_codec_version,
810
+ source_item.active, NULL, NULL, NULL, source_item.created_at
811
+ FROM opengeni_session_fork_history_spool source_item
812
+ ORDER BY source_item.position;
813
+
814
+ INSERT INTO session_events (
815
+ account_id, workspace_id, session_id, sequence, type, payload, occurred_at
816
+ ) VALUES (
817
+ p_account_id, p_source_workspace_id, destination_session_id, 1,
818
+ 'session.created', jsonb_build_object(
819
+ 'forked', true, 'sourceSessionId', p_source_session_id,
820
+ 'sourceAuthorityEpoch', source_session.authority_epoch,
821
+ 'sourceVisibility', CASE source_session.visibility
822
+ WHEN 'user_private' THEN 'private' ELSE 'workspace' END,
823
+ 'visibility', 'private', 'copiedHistoryItemCount', history_count
824
+ ), clock_timestamp()
825
+ ) RETURNING id INTO event_row_id;
826
+
827
+ PERFORM set_config('opengeni.session_activity_gate_state', 'preparing', true);
828
+ SET CONSTRAINTS ALL IMMEDIATE;
829
+ SET CONSTRAINTS sessions_activity_insert_commit_guard,
830
+ sessions_activity_update_commit_guard DEFERRED;
831
+ PERFORM set_config('opengeni.session_activity_gate_state', 'finalizing', true);
832
+ UPDATE workspace_session_activity_revisions counter
833
+ SET revision = counter.revision + 1
834
+ WHERE counter.workspace_id = p_source_workspace_id
835
+ RETURNING counter.revision INTO destination_activity_revision;
836
+ IF destination_activity_revision IS NULL THEN
837
+ RAISE EXCEPTION 'session fork destination activity counter is unavailable'
838
+ USING ERRCODE = '55000';
839
+ END IF;
840
+ UPDATE sessions destination_session SET
841
+ activity_revision = destination_activity_revision,
842
+ activity_revision_pending_xid = NULL
843
+ WHERE destination_session.id = destination_session_id
844
+ AND destination_session.activity_revision_pending_xid
845
+ = pg_current_xact_id()::text::bigint;
846
+ IF NOT FOUND THEN RAISE EXCEPTION 'session fork activity was not finalized'
847
+ USING ERRCODE = '55000'; END IF;
848
+ SET CONSTRAINTS sessions_activity_insert_commit_guard,
849
+ sessions_activity_update_commit_guard IMMEDIATE;
850
+
851
+ PERFORM set_config('opengeni.session_activity_gate_state', '', true);
852
+ PERFORM set_config('opengeni.session_activity_gate_workspace_id', '', true);
853
+ DELETE FROM session_visibility_write_capabilities capability
854
+ WHERE capability.backend_pid = pg_backend_pid()
855
+ AND capability.transaction_id = pg_current_xact_id()
856
+ AND capability.capability_id = visibility_write_capability_id;
857
+ PERFORM set_config('opengeni.session_visibility_write_capability',
858
+ CASE WHEN previous_visibility_capability IS NULL THEN '' ELSE previous_visibility_capability END,
859
+ true);
860
+
861
+ UPDATE session_command_receipts SET result = jsonb_build_object(
862
+ 'status', 'applied', 'eventId', event_row_id, 'eventSequence', 1,
863
+ 'sessionId', destination_session_id, 'workspaceId', p_source_workspace_id,
864
+ 'visibility', 'user_private', 'authorityEpoch', 1,
865
+ 'copiedHistoryItemCount', history_count
866
+ ), updated_at = clock_timestamp() WHERE id = receipt_row.id;
867
+
868
+ operation_id := receipt_row.id; event_id := event_row_id; event_sequence := 1;
869
+ session_id := destination_session_id; workspace_id := p_source_workspace_id;
870
+ visibility := 'user_private'; authority_epoch := 1;
871
+ copied_history_item_count := history_count; replay := false;
872
+ PERFORM set_config('opengeni.organization_tenancy_lifecycle',
873
+ CASE WHEN previous_lifecycle IS NULL THEN '' ELSE previous_lifecycle END, true);
874
+ RETURN NEXT;
875
+ EXCEPTION WHEN OTHERS THEN
876
+ PERFORM set_config('opengeni.organization_tenancy_lifecycle',
877
+ CASE WHEN previous_lifecycle IS NULL THEN '' ELSE previous_lifecycle END, true);
878
+ PERFORM set_config('opengeni.session_activity_gate_state',
879
+ CASE WHEN previous_gate_state IS NULL THEN '' ELSE previous_gate_state END, true);
880
+ PERFORM set_config('opengeni.session_activity_gate_workspace_id',
881
+ CASE WHEN previous_gate_workspace IS NULL THEN '' ELSE previous_gate_workspace END, true);
882
+ PERFORM set_config('opengeni.session_visibility_write_capability',
883
+ CASE WHEN previous_visibility_capability IS NULL THEN '' ELSE previous_visibility_capability END,
884
+ true);
885
+ RAISE;
886
+ END
887
+ $$;
888
+ REVOKE ALL ON FUNCTION fork_session_content(
889
+ uuid, uuid, uuid, text, uuid, text, text, text, integer
890
+ ) FROM PUBLIC;
891
+
892
+ DO $session_tenancy_runtime_grants$
893
+ BEGIN
894
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
895
+ GRANT SELECT ON session_tenancy_activations TO opengeni_app;
896
+ GRANT EXECUTE ON FUNCTION session_tenancy_product_activated(uuid, integer)
897
+ TO opengeni_app;
898
+ GRANT EXECUTE ON FUNCTION session_tenancy_any_product_activation()
899
+ TO opengeni_app;
900
+ REVOKE ALL ON FUNCTION assert_session_tenancy_quiescent(uuid, uuid, uuid, boolean)
901
+ FROM opengeni_app;
902
+ GRANT EXECUTE ON FUNCTION transition_session_visibility(
903
+ uuid, uuid, uuid, text, text, integer, text, text, integer
904
+ ) TO opengeni_app;
905
+ GRANT EXECUTE ON FUNCTION fork_session_content(
906
+ uuid, uuid, uuid, text, uuid, text, text, text, integer
907
+ ) TO opengeni_app;
908
+ END IF;
909
+ END
910
+ $session_tenancy_runtime_grants$;
911
+
912
+ COMMENT ON TABLE session_tenancy_activations IS
913
+ 'Forward-only per-organization activation receipt for session visibility mutation and private fork.';
914
+ COMMENT ON FUNCTION transition_session_visibility(
915
+ uuid, uuid, uuid, text, text, integer, text, text, integer
916
+ ) IS 'Activated owner-only, proven-quiescent visibility transition. It never cancels accepted work or wakes a workflow.';
917
+ COMMENT ON FUNCTION fork_session_content(
918
+ uuid, uuid, uuid, text, uuid, text, text, text, integer
919
+ ) IS 'Activated same-workspace private fork of canonical durable content into a new root and singleton sandbox group.';