@opengeni/db 1.0.1 → 3.2.0-canary.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (271) hide show
  1. package/dist/attached-browser-devices.d.ts +7 -0
  2. package/dist/automations.d.ts +154 -0
  3. package/dist/canonical-human-identities.js +3 -3
  4. package/dist/capability-integrations.d.ts +13 -0
  5. package/dist/child-lifecycle-notices.d.ts +68 -0
  6. package/dist/{chunk-4SR4HKKA.js → chunk-2EH4WRHL.js} +19 -11
  7. package/dist/chunk-2EH4WRHL.js.map +1 -0
  8. package/dist/{chunk-CUPM3FGB.js → chunk-2PRS4J5Q.js} +3 -3
  9. package/dist/chunk-6HMM2R5T.js +390 -0
  10. package/dist/chunk-6HMM2R5T.js.map +1 -0
  11. package/dist/{chunk-ZWREYEWA.js → chunk-AZHW7R5V.js} +56 -11
  12. package/dist/chunk-AZHW7R5V.js.map +1 -0
  13. package/dist/{chunk-LTG3S6ZY.js → chunk-E6CLMKEE.js} +2 -2
  14. package/dist/{chunk-6ZYKCRFO.js → chunk-KRLPEMLH.js} +7591 -4417
  15. package/dist/chunk-KRLPEMLH.js.map +1 -0
  16. package/dist/chunk-NAB6FKFN.js +516 -0
  17. package/dist/chunk-NAB6FKFN.js.map +1 -0
  18. package/dist/{chunk-SK7YYHBI.js → chunk-SIBYBA2D.js} +46 -1
  19. package/dist/chunk-SIBYBA2D.js.map +1 -0
  20. package/dist/chunk-UZNXG44K.js +4508 -0
  21. package/dist/chunk-UZNXG44K.js.map +1 -0
  22. package/dist/codemode-operations.d.ts +1 -6
  23. package/dist/company-brain-context-selection-schema.d.ts +705 -0
  24. package/dist/company-brain-context-selection.d.ts +94 -0
  25. package/dist/company-brain-governed-writes.d.ts +24 -0
  26. package/dist/company-brain.d.ts +55 -0
  27. package/dist/company-profile-agent-admin.d.ts +15 -0
  28. package/dist/company-profile-schema.d.ts +619 -2
  29. package/dist/company-profile.d.ts +9 -4
  30. package/dist/connection-authority.d.ts +95 -0
  31. package/dist/connection-scopes.d.ts +8 -0
  32. package/dist/connection-tenancy-backfill.d.ts +85 -0
  33. package/dist/connection-token-resolver.d.ts +30 -1
  34. package/dist/database.d.ts +23 -4
  35. package/dist/editable-artifact-durable-export.js +2 -2
  36. package/dist/editable-artifacts.d.ts +2 -2
  37. package/dist/editable-artifacts.js +3 -3
  38. package/dist/governed-learning-activation-schema.d.ts +984 -0
  39. package/dist/governed-learning-activation.d.ts +98 -0
  40. package/dist/governed-learning-evaluator-schema.d.ts +633 -0
  41. package/dist/governed-learning-evaluator.d.ts +28 -0
  42. package/dist/index.d.ts +1606 -81
  43. package/dist/index.js +21898 -8147
  44. package/dist/index.js.map +1 -1
  45. package/dist/insights.d.ts +18 -0
  46. package/dist/integration-bindings.d.ts +1 -0
  47. package/dist/integration-facets.d.ts +30 -0
  48. package/dist/interaction-placement-loss.d.ts +22 -0
  49. package/dist/managed-human-provisioning.d.ts +25 -0
  50. package/dist/memory-domain.d.ts +3 -3
  51. package/dist/migrate.d.ts +11 -1
  52. package/dist/migrate.js +1 -1
  53. package/dist/new-session-drafts.d.ts +43 -5
  54. package/dist/organization-membership-backfill.d.ts +174 -0
  55. package/dist/organization-membership-lifecycle.d.ts +162 -0
  56. package/dist/pr-review.d.ts +107 -0
  57. package/dist/preference-registry-schema.d.ts +2 -0
  58. package/dist/preference-registry.d.ts +2 -0
  59. package/dist/provision-roles.js +1 -1
  60. package/dist/runtime-posture.d.ts +29 -8
  61. package/dist/schema.d.ts +24914 -13703
  62. package/dist/schema.js +99 -1
  63. package/dist/scoped-knowledge-schema.d.ts +52 -1
  64. package/dist/session-background-commands.d.ts +125 -0
  65. package/dist/session-background-commands.js +34 -0
  66. package/dist/session-background-commands.js.map +1 -0
  67. package/dist/session-control.d.ts +156 -3
  68. package/dist/session-goal-pacing.d.ts +68 -0
  69. package/dist/session-queue-commands.d.ts +43 -4
  70. package/dist/session-realtime-ledger.d.ts +2 -0
  71. package/dist/session-tenancy.d.ts +71 -1
  72. package/dist/session-tenancy.js +25 -3
  73. package/dist/session-tool-call-settlement.d.ts +9 -4
  74. package/dist/slack-routing-personal-workspace.d.ts +23 -0
  75. package/dist/slack-routing.d.ts +268 -0
  76. package/dist/slack-task-policy-schema.d.ts +34 -0
  77. package/dist/slack-task-policy.d.ts +8 -0
  78. package/dist/slack-user-link-access.d.ts +28 -0
  79. package/dist/task-notes-schema.d.ts +594 -0
  80. package/dist/task-notes.d.ts +39 -0
  81. package/dist/turn-initiator.d.ts +2 -0
  82. package/dist/user-resource-authority.d.ts +109 -0
  83. package/dist/video-generation.js +3 -3
  84. package/dist/workspace-authority.d.ts +81 -0
  85. package/dist/workspace-instruction-policies-schema.d.ts +279 -0
  86. package/dist/workspace-instruction-policies.d.ts +62 -0
  87. package/dist/workspace-learning-policy.d.ts +1 -0
  88. package/dist/workspace-membership-permissions.d.ts +2 -0
  89. package/drizzle/0240_enrollment_connection_authority.sql +39 -0
  90. package/drizzle/0241_atomic_personal_resource_delegation.sql +1000 -0
  91. package/drizzle/0241_enrollment_agent_runtime.sql +70 -0
  92. package/drizzle/0244_slack_app_home_refresh_queue.sql +120 -0
  93. package/drizzle/0245_model_context_contribution_facts.sql +88 -0
  94. package/drizzle/0246_integration_personal_instance_authority.sql +70 -0
  95. package/drizzle/0247_terraform_stacks_provenance_repair.sql +428 -0
  96. package/drizzle/0248_terraform_stacks_component_resolution_fence.sql +83 -0
  97. package/drizzle/0249_personal_resource_delegation_authority_correction.sql +662 -0
  98. package/drizzle/0250_direct_retained_process_owner_liveness.sql +199 -0
  99. package/drizzle/0251_connected_machine_operation_policy.sql +45 -0
  100. package/drizzle/0252_scheduled_personal_resource_delegation.sql +1867 -0
  101. package/drizzle/0253_common_user_resource_authority_lifecycle.sql +1555 -0
  102. package/drizzle/0254_scoped_variable_set_authority.sql +1255 -0
  103. package/drizzle/0255_company_brain_governed_write_proposals.sql +524 -0
  104. package/drizzle/0256_connection_authority_delegation.sql +703 -0
  105. package/drizzle/0257_goal_revision_decisions_and_root_constraints.sql +436 -0
  106. package/drizzle/0258_three_scope_document_knowledge_authority.sql +1273 -0
  107. package/drizzle/0259_company_brain_context_selection_receipts.sql +869 -0
  108. package/drizzle/0260_task_note_knowledge_promotion.sql +820 -0
  109. package/drizzle/0261_preference_knowledge_proposal_actor_binding.sql +471 -0
  110. package/drizzle/0262_scoped_connected_machines_and_rigs.sql +1700 -0
  111. package/drizzle/0263_organization_membership_lifecycle.sql +3284 -0
  112. package/drizzle/0264_connection_authority_runtime_activation.sql +1084 -0
  113. package/drizzle/0266_company_brain_context_receipt_inspection.sql +156 -0
  114. package/drizzle/0268_governed_learning_decision_receipts.sql +825 -0
  115. package/drizzle/0269_governed_learning_activation_controller.sql +1874 -0
  116. package/drizzle/0270_governed_learning_history_inspection.sql +170 -0
  117. package/drizzle/0271_company_brain_retrieval_only_default.sql +524 -0
  118. package/drizzle/0272_human_confirmed_learning_activation.sql +591 -0
  119. package/drizzle/0273_scheduled_variable_set_materialization.sql +268 -0
  120. package/drizzle/0274_human_confirmed_knowledge_review.sql +310 -0
  121. package/drizzle/0275_scheduled_connection_authority.sql +5527 -0
  122. package/drizzle/0276_onboarding_proposal_initiating_human_guc.sql +198 -0
  123. package/drizzle/0277_workspace_writer_authority_attribution.sql +393 -0
  124. package/drizzle/0278_workspace_membership_removal_fencing.sql +1174 -0
  125. package/drizzle/0279_workspace_connection_use_lane.sql +343 -0
  126. package/drizzle/0280_connection_and_variable_set_audit_attribution.sql +773 -0
  127. package/drizzle/0281_viewer_holder_authority_claims.sql +52 -0
  128. package/drizzle/0282_variable_set_session_attach_attribution.sql +178 -0
  129. package/drizzle/0283_editable_spreadsheet_authored_state.sql +82 -0
  130. package/drizzle/0284_truthful_human_confirmed_review_reason.sql +930 -0
  131. package/drizzle/0285_organization_tenancy_inventory.sql +329 -0
  132. package/drizzle/0286_widen_task_note_expiry_ceiling.sql +391 -0
  133. package/drizzle/0287_open_suffix_pending_tool_calls.sql +18 -0
  134. package/drizzle/0288_attached_browser_reenrollment.sql +64 -0
  135. package/drizzle/0289_session_composer_policy_authority.sql +597 -0
  136. package/drizzle/0290_organization_membership_backfill.sql +268 -0
  137. package/drizzle/0291_resource_authority_classification_assertion.sql +371 -0
  138. package/drizzle/0292_truthful_tenancy_inventory_counters.sql +291 -0
  139. package/drizzle/0293_confirm_time_rule_rebaseline.sql +615 -0
  140. package/drizzle/0294_preference_activation_authority.sql +237 -0
  141. package/drizzle/0295_retire_legacy_standing_memory_mode.sql +476 -0
  142. package/drizzle/0296_force_rls_backfill_noop_repair.sql +215 -0
  143. package/drizzle/0297_session_ownership_classification_and_backfill.sql +752 -0
  144. package/drizzle/0298_organization_tenancy_parity.sql +609 -0
  145. package/drizzle/0299_organization_membership_lock_order.sql +291 -0
  146. package/drizzle/0300_tenancy_backfill_ledger.sql +459 -0
  147. package/drizzle/0301_session_snapshot_and_pin_visibility.sql +181 -0
  148. package/drizzle/0302_personal_workspace_session_ownership.sql +303 -0
  149. package/drizzle/0303_session_tenancy_product_activation.sql +919 -0
  150. package/drizzle/0304_personal_workspace_private_session_reads.sql +97 -0
  151. package/drizzle/0305_personal_resource_grant_management.sql +584 -0
  152. package/drizzle/0306_atomic_personal_resource_attachments.sql +1374 -0
  153. package/drizzle/0307_session_attention_state.sql +51 -0
  154. package/drizzle/0308_session_archives.sql +35 -0
  155. package/drizzle/0309_channel_project_pins.sql +6 -0
  156. package/drizzle/0310_channel_project_order.sql +27 -0
  157. package/drizzle/0311_company_scope_and_private_session_create.sql +656 -0
  158. package/drizzle/0312_quiescent_session_tree_deletion.sql +39 -0
  159. package/drizzle/0313_private_child_session_authority.sql +365 -0
  160. package/drizzle/0314_unregistered_organization_invitations.sql +606 -0
  161. package/drizzle/0315_personal_github_repository_selection.sql +624 -0
  162. package/drizzle/0316_human_confirmed_activation_resumed_generation.sql +869 -0
  163. package/drizzle/0317_session_goal_continuation_hold.sql +64 -0
  164. package/drizzle/0318_session_turn_startup_milestones.sql +115 -0
  165. package/drizzle/0319_event_triggered_automations.sql +254 -0
  166. package/drizzle/0320_pr_review_pack.sql +113 -0
  167. package/drizzle/0321_slack_bot_environment_display_name.sql +15 -0
  168. package/drizzle/0322_session_turns_unclaimed_prompt_trigger_index.sql +5 -0
  169. package/drizzle/0323_organization_private_session_enablement.sql +489 -0
  170. package/drizzle/0324_human_confirmed_company_profile_agent_admin.sql +856 -0
  171. package/drizzle/0325_child_lifecycle_notices.sql +44 -0
  172. package/drizzle/0326_interaction_operation_error_codes.sql +62 -0
  173. package/drizzle/0327_slack_first_task_hint.sql +29 -0
  174. package/drizzle/0328_session_background_commands.sql +590 -0
  175. package/drizzle/0329_slack_orchestration_delivery_events.sql +91 -0
  176. package/drizzle/0330_api_key_descriptions.sql +20 -0
  177. package/drizzle/0331_managed_organization_creation.sql +163 -0
  178. package/drizzle/0332_organization_shared_workspace_control_plane.sql +539 -0
  179. package/drizzle/0333_session_turn_prompt_routing.sql +45 -0
  180. package/drizzle/0334_connected_machine_workspace_root.sql +99 -0
  181. package/drizzle/0335_slack_workspace_routing.sql +460 -0
  182. package/drizzle/0336_atomic_session_fork_visibility.sql +667 -0
  183. package/drizzle/0337_slack_routed_action_handles.sql +189 -0
  184. package/drizzle/0338_atomic_connected_machine_attachments.sql +410 -0
  185. package/drizzle/0339_document_authority_reclassification.sql +1153 -0
  186. package/drizzle/0340_tenancy_backfill_activation_evidence.sql +1347 -0
  187. package/drizzle/0341_slack_routing_probe_organization_fence.sql +109 -0
  188. package/drizzle/0342_slack_route_prompt_single_pending.sql +32 -0
  189. package/drizzle/0343_personal_document_force_rls_lock_repair.sql +146 -0
  190. package/drizzle/0344_private_session_visibility_transition_gate.sql +85 -0
  191. package/drizzle/0345_tenant_scoped_session_tenancy_fence.sql +2839 -0
  192. package/drizzle/0346_document_migration_audit_surface.sql +454 -0
  193. package/drizzle/0347_connection_authority_convergence_evidence.sql +396 -0
  194. package/package.json +10 -6
  195. package/src/attached-browser-devices.ts +233 -5
  196. package/src/automations.ts +911 -0
  197. package/src/browser-sessions.ts +10 -0
  198. package/src/capability-integrations.ts +175 -1
  199. package/src/child-lifecycle-notices.ts +241 -0
  200. package/src/codemode-operations.ts +1 -35
  201. package/src/company-brain-context-selection-schema.ts +100 -0
  202. package/src/company-brain-context-selection.ts +359 -0
  203. package/src/company-brain-governed-writes.ts +1170 -0
  204. package/src/company-brain.ts +309 -0
  205. package/src/company-profile-agent-admin.ts +225 -0
  206. package/src/company-profile-schema.ts +121 -1
  207. package/src/company-profile.ts +21 -0
  208. package/src/computer-sessions.ts +10 -1
  209. package/src/connection-authority.ts +290 -0
  210. package/src/connection-scopes.ts +20 -0
  211. package/src/connection-tenancy-backfill.ts +167 -0
  212. package/src/connection-token-resolver.ts +263 -31
  213. package/src/database.ts +87 -2
  214. package/src/editable-artifacts-schema.ts +10 -5
  215. package/src/editable-artifacts.ts +21 -11
  216. package/src/governed-learning-activation-schema.ts +121 -0
  217. package/src/governed-learning-activation.ts +588 -0
  218. package/src/governed-learning-evaluator-schema.ts +72 -0
  219. package/src/governed-learning-evaluator.ts +217 -0
  220. package/src/index.ts +11676 -1550
  221. package/src/insights.ts +107 -0
  222. package/src/integration-bindings.ts +88 -6
  223. package/src/integration-facets.ts +287 -84
  224. package/src/interaction-placement-loss.ts +234 -0
  225. package/src/managed-human-provisioning.ts +116 -0
  226. package/src/memory-domain.ts +27 -32
  227. package/src/migrate.ts +77 -1
  228. package/src/new-session-drafts.ts +229 -35
  229. package/src/organization-membership-backfill.ts +735 -0
  230. package/src/organization-membership-lifecycle.ts +995 -0
  231. package/src/pr-review.ts +890 -0
  232. package/src/preference-registry.ts +31 -1
  233. package/src/provision-roles.ts +905 -6
  234. package/src/runtime-posture-cli.ts +2 -0
  235. package/src/runtime-posture.ts +1317 -72
  236. package/src/schema.ts +3316 -428
  237. package/src/scoped-knowledge-schema.ts +16 -8
  238. package/src/scoped-knowledge.ts +1 -1
  239. package/src/session-background-commands.ts +759 -0
  240. package/src/session-control.ts +640 -74
  241. package/src/session-goal-pacing.ts +126 -0
  242. package/src/session-queue-commands.ts +441 -85
  243. package/src/session-realtime-context.ts +4 -6
  244. package/src/session-realtime-ledger.ts +28 -14
  245. package/src/session-realtime-mirror.ts +1 -1
  246. package/src/session-tenancy.ts +410 -49
  247. package/src/session-tool-call-settlement.ts +38 -29
  248. package/src/slack-routing-personal-workspace.ts +67 -0
  249. package/src/slack-routing.ts +867 -0
  250. package/src/slack-task-policy-schema.ts +5 -0
  251. package/src/slack-task-policy.ts +18 -0
  252. package/src/slack-user-link-access.ts +138 -11
  253. package/src/task-notes-schema.ts +74 -0
  254. package/src/task-notes.ts +124 -2
  255. package/src/turn-initiator.ts +8 -0
  256. package/src/user-resource-authority.ts +377 -0
  257. package/src/workspace-authority.ts +160 -0
  258. package/src/workspace-instruction-policies-schema.ts +53 -1
  259. package/src/workspace-instruction-policies.ts +528 -77
  260. package/src/workspace-learning-policy.ts +21 -5
  261. package/src/workspace-membership-permissions.ts +9 -0
  262. package/dist/chunk-4SR4HKKA.js.map +0 -1
  263. package/dist/chunk-6ZYKCRFO.js.map +0 -1
  264. package/dist/chunk-NXAFLCQX.js +0 -2468
  265. package/dist/chunk-NXAFLCQX.js.map +0 -1
  266. package/dist/chunk-SK7YYHBI.js.map +0 -1
  267. package/dist/chunk-WD5ROSLL.js +0 -117
  268. package/dist/chunk-WD5ROSLL.js.map +0 -1
  269. package/dist/chunk-ZWREYEWA.js.map +0 -1
  270. /package/dist/{chunk-CUPM3FGB.js.map → chunk-2PRS4J5Q.js.map} +0 -0
  271. /package/dist/{chunk-LTG3S6ZY.js.map → chunk-E6CLMKEE.js.map} +0 -0
