@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,609 @@
1
+ -- deployment-mode: rolling
2
+ -- Migration 0298: read-only organization tenancy PARITY seam (phase E).
3
+ --
4
+ -- Phase D shipped `inventory_organization_tenancy` (migration 0285): content-
5
+ -- free counts of every legacy-attribution population. Phase E needs the next
6
+ -- thing an operator can gate a cutover on - a shadow parity CHECK that proves
7
+ -- the structural tenancy invariants actually hold before any authority is
8
+ -- activated:
9
+ --
10
+ -- * organization / membership / workspace consistency;
11
+ -- * one personal workspace per active membership, and that workspace must
12
+ -- stay membership-row-free (a persisted `workspace_memberships` row there
13
+ -- is exactly how the owner-only projection would widen into delegable
14
+ -- access);
15
+ -- * stable authority uniqueness (one owning membership per user resource);
16
+ -- * provider-account collision rules (a disputed login binding must have
17
+ -- disputed its identity; an identity's active binding must be its own);
18
+ -- * session ownership provenance; and
19
+ -- * zero partial delegations (mode/session/epoch fences, live authority,
20
+ -- live owning membership, and a session fence that is never ahead of the
21
+ -- session it fences).
22
+ --
23
+ -- Plus the read-only shadow comparison between the legacy effective scope
24
+ -- (everything workspace-owned) and the proposed effective scope (the row's
25
+ -- declared authority): `user_scoped_resource_live_anchor` reports every row
26
+ -- that CLAIMS user authority without a live anchor. Such a row must resolve to
27
+ -- workspace authority or deny - never to user authority. The seam reports; it
28
+ -- never repairs, never widens, and never resolves an ambiguity.
29
+ --
30
+ -- STRICTLY READ-ONLY. The function's only writes are the claim/release of its
31
+ -- own transaction-local private capability row - never any inspected table.
32
+ -- Output is content-free: bounded row UUIDs as failure evidence, integers
33
+ -- everywhere else. No subjects, names, keys, credentials, or values.
34
+ --
35
+ -- FORCE RLS + SECURITY DEFINER note: identical posture to 0285. Every
36
+ -- inspected table is FORCE ROW LEVEL SECURITY and most of their ordinary
37
+ -- policies gate on an EXACT single workspace_id (or, for the four
38
+ -- organization-tenancy authority tables and the canonical identity tables, on
39
+ -- `USING (false)` / a lifecycle marker), which an organization-wide read can
40
+ -- never satisfy. Rather than widen any of those policies, this migration takes
41
+ -- 0254/0285's established capability-row pattern and gives the parity seam its
42
+ -- OWN transaction-scoped, migration-owner-only capability. It is deliberately
43
+ -- separate from 0285's capability so that `inventory_organization_tenancy`
44
+ -- gains no visibility it was not reviewed for. Account scoping is enforced by
45
+ -- this function's own `account_id = p_organization_id` predicates.
46
+
47
+ SET LOCAL lock_timeout = '5s';
48
+ SET LOCAL statement_timeout = '5min';
49
+
50
+ CREATE TABLE opengeni_private.organization_tenancy_parity_capabilities (
51
+ "backend_pid" integer NOT NULL,
52
+ "transaction_id" xid8 NOT NULL,
53
+ CONSTRAINT "organization_tenancy_parity_capabilities_pk" PRIMARY KEY (
54
+ "backend_pid", "transaction_id"
55
+ )
56
+ );
57
+ REVOKE ALL ON TABLE opengeni_private.organization_tenancy_parity_capabilities FROM PUBLIC;
58
+
59
+ CREATE OR REPLACE FUNCTION opengeni_private.organization_tenancy_parity_capability_active()
60
+ RETURNS boolean
61
+ LANGUAGE sql
62
+ STABLE
63
+ SECURITY DEFINER
64
+ SET search_path = pg_catalog
65
+ AS $capability_active$
66
+ SELECT EXISTS (
67
+ SELECT 1
68
+ FROM opengeni_private.organization_tenancy_parity_capabilities capability
69
+ WHERE capability.backend_pid = pg_catalog.pg_backend_pid()
70
+ AND capability.transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
71
+ )
72
+ $capability_active$;
73
+ REVOKE ALL ON FUNCTION
74
+ opengeni_private.organization_tenancy_parity_capability_active() FROM PUBLIC;
75
+
76
+ DO $organization_tenancy_parity_capability_policies$
77
+ DECLARE
78
+ data_schema text := current_schema();
79
+ migration_owner text := current_user;
80
+ table_name text;
81
+ BEGIN
82
+ FOREACH table_name IN ARRAY ARRAY[
83
+ 'sessions',
84
+ 'workspace_variable_sets',
85
+ 'rigs',
86
+ 'enrollments',
87
+ 'connections',
88
+ 'documents',
89
+ 'codex_subscription_credentials',
90
+ 'sandbox_workspace_mutation_admissions',
91
+ 'sandbox_retained_processes',
92
+ 'organization_memberships',
93
+ 'organization_user_resource_authorities',
94
+ 'organization_user_resource_grants',
95
+ 'connection_use_audit_facts',
96
+ 'canonical_human_identities',
97
+ 'canonical_human_identity_subjects',
98
+ 'canonical_human_login_bindings'
99
+ ] LOOP
100
+ -- Existence-guarded on purpose. A maintenance migration earlier in the
101
+ -- chain (0264, which introduces connection_use_audit_facts) can legitimately
102
+ -- be skipped or aborted, and the chain still runs forward over that partial
103
+ -- history. Creating a policy on an absent relation would break the whole
104
+ -- chain there; the parity seam simply cannot be executed against such a
105
+ -- database, which is already true of every other reader of that table.
106
+ IF to_regclass(format('%I.%I', data_schema, table_name)) IS NOT NULL THEN
107
+ EXECUTE format(
108
+ 'CREATE POLICY organization_tenancy_parity_capability_read ON %I.%I '
109
+ || 'FOR SELECT USING (current_user = %L AND '
110
+ || 'opengeni_private.organization_tenancy_parity_capability_active())',
111
+ data_schema,
112
+ table_name,
113
+ migration_owner
114
+ );
115
+ END IF;
116
+ END LOOP;
117
+ END
118
+ $organization_tenancy_parity_capability_policies$;
119
+
120
+ CREATE OR REPLACE FUNCTION check_organization_tenancy_parity(
121
+ p_organization_id uuid,
122
+ p_evidence_limit integer DEFAULT 10,
123
+ p_observation_window_days integer DEFAULT 30
124
+ ) RETURNS jsonb
125
+ LANGUAGE plpgsql
126
+ SECURITY DEFINER
127
+ SET search_path FROM CURRENT
128
+ SET statement_timeout = '60s'
129
+ AS $$
130
+ DECLARE
131
+ result jsonb;
132
+ evidence_limit integer := least(greatest(coalesce(p_evidence_limit, 10), 0), 50);
133
+ window_days integer := least(greatest(coalesce(p_observation_window_days, 30), 1), 365);
134
+ BEGIN
135
+ IF p_organization_id IS NULL THEN
136
+ RAISE EXCEPTION 'tenancy parity check requires an organization id'
137
+ USING ERRCODE = '22004';
138
+ END IF;
139
+ IF p_organization_id IS DISTINCT FROM nullif(
140
+ pg_catalog.current_setting('opengeni.account_id', true), ''
141
+ )::uuid
142
+ THEN
143
+ RAISE EXCEPTION 'tenancy parity scope mismatch'
144
+ USING ERRCODE = '42501';
145
+ END IF;
146
+
147
+ INSERT INTO opengeni_private.organization_tenancy_parity_capabilities (
148
+ backend_pid, transaction_id
149
+ ) VALUES (pg_catalog.pg_backend_pid(), pg_catalog.pg_current_xact_id())
150
+ ON CONFLICT DO NOTHING;
151
+
152
+ WITH gate_catalog(gate_id) AS (
153
+ VALUES
154
+ ('membership_personal_workspace_pointer'::text),
155
+ ('membership_personal_workspace_exclusive'),
156
+ ('membership_personal_workspace_same_organization'),
157
+ ('personal_workspace_has_no_membership_row'),
158
+ ('authority_resource_single_owner'),
159
+ ('grant_delegation_fence_complete'),
160
+ ('grant_owner_membership_active'),
161
+ ('grant_authority_live'),
162
+ ('grant_session_fence_not_ahead'),
163
+ ('session_owner_provenance_paired'),
164
+ ('session_owner_subject_matches_membership'),
165
+ ('session_owner_membership_same_organization'),
166
+ ('login_binding_dispute_propagated'),
167
+ ('identity_active_binding_owned'),
168
+ ('user_scoped_resource_live_anchor')
169
+ ),
170
+ -- Canonical human identities are organization-INDEPENDENT by design, so an
171
+ -- organization-scoped parity report may only inspect the identities that
172
+ -- actually anchor a subject of THIS organization. `user:<auth_user_id>` is
173
+ -- the canonical subject encoding used by organization_memberships.
174
+ scoped_identities AS (
175
+ SELECT DISTINCT identity_subject.identity_id
176
+ FROM canonical_human_identity_subjects identity_subject
177
+ JOIN organization_memberships membership
178
+ ON membership.account_id = p_organization_id
179
+ AND membership.subject_id = 'user:' || identity_subject.auth_user_id
180
+ ),
181
+ violations(gate_id, evidence_id) AS (
182
+ -- An active membership must identify a personal workspace.
183
+ SELECT 'membership_personal_workspace_pointer'::text, membership.id
184
+ FROM organization_memberships membership
185
+ WHERE membership.account_id = p_organization_id
186
+ AND membership.status = 'active'
187
+ AND membership.personal_workspace_id IS NULL
188
+
189
+ UNION ALL
190
+ -- A personal workspace may identify at most one organization membership.
191
+ SELECT 'membership_personal_workspace_exclusive', membership.id
192
+ FROM organization_memberships membership
193
+ WHERE membership.account_id = p_organization_id
194
+ AND membership.personal_workspace_id IS NOT NULL
195
+ AND EXISTS (
196
+ SELECT 1 FROM organization_memberships other
197
+ WHERE other.account_id = p_organization_id
198
+ AND other.id <> membership.id
199
+ AND other.personal_workspace_id = membership.personal_workspace_id
200
+ )
201
+
202
+ UNION ALL
203
+ -- The personal workspace must belong to the same organization.
204
+ SELECT 'membership_personal_workspace_same_organization', membership.id
205
+ FROM organization_memberships membership
206
+ WHERE membership.account_id = p_organization_id
207
+ AND membership.personal_workspace_id IS NOT NULL
208
+ AND NOT EXISTS (
209
+ SELECT 1 FROM workspaces workspace
210
+ WHERE workspace.id = membership.personal_workspace_id
211
+ AND workspace.account_id = p_organization_id
212
+ )
213
+
214
+ UNION ALL
215
+ -- A managed personal workspace deliberately has NO workspace_memberships
216
+ -- row: the owner-only access projection is derived, not delegable. A
217
+ -- persisted row there is precisely how membership CRUD and the subject-
218
+ -- membership fallback would widen it. Evidence is the workspace id.
219
+ SELECT DISTINCT 'personal_workspace_has_no_membership_row',
220
+ membership.personal_workspace_id
221
+ FROM organization_memberships membership
222
+ JOIN workspace_memberships workspace_membership
223
+ ON workspace_membership.account_id = p_organization_id
224
+ AND workspace_membership.workspace_id = membership.personal_workspace_id
225
+ WHERE membership.account_id = p_organization_id
226
+ AND membership.personal_workspace_id IS NOT NULL
227
+
228
+ UNION ALL
229
+ -- Stable authority uniqueness. The physical unique index is per
230
+ -- (account, membership, kind, resource), so two DIFFERENT memberships can
231
+ -- still claim the same resource - the ambiguity that must never be
232
+ -- resolved toward either user.
233
+ SELECT 'authority_resource_single_owner', authority.id
234
+ FROM organization_user_resource_authorities authority
235
+ WHERE authority.account_id = p_organization_id
236
+ AND authority.status <> 'revoked'
237
+ AND EXISTS (
238
+ SELECT 1 FROM organization_user_resource_authorities rival
239
+ WHERE rival.account_id = p_organization_id
240
+ AND rival.status <> 'revoked'
241
+ AND rival.resource_kind = authority.resource_kind
242
+ AND rival.resource_id = authority.resource_id
243
+ AND rival.organization_membership_id
244
+ <> authority.organization_membership_id
245
+ )
246
+
247
+ UNION ALL
248
+ -- Zero partial delegations: `always` carries neither fence, once/session
249
+ -- carry both.
250
+ SELECT 'grant_delegation_fence_complete', resource_grant.id
251
+ FROM organization_user_resource_grants resource_grant
252
+ WHERE resource_grant.account_id = p_organization_id
253
+ AND NOT (
254
+ (resource_grant.mode = 'always'
255
+ AND resource_grant.session_id IS NULL
256
+ AND resource_grant.authority_epoch IS NULL)
257
+ OR (resource_grant.mode IN ('once', 'session')
258
+ AND resource_grant.session_id IS NOT NULL
259
+ AND resource_grant.authority_epoch IS NOT NULL
260
+ AND resource_grant.authority_epoch > 0)
261
+ )
262
+
263
+ UNION ALL
264
+ -- A live personal delegation owned by a human whose organization
265
+ -- membership is suspended or revoked is a fallback to user authority.
266
+ -- Suspension revokes personal-resource grants and offboarding runs the
267
+ -- same teardown, so the drained state is the normal one.
268
+ SELECT 'grant_owner_membership_active', resource_grant.id
269
+ FROM organization_user_resource_grants resource_grant
270
+ JOIN organization_memberships membership
271
+ ON membership.id = resource_grant.owner_organization_membership_id
272
+ AND membership.account_id = resource_grant.account_id
273
+ WHERE resource_grant.account_id = p_organization_id
274
+ AND resource_grant.status = 'active'
275
+ AND membership.status <> 'active'
276
+
277
+ UNION ALL
278
+ -- An active grant over a revoked authority.
279
+ SELECT 'grant_authority_live', resource_grant.id
280
+ FROM organization_user_resource_grants resource_grant
281
+ JOIN organization_user_resource_authorities authority
282
+ ON authority.id = resource_grant.authority_id
283
+ AND authority.account_id = resource_grant.account_id
284
+ WHERE resource_grant.account_id = p_organization_id
285
+ AND resource_grant.status = 'active'
286
+ AND authority.status = 'revoked'
287
+
288
+ UNION ALL
289
+ -- A session-fenced grant whose epoch is AHEAD of the session it fences
290
+ -- would survive the epoch advance that is supposed to revoke it. Epochs
291
+ -- are monotonic and the fence is copied from the session at issuance, so
292
+ -- `grant.authority_epoch <= session.authority_epoch` always holds.
293
+ SELECT 'grant_session_fence_not_ahead', resource_grant.id
294
+ FROM organization_user_resource_grants resource_grant
295
+ JOIN sessions session_row
296
+ ON session_row.id = resource_grant.session_id
297
+ AND session_row.workspace_id = resource_grant.workspace_id
298
+ WHERE resource_grant.account_id = p_organization_id
299
+ AND resource_grant.session_id IS NOT NULL
300
+ AND resource_grant.authority_epoch > session_row.authority_epoch
301
+
302
+ UNION ALL
303
+ -- Session owner provenance is paired, and a user_private session always
304
+ -- has an owner.
305
+ SELECT 'session_owner_provenance_paired', session_row.id
306
+ FROM sessions session_row
307
+ WHERE session_row.account_id = p_organization_id
308
+ AND (
309
+ (session_row.owner_organization_membership_id IS NULL)
310
+ <> (session_row.owner_subject_id IS NULL)
311
+ OR (session_row.visibility = 'user_private'
312
+ AND session_row.owner_organization_membership_id IS NULL)
313
+ )
314
+
315
+ UNION ALL
316
+ -- The denormalized owner subject must still name the owning membership.
317
+ SELECT 'session_owner_subject_matches_membership', session_row.id
318
+ FROM sessions session_row
319
+ JOIN organization_memberships membership
320
+ ON membership.id = session_row.owner_organization_membership_id
321
+ AND membership.account_id = session_row.account_id
322
+ WHERE session_row.account_id = p_organization_id
323
+ AND session_row.owner_subject_id IS DISTINCT FROM membership.subject_id
324
+
325
+ UNION ALL
326
+ -- The owning membership must exist in this organization.
327
+ SELECT 'session_owner_membership_same_organization', session_row.id
328
+ FROM sessions session_row
329
+ WHERE session_row.account_id = p_organization_id
330
+ AND session_row.owner_organization_membership_id IS NOT NULL
331
+ AND NOT EXISTS (
332
+ SELECT 1 FROM organization_memberships membership
333
+ WHERE membership.id = session_row.owner_organization_membership_id
334
+ AND membership.account_id = p_organization_id
335
+ )
336
+
337
+ UNION ALL
338
+ -- Provider-account collision rule: the physical unique index makes a
339
+ -- second binding for one (provider, provider_account) impossible, so the
340
+ -- collision is recorded by DISPUTING the existing binding and BOTH
341
+ -- identities. A disputed binding whose identity is still usable is a
342
+ -- collision that never fenced its identity.
343
+ SELECT 'login_binding_dispute_propagated', binding.id
344
+ FROM canonical_human_login_bindings binding
345
+ JOIN scoped_identities scoped ON scoped.identity_id = binding.identity_id
346
+ JOIN canonical_human_identities identity ON identity.id = binding.identity_id
347
+ WHERE binding.status = 'disputed'
348
+ AND identity.status <> 'disputed'
349
+
350
+ UNION ALL
351
+ -- The FK on active_login_binding_id proves existence, never ownership.
352
+ SELECT 'identity_active_binding_owned', identity.id
353
+ FROM canonical_human_identities identity
354
+ JOIN scoped_identities scoped ON scoped.identity_id = identity.id
355
+ WHERE identity.active_login_binding_id IS NOT NULL
356
+ AND NOT EXISTS (
357
+ SELECT 1 FROM canonical_human_login_bindings binding
358
+ WHERE binding.id = identity.active_login_binding_id
359
+ AND binding.identity_id = identity.id
360
+ )
361
+
362
+ UNION ALL
363
+ -- Shadow comparison, connections lane: legacy effective scope is
364
+ -- workspace; the proposed effective scope is `user`. A row claiming user
365
+ -- authority without a live authority AND a live owning membership has no
366
+ -- reachable user resolution - it must fall back to workspace or deny.
367
+ SELECT 'user_scoped_resource_live_anchor', connection_row.id
368
+ FROM connections connection_row
369
+ WHERE connection_row.account_id = p_organization_id
370
+ AND connection_row.authority_scope = 'user'
371
+ AND NOT EXISTS (
372
+ SELECT 1
373
+ FROM organization_user_resource_authorities authority
374
+ JOIN organization_memberships membership
375
+ ON membership.id = authority.organization_membership_id
376
+ AND membership.account_id = authority.account_id
377
+ WHERE authority.id = connection_row.authority_id
378
+ AND authority.account_id = p_organization_id
379
+ AND authority.status = 'active'
380
+ AND membership.status = 'active'
381
+ )
382
+
383
+ UNION ALL
384
+ SELECT 'user_scoped_resource_live_anchor', variable_set.id
385
+ FROM workspace_variable_sets variable_set
386
+ WHERE variable_set.account_id = p_organization_id
387
+ AND variable_set.authority_scope = 'user'
388
+ AND NOT EXISTS (
389
+ SELECT 1
390
+ FROM organization_user_resource_authorities authority
391
+ JOIN organization_memberships membership
392
+ ON membership.id = authority.organization_membership_id
393
+ AND membership.account_id = authority.account_id
394
+ WHERE authority.id = variable_set.authority_id
395
+ AND authority.account_id = p_organization_id
396
+ AND authority.status = 'active'
397
+ AND membership.status = 'active'
398
+ )
399
+
400
+ UNION ALL
401
+ SELECT 'user_scoped_resource_live_anchor', rig_row.id
402
+ FROM rigs rig_row
403
+ WHERE rig_row.account_id = p_organization_id
404
+ AND rig_row.authority_scope = 'user'
405
+ AND NOT EXISTS (
406
+ SELECT 1
407
+ FROM organization_user_resource_authorities authority
408
+ JOIN organization_memberships membership
409
+ ON membership.id = authority.organization_membership_id
410
+ AND membership.account_id = authority.account_id
411
+ WHERE authority.id = rig_row.authority_id
412
+ AND authority.account_id = p_organization_id
413
+ AND authority.status = 'active'
414
+ AND membership.status = 'active'
415
+ )
416
+
417
+ UNION ALL
418
+ SELECT 'user_scoped_resource_live_anchor', enrollment.id
419
+ FROM enrollments enrollment
420
+ WHERE enrollment.account_id = p_organization_id
421
+ AND enrollment.authority_scope = 'user'
422
+ AND NOT EXISTS (
423
+ SELECT 1
424
+ FROM organization_user_resource_authorities authority
425
+ JOIN organization_memberships membership
426
+ ON membership.id = authority.organization_membership_id
427
+ AND membership.account_id = authority.account_id
428
+ WHERE authority.id = enrollment.authority_id
429
+ AND authority.account_id = p_organization_id
430
+ AND authority.status = 'active'
431
+ AND membership.status = 'active'
432
+ )
433
+
434
+ UNION ALL
435
+ -- Documents carry the same claim as authority_kind='personal' WITH a
436
+ -- non-null authority_id (a null authority_id there is the separate,
437
+ -- deliberately bounded legacy lane reported under `lanes`).
438
+ SELECT 'user_scoped_resource_live_anchor', document.id
439
+ FROM documents document
440
+ WHERE document.account_id = p_organization_id
441
+ AND document.authority_kind = 'personal'
442
+ AND document.authority_id IS NOT NULL
443
+ AND NOT EXISTS (
444
+ SELECT 1
445
+ FROM organization_user_resource_authorities authority
446
+ JOIN organization_memberships membership
447
+ ON membership.id = authority.organization_membership_id
448
+ AND membership.account_id = authority.account_id
449
+ WHERE authority.id = document.authority_id
450
+ AND authority.account_id = p_organization_id
451
+ AND authority.status = 'active'
452
+ AND membership.status = 'active'
453
+ )
454
+ ),
455
+ aggregated AS (
456
+ SELECT
457
+ violation.gate_id,
458
+ count(*)::int AS violation_count,
459
+ (pg_catalog.array_agg(
460
+ violation.evidence_id ORDER BY violation.evidence_id
461
+ ))[1:evidence_limit] AS evidence
462
+ FROM violations violation
463
+ GROUP BY violation.gate_id
464
+ )
465
+ SELECT pg_catalog.jsonb_build_object(
466
+ 'schemaVersion', 1,
467
+ 'organizationId', p_organization_id,
468
+ 'evidenceLimit', evidence_limit,
469
+ 'observationWindowDays', window_days,
470
+ 'gates', (
471
+ SELECT pg_catalog.jsonb_object_agg(
472
+ catalog.gate_id,
473
+ pg_catalog.jsonb_build_object(
474
+ 'violations', coalesce(aggregated.violation_count, 0),
475
+ 'evidence', pg_catalog.to_jsonb(
476
+ coalesce(aggregated.evidence, ARRAY[]::uuid[])
477
+ ),
478
+ 'truncated', coalesce(aggregated.violation_count, 0)
479
+ > coalesce(pg_catalog.array_length(aggregated.evidence, 1), 0)
480
+ )
481
+ )
482
+ FROM gate_catalog catalog
483
+ LEFT JOIN aggregated ON aggregated.gate_id = catalog.gate_id
484
+ ),
485
+ -- Compatibility lanes. These are populations the phase-D backfill drains;
486
+ -- they are NOT invariant violations and must never be read as such.
487
+ 'lanes', pg_catalog.jsonb_build_object(
488
+ 'connectionsLegacyUser', (
489
+ SELECT count(*)::int FROM connections connection_row
490
+ WHERE connection_row.account_id = p_organization_id
491
+ AND connection_row.authority_scope = 'legacy_user'
492
+ ),
493
+ -- Workspace-writer admissions and retained processes are immutable
494
+ -- history: they are settled by UPDATE, nothing in the tree DELETEs them,
495
+ -- and 0277's one-shot backfill only reached rows whose actor was a turn
496
+ -- (`actor_kind`/`owner_actor_kind` = 'turn'), so every pre-0277 `direct:`
497
+ -- or `process:` admission keeps the legacy_unattributed sentinel
498
+ -- permanently. An all-time count of them therefore can never drain, and
499
+ -- marking it drainable would make cutoverReady structurally unreachable
500
+ -- for any deployment carrying one. Same treatment as the connection-use
501
+ -- lane below: report the bounded recent window, where zero IS reachable
502
+ -- and the honest signal is "the lane stopped being exercised".
503
+ 'workspaceWriterAdmissionsLegacyUnattributedInWindow', (
504
+ SELECT count(*)::int FROM sandbox_workspace_mutation_admissions admission
505
+ WHERE admission.account_id = p_organization_id
506
+ AND admission.initiator_kind = 'legacy_unattributed'
507
+ AND admission.admitted_at
508
+ >= pg_catalog.now() - pg_catalog.make_interval(days => window_days)
509
+ ),
510
+ 'workspaceWriterProcessesLegacyUnattributedInWindow', (
511
+ SELECT count(*)::int FROM sandbox_retained_processes retained_process
512
+ WHERE retained_process.account_id = p_organization_id
513
+ AND retained_process.initiator_kind = 'legacy_unattributed'
514
+ AND retained_process.started_at
515
+ >= pg_catalog.now() - pg_catalog.make_interval(days => window_days)
516
+ ),
517
+ 'documentsLegacyPersonalNullAuthority', (
518
+ SELECT count(*)::int FROM documents document
519
+ WHERE document.account_id = p_organization_id
520
+ AND document.authority_kind = 'personal'
521
+ AND document.authority_id IS NULL
522
+ ),
523
+ 'codexCredentialsUnattributedConnector', (
524
+ SELECT count(*)::int FROM codex_subscription_credentials credential
525
+ WHERE credential.account_id = p_organization_id
526
+ AND credential.connected_by_subject_id IS NULL
527
+ ),
528
+ 'workspaceMemberSubjectsWithoutMembershipAnchor', (
529
+ SELECT count(DISTINCT workspace_membership.subject_id)::int
530
+ FROM workspace_memberships workspace_membership
531
+ JOIN workspaces workspace ON workspace.id = workspace_membership.workspace_id
532
+ WHERE workspace.account_id = p_organization_id
533
+ AND workspace_membership.subject_id LIKE 'user:%'
534
+ AND NOT EXISTS (
535
+ SELECT 1 FROM organization_memberships membership
536
+ WHERE membership.account_id = p_organization_id
537
+ AND membership.subject_id = workspace_membership.subject_id
538
+ )
539
+ ),
540
+ -- The DRAINABLE refinement of "ownerless sessions": a session whose
541
+ -- creating subject would be attributed by today's write fence but whose
542
+ -- owner is still null. Total ownerlessness is deliberately NOT reported
543
+ -- as drainable - a service/API-key session is permanently ownerless.
544
+ 'sessionsAttributableButUnattributed', (
545
+ SELECT count(*)::int FROM sessions session_row
546
+ WHERE session_row.account_id = p_organization_id
547
+ AND session_row.owner_organization_membership_id IS NULL
548
+ AND session_row.created_by_kind = 'subject'
549
+ AND session_row.created_by_subject_id IS NOT NULL
550
+ AND EXISTS (
551
+ SELECT 1 FROM organization_memberships membership
552
+ WHERE membership.account_id = p_organization_id
553
+ AND membership.subject_id = session_row.created_by_subject_id
554
+ AND membership.status = 'active'
555
+ )
556
+ AND EXISTS (
557
+ SELECT 1 FROM workspace_memberships workspace_membership
558
+ WHERE workspace_membership.account_id = p_organization_id
559
+ AND workspace_membership.workspace_id = session_row.workspace_id
560
+ AND workspace_membership.subject_id = session_row.created_by_subject_id
561
+ )
562
+ ),
563
+ -- Connection-use audit facts are immutable history, so the all-time
564
+ -- count can never drain. The reachable-zero signal is "was the bounded
565
+ -- pre-snapshot/legacy resolution lane exercised inside the observation
566
+ -- window".
567
+ 'connectionUseLegacyResolutionsInWindow', (
568
+ SELECT count(*)::int FROM connection_use_audit_facts audit_fact
569
+ WHERE audit_fact.account_id = p_organization_id
570
+ AND audit_fact.authority_scope = 'legacy_user'
571
+ AND audit_fact.occurred_at
572
+ >= pg_catalog.now() - pg_catalog.make_interval(days => window_days)
573
+ )
574
+ )
575
+ ) INTO result;
576
+
577
+ DELETE FROM opengeni_private.organization_tenancy_parity_capabilities
578
+ WHERE backend_pid = pg_catalog.pg_backend_pid()
579
+ AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned();
580
+ RETURN result;
581
+ EXCEPTION WHEN OTHERS THEN
582
+ DELETE FROM opengeni_private.organization_tenancy_parity_capabilities
583
+ WHERE backend_pid = pg_catalog.pg_backend_pid()
584
+ AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned();
585
+ RAISE;
586
+ END
587
+ $$;
588
+
589
+ REVOKE ALL ON FUNCTION
590
+ check_organization_tenancy_parity(uuid, integer, integer) FROM PUBLIC;
591
+ DO $tenancy_parity_grant$
592
+ BEGIN
593
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
594
+ GRANT EXECUTE ON FUNCTION
595
+ check_organization_tenancy_parity(uuid, integer, integer) TO opengeni_app;
596
+ -- The predicate is a read-only boolean over a transaction-local
597
+ -- bookkeeping row and MUST be independently grantable: an ordinary
598
+ -- opengeni_app-effective role touching ANY table this capability protects
599
+ -- needs EXECUTE on every predicate function those tables' policies
600
+ -- reference, or the unrelated read fails closed with a permission error
601
+ -- instead of the policy simply evaluating false. Same explicit grant as
602
+ -- 0254 and 0285 - provisionRoles' blanket schema-wide sweep is not
603
+ -- sufficient for a database whose test/deploy path never runs that step.
604
+ GRANT EXECUTE ON FUNCTION
605
+ opengeni_private.organization_tenancy_parity_capability_active()
606
+ TO opengeni_app;
607
+ END IF;
608
+ END
609
+ $tenancy_parity_grant$;