@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,856 @@
1
+ -- deployment-mode: rolling
2
+ -- Add an explicit, exact-attempt organization-administration path for company
3
+ -- profile proposals. Agents may stage one immutable proposal only for the live
4
+ -- turn's initiating human when that human is the organization's active owner
5
+ -- (the same authority the manual `account:admin` route requires); activation
6
+ -- requires that same human's canonical structured-input confirmation and
7
+ -- records a dedicated receipt.
8
+ --
9
+ -- Lock order (AGENTS.md canonical event-write prefix, migration 0299):
10
+ -- workspaces FOR KEY SHARE
11
+ -- -> organization_memberships FOR KEY SHARE (the 0299 membership seam takes
12
+ -- workspaces, then memberships, then sessions)
13
+ -- -> sessions / session_turns / session_turn_attempts FOR SHARE
14
+ -- -> managed_accounts: propose takes FOR KEY SHARE only; confirm reaches the
15
+ -- organization row FOR UPDATE solely through the nested
16
+ -- company_profile_apply_activation call, AFTER the session prefix.
17
+ -- No lock stronger than managed_accounts FOR KEY SHARE may precede the session
18
+ -- prefix: the canonical event writer holds sessions FOR NO KEY UPDATE and then
19
+ -- reaches managed_accounts FOR KEY SHARE through the session_events account FK,
20
+ -- so an organization-row FOR UPDATE taken before the session rows would form an
21
+ -- ABBA cycle with every concurrent same-session event append.
22
+ -- Residual: the nested company_profile_apply_activation (0201) still takes
23
+ -- managed_accounts FOR UPDATE after the session/turn/attempt FOR SHARE rows,
24
+ -- while the post-0299 membership seam (0263 suspend/offboard) opens
25
+ -- managed_accounts FOR KEY SHARE and then locks the target human's sessions FOR
26
+ -- NO KEY UPDATE; an owner confirming while being suspended/offboarded can
27
+ -- therefore be deadlock-detected (40P01, both sides retry-safe). The fix is
28
+ -- downgrading company_profile_apply_activation to KEY SHARE plus the
29
+ -- organization advisory key in the 0299 style (follow-up).
30
+ --
31
+ -- Idempotency: the propose/confirm input hashes bind account, workspace,
32
+ -- session, logical turn, operation id, content/proposal identity, and the
33
+ -- initiating membership, never the attempt id or execution generation (those
34
+ -- are persisted on the receipt rows). A replacement attempt of the same logical
35
+ -- turn re-issuing the same operation id therefore replays; a confirm retry
36
+ -- under a fresh operation id after a successful activation replays the
37
+ -- existing confirmation for that proposal instead of failing the head CAS.
38
+
39
+ SET LOCAL lock_timeout = '5s';
40
+ SET LOCAL statement_timeout = '10min';
41
+
42
+ ALTER TABLE company_profile_revisions
43
+ ADD CONSTRAINT company_profile_revisions_id_account_uq UNIQUE (id, account_id);
44
+ ALTER TABLE company_profile_activation_events
45
+ ADD CONSTRAINT company_profile_events_id_account_uq UNIQUE (id, account_id);
46
+ ALTER TABLE company_profile_revisions
47
+ DROP CONSTRAINT company_profile_revisions_provenance_chk;
48
+ ALTER TABLE company_profile_revisions
49
+ ADD CONSTRAINT company_profile_revisions_provenance_chk CHECK (
50
+ provenance_source IN ('human', 'agent_admin', 'durable_learning', 'migration')
51
+ AND (
52
+ provenance_source_id IS NULL
53
+ OR length(provenance_source_id) BETWEEN 1 AND 512
54
+ )
55
+ ) NOT VALID;
56
+ ALTER TABLE company_profile_revisions
57
+ VALIDATE CONSTRAINT company_profile_revisions_provenance_chk;
58
+
59
+ CREATE TABLE company_profile_agent_proposal_receipts (
60
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
61
+ operation_id uuid NOT NULL,
62
+ input_hash text NOT NULL,
63
+ account_id uuid NOT NULL REFERENCES managed_accounts(id) ON DELETE CASCADE,
64
+ workspace_id uuid NOT NULL,
65
+ session_id uuid NOT NULL,
66
+ turn_id uuid NOT NULL,
67
+ creation_attempt_id uuid NOT NULL,
68
+ execution_generation integer NOT NULL,
69
+ initiating_human_subject_id text NOT NULL,
70
+ initiating_membership_id uuid NOT NULL,
71
+ revision_id uuid NOT NULL,
72
+ expected_current_revision_id uuid,
73
+ expected_activation_version bigint NOT NULL,
74
+ reason text NOT NULL,
75
+ human_input jsonb NOT NULL,
76
+ created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
77
+ CONSTRAINT company_profile_agent_proposals_workspace_fk
78
+ FOREIGN KEY (workspace_id, account_id)
79
+ REFERENCES workspaces(id, account_id) ON DELETE CASCADE,
80
+ CONSTRAINT company_profile_agent_proposals_session_fk
81
+ FOREIGN KEY (workspace_id, session_id)
82
+ REFERENCES sessions(workspace_id, id) ON DELETE CASCADE,
83
+ CONSTRAINT company_profile_agent_proposals_turn_fk
84
+ FOREIGN KEY (workspace_id, turn_id)
85
+ REFERENCES session_turns(workspace_id, id) ON DELETE CASCADE,
86
+ CONSTRAINT company_profile_agent_proposals_attempt_fk
87
+ FOREIGN KEY (account_id, workspace_id, session_id, turn_id, creation_attempt_id)
88
+ REFERENCES session_turn_attempts(account_id, workspace_id, session_id, turn_id, id)
89
+ ON DELETE CASCADE,
90
+ CONSTRAINT company_profile_agent_proposals_membership_fk
91
+ FOREIGN KEY (initiating_membership_id, account_id)
92
+ REFERENCES organization_memberships(id, account_id) ON DELETE RESTRICT,
93
+ CONSTRAINT company_profile_agent_proposals_revision_fk
94
+ FOREIGN KEY (revision_id, account_id)
95
+ REFERENCES company_profile_revisions(id, account_id) ON DELETE RESTRICT,
96
+ CONSTRAINT company_profile_agent_proposals_expected_revision_fk
97
+ FOREIGN KEY (expected_current_revision_id, account_id)
98
+ REFERENCES company_profile_revisions(id, account_id) ON DELETE RESTRICT,
99
+ CONSTRAINT company_profile_agent_proposals_input_hash_check CHECK (
100
+ input_hash ~ '^[0-9a-f]{64}$'
101
+ ),
102
+ CONSTRAINT company_profile_agent_proposals_generation_check CHECK (
103
+ execution_generation > 0 AND expected_activation_version >= 0
104
+ ),
105
+ CONSTRAINT company_profile_agent_proposals_subject_check CHECK (
106
+ octet_length(btrim(initiating_human_subject_id)) BETWEEN 1 AND 1024
107
+ ),
108
+ CONSTRAINT company_profile_agent_proposals_reason_check CHECK (
109
+ char_length(btrim(reason)) BETWEEN 1 AND 4096
110
+ AND octet_length(convert_to(btrim(reason), 'UTF8')) <= 16384
111
+ ),
112
+ CONSTRAINT company_profile_agent_proposals_human_input_check CHECK (
113
+ jsonb_typeof(human_input) = 'object'
114
+ AND jsonb_array_length(human_input->'questions') = 1
115
+ AND human_input->>'allowSkip' = 'false'
116
+ AND octet_length(human_input::text) <= 49152
117
+ )
118
+ );
119
+ CREATE UNIQUE INDEX company_profile_agent_proposals_account_operation_uq
120
+ ON company_profile_agent_proposal_receipts (account_id, operation_id);
121
+ CREATE UNIQUE INDEX company_profile_agent_proposals_account_revision_uq
122
+ ON company_profile_agent_proposal_receipts (account_id, revision_id);
123
+ ALTER TABLE company_profile_agent_proposal_receipts
124
+ ADD CONSTRAINT company_profile_agent_proposals_id_account_uq UNIQUE (id, account_id);
125
+ CREATE INDEX company_profile_agent_proposals_turn_idx
126
+ ON company_profile_agent_proposal_receipts (
127
+ workspace_id, session_id, turn_id, execution_generation
128
+ );
129
+
130
+ CREATE TABLE company_profile_agent_confirmation_receipts (
131
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
132
+ operation_id uuid NOT NULL,
133
+ input_hash text NOT NULL,
134
+ account_id uuid NOT NULL REFERENCES managed_accounts(id) ON DELETE CASCADE,
135
+ workspace_id uuid NOT NULL,
136
+ session_id uuid NOT NULL,
137
+ turn_id uuid NOT NULL,
138
+ confirmation_attempt_id uuid NOT NULL,
139
+ execution_generation integer NOT NULL,
140
+ proposal_receipt_id uuid NOT NULL,
141
+ proposal_revision_id uuid NOT NULL,
142
+ human_input_request_id uuid NOT NULL,
143
+ approver_subject_id text NOT NULL,
144
+ approver_membership_id uuid NOT NULL,
145
+ activation_event_id uuid NOT NULL,
146
+ created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
147
+ CONSTRAINT company_profile_agent_confirmations_workspace_fk
148
+ FOREIGN KEY (workspace_id, account_id)
149
+ REFERENCES workspaces(id, account_id) ON DELETE CASCADE,
150
+ CONSTRAINT company_profile_agent_confirmations_session_fk
151
+ FOREIGN KEY (workspace_id, session_id)
152
+ REFERENCES sessions(workspace_id, id) ON DELETE CASCADE,
153
+ CONSTRAINT company_profile_agent_confirmations_turn_fk
154
+ FOREIGN KEY (workspace_id, turn_id)
155
+ REFERENCES session_turns(workspace_id, id) ON DELETE CASCADE,
156
+ CONSTRAINT company_profile_agent_confirmations_attempt_fk
157
+ FOREIGN KEY (account_id, workspace_id, session_id, turn_id, confirmation_attempt_id)
158
+ REFERENCES session_turn_attempts(account_id, workspace_id, session_id, turn_id, id)
159
+ ON DELETE CASCADE,
160
+ CONSTRAINT company_profile_agent_confirmations_proposal_fk
161
+ FOREIGN KEY (proposal_receipt_id, account_id)
162
+ REFERENCES company_profile_agent_proposal_receipts(id, account_id) ON DELETE RESTRICT,
163
+ CONSTRAINT company_profile_agent_confirmations_revision_fk
164
+ FOREIGN KEY (proposal_revision_id, account_id)
165
+ REFERENCES company_profile_revisions(id, account_id) ON DELETE RESTRICT,
166
+ CONSTRAINT company_profile_agent_confirmations_human_input_fk
167
+ FOREIGN KEY (human_input_request_id)
168
+ REFERENCES session_human_input_requests(id) ON DELETE CASCADE,
169
+ CONSTRAINT company_profile_agent_confirmations_membership_fk
170
+ FOREIGN KEY (approver_membership_id, account_id)
171
+ REFERENCES organization_memberships(id, account_id) ON DELETE RESTRICT,
172
+ CONSTRAINT company_profile_agent_confirmations_event_fk
173
+ FOREIGN KEY (activation_event_id, account_id)
174
+ REFERENCES company_profile_activation_events(id, account_id) ON DELETE RESTRICT,
175
+ CONSTRAINT company_profile_agent_confirmations_input_hash_check CHECK (
176
+ input_hash ~ '^[0-9a-f]{64}$'
177
+ ),
178
+ CONSTRAINT company_profile_agent_confirmations_generation_check CHECK (
179
+ execution_generation > 0
180
+ ),
181
+ CONSTRAINT company_profile_agent_confirmations_subject_check CHECK (
182
+ octet_length(btrim(approver_subject_id)) BETWEEN 1 AND 1024
183
+ )
184
+ );
185
+ CREATE UNIQUE INDEX company_profile_agent_confirmations_account_operation_uq
186
+ ON company_profile_agent_confirmation_receipts (account_id, operation_id);
187
+ CREATE UNIQUE INDEX company_profile_agent_confirmations_proposal_uq
188
+ ON company_profile_agent_confirmation_receipts (account_id, proposal_receipt_id);
189
+ CREATE UNIQUE INDEX company_profile_agent_confirmations_event_uq
190
+ ON company_profile_agent_confirmation_receipts (account_id, activation_event_id);
191
+
192
+ ALTER TABLE company_profile_agent_proposal_receipts ENABLE ROW LEVEL SECURITY;
193
+ ALTER TABLE company_profile_agent_proposal_receipts FORCE ROW LEVEL SECURITY;
194
+ ALTER TABLE company_profile_agent_confirmation_receipts ENABLE ROW LEVEL SECURITY;
195
+ ALTER TABLE company_profile_agent_confirmation_receipts FORCE ROW LEVEL SECURITY;
196
+ REVOKE ALL ON TABLE company_profile_agent_proposal_receipts FROM PUBLIC;
197
+ REVOKE ALL ON TABLE company_profile_agent_confirmation_receipts FROM PUBLIC;
198
+ CREATE POLICY company_profile_agent_admin_lifecycle
199
+ ON company_profile_agent_proposal_receipts
200
+ USING (current_setting('opengeni.company_profile_agent_admin_lifecycle', true)
201
+ = 'company_profile_agent_admin')
202
+ WITH CHECK (current_setting('opengeni.company_profile_agent_admin_lifecycle', true)
203
+ = 'company_profile_agent_admin');
204
+ CREATE POLICY company_profile_agent_admin_lifecycle
205
+ ON company_profile_agent_confirmation_receipts
206
+ USING (current_setting('opengeni.company_profile_agent_admin_lifecycle', true)
207
+ = 'company_profile_agent_admin')
208
+ WITH CHECK (current_setting('opengeni.company_profile_agent_admin_lifecycle', true)
209
+ = 'company_profile_agent_admin');
210
+ CREATE POLICY session_visibility_isolation
211
+ ON company_profile_agent_proposal_receipts AS RESTRICTIVE
212
+ USING (session_reference_visible(account_id, workspace_id, session_id))
213
+ WITH CHECK (session_reference_visible(account_id, workspace_id, session_id));
214
+ CREATE POLICY session_visibility_isolation
215
+ ON company_profile_agent_confirmation_receipts AS RESTRICTIVE
216
+ USING (session_reference_visible(account_id, workspace_id, session_id))
217
+ WITH CHECK (session_reference_visible(account_id, workspace_id, session_id));
218
+
219
+ CREATE FUNCTION company_profile_agent_receipts_immutable()
220
+ RETURNS trigger
221
+ LANGUAGE plpgsql
222
+ AS $body$
223
+ BEGIN
224
+ IF TG_OP = 'DELETE'
225
+ AND pg_trigger_depth() > 1
226
+ AND (
227
+ NOT EXISTS (SELECT 1 FROM managed_accounts account WHERE account.id = OLD.account_id)
228
+ OR NOT EXISTS (
229
+ SELECT 1 FROM sessions session
230
+ WHERE session.account_id = OLD.account_id
231
+ AND session.workspace_id = OLD.workspace_id
232
+ AND session.id = OLD.session_id
233
+ )
234
+ )
235
+ THEN
236
+ RETURN OLD;
237
+ END IF;
238
+ RAISE EXCEPTION 'company-profile agent administration receipts are immutable'
239
+ USING ERRCODE = '55000';
240
+ END
241
+ $body$;
242
+ CREATE TRIGGER company_profile_agent_proposals_immutable
243
+ BEFORE UPDATE OR DELETE ON company_profile_agent_proposal_receipts
244
+ FOR EACH ROW EXECUTE FUNCTION company_profile_agent_receipts_immutable();
245
+ CREATE TRIGGER company_profile_agent_confirmations_immutable
246
+ BEFORE UPDATE OR DELETE ON company_profile_agent_confirmation_receipts
247
+ FOR EACH ROW EXECUTE FUNCTION company_profile_agent_receipts_immutable();
248
+
249
+ CREATE FUNCTION company_profile_agent_confirmation_summary(p_content_json text)
250
+ RETURNS text
251
+ LANGUAGE plpgsql IMMUTABLE
252
+ AS $body$
253
+ DECLARE
254
+ profile jsonb := p_content_json::jsonb;
255
+ summary text;
256
+ list_name text;
257
+ list_text text;
258
+ BEGIN
259
+ summary := 'Identity: ' || coalesce(nullif(btrim(profile->>'identity'), ''), '(none)')
260
+ || E'\nMission: ' || coalesce(nullif(btrim(profile->>'mission'), ''), '(none)');
261
+ FOREACH list_name IN ARRAY ARRAY['products', 'customers', 'goals', 'constraints'] LOOP
262
+ SELECT string_agg('- ' || (item->>'key') || ': ' || (item->>'content'), E'\n'
263
+ ORDER BY entries.ordinality)
264
+ INTO list_text
265
+ FROM jsonb_array_elements(coalesce(profile->list_name, '[]'::jsonb))
266
+ WITH ORDINALITY AS entries(item, ordinality);
267
+ summary := summary || E'\n' || initcap(list_name) || ':' || E'\n'
268
+ || coalesce(list_text, '- (none)');
269
+ END LOOP;
270
+ -- HumanInputQuestion.helpText is capped at 2048 UTF-16 code units; the
271
+ -- revision/hash prefix uses under 100. Bound the rendered bytes (a UTF-16 unit
272
+ -- never needs more bytes than it needs units) without splitting a character.
273
+ IF octet_length(summary) > 1800 THEN
274
+ WHILE octet_length(summary) > 1797 LOOP
275
+ summary := left(
276
+ summary,
277
+ length(summary) - greatest(1, (octet_length(summary) - 1797) / 4)
278
+ );
279
+ END LOOP;
280
+ summary := summary || '...';
281
+ END IF;
282
+ RETURN summary;
283
+ END
284
+ $body$;
285
+ REVOKE ALL ON FUNCTION company_profile_agent_confirmation_summary(text) FROM PUBLIC;
286
+
287
+ CREATE FUNCTION company_profile_agent_confirmation_prompt(
288
+ p_revision_id uuid,
289
+ p_revision bigint,
290
+ p_content_hash text,
291
+ p_content_json text
292
+ ) RETURNS jsonb
293
+ LANGUAGE sql IMMUTABLE
294
+ AS $body$
295
+ SELECT jsonb_build_object(
296
+ 'questions', jsonb_build_array(jsonb_build_object(
297
+ 'id', 'company-profile:' || p_revision_id::text,
298
+ 'kind', 'single_select',
299
+ 'prompt', 'Activate this organization company profile and strategic goals?',
300
+ 'label', 'Company profile',
301
+ 'helpText', 'Revision ' || p_revision::text || '; SHA-256 ' || p_content_hash
302
+ || E'.\n\n' || company_profile_agent_confirmation_summary(p_content_json),
303
+ 'options', jsonb_build_array(
304
+ jsonb_build_object('id', 'activate', 'label', 'Activate'),
305
+ jsonb_build_object('id', 'skip', 'label', 'Do not activate')
306
+ ),
307
+ 'required', true,
308
+ -- The stock runtime exposes Other on every choice question. Keep the
309
+ -- persisted contract byte-for-byte stable across that normalization;
310
+ -- confirmation below still accepts only the exact `activate` value.
311
+ 'allowOther', true
312
+ )),
313
+ 'allowSkip', false
314
+ )
315
+ $body$;
316
+ REVOKE ALL ON FUNCTION company_profile_agent_confirmation_prompt(uuid, bigint, text, text)
317
+ FROM PUBLIC;
318
+
319
+ CREATE FUNCTION propose_company_profile_for_attempt(
320
+ p_account_id uuid,
321
+ p_workspace_id uuid,
322
+ p_session_id uuid,
323
+ p_turn_id uuid,
324
+ p_attempt_id uuid,
325
+ p_execution_generation integer,
326
+ p_operation_id uuid,
327
+ p_content_json text,
328
+ p_content_hash text,
329
+ p_reason text
330
+ ) RETURNS TABLE (
331
+ receipt_id uuid,
332
+ revision_id uuid,
333
+ human_input jsonb,
334
+ replayed boolean
335
+ )
336
+ LANGUAGE plpgsql SECURITY DEFINER
337
+ SET search_path FROM CURRENT
338
+ AS $body$
339
+ DECLARE
340
+ context_account_id uuid := nullif(current_setting('opengeni.account_id', true), '')::uuid;
341
+ context_workspace_id uuid := nullif(current_setting('opengeni.workspace_id', true), '')::uuid;
342
+ context_subject_id text := nullif(current_setting('opengeni.subject_id', true), '');
343
+ context_principal_kind text := nullif(current_setting('opengeni.principal_kind', true), '');
344
+ initiating_subject text;
345
+ actor_membership organization_memberships%ROWTYPE;
346
+ active_head company_profile_heads%ROWTYPE;
347
+ prior company_profile_agent_proposal_receipts%ROWTYPE;
348
+ inserted_revision company_profile_revisions%ROWTYPE;
349
+ input_hash_value text;
350
+ new_receipt_id uuid := gen_random_uuid();
351
+ new_revision_id uuid := gen_random_uuid();
352
+ normalized_reason text := btrim(p_reason);
353
+ prompt jsonb;
354
+ BEGIN
355
+ IF context_account_id IS DISTINCT FROM p_account_id
356
+ OR context_workspace_id IS DISTINCT FROM p_workspace_id
357
+ OR context_subject_id IS NULL
358
+ OR context_principal_kind IS DISTINCT FROM 'agent_attempt'
359
+ OR p_execution_generation < 1
360
+ OR p_operation_id IS NULL
361
+ OR p_content_json IS NULL
362
+ OR p_content_hash !~ '^[0-9a-f]{64}$'
363
+ OR p_content_hash IS DISTINCT FROM encode(sha256(convert_to(p_content_json, 'UTF8')), 'hex')
364
+ OR octet_length(convert_to(p_content_json, 'UTF8')) NOT BETWEEN 1 AND 28672
365
+ OR octet_length(convert_to(normalized_reason, 'UTF8')) NOT BETWEEN 1 AND 16384
366
+ THEN
367
+ RAISE EXCEPTION 'company-profile agent proposal is invalid' USING ERRCODE = '22023';
368
+ END IF;
369
+
370
+ -- Resolve the exact logical turn's initiating human with a plain read; no
371
+ -- row lock may precede the canonical workspace/session prefix below.
372
+ SELECT coalesce(
373
+ turn.initiating_human_subject_id,
374
+ CASE WHEN turn.initiator_kind = 'subject' THEN turn.initiator_subject_id END
375
+ ) INTO initiating_subject
376
+ FROM session_turns turn
377
+ WHERE turn.account_id = p_account_id
378
+ AND turn.workspace_id = p_workspace_id
379
+ AND turn.session_id = p_session_id
380
+ AND turn.id = p_turn_id;
381
+ IF initiating_subject IS NULL THEN
382
+ RAISE EXCEPTION
383
+ 'company-profile agent proposals require the exact live turn of the active organization owner'
384
+ USING ERRCODE = '42501';
385
+ END IF;
386
+
387
+ -- Canonical prefix: workspaces FOR KEY SHARE, then the membership row FOR KEY
388
+ -- SHARE (the 0299 membership seam orders workspaces -> memberships ->
389
+ -- sessions), then the exact session/turn/attempt rows FOR SHARE.
390
+ PERFORM 1 FROM workspaces workspace
391
+ WHERE workspace.account_id = p_account_id AND workspace.id = p_workspace_id
392
+ FOR KEY SHARE;
393
+ IF NOT FOUND THEN
394
+ RAISE EXCEPTION 'organization workspace is unavailable' USING ERRCODE = '42501';
395
+ END IF;
396
+ SELECT membership.* INTO actor_membership
397
+ FROM organization_memberships membership
398
+ WHERE membership.account_id = p_account_id
399
+ AND membership.subject_id = initiating_subject
400
+ AND membership.status = 'active'
401
+ AND membership.role = 'owner'
402
+ FOR KEY SHARE;
403
+ IF NOT FOUND THEN
404
+ RAISE EXCEPTION
405
+ 'company-profile agent proposals require the exact live turn of the active organization owner'
406
+ USING ERRCODE = '42501';
407
+ END IF;
408
+ PERFORM 1
409
+ FROM sessions session
410
+ JOIN session_turns turn
411
+ ON turn.account_id = session.account_id
412
+ AND turn.workspace_id = session.workspace_id
413
+ AND turn.session_id = session.id
414
+ JOIN session_turn_attempts attempt
415
+ ON attempt.account_id = turn.account_id
416
+ AND attempt.workspace_id = turn.workspace_id
417
+ AND attempt.session_id = turn.session_id
418
+ AND attempt.turn_id = turn.id
419
+ WHERE session.account_id = p_account_id
420
+ AND session.workspace_id = p_workspace_id
421
+ AND session.id = p_session_id
422
+ AND session.active_turn_id = p_turn_id
423
+ AND turn.id = p_turn_id
424
+ AND turn.active_attempt_id = p_attempt_id
425
+ AND turn.execution_generation = p_execution_generation
426
+ AND turn.status IN ('running', 'requires_action', 'recovering', 'waiting_capacity')
427
+ AND coalesce(
428
+ turn.initiating_human_subject_id,
429
+ CASE WHEN turn.initiator_kind = 'subject' THEN turn.initiator_subject_id END
430
+ ) = actor_membership.subject_id
431
+ AND attempt.id = p_attempt_id
432
+ AND attempt.execution_generation = p_execution_generation
433
+ AND attempt.state IN ('claimed', 'running')
434
+ AND NOT EXISTS (
435
+ SELECT 1 FROM session_attempt_interruptions interruption
436
+ WHERE interruption.workspace_id = attempt.workspace_id
437
+ AND interruption.attempt_id = attempt.id
438
+ AND interruption.state IN ('pending', 'delivered', 'acknowledged')
439
+ )
440
+ FOR SHARE OF session, turn, attempt;
441
+ IF NOT FOUND THEN
442
+ RAISE EXCEPTION
443
+ 'company-profile agent proposals require the exact live turn of the active organization owner'
444
+ USING ERRCODE = '42501';
445
+ END IF;
446
+
447
+ -- Organization row AFTER the session prefix, FOR KEY SHARE only: compatible
448
+ -- with every ordinary writer's FK check, while the manual route's FOR UPDATE
449
+ -- (held across its head mutation) cannot interleave with the head read below.
450
+ PERFORM 1 FROM managed_accounts account
451
+ WHERE account.id = p_account_id FOR KEY SHARE;
452
+ IF NOT FOUND THEN
453
+ RAISE EXCEPTION 'organization account is unavailable' USING ERRCODE = '42501';
454
+ END IF;
455
+
456
+ -- The hash binds the logical turn, never the attempt id or execution
457
+ -- generation: a replacement attempt re-issuing the same operation id replays.
458
+ input_hash_value := encode(sha256(convert_to(jsonb_build_array(
459
+ 'company-profile-agent-proposal', 1, p_account_id, p_workspace_id,
460
+ p_session_id, p_turn_id, p_operation_id, p_content_hash, normalized_reason,
461
+ actor_membership.id, actor_membership.subject_id
462
+ )::text, 'UTF8')), 'hex');
463
+ PERFORM set_config(
464
+ 'opengeni.company_profile_agent_admin_lifecycle',
465
+ 'company_profile_agent_admin', true
466
+ );
467
+ SELECT * INTO prior FROM company_profile_agent_proposal_receipts receipt
468
+ WHERE receipt.account_id = p_account_id AND receipt.operation_id = p_operation_id;
469
+ IF FOUND THEN
470
+ IF prior.input_hash IS DISTINCT FROM input_hash_value THEN
471
+ RAISE EXCEPTION 'company-profile agent proposal operation id was reused'
472
+ USING ERRCODE = 'P1851';
473
+ END IF;
474
+ receipt_id := prior.id;
475
+ revision_id := prior.revision_id;
476
+ human_input := prior.human_input;
477
+ replayed := true;
478
+ RETURN NEXT;
479
+ RETURN;
480
+ END IF;
481
+ IF EXISTS (
482
+ SELECT 1 FROM company_profile_revisions revision
483
+ WHERE revision.account_id = p_account_id AND revision.operation_id = p_operation_id
484
+ ) THEN
485
+ RAISE EXCEPTION 'company-profile agent proposal operation id was reused'
486
+ USING ERRCODE = 'P1851';
487
+ END IF;
488
+
489
+ SELECT * INTO active_head FROM company_profile_heads head
490
+ WHERE head.account_id = p_account_id;
491
+ INSERT INTO company_profile_revisions (
492
+ id, operation_id, request_fingerprint, account_id, intent,
493
+ content_json, content_hash, provenance_source, provenance_source_id,
494
+ supersedes_revision_id, created_by_subject_id
495
+ ) VALUES (
496
+ new_revision_id, p_operation_id, input_hash_value, p_account_id, 'proposal',
497
+ p_content_json, p_content_hash, 'agent_admin',
498
+ 'agent-admin-proposal:' || new_receipt_id::text,
499
+ active_head.revision_id, context_subject_id
500
+ ) RETURNING * INTO inserted_revision;
501
+ prompt := company_profile_agent_confirmation_prompt(
502
+ inserted_revision.id, inserted_revision.revision,
503
+ inserted_revision.content_hash, inserted_revision.content_json
504
+ );
505
+ INSERT INTO company_profile_agent_proposal_receipts (
506
+ id, operation_id, input_hash, account_id, workspace_id, session_id,
507
+ turn_id, creation_attempt_id, execution_generation,
508
+ initiating_human_subject_id, initiating_membership_id, revision_id,
509
+ expected_current_revision_id, expected_activation_version, reason, human_input
510
+ ) VALUES (
511
+ new_receipt_id, p_operation_id, input_hash_value, p_account_id, p_workspace_id,
512
+ p_session_id, p_turn_id, p_attempt_id, p_execution_generation,
513
+ actor_membership.subject_id, actor_membership.id, inserted_revision.id,
514
+ active_head.revision_id, coalesce(active_head.activation_version, 0),
515
+ normalized_reason, prompt
516
+ );
517
+ receipt_id := new_receipt_id;
518
+ revision_id := inserted_revision.id;
519
+ human_input := prompt;
520
+ replayed := false;
521
+ RETURN NEXT;
522
+ END
523
+ $body$;
524
+
525
+ CREATE FUNCTION confirm_company_profile_for_attempt(
526
+ p_account_id uuid,
527
+ p_workspace_id uuid,
528
+ p_session_id uuid,
529
+ p_turn_id uuid,
530
+ p_attempt_id uuid,
531
+ p_execution_generation integer,
532
+ p_operation_id uuid,
533
+ p_proposal_receipt_id uuid,
534
+ p_human_input_request_id uuid
535
+ ) RETURNS TABLE (
536
+ receipt_id uuid,
537
+ activation_event_id uuid,
538
+ replayed boolean
539
+ )
540
+ LANGUAGE plpgsql SECURITY DEFINER
541
+ SET search_path FROM CURRENT
542
+ AS $body$
543
+ DECLARE
544
+ context_account_id uuid := nullif(current_setting('opengeni.account_id', true), '')::uuid;
545
+ context_workspace_id uuid := nullif(current_setting('opengeni.workspace_id', true), '')::uuid;
546
+ context_subject_id text := nullif(current_setting('opengeni.subject_id', true), '');
547
+ context_principal_kind text := nullif(current_setting('opengeni.principal_kind', true), '');
548
+ initiating_subject text;
549
+ actor_membership organization_memberships%ROWTYPE;
550
+ proposal company_profile_agent_proposal_receipts%ROWTYPE;
551
+ proposal_revision company_profile_revisions%ROWTYPE;
552
+ human_input_row session_human_input_requests%ROWTYPE;
553
+ active_head company_profile_heads%ROWTYPE;
554
+ prior company_profile_agent_confirmation_receipts%ROWTYPE;
555
+ input_hash_value text;
556
+ event_id_value uuid;
557
+ new_receipt_id uuid := gen_random_uuid();
558
+ question_id text;
559
+ BEGIN
560
+ IF context_account_id IS DISTINCT FROM p_account_id
561
+ OR context_workspace_id IS DISTINCT FROM p_workspace_id
562
+ OR context_subject_id IS NULL
563
+ OR context_principal_kind IS DISTINCT FROM 'agent_attempt'
564
+ OR p_execution_generation < 1
565
+ OR p_operation_id IS NULL
566
+ OR p_proposal_receipt_id IS NULL
567
+ OR p_human_input_request_id IS NULL
568
+ THEN
569
+ RAISE EXCEPTION 'company-profile agent confirmation is invalid' USING ERRCODE = '22023';
570
+ END IF;
571
+
572
+ -- Resolve the exact logical turn's initiating human with a plain read; no
573
+ -- row lock may precede the canonical workspace/session prefix below.
574
+ SELECT coalesce(
575
+ turn.initiating_human_subject_id,
576
+ CASE WHEN turn.initiator_kind = 'subject' THEN turn.initiator_subject_id END
577
+ ) INTO initiating_subject
578
+ FROM session_turns turn
579
+ WHERE turn.account_id = p_account_id
580
+ AND turn.workspace_id = p_workspace_id
581
+ AND turn.session_id = p_session_id
582
+ AND turn.id = p_turn_id;
583
+ IF initiating_subject IS NULL THEN
584
+ RAISE EXCEPTION
585
+ 'company-profile confirmation requires the exact live turn of the active organization owner'
586
+ USING ERRCODE = '42501';
587
+ END IF;
588
+
589
+ -- Canonical prefix: workspaces FOR KEY SHARE, then the membership row FOR KEY
590
+ -- SHARE (the 0299 membership seam orders workspaces -> memberships ->
591
+ -- sessions), then the exact session/turn/attempt rows FOR SHARE.
592
+ PERFORM 1 FROM workspaces workspace
593
+ WHERE workspace.account_id = p_account_id AND workspace.id = p_workspace_id
594
+ FOR KEY SHARE;
595
+ IF NOT FOUND THEN
596
+ RAISE EXCEPTION 'organization workspace is unavailable' USING ERRCODE = '42501';
597
+ END IF;
598
+ SELECT membership.* INTO actor_membership
599
+ FROM organization_memberships membership
600
+ WHERE membership.account_id = p_account_id
601
+ AND membership.subject_id = initiating_subject
602
+ AND membership.status = 'active'
603
+ AND membership.role = 'owner'
604
+ FOR KEY SHARE;
605
+ IF NOT FOUND THEN
606
+ RAISE EXCEPTION
607
+ 'company-profile confirmation requires the exact live turn of the active organization owner'
608
+ USING ERRCODE = '42501';
609
+ END IF;
610
+ PERFORM 1
611
+ FROM sessions session
612
+ JOIN session_turns turn
613
+ ON turn.account_id = session.account_id
614
+ AND turn.workspace_id = session.workspace_id
615
+ AND turn.session_id = session.id
616
+ JOIN session_turn_attempts attempt
617
+ ON attempt.account_id = turn.account_id
618
+ AND attempt.workspace_id = turn.workspace_id
619
+ AND attempt.session_id = turn.session_id
620
+ AND attempt.turn_id = turn.id
621
+ WHERE session.account_id = p_account_id
622
+ AND session.workspace_id = p_workspace_id
623
+ AND session.id = p_session_id
624
+ AND session.active_turn_id = p_turn_id
625
+ AND turn.id = p_turn_id
626
+ AND turn.active_attempt_id = p_attempt_id
627
+ AND turn.execution_generation = p_execution_generation
628
+ AND turn.status IN ('running', 'requires_action', 'recovering', 'waiting_capacity')
629
+ AND coalesce(
630
+ turn.initiating_human_subject_id,
631
+ CASE WHEN turn.initiator_kind = 'subject' THEN turn.initiator_subject_id END
632
+ ) = actor_membership.subject_id
633
+ AND attempt.id = p_attempt_id
634
+ AND attempt.execution_generation = p_execution_generation
635
+ AND attempt.state IN ('claimed', 'running')
636
+ AND NOT EXISTS (
637
+ SELECT 1 FROM session_attempt_interruptions interruption
638
+ WHERE interruption.workspace_id = attempt.workspace_id
639
+ AND interruption.attempt_id = attempt.id
640
+ AND interruption.state IN ('pending', 'delivered', 'acknowledged')
641
+ )
642
+ FOR SHARE OF session, turn, attempt;
643
+ IF NOT FOUND THEN
644
+ RAISE EXCEPTION
645
+ 'company-profile confirmation requires the exact live turn of the active organization owner'
646
+ USING ERRCODE = '42501';
647
+ END IF;
648
+
649
+ PERFORM set_config(
650
+ 'opengeni.company_profile_agent_admin_lifecycle',
651
+ 'company_profile_agent_admin', true
652
+ );
653
+ SELECT * INTO proposal FROM company_profile_agent_proposal_receipts receipt
654
+ WHERE receipt.id = p_proposal_receipt_id
655
+ AND receipt.account_id = p_account_id
656
+ AND receipt.workspace_id = p_workspace_id
657
+ AND receipt.session_id = p_session_id
658
+ AND receipt.turn_id = p_turn_id
659
+ FOR SHARE;
660
+ IF NOT FOUND
661
+ OR proposal.initiating_human_subject_id IS DISTINCT FROM actor_membership.subject_id
662
+ OR proposal.initiating_membership_id IS DISTINCT FROM actor_membership.id
663
+ THEN
664
+ RAISE EXCEPTION 'company-profile proposal is unavailable for this authority'
665
+ USING ERRCODE = '42501';
666
+ END IF;
667
+
668
+ -- The hash binds the logical turn, never the execution generation: a
669
+ -- replacement attempt re-issuing the same operation id replays.
670
+ input_hash_value := encode(sha256(convert_to(jsonb_build_array(
671
+ 'company-profile-agent-confirmation', 1, p_account_id, p_workspace_id,
672
+ p_session_id, p_turn_id, p_operation_id,
673
+ proposal.id, proposal.revision_id, p_human_input_request_id,
674
+ actor_membership.id, actor_membership.subject_id
675
+ )::text, 'UTF8')), 'hex');
676
+ SELECT * INTO prior FROM company_profile_agent_confirmation_receipts receipt
677
+ WHERE receipt.account_id = p_account_id AND receipt.operation_id = p_operation_id;
678
+ IF FOUND THEN
679
+ IF prior.input_hash IS DISTINCT FROM input_hash_value THEN
680
+ RAISE EXCEPTION 'company-profile agent confirmation operation id was reused'
681
+ USING ERRCODE = 'P1851';
682
+ END IF;
683
+ receipt_id := prior.id;
684
+ activation_event_id := prior.activation_event_id;
685
+ replayed := true;
686
+ RETURN NEXT;
687
+ RETURN;
688
+ END IF;
689
+ -- A proposal activates at most once (company_profile_agent_confirmations_
690
+ -- proposal_uq). A retry under a fresh operation id after a successful
691
+ -- activation, for the same proposal and the same bound human-input request,
692
+ -- replays the existing confirmation instead of failing the head CAS below.
693
+ SELECT * INTO prior FROM company_profile_agent_confirmation_receipts receipt
694
+ WHERE receipt.account_id = p_account_id AND receipt.proposal_receipt_id = proposal.id;
695
+ IF FOUND THEN
696
+ IF prior.human_input_request_id IS DISTINCT FROM p_human_input_request_id THEN
697
+ RAISE EXCEPTION
698
+ 'company-profile proposal was already activated through a different human confirmation'
699
+ USING ERRCODE = '42501';
700
+ END IF;
701
+ receipt_id := prior.id;
702
+ activation_event_id := prior.activation_event_id;
703
+ replayed := true;
704
+ RETURN NEXT;
705
+ RETURN;
706
+ END IF;
707
+
708
+ SELECT * INTO proposal_revision FROM company_profile_revisions revision
709
+ WHERE revision.account_id = p_account_id
710
+ AND revision.id = proposal.revision_id
711
+ AND revision.intent = 'proposal'
712
+ AND revision.provenance_source = 'agent_admin'
713
+ AND revision.provenance_source_id = 'agent-admin-proposal:' || proposal.id::text
714
+ FOR SHARE;
715
+ IF NOT FOUND THEN
716
+ RAISE EXCEPTION 'company-profile proposal revision is unavailable'
717
+ USING ERRCODE = '42501';
718
+ END IF;
719
+ question_id := 'company-profile:' || proposal_revision.id::text;
720
+ SELECT * INTO human_input_row FROM session_human_input_requests request
721
+ WHERE request.id = p_human_input_request_id
722
+ AND request.account_id = p_account_id
723
+ AND request.workspace_id = p_workspace_id
724
+ AND request.session_id = p_session_id
725
+ AND request.turn_id = p_turn_id
726
+ AND proposal.execution_generation <= request.turn_generation
727
+ AND request.turn_generation < p_execution_generation
728
+ AND request.status = 'answered'
729
+ AND request.responded_by = actor_membership.subject_id
730
+ AND request.allow_skip = false
731
+ AND request.questions = proposal.human_input->'questions'
732
+ AND request.response->>'outcome' = 'answered'
733
+ AND EXISTS (
734
+ SELECT 1 FROM jsonb_array_elements(request.response->'answers') answer(value)
735
+ WHERE answer.value->>'questionId' = question_id
736
+ AND answer.value->'values' = '["activate"]'::jsonb
737
+ )
738
+ FOR SHARE;
739
+ IF NOT FOUND THEN
740
+ RAISE EXCEPTION 'human confirmation for this company profile is unavailable'
741
+ USING ERRCODE = '42501';
742
+ END IF;
743
+
744
+ SELECT * INTO active_head FROM company_profile_heads head
745
+ WHERE head.account_id = p_account_id;
746
+ IF active_head.revision_id IS DISTINCT FROM proposal.expected_current_revision_id
747
+ OR coalesce(active_head.activation_version, 0)
748
+ IS DISTINCT FROM proposal.expected_activation_version
749
+ THEN
750
+ RAISE EXCEPTION 'company profile changed after the proposal was reviewed'
751
+ USING ERRCODE = '40001';
752
+ END IF;
753
+
754
+ -- The nested lifecycle function takes managed_accounts FOR UPDATE and then
755
+ -- company_profile_heads FOR UPDATE; both happen after the session prefix
756
+ -- above, matching the canonical event writer's sessions -> managed_accounts
757
+ -- direction.
758
+ PERFORM set_config('opengeni.subject_id', actor_membership.subject_id, true);
759
+ PERFORM set_config('opengeni.principal_kind', 'human_session', true);
760
+ SELECT activation.event_id INTO event_id_value
761
+ FROM company_profile_apply_activation(
762
+ p_operation_id, input_hash_value, p_account_id, p_workspace_id,
763
+ proposal_revision.id, proposal.expected_current_revision_id,
764
+ proposal.expected_activation_version, 'activate',
765
+ actor_membership.subject_id, 'human_session', proposal.reason
766
+ ) activation;
767
+ IF event_id_value IS NULL THEN
768
+ RAISE EXCEPTION 'company-profile activation returned no event' USING ERRCODE = '55000';
769
+ END IF;
770
+ PERFORM set_config('opengeni.subject_id', context_subject_id, true);
771
+ PERFORM set_config('opengeni.principal_kind', context_principal_kind, true);
772
+
773
+ INSERT INTO company_profile_agent_confirmation_receipts (
774
+ id, operation_id, input_hash, account_id, workspace_id, session_id, turn_id,
775
+ confirmation_attempt_id, execution_generation, proposal_receipt_id,
776
+ proposal_revision_id, human_input_request_id, approver_subject_id,
777
+ approver_membership_id, activation_event_id
778
+ ) VALUES (
779
+ new_receipt_id, p_operation_id, input_hash_value, p_account_id, p_workspace_id,
780
+ p_session_id, p_turn_id, p_attempt_id, p_execution_generation, proposal.id,
781
+ proposal_revision.id, human_input_row.id, actor_membership.subject_id,
782
+ actor_membership.id, event_id_value
783
+ );
784
+ receipt_id := new_receipt_id;
785
+ activation_event_id := event_id_value;
786
+ replayed := false;
787
+ RETURN NEXT;
788
+ END
789
+ $body$;
790
+
791
+ REVOKE ALL ON FUNCTION propose_company_profile_for_attempt(
792
+ uuid, uuid, uuid, uuid, uuid, integer, uuid, text, text, text
793
+ ) FROM PUBLIC;
794
+ REVOKE ALL ON FUNCTION confirm_company_profile_for_attempt(
795
+ uuid, uuid, uuid, uuid, uuid, integer, uuid, uuid, uuid
796
+ ) FROM PUBLIC;
797
+
798
+ DO $company_profile_agent_admin_hardening$
799
+ DECLARE
800
+ target_schema text := current_schema();
801
+ runtime_role text := nullif(current_setting('opengeni.runtime_role', true), '');
802
+ BEGIN
803
+ EXECUTE format(
804
+ 'ALTER FUNCTION %I.company_profile_agent_receipts_immutable() '
805
+ || 'SET search_path = pg_catalog, %I, pg_temp',
806
+ target_schema, target_schema
807
+ );
808
+ EXECUTE format(
809
+ 'ALTER FUNCTION %I.company_profile_agent_confirmation_summary(text) '
810
+ || 'SET search_path = pg_catalog, %I, pg_temp',
811
+ target_schema, target_schema
812
+ );
813
+ EXECUTE format(
814
+ 'ALTER FUNCTION %I.company_profile_agent_confirmation_prompt(uuid,bigint,text,text) '
815
+ || 'SET search_path = pg_catalog, %I, pg_temp',
816
+ target_schema, target_schema
817
+ );
818
+ EXECUTE format(
819
+ 'ALTER FUNCTION %I.propose_company_profile_for_attempt('
820
+ || 'uuid,uuid,uuid,uuid,uuid,integer,uuid,text,text,text) '
821
+ || 'SET search_path = pg_catalog, %I, pg_temp',
822
+ target_schema, target_schema
823
+ );
824
+ EXECUTE format(
825
+ 'ALTER FUNCTION %I.confirm_company_profile_for_attempt('
826
+ || 'uuid,uuid,uuid,uuid,uuid,integer,uuid,uuid,uuid) '
827
+ || 'SET search_path = pg_catalog, %I, pg_temp',
828
+ target_schema, target_schema
829
+ );
830
+ IF runtime_role IS NULL AND EXISTS (
831
+ SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app'
832
+ ) THEN
833
+ runtime_role := 'opengeni_app';
834
+ END IF;
835
+ IF runtime_role IS NOT NULL THEN
836
+ EXECUTE format(
837
+ 'GRANT EXECUTE ON FUNCTION %I.propose_company_profile_for_attempt('
838
+ || 'uuid,uuid,uuid,uuid,uuid,integer,uuid,text,text,text) TO %I',
839
+ target_schema, runtime_role
840
+ );
841
+ EXECUTE format(
842
+ 'GRANT EXECUTE ON FUNCTION %I.confirm_company_profile_for_attempt('
843
+ || 'uuid,uuid,uuid,uuid,uuid,integer,uuid,uuid,uuid) TO %I',
844
+ target_schema, runtime_role
845
+ );
846
+ EXECUTE format(
847
+ 'REVOKE ALL ON TABLE %I.company_profile_agent_proposal_receipts FROM %I',
848
+ target_schema, runtime_role
849
+ );
850
+ EXECUTE format(
851
+ 'REVOKE ALL ON TABLE %I.company_profile_agent_confirmation_receipts FROM %I',
852
+ target_schema, runtime_role
853
+ );
854
+ END IF;
855
+ END
856
+ $company_profile_agent_admin_hardening$;