@opengeni/db 0.28.9 → 0.36.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 (225) hide show
  1. package/dist/attached-browser-devices.d.ts +42 -0
  2. package/dist/attempt-tool-catalogs.d.ts +16 -0
  3. package/dist/browser-auth.d.ts +299 -0
  4. package/dist/browser-downloads.d.ts +51 -0
  5. package/dist/browser-identities.d.ts +137 -0
  6. package/dist/browser-sessions.d.ts +354 -0
  7. package/dist/browser-state-artifacts.d.ts +91 -0
  8. package/dist/canonical-human-identities.d.ts +48 -0
  9. package/dist/canonical-human-identities.js +28 -0
  10. package/dist/canonical-human-identities.js.map +1 -0
  11. package/dist/capability-components.d.ts +37 -0
  12. package/dist/capability-integrations.d.ts +194 -0
  13. package/dist/chunk-5KTIEDX7.js +13200 -0
  14. package/dist/chunk-5KTIEDX7.js.map +1 -0
  15. package/dist/chunk-CQZ4QSP5.js +995 -0
  16. package/dist/chunk-CQZ4QSP5.js.map +1 -0
  17. package/dist/chunk-F32YT7MP.js +117 -0
  18. package/dist/chunk-F32YT7MP.js.map +1 -0
  19. package/dist/chunk-JC5QPLUM.js +666 -0
  20. package/dist/chunk-JC5QPLUM.js.map +1 -0
  21. package/dist/chunk-P3RE4D2B.js +3535 -0
  22. package/dist/chunk-P3RE4D2B.js.map +1 -0
  23. package/dist/chunk-TAUTWOS5.js +259 -0
  24. package/dist/chunk-TAUTWOS5.js.map +1 -0
  25. package/dist/chunk-VKLB2ZGI.js +2389 -0
  26. package/dist/chunk-VKLB2ZGI.js.map +1 -0
  27. package/dist/codemode-operations.d.ts +117 -0
  28. package/dist/codex-token-resolver.d.ts +2 -2
  29. package/dist/company-profile-schema.d.ts +798 -0
  30. package/dist/company-profile.d.ts +268 -0
  31. package/dist/computer-sessions.d.ts +109 -0
  32. package/dist/connection-token-resolver.d.ts +9 -4
  33. package/dist/database.d.ts +57 -3
  34. package/dist/editable-artifact-durable-export.d.ts +153 -0
  35. package/dist/editable-artifact-durable-export.js +788 -0
  36. package/dist/editable-artifact-durable-export.js.map +1 -0
  37. package/dist/editable-artifact-materialization.d.ts +123 -0
  38. package/dist/editable-artifacts-schema.d.ts +4152 -0
  39. package/dist/editable-artifacts.d.ts +610 -0
  40. package/dist/editable-artifacts.js +26 -0
  41. package/dist/editable-artifacts.js.map +1 -0
  42. package/dist/generated-images.d.ts +139 -0
  43. package/dist/index.d.ts +1122 -126
  44. package/dist/index.js +42329 -18407
  45. package/dist/index.js.map +1 -1
  46. package/dist/insights.d.ts +73 -10
  47. package/dist/integration-bindings.d.ts +70 -0
  48. package/dist/integration-facets.d.ts +99 -0
  49. package/dist/interaction-revisions.d.ts +14 -0
  50. package/dist/interaction-schema.d.ts +6036 -0
  51. package/dist/knowledge-source-sync-schema.d.ts +1679 -0
  52. package/dist/knowledge-source-sync.d.ts +274 -0
  53. package/dist/memory-governance.d.ts +2 -2
  54. package/dist/memory-slack-delivery.d.ts +101 -0
  55. package/dist/new-session-drafts.d.ts +2 -2
  56. package/dist/pack-components.d.ts +76 -0
  57. package/dist/pack-installations.d.ts +86 -0
  58. package/dist/persistence-errors.d.ts +3 -3
  59. package/dist/plugin-packages.d.ts +126 -0
  60. package/dist/preference-registry.d.ts +1 -1
  61. package/dist/provision-roles.d.ts +9 -1
  62. package/dist/provision-roles.js +1 -1
  63. package/dist/runtime-posture.d.ts +19 -6
  64. package/dist/schema.d.ts +22654 -9359
  65. package/dist/schema.js +209 -3
  66. package/dist/scoped-knowledge.d.ts +97 -1
  67. package/dist/session-control.d.ts +33 -7
  68. package/dist/session-queue-commands.d.ts +19 -11
  69. package/dist/session-realtime-context.d.ts +2 -2
  70. package/dist/session-realtime-ledger.d.ts +4 -4
  71. package/dist/session-realtime-mirror.d.ts +2 -2
  72. package/dist/session-realtime-state.d.ts +1 -1
  73. package/dist/session-realtime-terminal.d.ts +1 -1
  74. package/dist/session-realtime.d.ts +7 -7
  75. package/dist/session-tenancy.d.ts +44 -0
  76. package/dist/session-tenancy.js +16 -0
  77. package/dist/session-tenancy.js.map +1 -0
  78. package/dist/session-tool-call-settlement.d.ts +1 -1
  79. package/dist/slack-task-policy-schema.d.ts +847 -0
  80. package/dist/slack-task-policy.d.ts +65 -0
  81. package/dist/slack-user-link-access.d.ts +54 -0
  82. package/dist/task-notes-schema.d.ts +924 -0
  83. package/dist/task-notes.d.ts +85 -0
  84. package/dist/transcription-recordings.d.ts +2 -2
  85. package/dist/turn-initiator.d.ts +2 -2
  86. package/dist/video-generation.d.ts +225 -0
  87. package/dist/video-generation.js +68 -0
  88. package/dist/video-generation.js.map +1 -0
  89. package/dist/workspace-artifacts.d.ts +1 -1
  90. package/dist/workspace-instruction-policies.d.ts +1 -1
  91. package/dist/workspace-learning-policy-schema.d.ts +932 -0
  92. package/dist/workspace-learning-policy.d.ts +161 -0
  93. package/dist/xai-subscription.d.ts +326 -0
  94. package/drizzle/0183_model_call_provider_cost_estimates.sql +39 -0
  95. package/drizzle/0184_sandbox_drain_teardown_fence.sql +682 -0
  96. package/drizzle/0185_temporal_schedule_cleanup_outbox.sql +168 -0
  97. package/drizzle/0186_sandbox_capture_provider_contract.sql +400 -0
  98. package/drizzle/0187_generated_image_artifacts.sql +173 -0
  99. package/drizzle/0188_image_generation_retention_failure.sql +25 -0
  100. package/drizzle/0189_retained_session_image_formats.sql +13 -0
  101. package/drizzle/0190_timeline_annotations.sql +31 -0
  102. package/drizzle/0191_editable_artifact_engine.sql +3853 -0
  103. package/drizzle/0192_editable_artifact_live_tickets.sql +338 -0
  104. package/drizzle/0193_editable_artifact_authorization.sql +190 -0
  105. package/drizzle/0194_editable_artifact_durable_exports.sql +160 -0
  106. package/drizzle/0195_editable_artifact_import_authorization.sql +203 -0
  107. package/drizzle/0196_rig_provider_images.sql +152 -0
  108. package/drizzle/0197_knowledge_source_sync_schedules.sql +523 -0
  109. package/drizzle/0198_memory_slack_publication_delivery.sql +393 -0
  110. package/drizzle/0199_workspace_learning_policy.sql +980 -0
  111. package/drizzle/0201_company_profile_authority.sql +849 -0
  112. package/drizzle/0202_document_index_checkpoints.sql +85 -0
  113. package/drizzle/0203_durable_video_generation.sql +353 -0
  114. package/drizzle/0204_video_generation_funding.sql +44 -0
  115. package/drizzle/0205_attempt_tool_catalogs.sql +230 -0
  116. package/drizzle/0206_browser_sessions.sql +646 -0
  117. package/drizzle/0207_browser_identities.sql +379 -0
  118. package/drizzle/0208_attached_browser_devices.sql +165 -0
  119. package/drizzle/0209_computer_sessions.sql +575 -0
  120. package/drizzle/0210_browser_auth_network_interventions.sql +441 -0
  121. package/drizzle/0211_editable_artifact_session_links.sql +58 -0
  122. package/drizzle/0212_browser_state_transfer_hardening.sql +560 -0
  123. package/drizzle/0212_slack_installation_bindings.sql +348 -0
  124. package/drizzle/0213_browser_interaction_authority.sql +67 -0
  125. package/drizzle/0213_slack_user_link_access_requests.sql +177 -0
  126. package/drizzle/0214_browser_download_saves.sql +34 -0
  127. package/drizzle/0214_session_activity_commit_gate.sql +262 -0
  128. package/drizzle/0215_browser_controller_host.sql +385 -0
  129. package/drizzle/0215_capabilities_platform.sql +980 -0
  130. package/drizzle/0216_browser_auth_health_evidence.sql +137 -0
  131. package/drizzle/0216_pack_component_ownership.sql +235 -0
  132. package/drizzle/0217_capability_definition_delete_authority.sql +32 -0
  133. package/drizzle/0217_external_browser_auth_operations.sql +22 -0
  134. package/drizzle/0218_organization_tenancy_foundation.sql +299 -0
  135. package/drizzle/0219_organization_tenancy_managed_human_provisioning.sql +317 -0
  136. package/drizzle/0219_site_auth_maintenance_sessions.sql +234 -0
  137. package/drizzle/0220_memory_slack_append_only_cascade.sql +38 -0
  138. package/drizzle/0220_session_channels.sql +54 -0
  139. package/drizzle/0221_sessions_channel_index.sql +5 -0
  140. package/drizzle/0222_session_visibility_authority_epochs.sql +158 -0
  141. package/drizzle/0222_sessions_channel_fk.sql +89 -0
  142. package/drizzle/0223_pending_tool_event_output.sql +55 -0
  143. package/drizzle/0223_sessions_channel_fk_validate.sql +118 -0
  144. package/drizzle/0224_slack_post_outcome_reconciliation.sql +123 -0
  145. package/drizzle/0225_session_visibility_fork_activation.sql +1363 -0
  146. package/drizzle/0226_personal_codex_authority_foundation.sql +365 -0
  147. package/drizzle/0227_slack_native_actions.sql +145 -0
  148. package/drizzle/0228_interaction_controller_data_plane.sql +14 -0
  149. package/drizzle/0228_slack_task_policy.sql +400 -0
  150. package/drizzle/0229_slack_inbox_file_fact.sql +10 -0
  151. package/drizzle/0230_user_scoped_variable_sets_rigs.sql +84 -0
  152. package/drizzle/0231_integration_definition_identity_cutover.sql +271 -0
  153. package/drizzle/0232_integration_facet_authority_cutover.sql +610 -0
  154. package/drizzle/0233_skill_and_integration_authority_cutover.sql +1162 -0
  155. package/drizzle/0234_xai_subscription_authority.sql +1139 -0
  156. package/drizzle/0235_canonical_human_login_bindings.sql +920 -0
  157. package/drizzle/0236_browser_identity_lifecycle.sql +39 -0
  158. package/drizzle/0236_session_visibility_slack_policy.sql +72 -0
  159. package/drizzle/0237_interaction_transition_reaper.sql +263 -0
  160. package/drizzle/0238_goal_persistence_policy.sql +471 -0
  161. package/drizzle/0238_supergrok_realtime_model.sql +29 -0
  162. package/drizzle/0239_supergrok_video_funding.sql +50 -0
  163. package/drizzle/0239_task_tree_notes.sql +990 -0
  164. package/package.json +26 -5
  165. package/src/attached-browser-devices.ts +371 -0
  166. package/src/attempt-tool-catalogs.ts +131 -0
  167. package/src/browser-auth.ts +4373 -0
  168. package/src/browser-downloads.ts +517 -0
  169. package/src/browser-identities.ts +1385 -0
  170. package/src/browser-sessions.ts +2294 -0
  171. package/src/browser-state-artifacts.ts +449 -0
  172. package/src/canonical-human-identities.ts +317 -0
  173. package/src/capability-components.ts +166 -0
  174. package/src/capability-integrations.ts +1571 -0
  175. package/src/codemode-operations.ts +674 -0
  176. package/src/company-profile-schema.ts +143 -0
  177. package/src/company-profile.ts +968 -0
  178. package/src/computer-sessions.ts +1130 -0
  179. package/src/connection-token-resolver.ts +270 -25
  180. package/src/database.ts +468 -24
  181. package/src/editable-artifact-durable-export.ts +1201 -0
  182. package/src/editable-artifact-materialization.ts +893 -0
  183. package/src/editable-artifacts-schema.ts +879 -0
  184. package/src/editable-artifacts.ts +5404 -0
  185. package/src/generated-images.ts +740 -0
  186. package/src/index.ts +9963 -1465
  187. package/src/insights.ts +251 -23
  188. package/src/integration-bindings.ts +455 -0
  189. package/src/integration-facets.ts +862 -0
  190. package/src/interaction-revisions.ts +70 -0
  191. package/src/interaction-schema.ts +1606 -0
  192. package/src/knowledge-source-sync-schema.ts +192 -0
  193. package/src/knowledge-source-sync.ts +1922 -0
  194. package/src/memory-slack-delivery.ts +801 -0
  195. package/src/pack-components.ts +954 -0
  196. package/src/pack-installations.ts +886 -0
  197. package/src/persistence-errors.ts +4 -7
  198. package/src/plugin-packages.ts +1186 -0
  199. package/src/provision-roles.ts +534 -1
  200. package/src/runtime-posture-cli.ts +9 -0
  201. package/src/runtime-posture.ts +721 -5
  202. package/src/schema.ts +6270 -3159
  203. package/src/scoped-knowledge-schema.ts +1 -1
  204. package/src/scoped-knowledge.ts +655 -3
  205. package/src/session-control.ts +130 -42
  206. package/src/session-queue-commands.ts +265 -15
  207. package/src/session-realtime-context.ts +2 -2
  208. package/src/session-realtime-ledger.ts +3 -3
  209. package/src/session-realtime.ts +7 -7
  210. package/src/session-tenancy.ts +188 -0
  211. package/src/session-tool-call-settlement.ts +18 -3
  212. package/src/slack-task-policy-schema.ts +130 -0
  213. package/src/slack-task-policy.ts +341 -0
  214. package/src/slack-user-link-access.ts +702 -0
  215. package/src/task-notes-schema.ts +135 -0
  216. package/src/task-notes.ts +178 -0
  217. package/src/video-generation.ts +1530 -0
  218. package/src/workspace-artifacts.ts +1 -1
  219. package/src/workspace-learning-policy-schema.ts +139 -0
  220. package/src/workspace-learning-policy.ts +595 -0
  221. package/src/xai-subscription.ts +1887 -0
  222. package/dist/chunk-P6CUHXQZ.js +0 -7248
  223. package/dist/chunk-P6CUHXQZ.js.map +0 -1
  224. package/dist/chunk-VHBFHMPC.js +0 -1219
  225. package/dist/chunk-VHBFHMPC.js.map +0 -1
