@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,198 @@
1
+ -- deployment-mode: rolling
2
+ SET lock_timeout = '5s';
3
+ SET statement_timeout = '60s';
4
+
5
+ -- The onboarding-proposal draft validation trigger bound a knowledge-proposal
6
+ -- draft to `current_setting('opengeni.subject_id')`. On the real first-party
7
+ -- MCP path that GUC carries the *service* subject (`worker:first-party-mcp`)
8
+ -- while the frozen human travels in `opengeni.initiating_human_subject_id`,
9
+ -- so every real agent `remember` rule call failed the draft check with
10
+ -- SQLSTATE 23514 regardless of workspace state. Compare against the canonical
11
+ -- initiating-human GUC first (the pattern every authority migration since
12
+ -- 0225 uses), falling back to `opengeni.subject_id` for direct human writers
13
+ -- that never set the initiating-human GUC. The rest of the function body is
14
+ -- byte-identical to migration 0269.
15
+
16
+ CREATE OR REPLACE FUNCTION workspace_instruction_policy_validate_onboarding_proposal()
17
+ RETURNS trigger LANGUAGE plpgsql AS $$
18
+ BEGIN
19
+ IF NEW.baseline_revision_id IS NULL THEN
20
+ IF EXISTS (
21
+ SELECT 1 FROM workspace_instruction_policy_heads head
22
+ WHERE head.account_id = NEW.account_id
23
+ AND head.workspace_id = NEW.workspace_id
24
+ AND head.kind = NEW.kind
25
+ AND head.scope = NEW.scope
26
+ AND head.role_key IS NOT DISTINCT FROM NEW.role_key
27
+ ) THEN
28
+ RAISE EXCEPTION 'instruction-policy proposal must capture the exact active head baseline'
29
+ USING ERRCODE = '23514';
30
+ END IF;
31
+ IF NOT (
32
+ (
33
+ NEW.baseline_activation_version = 0
34
+ AND NOT EXISTS (
35
+ SELECT 1 FROM workspace_instruction_policy_heads head
36
+ WHERE head.account_id = NEW.account_id
37
+ AND head.workspace_id = NEW.workspace_id
38
+ AND head.kind = NEW.kind
39
+ AND head.scope = NEW.scope
40
+ AND head.role_key IS NOT DISTINCT FROM NEW.role_key
41
+ )
42
+ AND NOT EXISTS (
43
+ SELECT 1 FROM workspace_instruction_policy_deactivation_events event
44
+ WHERE event.account_id = NEW.account_id
45
+ AND event.workspace_id = NEW.workspace_id
46
+ AND event.kind = NEW.kind
47
+ AND event.scope = NEW.scope
48
+ AND event.role_key IS NOT DISTINCT FROM NEW.role_key
49
+ )
50
+ ) OR (
51
+ NEW.baseline_activation_version > 0
52
+ AND NOT EXISTS (
53
+ SELECT 1 FROM workspace_instruction_policy_heads head
54
+ WHERE head.account_id = NEW.account_id
55
+ AND head.workspace_id = NEW.workspace_id
56
+ AND head.kind = NEW.kind
57
+ AND head.scope = NEW.scope
58
+ AND head.role_key IS NOT DISTINCT FROM NEW.role_key
59
+ )
60
+ AND EXISTS (
61
+ SELECT 1 FROM workspace_instruction_policy_deactivation_events event
62
+ WHERE event.account_id = NEW.account_id
63
+ AND event.workspace_id = NEW.workspace_id
64
+ AND event.kind = NEW.kind
65
+ AND event.scope = NEW.scope
66
+ AND event.role_key IS NOT DISTINCT FROM NEW.role_key
67
+ AND event.activation_version = NEW.baseline_activation_version
68
+ AND NOT EXISTS (
69
+ SELECT 1 FROM workspace_instruction_policy_deactivation_events newer
70
+ WHERE newer.account_id = event.account_id
71
+ AND newer.workspace_id = event.workspace_id
72
+ AND newer.kind = event.kind
73
+ AND newer.scope = event.scope
74
+ AND newer.role_key IS NOT DISTINCT FROM event.role_key
75
+ AND newer.activation_version > event.activation_version
76
+ )
77
+ )
78
+ )
79
+ ) THEN
80
+ RAISE EXCEPTION 'instruction-policy proposal must capture the exact inactive boundary'
81
+ USING ERRCODE = '23514';
82
+ END IF;
83
+ ELSIF NOT EXISTS (
84
+ SELECT 1 FROM workspace_instruction_policy_heads head
85
+ WHERE head.account_id = NEW.account_id
86
+ AND head.workspace_id = NEW.workspace_id
87
+ AND head.kind = NEW.kind
88
+ AND head.scope = NEW.scope
89
+ AND head.role_key IS NOT DISTINCT FROM NEW.role_key
90
+ AND head.revision_id = NEW.baseline_revision_id
91
+ AND head.revision = NEW.baseline_revision
92
+ AND head.content_hash = NEW.baseline_content_hash
93
+ AND head.activation_version = NEW.baseline_activation_version
94
+ AND head.activated_at = NEW.baseline_activated_at
95
+ ) THEN
96
+ RAISE EXCEPTION 'instruction-policy proposal must capture the exact active head baseline'
97
+ USING ERRCODE = '23514';
98
+ END IF;
99
+
100
+ IF NEW.baseline_revision_id IS NOT NULL AND NOT EXISTS (
101
+ SELECT 1 FROM workspace_instruction_policy_revisions revision
102
+ WHERE revision.id = NEW.baseline_revision_id
103
+ AND revision.account_id = NEW.account_id
104
+ AND revision.workspace_id = NEW.workspace_id
105
+ AND revision.kind = NEW.kind
106
+ AND revision.scope = NEW.scope
107
+ AND revision.role_key IS NOT DISTINCT FROM NEW.role_key
108
+ AND revision.revision = NEW.baseline_revision
109
+ AND revision.content_hash = NEW.baseline_content_hash
110
+ ) THEN
111
+ RAISE EXCEPTION 'instruction-policy proposal has an invalid baseline revision'
112
+ USING ERRCODE = '23514';
113
+ END IF;
114
+
115
+ IF NOT EXISTS (
116
+ SELECT 1 FROM workspace_instruction_policy_revisions revision
117
+ WHERE revision.id = NEW.draft_revision_id
118
+ AND revision.operation_id = NEW.operation_id
119
+ AND revision.request_fingerprint = NEW.request_fingerprint
120
+ AND revision.account_id = NEW.account_id
121
+ AND revision.workspace_id = NEW.workspace_id
122
+ AND revision.kind = NEW.kind
123
+ AND revision.scope = NEW.scope
124
+ AND revision.role_key IS NOT DISTINCT FROM NEW.role_key
125
+ AND revision.revision = NEW.draft_revision
126
+ AND revision.content_hash = NEW.draft_content_hash
127
+ AND revision.supersedes_revision_id IS NOT DISTINCT FROM NEW.baseline_revision_id
128
+ AND revision.created_by_subject_id = NEW.created_by_subject_id
129
+ AND (
130
+ (
131
+ revision.provenance_source = 'onboarding'
132
+ AND revision.provenance_source_id = NEW.id::text
133
+ ) OR (
134
+ revision.provenance_source = 'knowledge_proposal'
135
+ AND revision.provenance_source_id = NEW.source_id
136
+ AND NEW.source_version = NEW.draft_content_hash
137
+ AND EXISTS (
138
+ SELECT 1 FROM knowledge_change_proposals proposal
139
+ WHERE proposal.id::text = NEW.source_id
140
+ AND proposal.account_id = NEW.account_id
141
+ AND proposal.scope_kind = 'workspace'
142
+ AND proposal.scope_workspace_id = NEW.workspace_id
143
+ AND proposal.scope_subject_id IS NULL
144
+ AND proposal.target_kind = 'instruction_policy'
145
+ AND proposal.target_scope = NEW.scope
146
+ AND proposal.target_key IS NOT DISTINCT FROM CASE
147
+ WHEN NEW.scope = 'role' THEN NEW.role_key
148
+ ELSE NULL
149
+ END
150
+ AND proposal.content_hash = NEW.source_version
151
+ AND proposal.actor_kind = 'service'
152
+ AND proposal.actor_subject_id = NEW.created_by_subject_id
153
+ AND proposal.initiating_human_subject_id = COALESCE(
154
+ NULLIF(current_setting('opengeni.initiating_human_subject_id', true), ''),
155
+ current_setting('opengeni.subject_id', true)
156
+ )
157
+ AND proposal.claim_id IS NOT NULL
158
+ AND proposal.evidence_id IS NOT NULL
159
+ AND proposal.status = 'proposed'
160
+ )
161
+ )
162
+ )
163
+ ) THEN
164
+ RAISE EXCEPTION 'instruction-policy proposal must identify its exact inactive draft'
165
+ USING ERRCODE = '23514';
166
+ END IF;
167
+
168
+ IF EXISTS (
169
+ SELECT 1 FROM workspace_instruction_policy_heads head
170
+ WHERE head.account_id = NEW.account_id
171
+ AND head.workspace_id = NEW.workspace_id
172
+ AND head.revision_id = NEW.draft_revision_id
173
+ ) OR EXISTS (
174
+ SELECT 1 FROM workspace_instruction_policy_activation_events event
175
+ WHERE event.account_id = NEW.account_id
176
+ AND event.workspace_id = NEW.workspace_id
177
+ AND event.new_revision_id = NEW.draft_revision_id
178
+ ) THEN
179
+ RAISE EXCEPTION 'instruction-policy proposal must identify a never-activated inactive draft'
180
+ USING ERRCODE = '23514';
181
+ END IF;
182
+ RETURN NEW;
183
+ END;
184
+ $$;
185
+
186
+
187
+ -- CREATE OR REPLACE drops the previously pinned search_path; re-pin it exactly
188
+ -- as migration 0269's hardening block did.
189
+ DO $onboarding_proposal_initiating_human_hardening$
190
+ DECLARE
191
+ target_schema text := current_schema();
192
+ BEGIN
193
+ EXECUTE format(
194
+ 'ALTER FUNCTION %I.workspace_instruction_policy_validate_onboarding_proposal() '
195
+ || 'SET search_path = pg_catalog, %I, pg_temp', target_schema, target_schema
196
+ );
197
+ END
198
+ $onboarding_proposal_initiating_human_hardening$;
@@ -0,0 +1,393 @@
1
+ -- deployment-mode: rolling
2
+ -- Migration 0277: every admitted persistable /workspace writer and every
3
+ -- retained process carries its own exact authority tuple - causal initiator,
4
+ -- initiating human, organization membership (the grant identity) and its
5
+ -- observed authorization revision, plus the session tenancy epoch/visibility
6
+ -- and owning membership that were true when the operation was admitted.
7
+ --
8
+ -- Before this migration only the `turn` actor was attributable, through
9
+ -- `session_turn_attempts` (migration 0222) and `session_turns` (migration
10
+ -- 0096). `direct` (API request) and `process` (retained yielded shell) actors
11
+ -- recorded no owner, no causal initiator, no visibility and no authority
12
+ -- epoch, so organization offboarding (migration 0263) had nothing to fence and
13
+ -- `docs/organization-tenancy.md` explicitly deferred that attribution. This is
14
+ -- that attribution; it stores identities and epochs only and never a secret
15
+ -- value.
16
+ --
17
+ -- Rolling window. The new columns are nullable or carry the explicit
18
+ -- `legacy_unattributed` / `unattributed-legacy` sentinels, so a pre-0277
19
+ -- API/worker image keeps inserting admissions and retained processes exactly
20
+ -- as before and lands on the sentinel. Historical and mixed-window rows are
21
+ -- promoted only where the frozen turn/attempt snapshot makes the authority
22
+ -- unambiguous; nothing is inferred for a `direct` or `process` row that never
23
+ -- had an owner. A sentinel row is refused by NEW admission in the post-0277
24
+ -- application (fail closed), but the live provider process behind it is never
25
+ -- terminated by this migration or by that refusal - the user sees a typed
26
+ -- rejection on the next write instead of losing a running shell.
27
+
28
+ SET LOCAL lock_timeout = '5s';
29
+ SET LOCAL statement_timeout = '10min';
30
+
31
+ -- 1. Expand -------------------------------------------------------------
32
+
33
+ ALTER TABLE "sandbox_workspace_mutation_admissions"
34
+ ADD COLUMN IF NOT EXISTS "initiator_kind" text NOT NULL DEFAULT 'legacy_unattributed',
35
+ ADD COLUMN IF NOT EXISTS "initiator_subject_id" text NOT NULL DEFAULT 'unattributed-legacy',
36
+ ADD COLUMN IF NOT EXISTS "initiating_human_subject_id" text,
37
+ ADD COLUMN IF NOT EXISTS "initiator_organization_membership_id" uuid,
38
+ ADD COLUMN IF NOT EXISTS "initiator_authorization_revision" bigint,
39
+ ADD COLUMN IF NOT EXISTS "authority_epoch" integer,
40
+ ADD COLUMN IF NOT EXISTS "authority_visibility" text,
41
+ ADD COLUMN IF NOT EXISTS "authority_owner_organization_membership_id" uuid;
42
+
43
+ ALTER TABLE "sandbox_retained_processes"
44
+ ADD COLUMN IF NOT EXISTS "initiator_kind" text NOT NULL DEFAULT 'legacy_unattributed',
45
+ ADD COLUMN IF NOT EXISTS "initiator_subject_id" text NOT NULL DEFAULT 'unattributed-legacy',
46
+ ADD COLUMN IF NOT EXISTS "initiating_human_subject_id" text,
47
+ ADD COLUMN IF NOT EXISTS "initiator_organization_membership_id" uuid,
48
+ ADD COLUMN IF NOT EXISTS "initiator_authorization_revision" bigint,
49
+ ADD COLUMN IF NOT EXISTS "authority_epoch" integer,
50
+ ADD COLUMN IF NOT EXISTS "authority_visibility" text,
51
+ ADD COLUMN IF NOT EXISTS "authority_owner_organization_membership_id" uuid;
52
+
53
+ -- The grant identity is a real membership row in the same organization. It is
54
+ -- never cascaded away: offboarding keeps the membership row (revoked) exactly
55
+ -- so retained authority evidence stays resolvable.
56
+ ALTER TABLE "sandbox_workspace_mutation_admissions"
57
+ ADD CONSTRAINT "sandbox_workspace_mutation_admissions_initiator_membership_fk"
58
+ FOREIGN KEY ("initiator_organization_membership_id", "account_id")
59
+ REFERENCES "organization_memberships" ("id", "account_id")
60
+ ON DELETE RESTRICT NOT VALID,
61
+ ADD CONSTRAINT "sandbox_workspace_mutation_admissions_authority_owner_fk"
62
+ FOREIGN KEY ("authority_owner_organization_membership_id", "account_id")
63
+ REFERENCES "organization_memberships" ("id", "account_id")
64
+ ON DELETE RESTRICT NOT VALID;
65
+
66
+ ALTER TABLE "sandbox_retained_processes"
67
+ ADD CONSTRAINT "sandbox_retained_processes_initiator_membership_fk"
68
+ FOREIGN KEY ("initiator_organization_membership_id", "account_id")
69
+ REFERENCES "organization_memberships" ("id", "account_id")
70
+ ON DELETE RESTRICT NOT VALID,
71
+ ADD CONSTRAINT "sandbox_retained_processes_authority_owner_fk"
72
+ FOREIGN KEY ("authority_owner_organization_membership_id", "account_id")
73
+ REFERENCES "organization_memberships" ("id", "account_id")
74
+ ON DELETE RESTRICT NOT VALID;
75
+
76
+ -- 2. Promote the unambiguous historical rows ----------------------------
77
+ -- The retained-process identity constraint trigger is INITIALLY DEFERRED.
78
+ -- Fire it per-row instead: a deferred event queued by this backfill would
79
+ -- otherwise make the later ALTER TABLE in this same transaction fail with
80
+ -- "pending trigger events" on any deployment that has retained processes.
81
+ -- The promotion touches only the new authority columns, so the re-validation
82
+ -- against the parent admission is unaffected.
83
+ SET CONSTRAINTS "sandbox_retained_processes_identity_v2" IMMEDIATE;
84
+
85
+ -- Only a `turn` actor has a frozen accepted-authority snapshot. Copy it
86
+ -- verbatim: the attempt owns tenancy epoch/visibility/owner membership and the
87
+ -- logical turn owns the causal initiator. Nothing here derives an owner from
88
+ -- the session creator, the current membership table, or provenance metadata.
89
+
90
+ UPDATE "sandbox_workspace_mutation_admissions" AS admission
91
+ SET
92
+ -- A turn frozen before migration 0096 carries the subject sentinel with the
93
+ -- default 'service' kind. That is honestly "no causal initiator", so it is
94
+ -- normalized to the explicit sentinel rather than promoted to a real one.
95
+ "initiator_kind" = CASE
96
+ WHEN turn."initiator_subject_id" = 'unattributed-legacy' THEN 'legacy_unattributed'
97
+ ELSE turn."initiator_kind"
98
+ END,
99
+ "initiator_subject_id" = turn."initiator_subject_id",
100
+ "initiating_human_subject_id" = CASE
101
+ WHEN turn."initiator_subject_id" = 'unattributed-legacy' THEN NULL
102
+ ELSE turn."initiating_human_subject_id"
103
+ END,
104
+ "authority_epoch" = attempt."authority_epoch",
105
+ "authority_visibility" = attempt."authority_visibility",
106
+ "authority_owner_organization_membership_id" =
107
+ attempt."authority_owner_organization_membership_id"
108
+ FROM "session_turn_attempts" AS attempt
109
+ JOIN "session_turns" AS turn
110
+ ON turn."workspace_id" = attempt."workspace_id"
111
+ AND turn."id" = attempt."turn_id"
112
+ WHERE admission."actor_kind" = 'turn'
113
+ AND admission."initiator_kind" = 'legacy_unattributed'
114
+ AND attempt."workspace_id" = admission."workspace_id"
115
+ AND attempt."id" = admission."attempt_id"
116
+ AND admission."turn_id" = attempt."turn_id";
117
+
118
+ UPDATE "sandbox_retained_processes" AS process
119
+ SET
120
+ -- A turn frozen before migration 0096 carries the subject sentinel with the
121
+ -- default 'service' kind. That is honestly "no causal initiator", so it is
122
+ -- normalized to the explicit sentinel rather than promoted to a real one.
123
+ "initiator_kind" = CASE
124
+ WHEN turn."initiator_subject_id" = 'unattributed-legacy' THEN 'legacy_unattributed'
125
+ ELSE turn."initiator_kind"
126
+ END,
127
+ "initiator_subject_id" = turn."initiator_subject_id",
128
+ "initiating_human_subject_id" = CASE
129
+ WHEN turn."initiator_subject_id" = 'unattributed-legacy' THEN NULL
130
+ ELSE turn."initiating_human_subject_id"
131
+ END,
132
+ "authority_epoch" = attempt."authority_epoch",
133
+ "authority_visibility" = attempt."authority_visibility",
134
+ "authority_owner_organization_membership_id" =
135
+ attempt."authority_owner_organization_membership_id"
136
+ FROM "session_turn_attempts" AS attempt
137
+ JOIN "session_turns" AS turn
138
+ ON turn."workspace_id" = attempt."workspace_id"
139
+ AND turn."id" = attempt."turn_id"
140
+ WHERE process."owner_actor_kind" = 'turn'
141
+ AND process."initiator_kind" = 'legacy_unattributed'
142
+ AND attempt."workspace_id" = process."workspace_id"
143
+ AND attempt."id" = process."owner_attempt_id"
144
+ AND process."owner_turn_id" = attempt."turn_id";
145
+
146
+ -- The membership that authorized a promoted human initiator is resolvable
147
+ -- exactly (organization_memberships is unique per account+subject). A machine
148
+ -- or service initiator legitimately has none. The observed revision is audit
149
+ -- evidence, not a fence: a role change is not a revocation.
150
+ UPDATE "sandbox_workspace_mutation_admissions" AS admission
151
+ SET
152
+ "initiator_organization_membership_id" = membership."id",
153
+ "initiator_authorization_revision" = membership."authorization_revision"
154
+ FROM "organization_memberships" AS membership
155
+ WHERE admission."initiator_kind" <> 'legacy_unattributed'
156
+ AND admission."initiating_human_subject_id" IS NOT NULL
157
+ AND admission."initiator_organization_membership_id" IS NULL
158
+ AND membership."account_id" = admission."account_id"
159
+ AND membership."subject_id" = admission."initiating_human_subject_id";
160
+
161
+ UPDATE "sandbox_retained_processes" AS process
162
+ SET
163
+ "initiator_organization_membership_id" = membership."id",
164
+ "initiator_authorization_revision" = membership."authorization_revision"
165
+ FROM "organization_memberships" AS membership
166
+ WHERE process."initiator_kind" <> 'legacy_unattributed'
167
+ AND process."initiating_human_subject_id" IS NOT NULL
168
+ AND process."initiator_organization_membership_id" IS NULL
169
+ AND membership."account_id" = process."account_id"
170
+ AND membership."subject_id" = process."initiating_human_subject_id";
171
+
172
+ -- 3. Contract -----------------------------------------------------------
173
+ -- The tuple has two INDEPENDENT halves, and conflating them was the mistake
174
+ -- worth avoiding: a legacy turn frozen before migration 0096 legitimately has
175
+ -- no causal initiator while still having a perfectly real session tenancy
176
+ -- epoch and visibility. Requiring both together would fence ordinary turn work
177
+ -- on those sessions for no security gain.
178
+ --
179
+ -- * initiator half - either a complete causal identity, or the explicit
180
+ -- `legacy_unattributed` sentinel with every initiator field absent, so a
181
+ -- partial grant can never masquerade as a whole one.
182
+ -- * tenancy half - either the complete epoch/visibility/owner triple, or
183
+ -- nothing. A private session must name its owning membership, exactly as
184
+ -- `sessions` and `session_turn_attempts` already require.
185
+ --
186
+ -- Only a MISSING TENANCY HALF marks a row as predating this migration, and
187
+ -- that is the condition that fails a retained process closed.
188
+
189
+ ALTER TABLE "sandbox_workspace_mutation_admissions"
190
+ ADD CONSTRAINT "sandbox_workspace_mutation_admissions_initiator_check"
191
+ CHECK (
192
+ (
193
+ "initiator_kind" = 'legacy_unattributed'
194
+ AND "initiator_subject_id" = 'unattributed-legacy'
195
+ AND "initiating_human_subject_id" IS NULL
196
+ AND "initiator_organization_membership_id" IS NULL
197
+ AND "initiator_authorization_revision" IS NULL
198
+ ) OR (
199
+ "initiator_kind" IN ('subject', 'service')
200
+ AND length(btrim("initiator_subject_id")) BETWEEN 1 AND 1024
201
+ AND "initiator_subject_id" <> 'unattributed-legacy'
202
+ AND (
203
+ "initiating_human_subject_id" IS NULL
204
+ OR length(btrim("initiating_human_subject_id")) BETWEEN 1 AND 1024
205
+ )
206
+ AND (
207
+ "initiator_organization_membership_id" IS NULL
208
+ OR "initiating_human_subject_id" IS NOT NULL
209
+ )
210
+ AND (
211
+ "initiator_authorization_revision" IS NULL
212
+ OR (
213
+ "initiator_authorization_revision" > 0
214
+ AND "initiator_organization_membership_id" IS NOT NULL
215
+ )
216
+ )
217
+ )
218
+ ) NOT VALID;
219
+
220
+ ALTER TABLE "sandbox_workspace_mutation_admissions"
221
+ ADD CONSTRAINT "sandbox_workspace_mutation_admissions_tenancy_check"
222
+ CHECK (
223
+ (
224
+ "authority_epoch" IS NULL
225
+ AND "authority_visibility" IS NULL
226
+ AND "authority_owner_organization_membership_id" IS NULL
227
+ ) OR (
228
+ "authority_epoch" IS NOT NULL
229
+ AND "authority_visibility" IS NOT NULL
230
+ AND "authority_epoch" > 0
231
+ AND "authority_visibility" IN ('user_private', 'workspace_shared')
232
+ AND (
233
+ "authority_visibility" <> 'user_private'
234
+ OR "authority_owner_organization_membership_id" IS NOT NULL
235
+ )
236
+ )
237
+ ) NOT VALID;
238
+
239
+ ALTER TABLE "sandbox_retained_processes"
240
+ ADD CONSTRAINT "sandbox_retained_processes_initiator_check"
241
+ CHECK (
242
+ (
243
+ "initiator_kind" = 'legacy_unattributed'
244
+ AND "initiator_subject_id" = 'unattributed-legacy'
245
+ AND "initiating_human_subject_id" IS NULL
246
+ AND "initiator_organization_membership_id" IS NULL
247
+ AND "initiator_authorization_revision" IS NULL
248
+ ) OR (
249
+ "initiator_kind" IN ('subject', 'service')
250
+ AND length(btrim("initiator_subject_id")) BETWEEN 1 AND 1024
251
+ AND "initiator_subject_id" <> 'unattributed-legacy'
252
+ AND (
253
+ "initiating_human_subject_id" IS NULL
254
+ OR length(btrim("initiating_human_subject_id")) BETWEEN 1 AND 1024
255
+ )
256
+ AND (
257
+ "initiator_organization_membership_id" IS NULL
258
+ OR "initiating_human_subject_id" IS NOT NULL
259
+ )
260
+ AND (
261
+ "initiator_authorization_revision" IS NULL
262
+ OR (
263
+ "initiator_authorization_revision" > 0
264
+ AND "initiator_organization_membership_id" IS NOT NULL
265
+ )
266
+ )
267
+ )
268
+ ) NOT VALID;
269
+
270
+ ALTER TABLE "sandbox_retained_processes"
271
+ ADD CONSTRAINT "sandbox_retained_processes_tenancy_check"
272
+ CHECK (
273
+ (
274
+ "authority_epoch" IS NULL
275
+ AND "authority_visibility" IS NULL
276
+ AND "authority_owner_organization_membership_id" IS NULL
277
+ ) OR (
278
+ "authority_epoch" IS NOT NULL
279
+ AND "authority_visibility" IS NOT NULL
280
+ AND "authority_epoch" > 0
281
+ AND "authority_visibility" IN ('user_private', 'workspace_shared')
282
+ AND (
283
+ "authority_visibility" <> 'user_private'
284
+ OR "authority_owner_organization_membership_id" IS NOT NULL
285
+ )
286
+ )
287
+ ) NOT VALID;
288
+
289
+ ALTER TABLE "sandbox_workspace_mutation_admissions"
290
+ VALIDATE CONSTRAINT "sandbox_workspace_mutation_admissions_initiator_check";
291
+ ALTER TABLE "sandbox_workspace_mutation_admissions"
292
+ VALIDATE CONSTRAINT "sandbox_workspace_mutation_admissions_tenancy_check";
293
+ ALTER TABLE "sandbox_retained_processes"
294
+ VALIDATE CONSTRAINT "sandbox_retained_processes_initiator_check";
295
+ ALTER TABLE "sandbox_retained_processes"
296
+ VALIDATE CONSTRAINT "sandbox_retained_processes_tenancy_check";
297
+
298
+ ALTER TABLE "sandbox_workspace_mutation_admissions"
299
+ VALIDATE CONSTRAINT "sandbox_workspace_mutation_admissions_initiator_membership_fk";
300
+ ALTER TABLE "sandbox_workspace_mutation_admissions"
301
+ VALIDATE CONSTRAINT "sandbox_workspace_mutation_admissions_authority_owner_fk";
302
+ ALTER TABLE "sandbox_retained_processes"
303
+ VALIDATE CONSTRAINT "sandbox_retained_processes_initiator_membership_fk";
304
+ ALTER TABLE "sandbox_retained_processes"
305
+ VALIDATE CONSTRAINT "sandbox_retained_processes_authority_owner_fk";
306
+
307
+ -- 4. The grant-identity seam --------------------------------------------
308
+ -- `organization_memberships` stays FORCE-RLS with no runtime DML and no
309
+ -- runtime SELECT (migration 0263). Admission still has to answer one narrow
310
+ -- question inside its own transaction - "is this exact subject's grant still
311
+ -- live, and which membership is it?" - so expose exactly that and nothing
312
+ -- more: an id, a lifecycle status and an authorization revision, fenced to the
313
+ -- caller's own tenant. No role, no personal workspace, no retention state, no
314
+ -- other subject's row, and never a secret value.
315
+ --
316
+ -- The existing 0263 assertion cannot serve this: it is scoped to the CURRENT
317
+ -- RLS subject, while a workspace writer must resolve the initiator frozen on
318
+ -- an accepted turn - a subject the worker's transaction is not acting as.
319
+
320
+ CREATE FUNCTION resolve_workspace_writer_grant_identity(
321
+ p_account_id uuid,
322
+ p_subject_id text
323
+ ) RETURNS TABLE(membership_id uuid, membership_status text, authorization_revision bigint)
324
+ LANGUAGE sql
325
+ STABLE
326
+ SECURITY DEFINER
327
+ SET search_path FROM CURRENT
328
+ AS $body$
329
+ SELECT membership.id, membership.status, membership.authorization_revision
330
+ FROM organization_memberships membership
331
+ WHERE p_account_id IS NOT NULL
332
+ AND p_subject_id IS NOT NULL
333
+ AND membership.account_id = p_account_id
334
+ -- Tenant fence: a caller can only resolve grants inside the organization
335
+ -- its own RLS context already establishes.
336
+ AND membership.account_id = opengeni_private.current_account_id()
337
+ AND membership.subject_id = p_subject_id
338
+ $body$;
339
+
340
+ REVOKE ALL ON FUNCTION resolve_workspace_writer_grant_identity(uuid,text) FROM PUBLIC;
341
+
342
+ DO $workspace_writer_grant_seam$
343
+ DECLARE
344
+ data_schema text := pg_catalog.current_schema();
345
+ BEGIN
346
+ EXECUTE format(
347
+ 'ALTER FUNCTION %I.resolve_workspace_writer_grant_identity(uuid,text) '
348
+ || 'SET search_path = pg_catalog, %I, pg_temp',
349
+ data_schema, data_schema
350
+ );
351
+ -- Same conditional grant as migration 0263: dedicated-schema/embedded
352
+ -- deployments replay this ledger without the opengeni_app role because the
353
+ -- host runs OpenGeni's queries over its own role and never uses it.
354
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
355
+ EXECUTE format(
356
+ 'GRANT EXECUTE ON FUNCTION %I.resolve_workspace_writer_grant_identity(uuid,text) '
357
+ || 'TO opengeni_app',
358
+ data_schema
359
+ );
360
+ END IF;
361
+ END
362
+ $workspace_writer_grant_seam$;
363
+
364
+ -- 5. Revocation sweep support -------------------------------------------
365
+ -- Membership revocation and workspace-membership removal need the live
366
+ -- unsettled writers owned by one exact grant identity, cheaply.
367
+
368
+ CREATE INDEX IF NOT EXISTS "sandbox_workspace_mutation_admissions_initiator_idx"
369
+ ON "sandbox_workspace_mutation_admissions"
370
+ ("account_id", "initiator_organization_membership_id")
371
+ WHERE "settled_at" IS NULL;
372
+
373
+ CREATE INDEX IF NOT EXISTS "sandbox_retained_processes_initiator_idx"
374
+ ON "sandbox_retained_processes"
375
+ ("account_id", "initiator_organization_membership_id")
376
+ WHERE "state" = 'active';
377
+
378
+ CREATE INDEX IF NOT EXISTS "sandbox_retained_processes_initiating_human_idx"
379
+ ON "sandbox_retained_processes" ("account_id", "initiating_human_subject_id")
380
+ WHERE "state" = 'active';
381
+
382
+ COMMENT ON COLUMN "sandbox_workspace_mutation_admissions"."initiator_kind" IS
383
+ 'Causal principal kind for this admission: subject | service, or the '
384
+ 'legacy_unattributed sentinel for a pre-0277 row whose authority was never '
385
+ 'recorded. The sentinel is refused by new admission and is never written by '
386
+ 'a post-0277 writer.';
387
+ COMMENT ON COLUMN "sandbox_workspace_mutation_admissions"."authority_epoch" IS
388
+ 'Session tenancy authority epoch observed when the operation was admitted. '
389
+ 'Identity and epoch only - never a secret value.';
390
+ COMMENT ON COLUMN "sandbox_retained_processes"."initiator_kind" IS
391
+ 'Causal principal kind frozen when the process was retained. A '
392
+ 'legacy_unattributed process keeps running; only its next workspace mutation '
393
+ 'is refused.';