@@ -0,0 +1,773 @@
1
+ -- deployment-mode: rolling
2
+ -- Migration 0280: connection-use audit facts and variable-set audit events
3
+ -- carry the accepted authority attribution.
4
+ --
5
+ -- `connection_use_audit_facts` (0264) recorded what was used but not by whose
6
+ -- causal authority: no causal initiator, no session tenancy epoch/visibility,
7
+ -- no owning membership. The columns below are nullable audit evidence written
8
+ -- by the resolver from the exact rows the same transaction already locked -
9
+ -- the frozen turn identity verbatim (a pre-0096 sentinel stays the sentinel)
10
+ -- and the live session authority triple. A denial whose lifecycle fence never
11
+ -- loaded the row leaves NULL attribution, which is itself the honest fact.
12
+ -- No foreign keys: audit rows are immutable evidence and must never block a
13
+ -- membership lifecycle or retention operation.
14
+ --
15
+ -- The variable-set audit events (0254/0273) gain the same attribution in
16
+ -- their metadata: causal human, attempt authority epoch/visibility/owner
17
+ -- membership, and the variable set's owner authority/membership/origin.
18
+ -- Denials in these functions RAISE and roll the transaction back, so denial
19
+ -- recording stays with the application caller in a fresh transaction
20
+ -- (metadata-only, content-free) - recording inside the aborted transaction is
21
+ -- impossible by design, and weakening RAISE to a soft return would change the
22
+ -- authority contract.
23
+ --
24
+ -- Rolling window: nullable columns plus CREATE OR REPLACE of three existing
25
+ -- functions with unchanged signatures and grants; an old image keeps writing
26
+ -- audit rows without attribution exactly as before.
27
+
28
+ SET LOCAL lock_timeout = '5s';
29
+ SET LOCAL statement_timeout = '5min';
30
+
31
+ -- 1. Attribution columns ------------------------------------------------
32
+ -- Conditional on the 0264 cutover having been activated: a deployment that
33
+ -- defers the connection-authority maintenance cutover replays this rolling
34
+ -- migration without the audit table, and the resolver redefinition below is
35
+ -- inert there for the same reason the 0264 activation gate is.
36
+
37
+ DO $connection_audit_attribution_columns$
38
+ BEGIN
39
+ IF to_regclass('connection_use_audit_facts') IS NULL THEN
40
+ RETURN;
41
+ END IF;
42
+ ALTER TABLE "connection_use_audit_facts"
43
+ ADD COLUMN IF NOT EXISTS "initiator_kind" text,
44
+ ADD COLUMN IF NOT EXISTS "initiator_subject_id" text,
45
+ ADD COLUMN IF NOT EXISTS "initiating_human_subject_id" text,
46
+ ADD COLUMN IF NOT EXISTS "authority_epoch" integer,
47
+ ADD COLUMN IF NOT EXISTS "authority_visibility" text,
48
+ ADD COLUMN IF NOT EXISTS "authority_owner_organization_membership_id" uuid;
49
+ IF NOT EXISTS (
50
+ SELECT 1 FROM pg_constraint
51
+ WHERE conname = 'connection_use_audit_attribution_check'
52
+ AND conrelid = 'connection_use_audit_facts'::regclass
53
+ ) THEN
54
+ ALTER TABLE "connection_use_audit_facts"
55
+ ADD CONSTRAINT "connection_use_audit_attribution_check"
56
+ CHECK (
57
+ ("authority_visibility" IS NULL
58
+ OR "authority_visibility" IN ('user_private', 'workspace_shared'))
59
+ AND ("authority_epoch" IS NULL OR "authority_epoch" > 0)
60
+ ) NOT VALID;
61
+ END IF;
62
+ ALTER TABLE "connection_use_audit_facts"
63
+ VALIDATE CONSTRAINT "connection_use_audit_attribution_check";
64
+ END
65
+ $connection_audit_attribution_columns$;
66
+
67
+ -- 2. The connection-use resolver records attribution --------------------
68
+
69
+ CREATE OR REPLACE FUNCTION resolve_accepted_connection_use(
70
+ p_account_id uuid, p_workspace_id uuid, p_session_id uuid, p_turn_id uuid,
71
+ p_attempt_id uuid, p_execution_generation integer, p_physical_request_id uuid,
72
+ p_use_phase text, p_server_id text, p_connection_id uuid,
73
+ p_provider_domain text, p_connection_kind text, p_subject_scope text,
74
+ p_owner_subject_id text DEFAULT NULL
75
+ ) RETURNS TABLE (
76
+ authorization_status text, denial_reason text, resolved_connection_id uuid,
77
+ connection_generation bigint, origin_workspace_id uuid,
78
+ resolved_connection_kind text, authority_scope text, owner_subject_id text,
79
+ authority_id uuid, grant_id uuid
80
+ )
81
+ LANGUAGE plpgsql SECURITY DEFINER
82
+ SET search_path = pg_catalog, pg_temp
83
+ AS $body$
84
+ #variable_conflict use_column
85
+ DECLARE
86
+ prior record;
87
+ connection_row record;
88
+ snapshot record;
89
+ session_row record;
90
+ turn_row record;
91
+ audit_initiator_kind text;
92
+ audit_initiator_subject text;
93
+ audit_initiating_human text;
94
+ audit_authority_epoch integer;
95
+ audit_authority_visibility text;
96
+ audit_owner_membership uuid;
97
+ grant_row record;
98
+ request_digest bytea;
99
+ has_prior boolean := false;
100
+ reason text;
101
+ scheduled_run_id uuid;
102
+ BEGIN
103
+ IF p_account_id IS DISTINCT FROM nullif(current_setting('opengeni.account_id', true), '')::uuid
104
+ OR p_workspace_id IS DISTINCT FROM nullif(current_setting('opengeni.workspace_id', true), '')::uuid
105
+ OR p_execution_generation <= 0
106
+ OR p_use_phase NOT IN ('credential_resolution', 'provider_request')
107
+ OR nullif(btrim(p_server_id), '') IS NULL OR octet_length(p_server_id) > 256
108
+ THEN RAISE EXCEPTION 'connection use scope mismatch' USING ERRCODE = '42501';
109
+ END IF;
110
+ request_digest := digest(convert_to(jsonb_build_object(
111
+ 'accountId', p_account_id, 'workspaceId', p_workspace_id,
112
+ 'sessionId', p_session_id, 'turnId', p_turn_id, 'attemptId', p_attempt_id,
113
+ 'executionGeneration', p_execution_generation, 'usePhase', p_use_phase,
114
+ 'serverId', p_server_id, 'connectionId', p_connection_id,
115
+ 'providerDomain', lower(p_provider_domain), 'connectionKind', p_connection_kind,
116
+ 'subjectScope', p_subject_scope, 'ownerSubjectId', p_owner_subject_id
117
+ )::text, 'UTF8'), 'sha256');
118
+
119
+ -- Canonical lifecycle lock prefix: control -> workspace -> session -> turn -> attempt.
120
+ PERFORM 1 FROM workspace_inference_controls control_row
121
+ WHERE control_row.account_id = p_account_id AND control_row.workspace_id = p_workspace_id
122
+ FOR SHARE;
123
+ IF NOT FOUND THEN reason := 'session_identity_changed'; END IF;
124
+ IF reason IS NULL THEN
125
+ PERFORM 1 FROM workspaces workspace_value
126
+ WHERE workspace_value.account_id = p_account_id AND workspace_value.id = p_workspace_id
127
+ FOR KEY SHARE;
128
+ IF NOT FOUND THEN reason := 'session_identity_changed'; END IF;
129
+ END IF;
130
+ IF reason IS NULL THEN
131
+ SELECT session_value.* INTO session_row FROM sessions session_value
132
+ WHERE session_value.id = p_session_id AND session_value.account_id = p_account_id
133
+ AND session_value.workspace_id = p_workspace_id
134
+ FOR NO KEY UPDATE;
135
+ IF NOT FOUND OR session_row.active_turn_id IS DISTINCT FROM p_turn_id
136
+ OR session_row.status = 'cancelled'
137
+ THEN reason := 'session_identity_changed'; END IF;
138
+ -- Attribution evidence (0280): captured from the row this same
139
+ -- transaction locked, NULL when the fence never loaded it.
140
+ audit_authority_epoch := session_row.authority_epoch;
141
+ audit_authority_visibility := session_row.visibility;
142
+ audit_owner_membership := session_row.owner_organization_membership_id;
143
+ END IF;
144
+ IF reason IS NULL THEN
145
+ SELECT turn_value.* INTO turn_row FROM session_turns turn_value
146
+ WHERE turn_value.id = p_turn_id AND turn_value.account_id = p_account_id
147
+ AND turn_value.workspace_id = p_workspace_id AND turn_value.session_id = p_session_id
148
+ AND turn_value.active_attempt_id = p_attempt_id
149
+ AND turn_value.execution_generation = p_execution_generation
150
+ AND turn_value.status = 'running'
151
+ FOR UPDATE;
152
+ IF NOT FOUND THEN reason := 'session_identity_changed'; END IF;
153
+ -- Causal-initiator evidence (0280): the frozen turn identity, recorded
154
+ -- verbatim (a pre-0096 sentinel stays the sentinel), NULL when the turn
155
+ -- fence never matched a row.
156
+ audit_initiator_kind := turn_row.initiator_kind;
157
+ audit_initiator_subject := turn_row.initiator_subject_id;
158
+ audit_initiating_human := turn_row.initiating_human_subject_id;
159
+ END IF;
160
+ IF reason IS NULL THEN
161
+ PERFORM 1 FROM session_turn_attempts attempt
162
+ WHERE attempt.id = p_attempt_id AND attempt.account_id = p_account_id
163
+ AND attempt.workspace_id = p_workspace_id AND attempt.session_id = p_session_id
164
+ AND attempt.turn_id = p_turn_id
165
+ AND attempt.execution_generation = p_execution_generation
166
+ AND attempt.state IN ('claimed', 'running')
167
+ AND attempt.closed_at IS NULL AND attempt.quiesced_at IS NULL
168
+ AND attempt.authority_visibility = session_row.visibility
169
+ AND attempt.authority_epoch = session_row.authority_epoch
170
+ AND attempt.authority_owner_organization_membership_id
171
+ IS NOT DISTINCT FROM session_row.owner_organization_membership_id
172
+ FOR UPDATE;
173
+ IF NOT FOUND OR EXISTS (
174
+ SELECT 1 FROM session_attempt_interruptions interruption
175
+ WHERE interruption.account_id = p_account_id
176
+ AND interruption.workspace_id = p_workspace_id
177
+ AND interruption.session_id = p_session_id
178
+ AND interruption.attempt_id = p_attempt_id
179
+ AND interruption.state IN ('pending', 'delivered', 'acknowledged')
180
+ ) THEN reason := 'session_identity_changed'; END IF;
181
+ END IF;
182
+
183
+ PERFORM pg_advisory_xact_lock(hashtextextended(p_physical_request_id::text, 0));
184
+ SELECT EXISTS (SELECT 1 FROM connection_use_audit_facts audit
185
+ WHERE audit.physical_request_id = p_physical_request_id) INTO has_prior;
186
+ -- Only dereference `prior` inside the guarded branch: PL/pgSQL evaluates
187
+ -- both operands of `has_prior AND prior.<field>`, and an unassigned record
188
+ -- raises instead of yielding NULL.
189
+ IF has_prior THEN
190
+ SELECT audit.* INTO STRICT prior FROM connection_use_audit_facts audit
191
+ WHERE audit.physical_request_id = p_physical_request_id;
192
+ IF prior.request_digest IS DISTINCT FROM request_digest THEN
193
+ RAISE EXCEPTION 'physical connection request id was reused for different work'
194
+ USING ERRCODE = '23505';
195
+ END IF;
196
+ IF reason IS NULL AND prior.outcome = 'denied' THEN
197
+ authorization_status := prior.outcome; denial_reason := prior.denial_reason;
198
+ RETURN NEXT; RETURN;
199
+ END IF;
200
+ END IF;
201
+
202
+ IF reason IS NULL THEN
203
+ SELECT authority_snapshot.* INTO snapshot
204
+ FROM turn_connection_authority_snapshots authority_snapshot
205
+ WHERE authority_snapshot.turn_id = p_turn_id
206
+ AND authority_snapshot.server_id = p_server_id
207
+ AND authority_snapshot.account_id = p_account_id
208
+ AND authority_snapshot.workspace_id = p_workspace_id
209
+ AND authority_snapshot.session_id = p_session_id;
210
+ IF FOUND THEN
211
+ scheduled_run_id := CASE
212
+ WHEN snapshot.canonical_snapshot -> 'acceptedWork' ->> 'kind' = 'scheduled_task'
213
+ THEN nullif(snapshot.canonical_snapshot -> 'acceptedWork' ->> 'runId', '')::uuid
214
+ ELSE NULL END;
215
+ SELECT connection_value.* INTO connection_row FROM connections connection_value
216
+ WHERE connection_value.id = snapshot.connection_id
217
+ AND connection_value.account_id = p_account_id;
218
+ IF snapshot.snapshot_digest IS DISTINCT FROM digest(
219
+ convert_to(snapshot.canonical_snapshot::text, 'UTF8'), 'sha256'
220
+ ) THEN reason := 'accepted_snapshot_digest_changed';
221
+ ELSIF NOT FOUND THEN reason := 'connection_missing';
222
+ ELSIF snapshot.authority_scope IS DISTINCT FROM 'user'
223
+ OR p_subject_scope IS DISTINCT FROM 'subject'
224
+ OR p_connection_id IS DISTINCT FROM snapshot.connection_id
225
+ OR lower(p_provider_domain) IS DISTINCT FROM snapshot.provider_domain
226
+ OR (p_connection_kind IS NOT NULL
227
+ AND p_connection_kind IS DISTINCT FROM snapshot.connection_kind)
228
+ THEN reason := 'connection_identity_changed';
229
+ ELSIF connection_row.status <> 'active' THEN reason := 'connection_status_inactive';
230
+ ELSIF connection_row.authority_scope IS DISTINCT FROM 'user'
231
+ OR connection_row.subject_id IS DISTINCT FROM snapshot.owner_subject_id
232
+ OR connection_row.owner_organization_membership_id
233
+ IS DISTINCT FROM snapshot.owner_organization_membership_id
234
+ OR connection_row.origin_workspace_id IS DISTINCT FROM snapshot.origin_workspace_id
235
+ THEN reason := 'connection_owner_changed';
236
+ ELSIF connection_row.authority_generation IS DISTINCT FROM snapshot.connection_generation
237
+ THEN reason := 'connection_generation_changed';
238
+ ELSIF session_row.visibility IS DISTINCT FROM snapshot.session_visibility
239
+ OR session_row.authority_epoch IS DISTINCT FROM snapshot.session_authority_epoch
240
+ THEN reason := 'session_authority_epoch_changed';
241
+ ELSIF NOT EXISTS (
242
+ SELECT 1 FROM organization_memberships membership
243
+ WHERE membership.id = snapshot.owner_organization_membership_id
244
+ AND membership.account_id = p_account_id
245
+ AND membership.subject_id = snapshot.owner_subject_id
246
+ AND membership.status = 'active' AND membership.revoked_at IS NULL
247
+ AND membership.authorization_revision = snapshot.membership_authorization_revision
248
+ AND (membership.personal_workspace_id = p_workspace_id OR EXISTS (
249
+ SELECT 1 FROM workspace_memberships workspace_membership
250
+ WHERE workspace_membership.account_id = p_account_id
251
+ AND workspace_membership.workspace_id = p_workspace_id
252
+ AND workspace_membership.subject_id = snapshot.owner_subject_id
253
+ ))
254
+ ) THEN reason := 'owner_membership_inactive';
255
+ ELSIF NOT EXISTS (
256
+ SELECT 1 FROM organization_user_resource_authorities authority
257
+ WHERE authority.id = snapshot.authority_id AND authority.account_id = p_account_id
258
+ AND authority.organization_membership_id = snapshot.owner_organization_membership_id
259
+ AND authority.resource_kind = 'connection'
260
+ AND authority.resource_id = snapshot.connection_id
261
+ AND authority.origin_workspace_id = snapshot.origin_workspace_id
262
+ AND authority.generation = snapshot.authority_generation
263
+ AND authority.status = 'active' AND authority.revoked_at IS NULL
264
+ ) THEN reason := 'authority_status_inactive';
265
+ ELSE
266
+ SELECT grant_value.* INTO grant_row FROM organization_user_resource_grants grant_value
267
+ WHERE grant_value.id = snapshot.grant_id AND grant_value.account_id = p_account_id
268
+ FOR UPDATE;
269
+ IF NOT FOUND THEN reason := 'grant_missing';
270
+ ELSIF grant_row.authority_id IS DISTINCT FROM snapshot.authority_id
271
+ OR grant_row.owner_organization_membership_id
272
+ IS DISTINCT FROM snapshot.owner_organization_membership_id
273
+ OR grant_row.workspace_id IS DISTINCT FROM p_workspace_id
274
+ OR grant_row.action <> 'connection.use'
275
+ OR grant_row.mode IS DISTINCT FROM snapshot.grant_mode
276
+ OR grant_row.context IS DISTINCT FROM snapshot.grant_context
277
+ OR grant_row.session_id IS DISTINCT FROM snapshot.grant_session_id
278
+ OR grant_row.authority_epoch IS DISTINCT FROM snapshot.grant_authority_epoch
279
+ THEN reason := 'grant_identity_changed';
280
+ ELSIF grant_row.generation IS DISTINCT FROM snapshot.grant_generation
281
+ THEN reason := 'grant_generation_changed';
282
+ ELSIF grant_row.expires_at IS NOT NULL AND grant_row.expires_at <= clock_timestamp()
283
+ THEN reason := 'grant_expired';
284
+ ELSIF grant_row.mode = 'once' THEN
285
+ IF grant_row.status <> 'consumed' OR NOT EXISTS (
286
+ SELECT 1 FROM connection_use_once_consumption_receipts receipt
287
+ WHERE receipt.grant_id = grant_row.id
288
+ AND receipt.authority_id = snapshot.authority_id
289
+ AND receipt.authority_generation = snapshot.authority_generation
290
+ AND receipt.grant_generation = snapshot.grant_generation
291
+ AND (
292
+ (receipt.accepted_work_kind = 'turn'
293
+ AND receipt.accepted_work_id = p_turn_id)
294
+ OR (receipt.accepted_work_kind = 'scheduled_task'
295
+ AND scheduled_run_id IS NOT NULL
296
+ AND receipt.accepted_work_id = scheduled_run_id)
297
+ )
298
+ ) THEN reason := 'grant_already_consumed'; END IF;
299
+ ELSIF grant_row.status <> 'active' THEN reason := 'grant_status_inactive';
300
+ END IF;
301
+ END IF;
302
+ resolved_connection_id := snapshot.connection_id;
303
+ connection_generation := snapshot.connection_generation;
304
+ origin_workspace_id := snapshot.origin_workspace_id;
305
+ resolved_connection_kind := snapshot.connection_kind;
306
+ authority_scope := snapshot.authority_scope;
307
+ owner_subject_id := snapshot.owner_subject_id;
308
+ authority_id := snapshot.authority_id;
309
+ grant_id := snapshot.grant_id;
310
+ ELSIF p_subject_scope = 'workspace' THEN
311
+ -- Workspace lane (0279): a workspace-owned connection is ambient shared
312
+ -- workspace capability, never frozen on the turn, so it is validated
313
+ -- against the live row inside the same canonical lifecycle fences and
314
+ -- recorded in the same idempotent audit facts. A frozen per-turn
315
+ -- personal snapshot for this server takes the snapshot lane above and
316
+ -- therefore denies a workspace-scope request outright (fail closed).
317
+ SELECT connection_value.* INTO connection_row FROM connections connection_value
318
+ WHERE connection_value.id = p_connection_id AND connection_value.account_id = p_account_id;
319
+ IF p_connection_id IS NULL OR NOT FOUND THEN reason := 'connection_missing';
320
+ ELSIF connection_row.authority_scope IS DISTINCT FROM 'workspace'
321
+ OR p_owner_subject_id IS NOT NULL
322
+ OR connection_row.workspace_id IS DISTINCT FROM p_workspace_id
323
+ OR connection_row.origin_workspace_id IS DISTINCT FROM p_workspace_id
324
+ OR lower(connection_row.provider_domain) IS DISTINCT FROM lower(p_provider_domain)
325
+ OR (p_connection_kind IS NOT NULL
326
+ AND connection_row.kind IS DISTINCT FROM p_connection_kind)
327
+ THEN reason := 'connection_identity_changed';
328
+ ELSIF connection_row.status <> 'active' THEN reason := 'connection_status_inactive';
329
+ ELSE
330
+ resolved_connection_id := connection_row.id;
331
+ connection_generation := connection_row.authority_generation;
332
+ origin_workspace_id := connection_row.origin_workspace_id;
333
+ resolved_connection_kind := connection_row.kind;
334
+ authority_scope := 'workspace';
335
+ END IF;
336
+ ELSE
337
+ SELECT connection_value.* INTO connection_row FROM connections connection_value
338
+ WHERE connection_value.id = p_connection_id AND connection_value.account_id = p_account_id;
339
+ IF NOT FOUND THEN reason := 'connection_missing';
340
+ ELSIF connection_row.authority_scope IS DISTINCT FROM 'legacy_user'
341
+ OR p_subject_scope IS DISTINCT FROM 'subject' OR p_owner_subject_id IS NULL
342
+ OR connection_row.subject_id IS DISTINCT FROM p_owner_subject_id
343
+ OR connection_row.workspace_id IS DISTINCT FROM p_workspace_id
344
+ OR connection_row.origin_workspace_id IS DISTINCT FROM p_workspace_id
345
+ OR lower(connection_row.provider_domain) IS DISTINCT FROM lower(p_provider_domain)
346
+ OR (p_connection_kind IS NOT NULL
347
+ AND connection_row.kind IS DISTINCT FROM p_connection_kind)
348
+ THEN reason := 'connection_identity_changed';
349
+ ELSIF connection_row.status <> 'active' THEN reason := 'connection_status_inactive';
350
+ ELSE
351
+ resolved_connection_id := connection_row.id;
352
+ connection_generation := connection_row.authority_generation;
353
+ origin_workspace_id := connection_row.origin_workspace_id;
354
+ resolved_connection_kind := connection_row.kind;
355
+ authority_scope := 'legacy_user'; owner_subject_id := connection_row.subject_id;
356
+ END IF;
357
+ END IF;
358
+ END IF;
359
+
360
+ authorization_status := CASE WHEN reason IS NULL THEN 'authorized' ELSE 'denied' END;
361
+ denial_reason := reason;
362
+ IF NOT has_prior THEN
363
+ INSERT INTO connection_use_audit_facts (
364
+ physical_request_id, use_phase, request_digest, account_id, workspace_id,
365
+ session_id, turn_id, attempt_id, execution_generation, server_id,
366
+ connection_id, connection_generation, authority_scope, owner_subject_id,
367
+ authority_id, grant_id, outcome, denial_reason,
368
+ initiator_kind, initiator_subject_id, initiating_human_subject_id,
369
+ authority_epoch, authority_visibility,
370
+ authority_owner_organization_membership_id
371
+ ) VALUES (
372
+ p_physical_request_id, p_use_phase, request_digest, p_account_id,
373
+ p_workspace_id, p_session_id, p_turn_id, p_attempt_id,
374
+ p_execution_generation, p_server_id, resolved_connection_id,
375
+ connection_generation, authority_scope, owner_subject_id, authority_id,
376
+ grant_id, authorization_status, denial_reason,
377
+ audit_initiator_kind, audit_initiator_subject, audit_initiating_human,
378
+ audit_authority_epoch, audit_authority_visibility, audit_owner_membership
379
+ ) ON CONFLICT (physical_request_id) DO NOTHING;
380
+ END IF;
381
+ RETURN NEXT;
382
+ END
383
+ $body$;
384
+
385
+ DO $connection_audit_attribution_posture$
386
+ DECLARE
387
+ data_schema text := pg_catalog.current_schema();
388
+ signature text := 'resolve_accepted_connection_use'
389
+ || '(uuid,uuid,uuid,uuid,uuid,integer,uuid,text,text,uuid,text,text,text,text)';
390
+ BEGIN
391
+ -- Same re-pin as 0279: CREATE OR REPLACE replaced the attached search_path
392
+ -- with the minimal header value; `public` stays for the pgcrypto digest().
393
+ EXECUTE format(
394
+ 'ALTER FUNCTION %I.%s SET search_path = pg_catalog, %I, public, pg_temp',
395
+ data_schema, signature, data_schema
396
+ );
397
+ EXECUTE format('REVOKE ALL ON FUNCTION %I.%s FROM PUBLIC', data_schema, signature);
398
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
399
+ EXECUTE format('GRANT EXECUTE ON FUNCTION %I.%s TO opengeni_app', data_schema, signature);
400
+ END IF;
401
+ END
402
+ $connection_audit_attribution_posture$;
403
+
404
+ -- 3. Variable-set materialization audit attribution ---------------------
405
+
406
+ CREATE OR REPLACE FUNCTION materialize_scoped_variable_set_for_attempt(
407
+ p_account_id uuid,
408
+ p_workspace_id uuid,
409
+ p_session_id uuid,
410
+ p_turn_id uuid,
411
+ p_attempt_id uuid,
412
+ p_execution_generation integer,
413
+ p_variable_set_id uuid
414
+ ) RETURNS TABLE (
415
+ variable_set_id uuid,
416
+ variable_set_name text,
417
+ variable_set_description text,
418
+ authority_scope text,
419
+ variable_set_generation bigint,
420
+ variable_name text,
421
+ value_encrypted text
422
+ )
423
+ LANGUAGE plpgsql
424
+ SECURITY DEFINER
425
+ SET search_path FROM CURRENT
426
+ AS $$
427
+ DECLARE
428
+ audit_subject text;
429
+ causal_human text;
430
+ audit_actor_kind text;
431
+ variable_set_row workspace_variable_sets%ROWTYPE;
432
+ resolved_count integer;
433
+ attempt_authority_epoch integer;
434
+ attempt_authority_visibility text;
435
+ attempt_owner_membership uuid;
436
+ BEGIN
437
+ INSERT INTO opengeni_private.variable_set_authority_capabilities (
438
+ backend_pid, transaction_id, capability_kind
439
+ ) VALUES (pg_catalog.pg_backend_pid(), pg_catalog.pg_current_xact_id(), 'materialize')
440
+ ON CONFLICT DO NOTHING;
441
+
442
+ SELECT actor_subject, causal_human_subject, actor_kind
443
+ INTO STRICT audit_subject, causal_human, audit_actor_kind
444
+ FROM assert_scoped_variable_set_materialization_attempt(
445
+ p_account_id, p_workspace_id, p_session_id, p_turn_id, p_attempt_id,
446
+ p_execution_generation
447
+ );
448
+
449
+ -- Attribution evidence (0280): the exact attempt the assert above already
450
+ -- validated owns the frozen session tenancy authority for this use.
451
+ SELECT attempt.authority_epoch, attempt.authority_visibility,
452
+ attempt.authority_owner_organization_membership_id
453
+ INTO attempt_authority_epoch, attempt_authority_visibility, attempt_owner_membership
454
+ FROM session_turn_attempts attempt
455
+ WHERE attempt.id = p_attempt_id
456
+ AND attempt.account_id = p_account_id
457
+ AND attempt.workspace_id = p_workspace_id;
458
+
459
+ SELECT variable_set.* INTO STRICT variable_set_row
460
+ FROM workspace_variable_sets variable_set
461
+ WHERE variable_set.id = p_variable_set_id
462
+ AND variable_set.account_id = p_account_id
463
+ AND variable_set.status = 'active'
464
+ FOR SHARE;
465
+
466
+ PERFORM 1 FROM sessions session_value
467
+ LEFT JOIN rig_versions rig_version
468
+ ON rig_version.id = session_value.rig_version_id
469
+ AND rig_version.rig_id = session_value.rig_id
470
+ AND rig_version.account_id = session_value.account_id
471
+ WHERE session_value.id = p_session_id
472
+ AND session_value.account_id = p_account_id
473
+ AND session_value.workspace_id = p_workspace_id
474
+ AND (
475
+ session_value.variable_set_id = variable_set_row.id
476
+ OR coalesce(rig_version.default_variable_set_ids, '[]'::jsonb)
477
+ ? variable_set_row.id::text
478
+ );
479
+ IF NOT FOUND THEN
480
+ RAISE EXCEPTION 'runtime variable set was not selected by the exact session'
481
+ USING ERRCODE = '42501';
482
+ END IF;
483
+
484
+ IF variable_set_row.authority_scope = 'workspace'
485
+ AND variable_set_row.workspace_id IS DISTINCT FROM p_workspace_id
486
+ THEN
487
+ RAISE EXCEPTION 'workspace variable set is outside the runtime workspace'
488
+ USING ERRCODE = '42501';
489
+ ELSIF variable_set_row.authority_scope = 'user' THEN
490
+ IF causal_human IS NULL THEN
491
+ RAISE EXCEPTION 'personal variable-set materialization requires an initiating human subject'
492
+ USING ERRCODE = '42501';
493
+ END IF;
494
+ SELECT count(*)::integer INTO resolved_count
495
+ FROM resolve_session_attempt_personal_resources(
496
+ p_account_id, p_workspace_id, p_attempt_id
497
+ ) resolved
498
+ WHERE resolved.resource_kind = 'variable_set'
499
+ AND resolved.resource_id = variable_set_row.id
500
+ AND resolved.authority_id = variable_set_row.authority_id
501
+ AND resolved.authority_generation = variable_set_row.generation;
502
+ IF resolved_count <> 1 THEN
503
+ RAISE EXCEPTION 'personal variable-set grant is not exact or current'
504
+ USING ERRCODE = '42501';
505
+ END IF;
506
+ END IF;
507
+
508
+ INSERT INTO audit_events (
509
+ account_id, workspace_id, subject_id, action, target_type, target_id,
510
+ metadata, metadata_codec_version
511
+ ) VALUES (
512
+ p_account_id, p_workspace_id, audit_subject,
513
+ 'variable_set.materialized', 'workspace_variable_set',
514
+ variable_set_row.id::text,
515
+ pg_catalog.jsonb_build_object(
516
+ 'variableSetId', variable_set_row.id,
517
+ 'scope', variable_set_row.authority_scope,
518
+ 'generation', variable_set_row.generation,
519
+ 'actorKind', audit_actor_kind,
520
+ 'sessionId', p_session_id,
521
+ 'turnId', p_turn_id,
522
+ 'attemptId', p_attempt_id,
523
+ 'executionGeneration', p_execution_generation,
524
+ 'causalHumanSubjectId', causal_human,
525
+ 'authorityEpoch', attempt_authority_epoch,
526
+ 'authorityVisibility', attempt_authority_visibility,
527
+ 'authorityOwnerOrganizationMembershipId', attempt_owner_membership,
528
+ 'ownerAuthorityId', variable_set_row.authority_id,
529
+ 'ownerOrganizationMembershipId', variable_set_row.owner_organization_membership_id,
530
+ 'originWorkspaceId', variable_set_row.origin_workspace_id
531
+ ),
532
+ 1
533
+ );
534
+
535
+ RETURN QUERY
536
+ SELECT selected.id, selected.name, selected.description,
537
+ selected.authority_scope, selected.generation,
538
+ variable.name, variable.value_encrypted
539
+ FROM workspace_variable_sets selected
540
+ LEFT JOIN workspace_variable_set_variables variable
541
+ ON variable.account_id = selected.account_id
542
+ AND variable.variable_set_id = selected.id
543
+ WHERE selected.id = variable_set_row.id
544
+ ORDER BY variable.name;
545
+
546
+ DELETE FROM opengeni_private.variable_set_authority_capabilities
547
+ WHERE backend_pid = pg_catalog.pg_backend_pid()
548
+ AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
549
+ AND capability_kind = 'materialize';
550
+ RETURN;
551
+ EXCEPTION WHEN OTHERS THEN
552
+ DELETE FROM opengeni_private.variable_set_authority_capabilities
553
+ WHERE backend_pid = pg_catalog.pg_backend_pid()
554
+ AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
555
+ AND capability_kind = 'materialize';
556
+ RAISE;
557
+ END
558
+ $$;
559
+
560
+ REVOKE ALL ON FUNCTION materialize_scoped_variable_set_for_attempt(
561
+ uuid, uuid, uuid, uuid, uuid, integer, uuid
562
+ ) FROM PUBLIC;
563
+ DO $materialize_attribution_grant$
564
+ BEGIN
565
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
566
+ GRANT EXECUTE ON FUNCTION materialize_scoped_variable_set_for_attempt(
567
+ uuid, uuid, uuid, uuid, uuid, integer, uuid
568
+ ) TO opengeni_app;
569
+ END IF;
570
+ END
571
+ $materialize_attribution_grant$;
572
+
573
+ -- 4. Variable-set secret-read audit attribution -------------------------
574
+
575
+ DO $secret_read_attribution$
576
+ DECLARE
577
+ data_schema text := pg_catalog.current_schema();
578
+ BEGIN
579
+ EXECUTE format($ddl$
580
+ CREATE OR REPLACE FUNCTION %1$I.read_scoped_variable_set_secret(
581
+ p_account_id uuid,
582
+ p_workspace_id uuid,
583
+ p_variable_set_id uuid,
584
+ p_variable_set_name text,
585
+ p_variable_name text,
586
+ p_actor_kind text,
587
+ p_session_id uuid DEFAULT NULL,
588
+ p_turn_id uuid DEFAULT NULL,
589
+ p_attempt_id uuid DEFAULT NULL,
590
+ p_execution_generation integer DEFAULT NULL
591
+ ) RETURNS TABLE (
592
+ variable_set_id uuid,
593
+ variable_name text,
594
+ variable_version integer,
595
+ value_encrypted text,
596
+ authority_scope text,
597
+ variable_set_generation bigint
598
+ )
599
+ LANGUAGE plpgsql
600
+ SECURITY DEFINER
601
+ SET search_path = %1$I, pg_catalog
602
+ AS $body$
603
+ DECLARE
604
+ actor_membership_id uuid;
605
+ variable_set_row workspace_variable_sets%%ROWTYPE;
606
+ resolved_count integer;
607
+ attempt_authority_epoch integer;
608
+ attempt_authority_visibility text;
609
+ attempt_owner_membership uuid;
610
+ audit_subject text := coalesce(
611
+ nullif(pg_catalog.current_setting('opengeni.initiating_human_subject_id', true), ''),
612
+ nullif(pg_catalog.current_setting('opengeni.subject_id', true), '')
613
+ );
614
+ BEGIN
615
+ IF (p_variable_set_id IS NULL) = (p_variable_set_name IS NULL)
616
+ OR p_actor_kind NOT IN ('subject', 'agent_attempt')
617
+ THEN
618
+ RAISE EXCEPTION 'invalid exact variable-set read request' USING ERRCODE = '22023';
619
+ END IF;
620
+ INSERT INTO opengeni_private.variable_set_authority_capabilities (
621
+ backend_pid, transaction_id, capability_kind
622
+ ) VALUES (pg_catalog.pg_backend_pid(), pg_catalog.pg_current_xact_id(), 'materialize')
623
+ ON CONFLICT DO NOTHING;
624
+
625
+ IF p_actor_kind = 'subject' THEN
626
+ actor_membership_id := scoped_variable_set_actor_membership(
627
+ p_account_id, p_workspace_id
628
+ );
629
+ ELSE
630
+ audit_subject := assert_scoped_variable_set_attempt(
631
+ p_account_id, p_workspace_id, p_session_id, p_turn_id, p_attempt_id,
632
+ p_execution_generation
633
+ );
634
+ -- Attribution evidence (0280): the exact attempt the assert above
635
+ -- already validated owns the frozen session tenancy authority.
636
+ SELECT attempt.authority_epoch, attempt.authority_visibility,
637
+ attempt.authority_owner_organization_membership_id
638
+ INTO attempt_authority_epoch, attempt_authority_visibility,
639
+ attempt_owner_membership
640
+ FROM session_turn_attempts attempt
641
+ WHERE attempt.id = p_attempt_id
642
+ AND attempt.account_id = p_account_id
643
+ AND attempt.workspace_id = p_workspace_id;
644
+ END IF;
645
+
646
+ SELECT variable_set.* INTO STRICT variable_set_row
647
+ FROM workspace_variable_sets variable_set
648
+ WHERE variable_set.account_id = p_account_id
649
+ AND variable_set.status = 'active'
650
+ AND (
651
+ (p_variable_set_id IS NOT NULL AND variable_set.id = p_variable_set_id)
652
+ OR (p_variable_set_name IS NOT NULL AND variable_set.name = p_variable_set_name)
653
+ )
654
+ AND (
655
+ variable_set.authority_scope = 'organization'
656
+ OR (
657
+ variable_set.authority_scope = 'workspace'
658
+ AND variable_set.workspace_id = p_workspace_id
659
+ )
660
+ OR (
661
+ variable_set.authority_scope = 'user'
662
+ AND (
663
+ (p_actor_kind = 'subject'
664
+ AND variable_set.owner_organization_membership_id = actor_membership_id)
665
+ OR p_actor_kind = 'agent_attempt'
666
+ )
667
+ )
668
+ )
669
+ ORDER BY CASE variable_set.authority_scope
670
+ WHEN 'user' THEN 1 WHEN 'workspace' THEN 2 ELSE 3 END
671
+ LIMIT 1
672
+ FOR SHARE;
673
+
674
+ IF p_actor_kind = 'agent_attempt' THEN
675
+ PERFORM 1 FROM sessions session_value
676
+ LEFT JOIN rig_versions rig_version
677
+ ON rig_version.id = session_value.rig_version_id
678
+ AND rig_version.rig_id = session_value.rig_id
679
+ AND rig_version.account_id = session_value.account_id
680
+ WHERE session_value.id = p_session_id
681
+ AND session_value.account_id = p_account_id
682
+ AND session_value.workspace_id = p_workspace_id
683
+ AND (
684
+ session_value.variable_set_id = variable_set_row.id
685
+ OR coalesce(rig_version.default_variable_set_ids, '[]'::jsonb)
686
+ ? variable_set_row.id::text
687
+ );
688
+ IF NOT FOUND THEN
689
+ RAISE EXCEPTION 'exact variable-set resource was not admitted for this attempt'
690
+ USING ERRCODE = '42501';
691
+ END IF;
692
+ IF variable_set_row.authority_scope = 'user' THEN
693
+ SELECT count(*)::integer INTO resolved_count
694
+ FROM resolve_session_attempt_personal_resources(
695
+ p_account_id, p_workspace_id, p_attempt_id
696
+ ) resolved
697
+ WHERE resolved.resource_kind = 'variable_set'
698
+ AND resolved.resource_id = variable_set_row.id
699
+ AND resolved.authority_id = variable_set_row.authority_id
700
+ AND resolved.authority_generation = variable_set_row.generation;
701
+ IF resolved_count <> 1 THEN
702
+ RAISE EXCEPTION 'personal variable-set grant is not exact or current'
703
+ USING ERRCODE = '42501';
704
+ END IF;
705
+ END IF;
706
+ END IF;
707
+
708
+ RETURN QUERY
709
+ SELECT variable_set_row.id, variable.name, variable.version,
710
+ variable.value_encrypted, variable_set_row.authority_scope,
711
+ variable_set_row.generation
712
+ FROM workspace_variable_set_variables variable
713
+ WHERE variable.account_id = p_account_id
714
+ AND variable.variable_set_id = variable_set_row.id
715
+ AND variable.name = p_variable_name;
716
+ IF FOUND THEN
717
+ INSERT INTO audit_events (
718
+ account_id, workspace_id, subject_id, action, target_type, target_id,
719
+ metadata, metadata_codec_version
720
+ ) VALUES (
721
+ p_account_id, p_workspace_id, audit_subject,
722
+ 'variable_set.variable.read', 'workspace_variable_set',
723
+ variable_set_row.id::text,
724
+ pg_catalog.jsonb_build_object(
725
+ 'variableSetId', variable_set_row.id,
726
+ 'name', p_variable_name,
727
+ 'actorKind', p_actor_kind,
728
+ 'scope', variable_set_row.authority_scope,
729
+ 'generation', variable_set_row.generation,
730
+ 'sessionId', p_session_id,
731
+ 'turnId', p_turn_id,
732
+ 'attemptId', p_attempt_id,
733
+ 'executionGeneration', p_execution_generation,
734
+ 'actorMembershipId', actor_membership_id,
735
+ 'authorityEpoch', attempt_authority_epoch,
736
+ 'authorityVisibility', attempt_authority_visibility,
737
+ 'authorityOwnerOrganizationMembershipId', attempt_owner_membership,
738
+ 'ownerAuthorityId', variable_set_row.authority_id,
739
+ 'ownerOrganizationMembershipId', variable_set_row.owner_organization_membership_id,
740
+ 'originWorkspaceId', variable_set_row.origin_workspace_id
741
+ ),
742
+ 1
743
+ );
744
+ END IF;
745
+ DELETE FROM opengeni_private.variable_set_authority_capabilities
746
+ WHERE backend_pid = pg_catalog.pg_backend_pid()
747
+ AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
748
+ AND capability_kind = 'materialize';
749
+ RETURN;
750
+ EXCEPTION WHEN OTHERS THEN
751
+ DELETE FROM opengeni_private.variable_set_authority_capabilities
752
+ WHERE backend_pid = pg_catalog.pg_backend_pid()
753
+ AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
754
+ AND capability_kind = 'materialize';
755
+ RAISE;
756
+ END
757
+ $body$;
758
+ $ddl$, data_schema);
759
+ END
760
+ $secret_read_attribution$;
761
+
762
+ REVOKE ALL ON FUNCTION read_scoped_variable_set_secret(
763
+ uuid, uuid, uuid, text, text, text, uuid, uuid, uuid, integer
764
+ ) FROM PUBLIC;
765
+ DO $secret_read_attribution_grant$
766
+ BEGIN
767
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
768
+ GRANT EXECUTE ON FUNCTION read_scoped_variable_set_secret(
769
+ uuid, uuid, uuid, text, text, text, uuid, uuid, uuid, integer
770
+ ) TO opengeni_app;
771
+ END IF;
772
+ END
773
+ $secret_read_attribution_grant$;