@@ -0,0 +1,980 @@
1
+ -- deployment-mode: rolling
2
+ -- Add the versioned workspace learning-policy domain and accepted-attempt
3
+ -- snapshot service. This migration does not add a learning router, evaluator,
4
+ -- activation controller, prompt composition, command/tool surface, UI, or
5
+ -- connector-specific behavior. Existing Memory and memory_save paths remain
6
+ -- unchanged.
7
+
8
+ SET LOCAL lock_timeout = '5s';
9
+ SET LOCAL statement_timeout = '10min';
10
+
11
+ CREATE SEQUENCE "workspace_learning_policy_revision_seq" AS bigint;
12
+
13
+ CREATE OR REPLACE FUNCTION workspace_learning_policy_source_overrides_valid(value jsonb)
14
+ RETURNS boolean
15
+ LANGUAGE plpgsql
16
+ IMMUTABLE
17
+ AS $$
18
+ DECLARE
19
+ entry jsonb;
20
+ canonical jsonb;
21
+ BEGIN
22
+ IF jsonb_typeof(value) IS DISTINCT FROM 'array'
23
+ OR jsonb_array_length(value) > 256
24
+ OR octet_length(convert_to(value::text, 'UTF8')) > 65536
25
+ THEN
26
+ RETURN false;
27
+ END IF;
28
+
29
+ FOR entry IN SELECT item FROM jsonb_array_elements(value) AS entries(item) LOOP
30
+ IF jsonb_typeof(entry) IS DISTINCT FROM 'object'
31
+ OR (SELECT count(*) FROM jsonb_object_keys(entry)) <> 3
32
+ OR NOT (entry ? 'kind' AND entry ? 'id' AND entry ? 'mode')
33
+ OR jsonb_typeof(entry->'kind') IS DISTINCT FROM 'string'
34
+ OR jsonb_typeof(entry->'id') IS DISTINCT FROM 'string'
35
+ OR jsonb_typeof(entry->'mode') IS DISTINCT FROM 'string'
36
+ OR entry->>'kind' IS DISTINCT FROM lower(btrim(entry->>'kind'))
37
+ OR length(entry->>'kind') NOT BETWEEN 1 AND 96
38
+ OR entry->>'kind' !~ '^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$'
39
+ OR entry->>'id' IS DISTINCT FROM btrim(entry->>'id')
40
+ OR length(entry->>'id') NOT BETWEEN 1 AND 1024
41
+ OR entry->>'mode' NOT IN ('off', 'suggest', 'automatic')
42
+ THEN
43
+ RETURN false;
44
+ END IF;
45
+ END LOOP;
46
+
47
+ IF (
48
+ SELECT count(*)
49
+ FROM (
50
+ SELECT DISTINCT candidate->>'kind', candidate->>'id'
51
+ FROM jsonb_array_elements(value) AS candidates(candidate)
52
+ ) distinct_entries
53
+ ) <> jsonb_array_length(value)
54
+ THEN
55
+ RETURN false;
56
+ END IF;
57
+
58
+ SELECT coalesce(
59
+ jsonb_agg(candidate ORDER BY candidate->>'kind' COLLATE "C", candidate->>'id' COLLATE "C"),
60
+ '[]'::jsonb
61
+ ) INTO canonical
62
+ FROM jsonb_array_elements(value) AS candidates(candidate);
63
+
64
+ RETURN value = canonical;
65
+ END;
66
+ $$;
67
+
68
+ REVOKE ALL ON FUNCTION workspace_learning_policy_source_overrides_valid(jsonb) FROM PUBLIC;
69
+
70
+ CREATE OR REPLACE FUNCTION workspace_learning_policy_hash(
71
+ p_workspace_mode text,
72
+ p_source_overrides jsonb
73
+ ) RETURNS text
74
+ LANGUAGE sql
75
+ IMMUTABLE
76
+ AS $$
77
+ SELECT encode(
78
+ sha256(convert_to(
79
+ jsonb_build_array(
80
+ 'workspace_learning_policy',
81
+ 1,
82
+ p_workspace_mode,
83
+ p_source_overrides
84
+ )::text,
85
+ 'UTF8'
86
+ )),
87
+ 'hex'
88
+ );
89
+ $$;
90
+
91
+ REVOKE ALL ON FUNCTION workspace_learning_policy_hash(text, jsonb) FROM PUBLIC;
92
+
93
+ CREATE OR REPLACE FUNCTION workspace_learning_policy_snapshot_hash(
94
+ p_revision_id uuid,
95
+ p_revision bigint,
96
+ p_policy_hash text,
97
+ p_activation_version bigint,
98
+ p_activated_at timestamptz,
99
+ p_workspace_mode text,
100
+ p_source_overrides jsonb
101
+ ) RETURNS text
102
+ LANGUAGE sql
103
+ IMMUTABLE
104
+ AS $$
105
+ SELECT encode(
106
+ sha256(convert_to(
107
+ jsonb_build_array(
108
+ 'workspace_learning_policy_snapshot',
109
+ 1,
110
+ p_revision_id,
111
+ p_revision,
112
+ p_policy_hash,
113
+ p_activation_version,
114
+ CASE WHEN p_activated_at IS NULL THEN NULL ELSE to_char(
115
+ p_activated_at AT TIME ZONE 'UTC',
116
+ 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"'
117
+ ) END,
118
+ p_workspace_mode,
119
+ p_source_overrides
120
+ )::text,
121
+ 'UTF8'
122
+ )),
123
+ 'hex'
124
+ );
125
+ $$;
126
+
127
+ REVOKE ALL ON FUNCTION workspace_learning_policy_snapshot_hash(
128
+ uuid, bigint, text, bigint, timestamptz, text, jsonb
129
+ ) FROM PUBLIC;
130
+
131
+ CREATE TABLE "workspace_learning_policy_revisions" (
132
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
133
+ "operation_id" uuid NOT NULL,
134
+ "request_fingerprint" text NOT NULL,
135
+ "account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
136
+ "workspace_id" uuid NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
137
+ "revision" bigint NOT NULL DEFAULT nextval('workspace_learning_policy_revision_seq'),
138
+ "workspace_mode" text NOT NULL,
139
+ "source_overrides" jsonb NOT NULL DEFAULT '[]'::jsonb,
140
+ "policy_hash" text NOT NULL,
141
+ "supersedes_revision_id" uuid REFERENCES "workspace_learning_policy_revisions"("id") ON DELETE RESTRICT,
142
+ "created_by_subject_id" text NOT NULL,
143
+ "created_at" timestamptz NOT NULL DEFAULT now(),
144
+ CONSTRAINT "workspace_learning_policy_revisions_revision_chk" CHECK ("revision" > 0),
145
+ CONSTRAINT "workspace_learning_policy_revisions_mode_chk" CHECK (
146
+ "workspace_mode" IN ('off', 'suggest', 'automatic')
147
+ ),
148
+ CONSTRAINT "workspace_learning_policy_revisions_overrides_chk" CHECK (
149
+ workspace_learning_policy_source_overrides_valid("source_overrides")
150
+ ),
151
+ CONSTRAINT "workspace_learning_policy_revisions_hash_chk" CHECK (
152
+ "policy_hash" ~ '^[0-9a-f]{64}$'
153
+ AND "policy_hash" = workspace_learning_policy_hash("workspace_mode", "source_overrides")
154
+ ),
155
+ CONSTRAINT "workspace_learning_policy_revisions_operation_chk" CHECK (
156
+ "request_fingerprint" ~ '^[0-9a-f]{64}$'
157
+ ),
158
+ CONSTRAINT "workspace_learning_policy_revisions_actor_chk" CHECK (
159
+ length(btrim("created_by_subject_id")) BETWEEN 1 AND 1024
160
+ ),
161
+ CONSTRAINT "workspace_learning_policy_revisions_workspace_operation_uq"
162
+ UNIQUE ("workspace_id", "operation_id"),
163
+ CONSTRAINT "workspace_learning_policy_revisions_workspace_revision_uq"
164
+ UNIQUE ("workspace_id", "revision"),
165
+ CONSTRAINT "workspace_learning_policy_revisions_workspace_identity_uq"
166
+ UNIQUE ("account_id", "workspace_id", "id")
167
+ );
168
+
169
+ CREATE INDEX "workspace_learning_policy_revisions_workspace_history_idx"
170
+ ON "workspace_learning_policy_revisions" ("workspace_id", "revision" DESC);
171
+
172
+ CREATE TABLE "workspace_learning_policy_heads" (
173
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
174
+ "account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
175
+ "workspace_id" uuid NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
176
+ "revision_id" uuid NOT NULL,
177
+ "revision" bigint NOT NULL,
178
+ "policy_hash" text NOT NULL,
179
+ "activation_version" bigint NOT NULL,
180
+ "activated_at" timestamptz NOT NULL DEFAULT now(),
181
+ CONSTRAINT "workspace_learning_policy_heads_revision_fk"
182
+ FOREIGN KEY ("account_id", "workspace_id", "revision_id")
183
+ REFERENCES "workspace_learning_policy_revisions"("account_id", "workspace_id", "id")
184
+ ON DELETE RESTRICT,
185
+ CONSTRAINT "workspace_learning_policy_heads_workspace_uq" UNIQUE ("workspace_id"),
186
+ CONSTRAINT "workspace_learning_policy_heads_version_chk" CHECK (
187
+ "revision" > 0
188
+ AND "activation_version" > 0
189
+ AND "policy_hash" ~ '^[0-9a-f]{64}$'
190
+ )
191
+ );
192
+
193
+ CREATE TABLE "workspace_learning_policy_activation_events" (
194
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
195
+ "operation_id" uuid NOT NULL,
196
+ "request_fingerprint" text NOT NULL,
197
+ "account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
198
+ "workspace_id" uuid NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
199
+ "type" text NOT NULL,
200
+ "activation_version" bigint NOT NULL,
201
+ "old_revision_id" uuid,
202
+ "old_revision" bigint,
203
+ "old_policy_hash" text,
204
+ "new_revision_id" uuid NOT NULL,
205
+ "new_revision" bigint NOT NULL,
206
+ "new_policy_hash" text NOT NULL,
207
+ "actor_subject_id" text NOT NULL,
208
+ "reason" text NOT NULL,
209
+ "created_at" timestamptz NOT NULL DEFAULT now(),
210
+ CONSTRAINT "workspace_learning_policy_events_type_chk" CHECK (
211
+ "type" IN ('activate', 'rollback')
212
+ ),
213
+ CONSTRAINT "workspace_learning_policy_events_old_revision_chk" CHECK (
214
+ ("old_revision_id" IS NULL AND "old_revision" IS NULL AND "old_policy_hash" IS NULL)
215
+ OR (
216
+ "old_revision_id" IS NOT NULL
217
+ AND "old_revision" > 0
218
+ AND "old_policy_hash" ~ '^[0-9a-f]{64}$'
219
+ )
220
+ ),
221
+ CONSTRAINT "workspace_learning_policy_events_new_revision_chk" CHECK (
222
+ "new_revision" > 0
223
+ AND "new_policy_hash" ~ '^[0-9a-f]{64}$'
224
+ AND "activation_version" > 0
225
+ ),
226
+ CONSTRAINT "workspace_learning_policy_events_operation_chk" CHECK (
227
+ "request_fingerprint" ~ '^[0-9a-f]{64}$'
228
+ ),
229
+ CONSTRAINT "workspace_learning_policy_events_audit_chk" CHECK (
230
+ length(btrim("actor_subject_id")) BETWEEN 1 AND 1024
231
+ AND length(btrim("reason")) BETWEEN 1 AND 4096
232
+ ),
233
+ CONSTRAINT "workspace_learning_policy_events_workspace_operation_uq"
234
+ UNIQUE ("workspace_id", "operation_id"),
235
+ CONSTRAINT "workspace_learning_policy_events_workspace_version_uq"
236
+ UNIQUE ("workspace_id", "activation_version")
237
+ );
238
+
239
+ CREATE INDEX "workspace_learning_policy_events_workspace_time_idx"
240
+ ON "workspace_learning_policy_activation_events" ("workspace_id", "created_at" DESC, "id");
241
+
242
+ CREATE TABLE "workspace_learning_policy_snapshots" (
243
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
244
+ "account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
245
+ "workspace_id" uuid NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
246
+ "session_id" uuid NOT NULL REFERENCES "sessions"("id") ON DELETE CASCADE,
247
+ "turn_id" uuid NOT NULL REFERENCES "session_turns"("id") ON DELETE CASCADE,
248
+ "attempt_id" uuid NOT NULL REFERENCES "session_turn_attempts"("id") ON DELETE CASCADE,
249
+ "execution_generation" integer NOT NULL,
250
+ "revision_id" uuid,
251
+ "revision" bigint,
252
+ "policy_hash" text,
253
+ "activation_version" bigint NOT NULL,
254
+ "activated_at" timestamptz,
255
+ "workspace_mode" text NOT NULL,
256
+ "source_overrides" jsonb NOT NULL,
257
+ "snapshot_hash" text NOT NULL,
258
+ "created_at" timestamptz NOT NULL DEFAULT now(),
259
+ CONSTRAINT "workspace_learning_policy_snapshots_generation_chk" CHECK (
260
+ "execution_generation" > 0
261
+ ),
262
+ CONSTRAINT "workspace_learning_policy_snapshots_identity_chk" CHECK (
263
+ (
264
+ "revision_id" IS NULL
265
+ AND "revision" IS NULL
266
+ AND "policy_hash" IS NULL
267
+ AND "activation_version" = 0
268
+ AND "activated_at" IS NULL
269
+ AND "workspace_mode" = 'off'
270
+ AND "source_overrides" = '[]'::jsonb
271
+ )
272
+ OR (
273
+ "revision_id" IS NOT NULL
274
+ AND "revision" > 0
275
+ AND "policy_hash" ~ '^[0-9a-f]{64}$'
276
+ AND "activation_version" > 0
277
+ AND "activated_at" IS NOT NULL
278
+ )
279
+ ),
280
+ CONSTRAINT "workspace_learning_policy_snapshots_mode_chk" CHECK (
281
+ "workspace_mode" IN ('off', 'suggest', 'automatic')
282
+ ),
283
+ CONSTRAINT "workspace_learning_policy_snapshots_overrides_chk" CHECK (
284
+ workspace_learning_policy_source_overrides_valid("source_overrides")
285
+ ),
286
+ CONSTRAINT "workspace_learning_policy_snapshots_hash_chk" CHECK (
287
+ "snapshot_hash" ~ '^[0-9a-f]{64}$'
288
+ AND "snapshot_hash" = workspace_learning_policy_snapshot_hash(
289
+ "revision_id",
290
+ "revision",
291
+ "policy_hash",
292
+ "activation_version",
293
+ "activated_at",
294
+ "workspace_mode",
295
+ "source_overrides"
296
+ )
297
+ ),
298
+ CONSTRAINT "workspace_learning_policy_snapshots_attempt_uq"
299
+ UNIQUE ("account_id", "workspace_id", "attempt_id")
300
+ );
301
+
302
+ CREATE INDEX "workspace_learning_policy_snapshots_workspace_time_idx"
303
+ ON "workspace_learning_policy_snapshots" ("workspace_id", "created_at" DESC, "id");
304
+
305
+ CREATE OR REPLACE FUNCTION workspace_learning_policy_validate_revision_link()
306
+ RETURNS trigger
307
+ LANGUAGE plpgsql
308
+ AS $$
309
+ BEGIN
310
+ IF NEW."supersedes_revision_id" IS NOT NULL AND NOT EXISTS (
311
+ SELECT 1
312
+ FROM "workspace_learning_policy_revisions" previous
313
+ WHERE previous."id" = NEW."supersedes_revision_id"
314
+ AND previous."account_id" = NEW."account_id"
315
+ AND previous."workspace_id" = NEW."workspace_id"
316
+ ) THEN
317
+ RAISE EXCEPTION 'superseded learning-policy revision must belong to the exact workspace'
318
+ USING ERRCODE = '23514';
319
+ END IF;
320
+ RETURN NEW;
321
+ END;
322
+ $$;
323
+
324
+ CREATE TRIGGER workspace_learning_policy_revisions_validate_link
325
+ BEFORE INSERT OR UPDATE ON "workspace_learning_policy_revisions"
326
+ FOR EACH ROW EXECUTE FUNCTION workspace_learning_policy_validate_revision_link();
327
+
328
+ CREATE OR REPLACE FUNCTION workspace_learning_policy_validate_head()
329
+ RETURNS trigger
330
+ LANGUAGE plpgsql
331
+ AS $$
332
+ BEGIN
333
+ IF NOT EXISTS (
334
+ SELECT 1
335
+ FROM "workspace_learning_policy_revisions" revision
336
+ WHERE revision."id" = NEW."revision_id"
337
+ AND revision."account_id" = NEW."account_id"
338
+ AND revision."workspace_id" = NEW."workspace_id"
339
+ AND revision."revision" = NEW."revision"
340
+ AND revision."policy_hash" = NEW."policy_hash"
341
+ ) THEN
342
+ RAISE EXCEPTION 'learning-policy head must identify an exact immutable revision'
343
+ USING ERRCODE = '23514';
344
+ END IF;
345
+ RETURN NEW;
346
+ END;
347
+ $$;
348
+
349
+ CREATE TRIGGER workspace_learning_policy_heads_validate_revision
350
+ BEFORE INSERT OR UPDATE ON "workspace_learning_policy_heads"
351
+ FOR EACH ROW EXECUTE FUNCTION workspace_learning_policy_validate_head();
352
+
353
+ CREATE OR REPLACE FUNCTION workspace_learning_policy_guard_head_mutation()
354
+ RETURNS trigger
355
+ LANGUAGE plpgsql
356
+ AS $$
357
+ DECLARE
358
+ lifecycle_workspace_id text;
359
+ BEGIN
360
+ IF TG_OP = 'DELETE'
361
+ AND pg_trigger_depth() > 1
362
+ AND NOT EXISTS (
363
+ SELECT 1 FROM "workspaces" workspace WHERE workspace."id" = OLD."workspace_id"
364
+ )
365
+ THEN
366
+ RETURN OLD;
367
+ END IF;
368
+ lifecycle_workspace_id := current_setting(
369
+ 'opengeni.workspace_learning_policy_lifecycle_workspace_id',
370
+ true
371
+ );
372
+ IF lifecycle_workspace_id IS DISTINCT FROM coalesce(NEW."workspace_id", OLD."workspace_id")::text THEN
373
+ RAISE EXCEPTION 'learning-policy heads change only through the lifecycle function'
374
+ USING ERRCODE = '55000';
375
+ END IF;
376
+ IF TG_OP = 'UPDATE' THEN
377
+ IF NEW."id" IS DISTINCT FROM OLD."id"
378
+ OR NEW."account_id" IS DISTINCT FROM OLD."account_id"
379
+ OR NEW."workspace_id" IS DISTINCT FROM OLD."workspace_id"
380
+ OR NEW."activation_version" IS DISTINCT FROM OLD."activation_version" + 1
381
+ THEN
382
+ RAISE EXCEPTION 'invalid learning-policy head transition'
383
+ USING ERRCODE = '23514';
384
+ END IF;
385
+ ELSIF TG_OP = 'DELETE' THEN
386
+ RAISE EXCEPTION 'learning-policy heads cannot be deleted directly'
387
+ USING ERRCODE = '55000';
388
+ END IF;
389
+ RETURN NEW;
390
+ END;
391
+ $$;
392
+
393
+ CREATE TRIGGER workspace_learning_policy_heads_lifecycle_only
394
+ BEFORE INSERT OR UPDATE OR DELETE ON "workspace_learning_policy_heads"
395
+ FOR EACH ROW EXECUTE FUNCTION workspace_learning_policy_guard_head_mutation();
396
+
397
+ CREATE OR REPLACE FUNCTION workspace_learning_policy_validate_event()
398
+ RETURNS trigger
399
+ LANGUAGE plpgsql
400
+ AS $$
401
+ BEGIN
402
+ IF NOT EXISTS (
403
+ SELECT 1
404
+ FROM "workspace_learning_policy_revisions" revision
405
+ WHERE revision."id" = NEW."new_revision_id"
406
+ AND revision."account_id" = NEW."account_id"
407
+ AND revision."workspace_id" = NEW."workspace_id"
408
+ AND revision."revision" = NEW."new_revision"
409
+ AND revision."policy_hash" = NEW."new_policy_hash"
410
+ ) THEN
411
+ RAISE EXCEPTION 'learning-policy activation event has an invalid new revision'
412
+ USING ERRCODE = '23514';
413
+ END IF;
414
+ IF NEW."old_revision_id" IS NOT NULL AND NOT EXISTS (
415
+ SELECT 1
416
+ FROM "workspace_learning_policy_revisions" revision
417
+ WHERE revision."id" = NEW."old_revision_id"
418
+ AND revision."account_id" = NEW."account_id"
419
+ AND revision."workspace_id" = NEW."workspace_id"
420
+ AND revision."revision" = NEW."old_revision"
421
+ AND revision."policy_hash" = NEW."old_policy_hash"
422
+ ) THEN
423
+ RAISE EXCEPTION 'learning-policy activation event has an invalid old revision'
424
+ USING ERRCODE = '23514';
425
+ END IF;
426
+ RETURN NEW;
427
+ END;
428
+ $$;
429
+
430
+ CREATE TRIGGER workspace_learning_policy_events_validate_revisions
431
+ BEFORE INSERT OR UPDATE ON "workspace_learning_policy_activation_events"
432
+ FOR EACH ROW EXECUTE FUNCTION workspace_learning_policy_validate_event();
433
+
434
+ CREATE OR REPLACE FUNCTION workspace_learning_policy_reject_history_mutation()
435
+ RETURNS trigger
436
+ LANGUAGE plpgsql
437
+ AS $$
438
+ BEGIN
439
+ IF TG_OP = 'DELETE'
440
+ AND pg_trigger_depth() > 1
441
+ AND NOT EXISTS (
442
+ SELECT 1 FROM "workspaces" workspace WHERE workspace."id" = OLD."workspace_id"
443
+ )
444
+ THEN
445
+ RETURN OLD;
446
+ END IF;
447
+ RAISE EXCEPTION 'workspace learning-policy history is immutable'
448
+ USING ERRCODE = '55000';
449
+ END;
450
+ $$;
451
+
452
+ CREATE TRIGGER workspace_learning_policy_revisions_immutable
453
+ BEFORE UPDATE OR DELETE ON "workspace_learning_policy_revisions"
454
+ FOR EACH ROW EXECUTE FUNCTION workspace_learning_policy_reject_history_mutation();
455
+
456
+ CREATE TRIGGER workspace_learning_policy_events_immutable
457
+ BEFORE UPDATE OR DELETE ON "workspace_learning_policy_activation_events"
458
+ FOR EACH ROW EXECUTE FUNCTION workspace_learning_policy_reject_history_mutation();
459
+
460
+ CREATE OR REPLACE FUNCTION workspace_learning_policy_reject_snapshot_mutation()
461
+ RETURNS trigger
462
+ LANGUAGE plpgsql
463
+ AS $$
464
+ BEGIN
465
+ -- Parent lifecycle deletion is the sole mutation exception. PostgreSQL's
466
+ -- cascading constraint trigger runs only after its parent row is absent;
467
+ -- require both nested trigger context and one missing ownership edge so
468
+ -- direct or unrelated-trigger deletes remain fail-closed.
469
+ IF TG_OP = 'DELETE'
470
+ AND pg_trigger_depth() > 1
471
+ AND (
472
+ NOT EXISTS (SELECT 1 FROM "managed_accounts" WHERE "id" = OLD."account_id")
473
+ OR NOT EXISTS (SELECT 1 FROM "workspaces" WHERE "id" = OLD."workspace_id")
474
+ OR NOT EXISTS (SELECT 1 FROM "sessions" WHERE "id" = OLD."session_id")
475
+ OR NOT EXISTS (SELECT 1 FROM "session_turns" WHERE "id" = OLD."turn_id")
476
+ OR NOT EXISTS (SELECT 1 FROM "session_turn_attempts" WHERE "id" = OLD."attempt_id")
477
+ )
478
+ THEN
479
+ RETURN OLD;
480
+ END IF;
481
+ RAISE EXCEPTION 'workspace learning-policy snapshots are immutable'
482
+ USING ERRCODE = '55000';
483
+ END;
484
+ $$;
485
+
486
+ CREATE TRIGGER workspace_learning_policy_snapshots_immutable
487
+ BEFORE UPDATE OR DELETE ON "workspace_learning_policy_snapshots"
488
+ FOR EACH ROW EXECUTE FUNCTION workspace_learning_policy_reject_snapshot_mutation();
489
+
490
+ CREATE OR REPLACE FUNCTION workspace_learning_policy_canonical_at(
491
+ p_account_id uuid,
492
+ p_workspace_id uuid,
493
+ p_accepted_at timestamptz
494
+ ) RETURNS jsonb
495
+ LANGUAGE sql
496
+ STABLE
497
+ AS $$
498
+ SELECT CASE WHEN active."id" IS NULL THEN
499
+ jsonb_build_object(
500
+ 'revisionId', NULL,
501
+ 'revision', NULL,
502
+ 'policyHash', NULL,
503
+ 'activationVersion', 0,
504
+ 'activatedAt', NULL,
505
+ 'workspaceMode', 'off',
506
+ 'sourceOverrides', '[]'::jsonb
507
+ )
508
+ ELSE
509
+ jsonb_build_object(
510
+ 'revisionId', revision."id"::text,
511
+ 'revision', revision."revision",
512
+ 'policyHash', revision."policy_hash",
513
+ 'activationVersion', active."activation_version",
514
+ 'activatedAt', to_char(
515
+ active."created_at" AT TIME ZONE 'UTC',
516
+ 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"'
517
+ ),
518
+ 'workspaceMode', revision."workspace_mode",
519
+ 'sourceOverrides', revision."source_overrides"
520
+ )
521
+ END
522
+ FROM (SELECT 1) singleton
523
+ LEFT JOIN LATERAL (
524
+ SELECT event.*
525
+ FROM "workspace_learning_policy_activation_events" event
526
+ WHERE event."account_id" = p_account_id
527
+ AND event."workspace_id" = p_workspace_id
528
+ AND event."created_at" <= p_accepted_at
529
+ ORDER BY event."created_at" DESC, event."activation_version" DESC, event."id" DESC
530
+ LIMIT 1
531
+ ) active ON true
532
+ LEFT JOIN "workspace_learning_policy_revisions" revision
533
+ ON revision."account_id" = active."account_id"
534
+ AND revision."workspace_id" = active."workspace_id"
535
+ AND revision."id" = active."new_revision_id"
536
+ AND revision."revision" = active."new_revision"
537
+ AND revision."policy_hash" = active."new_policy_hash";
538
+ $$;
539
+
540
+ REVOKE ALL ON FUNCTION workspace_learning_policy_canonical_at(uuid, uuid, timestamptz)
541
+ FROM PUBLIC;
542
+
543
+ CREATE OR REPLACE FUNCTION workspace_learning_policy_validate_snapshot()
544
+ RETURNS trigger
545
+ LANGUAGE plpgsql
546
+ AS $$
547
+ DECLARE
548
+ accepted_at timestamptz;
549
+ canonical jsonb;
550
+ BEGIN
551
+ SELECT turn."created_at" INTO accepted_at
552
+ FROM "session_turn_attempts" attempt
553
+ JOIN "session_turns" turn
554
+ ON turn."account_id" = attempt."account_id"
555
+ AND turn."workspace_id" = attempt."workspace_id"
556
+ AND turn."session_id" = attempt."session_id"
557
+ AND turn."id" = attempt."turn_id"
558
+ JOIN "sessions" session
559
+ ON session."account_id" = attempt."account_id"
560
+ AND session."workspace_id" = attempt."workspace_id"
561
+ AND session."id" = attempt."session_id"
562
+ WHERE attempt."id" = NEW."attempt_id"
563
+ AND attempt."account_id" = NEW."account_id"
564
+ AND attempt."workspace_id" = NEW."workspace_id"
565
+ AND attempt."session_id" = NEW."session_id"
566
+ AND attempt."turn_id" = NEW."turn_id"
567
+ AND attempt."execution_generation" = NEW."execution_generation"
568
+ AND turn."execution_generation" = NEW."execution_generation"
569
+ AND attempt."state" IN ('claimed', 'running')
570
+ AND turn."active_attempt_id" = attempt."id"
571
+ AND session."active_turn_id" = turn."id"
572
+ AND turn."status" IN ('running', 'requires_action', 'recovering', 'waiting_capacity')
573
+ AND NOT EXISTS (
574
+ SELECT 1
575
+ FROM "session_attempt_interruptions" interruption
576
+ WHERE interruption."workspace_id" = NEW."workspace_id"
577
+ AND interruption."attempt_id" = NEW."attempt_id"
578
+ AND interruption."state" IN ('pending', 'delivered', 'acknowledged')
579
+ );
580
+ IF NOT FOUND THEN
581
+ RAISE EXCEPTION 'learning-policy snapshot requires an exact active attempt'
582
+ USING ERRCODE = '23514';
583
+ END IF;
584
+
585
+ canonical := workspace_learning_policy_canonical_at(
586
+ NEW."account_id",
587
+ NEW."workspace_id",
588
+ accepted_at
589
+ );
590
+ IF NEW."revision_id" IS DISTINCT FROM (canonical->>'revisionId')::uuid
591
+ OR NEW."revision" IS DISTINCT FROM (canonical->>'revision')::bigint
592
+ OR NEW."policy_hash" IS DISTINCT FROM canonical->>'policyHash'
593
+ OR NEW."activation_version" IS DISTINCT FROM (canonical->>'activationVersion')::bigint
594
+ OR NEW."activated_at" IS DISTINCT FROM (canonical->>'activatedAt')::timestamptz
595
+ OR NEW."workspace_mode" IS DISTINCT FROM canonical->>'workspaceMode'
596
+ OR NEW."source_overrides" IS DISTINCT FROM canonical->'sourceOverrides'
597
+ THEN
598
+ RAISE EXCEPTION 'learning-policy snapshot is not the canonical accepted policy'
599
+ USING ERRCODE = '23514';
600
+ END IF;
601
+ RETURN NEW;
602
+ END;
603
+ $$;
604
+
605
+ CREATE TRIGGER workspace_learning_policy_snapshots_validate
606
+ BEFORE INSERT ON "workspace_learning_policy_snapshots"
607
+ FOR EACH ROW EXECUTE FUNCTION workspace_learning_policy_validate_snapshot();
608
+
609
+ DO $activation_function$
610
+ DECLARE target_schema text := current_schema();
611
+ BEGIN
612
+ EXECUTE format($ddl$
613
+ CREATE OR REPLACE FUNCTION %I.workspace_learning_policy_apply_activation(
614
+ p_operation_id uuid,
615
+ p_request_fingerprint text,
616
+ p_account_id uuid,
617
+ p_workspace_id uuid,
618
+ p_target_revision_id uuid,
619
+ p_expected_current_revision_id uuid,
620
+ p_expected_activation_version bigint,
621
+ p_type text,
622
+ p_actor_subject_id text,
623
+ p_reason text
624
+ ) RETURNS TABLE (event_id uuid)
625
+ LANGUAGE plpgsql SECURITY DEFINER SET search_path = %I, pg_catalog
626
+ AS $body$
627
+ DECLARE
628
+ target_revision record;
629
+ current_head record;
630
+ existing_event record;
631
+ next_activation_version bigint;
632
+ event_created_at timestamptz;
633
+ context_account_id uuid;
634
+ context_workspace_id uuid;
635
+ context_subject_id text;
636
+ context_principal_kind text;
637
+ BEGIN
638
+ context_account_id := NULLIF(current_setting('opengeni.account_id', true), '')::uuid;
639
+ context_workspace_id := NULLIF(current_setting('opengeni.workspace_id', true), '')::uuid;
640
+ context_subject_id := NULLIF(current_setting('opengeni.subject_id', true), '');
641
+ context_principal_kind := NULLIF(current_setting('opengeni.principal_kind', true), '');
642
+ IF context_account_id IS DISTINCT FROM p_account_id
643
+ OR context_workspace_id IS DISTINCT FROM p_workspace_id
644
+ OR context_subject_id IS DISTINCT FROM p_actor_subject_id
645
+ OR context_principal_kind IS DISTINCT FROM 'human_session'
646
+ THEN
647
+ RAISE EXCEPTION 'learning-policy activation requires exact human workspace authority'
648
+ USING ERRCODE = '42501';
649
+ END IF;
650
+ IF p_request_fingerprint !~ '^[0-9a-f]{64}$'
651
+ OR p_expected_activation_version < 0
652
+ OR p_type NOT IN ('activate', 'rollback')
653
+ OR length(btrim(p_actor_subject_id)) NOT BETWEEN 1 AND 1024
654
+ OR length(btrim(p_reason)) NOT BETWEEN 1 AND 4096
655
+ THEN
656
+ RAISE EXCEPTION 'learning-policy activation input is invalid'
657
+ USING ERRCODE = '22023';
658
+ END IF;
659
+
660
+ PERFORM 1
661
+ FROM "workspaces" workspace
662
+ WHERE workspace."id" = p_workspace_id AND workspace."account_id" = p_account_id
663
+ FOR UPDATE;
664
+ IF NOT FOUND THEN
665
+ RAISE EXCEPTION 'workspace was not found' USING ERRCODE = '42501';
666
+ END IF;
667
+
668
+ SELECT * INTO existing_event
669
+ FROM "workspace_learning_policy_activation_events" event
670
+ WHERE event."workspace_id" = p_workspace_id
671
+ AND event."operation_id" = p_operation_id;
672
+ IF FOUND THEN
673
+ IF existing_event."request_fingerprint" IS DISTINCT FROM p_request_fingerprint THEN
674
+ RAISE EXCEPTION 'learning-policy operation id was reused'
675
+ USING ERRCODE = 'P1471';
676
+ END IF;
677
+ event_id := existing_event."id";
678
+ RETURN NEXT;
679
+ RETURN;
680
+ END IF;
681
+ IF EXISTS (
682
+ SELECT 1 FROM "workspace_learning_policy_revisions" revision
683
+ WHERE revision."workspace_id" = p_workspace_id
684
+ AND revision."operation_id" = p_operation_id
685
+ ) THEN
686
+ RAISE EXCEPTION 'learning-policy operation id was reused'
687
+ USING ERRCODE = 'P1471';
688
+ END IF;
689
+
690
+ SELECT * INTO target_revision
691
+ FROM "workspace_learning_policy_revisions" revision
692
+ WHERE revision."id" = p_target_revision_id
693
+ AND revision."account_id" = p_account_id
694
+ AND revision."workspace_id" = p_workspace_id;
695
+ IF NOT FOUND THEN
696
+ RAISE EXCEPTION 'learning-policy revision was not found' USING ERRCODE = '23503';
697
+ END IF;
698
+
699
+ SELECT * INTO current_head
700
+ FROM "workspace_learning_policy_heads" head
701
+ WHERE head."account_id" = p_account_id
702
+ AND head."workspace_id" = p_workspace_id
703
+ FOR UPDATE;
704
+ IF coalesce(current_head."activation_version", 0) IS DISTINCT FROM p_expected_activation_version
705
+ OR current_head."revision_id" IS DISTINCT FROM p_expected_current_revision_id
706
+ THEN
707
+ RAISE EXCEPTION 'learning-policy active revision changed'
708
+ USING ERRCODE = '40001';
709
+ END IF;
710
+ IF current_head."revision_id" IS NOT NULL
711
+ AND current_head."revision_id" = target_revision."id"
712
+ THEN
713
+ RAISE EXCEPTION 'learning-policy revision is already active'
714
+ USING ERRCODE = '23514';
715
+ END IF;
716
+ IF p_type = 'rollback' THEN
717
+ IF current_head."revision_id" IS NULL OR NOT EXISTS (
718
+ SELECT 1
719
+ FROM "workspace_learning_policy_activation_events" event
720
+ WHERE event."account_id" = p_account_id
721
+ AND event."workspace_id" = p_workspace_id
722
+ AND event."new_revision_id" = target_revision."id"
723
+ ) THEN
724
+ RAISE EXCEPTION 'learning-policy rollback target was not previously active'
725
+ USING ERRCODE = '23514';
726
+ END IF;
727
+ END IF;
728
+
729
+ -- The event timestamp is accepted-order evidence. Stamp it only after
730
+ -- workspace/head serialization and successful CAS so a dependent
731
+ -- activation that began earlier cannot sort before its predecessor.
732
+ event_created_at := clock_timestamp();
733
+ next_activation_version := coalesce(current_head."activation_version", 0) + 1;
734
+ PERFORM set_config(
735
+ 'opengeni.workspace_learning_policy_lifecycle_workspace_id',
736
+ p_workspace_id::text,
737
+ true
738
+ );
739
+ IF current_head."id" IS NULL THEN
740
+ INSERT INTO "workspace_learning_policy_heads" (
741
+ "account_id", "workspace_id", "revision_id", "revision",
742
+ "policy_hash", "activation_version", "activated_at"
743
+ ) VALUES (
744
+ p_account_id, p_workspace_id, target_revision."id", target_revision."revision",
745
+ target_revision."policy_hash", next_activation_version, event_created_at
746
+ );
747
+ ELSE
748
+ UPDATE "workspace_learning_policy_heads"
749
+ SET "revision_id" = target_revision."id",
750
+ "revision" = target_revision."revision",
751
+ "policy_hash" = target_revision."policy_hash",
752
+ "activation_version" = next_activation_version,
753
+ "activated_at" = event_created_at
754
+ WHERE "id" = current_head."id";
755
+ END IF;
756
+ PERFORM set_config('opengeni.workspace_learning_policy_lifecycle_workspace_id', '', true);
757
+
758
+ INSERT INTO "workspace_learning_policy_activation_events" (
759
+ "operation_id", "request_fingerprint", "account_id", "workspace_id",
760
+ "type", "activation_version",
761
+ "old_revision_id", "old_revision", "old_policy_hash",
762
+ "new_revision_id", "new_revision", "new_policy_hash",
763
+ "actor_subject_id", "reason", "created_at"
764
+ ) VALUES (
765
+ p_operation_id, p_request_fingerprint, p_account_id, p_workspace_id,
766
+ p_type, next_activation_version,
767
+ current_head."revision_id", current_head."revision", current_head."policy_hash",
768
+ target_revision."id", target_revision."revision", target_revision."policy_hash",
769
+ p_actor_subject_id, p_reason, event_created_at
770
+ ) RETURNING "id" INTO event_id;
771
+ RETURN NEXT;
772
+ END
773
+ $body$
774
+ $ddl$, target_schema, target_schema);
775
+ EXECUTE format(
776
+ 'REVOKE ALL ON FUNCTION %I.workspace_learning_policy_apply_activation(uuid,text,uuid,uuid,uuid,uuid,bigint,text,text,text) FROM PUBLIC',
777
+ target_schema
778
+ );
779
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
780
+ EXECUTE format(
781
+ 'GRANT EXECUTE ON FUNCTION %I.workspace_learning_policy_apply_activation(uuid, text, uuid, uuid, uuid, uuid, bigint, text, text, text) TO opengeni_app',
782
+ target_schema
783
+ );
784
+ END IF;
785
+ END $activation_function$;
786
+
787
+ DO $snapshot_function$
788
+ DECLARE target_schema text := current_schema();
789
+ BEGIN
790
+ EXECUTE format($ddl$
791
+ CREATE OR REPLACE FUNCTION %I.workspace_learning_policy_get_or_create_snapshot(
792
+ p_account_id uuid,
793
+ p_workspace_id uuid,
794
+ p_session_id uuid,
795
+ p_turn_id uuid,
796
+ p_attempt_id uuid,
797
+ p_execution_generation integer
798
+ ) RETURNS TABLE (snapshot_id uuid)
799
+ LANGUAGE plpgsql SECURITY DEFINER SET search_path = %I, pg_catalog
800
+ AS $body$
801
+ DECLARE
802
+ accepted_at timestamptz;
803
+ canonical jsonb;
804
+ existing_snapshot record;
805
+ snapshot_revision_id uuid;
806
+ snapshot_revision bigint;
807
+ snapshot_policy_hash text;
808
+ snapshot_activation_version bigint;
809
+ snapshot_activated_at timestamptz;
810
+ snapshot_workspace_mode text;
811
+ snapshot_source_overrides jsonb;
812
+ BEGIN
813
+ IF NULLIF(current_setting('opengeni.account_id', true), '')::uuid IS DISTINCT FROM p_account_id
814
+ OR NULLIF(current_setting('opengeni.workspace_id', true), '')::uuid IS DISTINCT FROM p_workspace_id
815
+ THEN
816
+ RAISE EXCEPTION 'learning-policy snapshot requires exact workspace authority'
817
+ USING ERRCODE = '42501';
818
+ END IF;
819
+
820
+ SELECT * INTO existing_snapshot
821
+ FROM "workspace_learning_policy_snapshots" snapshot
822
+ WHERE snapshot."account_id" = p_account_id
823
+ AND snapshot."workspace_id" = p_workspace_id
824
+ AND snapshot."attempt_id" = p_attempt_id;
825
+ IF FOUND THEN
826
+ IF existing_snapshot."session_id" IS DISTINCT FROM p_session_id
827
+ OR existing_snapshot."turn_id" IS DISTINCT FROM p_turn_id
828
+ OR existing_snapshot."execution_generation" IS DISTINCT FROM p_execution_generation
829
+ THEN
830
+ RAISE EXCEPTION 'learning-policy snapshot attempt identity conflicted'
831
+ USING ERRCODE = '23514';
832
+ END IF;
833
+ snapshot_id := existing_snapshot."id";
834
+ RETURN NEXT;
835
+ RETURN;
836
+ END IF;
837
+
838
+ SELECT turn."created_at" INTO accepted_at
839
+ FROM "session_turn_attempts" attempt
840
+ JOIN "session_turns" turn
841
+ ON turn."account_id" = attempt."account_id"
842
+ AND turn."workspace_id" = attempt."workspace_id"
843
+ AND turn."session_id" = attempt."session_id"
844
+ AND turn."id" = attempt."turn_id"
845
+ JOIN "sessions" session
846
+ ON session."account_id" = attempt."account_id"
847
+ AND session."workspace_id" = attempt."workspace_id"
848
+ AND session."id" = attempt."session_id"
849
+ WHERE attempt."id" = p_attempt_id
850
+ AND attempt."account_id" = p_account_id
851
+ AND attempt."workspace_id" = p_workspace_id
852
+ AND attempt."session_id" = p_session_id
853
+ AND attempt."turn_id" = p_turn_id
854
+ AND attempt."execution_generation" = p_execution_generation
855
+ AND turn."execution_generation" = p_execution_generation
856
+ AND attempt."state" IN ('claimed', 'running')
857
+ AND turn."active_attempt_id" = attempt."id"
858
+ AND session."active_turn_id" = turn."id"
859
+ AND turn."status" IN ('running', 'requires_action', 'recovering', 'waiting_capacity')
860
+ AND NOT EXISTS (
861
+ SELECT 1 FROM "session_attempt_interruptions" interruption
862
+ WHERE interruption."workspace_id" = p_workspace_id
863
+ AND interruption."attempt_id" = p_attempt_id
864
+ AND interruption."state" IN ('pending', 'delivered', 'acknowledged')
865
+ )
866
+ FOR SHARE OF attempt, turn, session;
867
+ IF NOT FOUND THEN
868
+ RAISE EXCEPTION 'learning-policy snapshot requires an exact active attempt'
869
+ USING ERRCODE = '23514';
870
+ END IF;
871
+
872
+ canonical := workspace_learning_policy_canonical_at(
873
+ p_account_id,
874
+ p_workspace_id,
875
+ accepted_at
876
+ );
877
+ snapshot_revision_id := (canonical->>'revisionId')::uuid;
878
+ snapshot_revision := (canonical->>'revision')::bigint;
879
+ snapshot_policy_hash := canonical->>'policyHash';
880
+ snapshot_activation_version := (canonical->>'activationVersion')::bigint;
881
+ snapshot_activated_at := (canonical->>'activatedAt')::timestamptz;
882
+ snapshot_workspace_mode := canonical->>'workspaceMode';
883
+ snapshot_source_overrides := canonical->'sourceOverrides';
884
+
885
+ INSERT INTO "workspace_learning_policy_snapshots" (
886
+ "account_id", "workspace_id", "session_id", "turn_id", "attempt_id",
887
+ "execution_generation", "revision_id", "revision", "policy_hash",
888
+ "activation_version", "activated_at", "workspace_mode", "source_overrides",
889
+ "snapshot_hash"
890
+ ) VALUES (
891
+ p_account_id, p_workspace_id, p_session_id, p_turn_id, p_attempt_id,
892
+ p_execution_generation, snapshot_revision_id, snapshot_revision, snapshot_policy_hash,
893
+ snapshot_activation_version, snapshot_activated_at, snapshot_workspace_mode,
894
+ snapshot_source_overrides,
895
+ workspace_learning_policy_snapshot_hash(
896
+ snapshot_revision_id,
897
+ snapshot_revision,
898
+ snapshot_policy_hash,
899
+ snapshot_activation_version,
900
+ snapshot_activated_at,
901
+ snapshot_workspace_mode,
902
+ snapshot_source_overrides
903
+ )
904
+ )
905
+ ON CONFLICT ("account_id", "workspace_id", "attempt_id") DO NOTHING
906
+ RETURNING "id" INTO snapshot_id;
907
+
908
+ IF snapshot_id IS NULL THEN
909
+ SELECT snapshot."id" INTO snapshot_id
910
+ FROM "workspace_learning_policy_snapshots" snapshot
911
+ WHERE snapshot."account_id" = p_account_id
912
+ AND snapshot."workspace_id" = p_workspace_id
913
+ AND snapshot."attempt_id" = p_attempt_id
914
+ AND snapshot."session_id" = p_session_id
915
+ AND snapshot."turn_id" = p_turn_id
916
+ AND snapshot."execution_generation" = p_execution_generation;
917
+ IF NOT FOUND THEN
918
+ RAISE EXCEPTION 'learning-policy snapshot concurrent identity conflicted'
919
+ USING ERRCODE = '23514';
920
+ END IF;
921
+ END IF;
922
+ RETURN NEXT;
923
+ END
924
+ $body$
925
+ $ddl$, target_schema, target_schema);
926
+ EXECUTE format(
927
+ 'REVOKE ALL ON FUNCTION %I.workspace_learning_policy_get_or_create_snapshot(uuid,uuid,uuid,uuid,uuid,integer) FROM PUBLIC',
928
+ target_schema
929
+ );
930
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
931
+ EXECUTE format(
932
+ 'GRANT EXECUTE ON FUNCTION %I.workspace_learning_policy_get_or_create_snapshot(uuid, uuid, uuid, uuid, uuid, integer) TO opengeni_app',
933
+ target_schema
934
+ );
935
+ END IF;
936
+ END $snapshot_function$;
937
+
938
+ ALTER TABLE "workspace_learning_policy_revisions" ENABLE ROW LEVEL SECURITY;
939
+ ALTER TABLE "workspace_learning_policy_revisions" FORCE ROW LEVEL SECURITY;
940
+ ALTER TABLE "workspace_learning_policy_heads" ENABLE ROW LEVEL SECURITY;
941
+ ALTER TABLE "workspace_learning_policy_heads" FORCE ROW LEVEL SECURITY;
942
+ ALTER TABLE "workspace_learning_policy_activation_events" ENABLE ROW LEVEL SECURITY;
943
+ ALTER TABLE "workspace_learning_policy_activation_events" FORCE ROW LEVEL SECURITY;
944
+ ALTER TABLE "workspace_learning_policy_snapshots" ENABLE ROW LEVEL SECURITY;
945
+ ALTER TABLE "workspace_learning_policy_snapshots" FORCE ROW LEVEL SECURITY;
946
+
947
+ CREATE POLICY workspace_isolation ON "workspace_learning_policy_revisions"
948
+ USING (opengeni_private.workspace_rls_visible("account_id", "workspace_id"))
949
+ WITH CHECK (opengeni_private.workspace_rls_visible("account_id", "workspace_id"));
950
+
951
+ CREATE POLICY workspace_isolation ON "workspace_learning_policy_heads"
952
+ USING (opengeni_private.workspace_rls_visible("account_id", "workspace_id"))
953
+ WITH CHECK (opengeni_private.workspace_rls_visible("account_id", "workspace_id"));
954
+
955
+ CREATE POLICY workspace_isolation ON "workspace_learning_policy_activation_events"
956
+ USING (opengeni_private.workspace_rls_visible("account_id", "workspace_id"))
957
+ WITH CHECK (opengeni_private.workspace_rls_visible("account_id", "workspace_id"));
958
+
959
+ CREATE POLICY workspace_isolation ON "workspace_learning_policy_snapshots"
960
+ USING (opengeni_private.workspace_rls_visible("account_id", "workspace_id"))
961
+ WITH CHECK (opengeni_private.workspace_rls_visible("account_id", "workspace_id"));
962
+
963
+ REVOKE ALL ON TABLE "workspace_learning_policy_revisions" FROM PUBLIC;
964
+ REVOKE ALL ON TABLE "workspace_learning_policy_heads" FROM PUBLIC;
965
+ REVOKE ALL ON TABLE "workspace_learning_policy_activation_events" FROM PUBLIC;
966
+ REVOKE ALL ON TABLE "workspace_learning_policy_snapshots" FROM PUBLIC;
967
+
968
+ DO $grants$
969
+ BEGIN
970
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
971
+ GRANT SELECT, INSERT ON TABLE "workspace_learning_policy_revisions" TO opengeni_app;
972
+ GRANT SELECT ON TABLE "workspace_learning_policy_heads" TO opengeni_app;
973
+ GRANT SELECT ON TABLE "workspace_learning_policy_activation_events" TO opengeni_app;
974
+ GRANT SELECT ON TABLE "workspace_learning_policy_snapshots" TO opengeni_app;
975
+ GRANT EXECUTE ON FUNCTION workspace_learning_policy_source_overrides_valid(jsonb)
976
+ TO opengeni_app;
977
+ GRANT EXECUTE ON FUNCTION workspace_learning_policy_hash(text, jsonb)
978
+ TO opengeni_app;
979
+ END IF;
980
+ END $grants$;