@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,338 @@
1
+ -- deployment-mode: rolling
2
+ -- Shared, one-use live admission tickets. Only token digests are persisted;
3
+ -- the opaque bearer token never crosses this storage boundary.
4
+
5
+ SET LOCAL lock_timeout = '5s';
6
+ SET LOCAL statement_timeout = '5min';
7
+
8
+ CREATE TABLE "editable_artifact_live_tickets" (
9
+ "token_digest" text PRIMARY KEY,
10
+ "account_id" uuid NOT NULL,
11
+ "workspace_id" uuid NOT NULL,
12
+ "artifact_id" text NOT NULL,
13
+ "modality" text NOT NULL,
14
+ "actor_kind" text NOT NULL,
15
+ "actor_subject_id" text NOT NULL,
16
+ "replica_id" text NOT NULL,
17
+ "agent_session_id" text,
18
+ "agent_turn_id" text,
19
+ "agent_attempt_id" text,
20
+ "agent_generation" integer,
21
+ "service_name" text,
22
+ "allow_edit" boolean NOT NULL,
23
+ "protocol_version" integer NOT NULL,
24
+ "issued_at" timestamptz NOT NULL,
25
+ "expires_at" timestamptz NOT NULL,
26
+ CONSTRAINT "editable_artifact_live_tickets_artifact_fk"
27
+ FOREIGN KEY ("account_id", "workspace_id", "artifact_id")
28
+ REFERENCES "editable_artifacts"("account_id", "workspace_id", "id")
29
+ ON DELETE CASCADE,
30
+ CONSTRAINT "editable_artifact_live_tickets_digest_chk" CHECK (
31
+ "token_digest" ~ '^sha256:[0-9a-f]{64}$'
32
+ ),
33
+ CONSTRAINT "editable_artifact_live_tickets_identity_chk" CHECK (
34
+ "artifact_id" ~ '^[0-9a-f]{32}$' AND "artifact_id" !~ '^0+$'
35
+ AND "replica_id" ~ '^[0-9a-f]{16}$' AND "replica_id" !~ '^0+$'
36
+ ),
37
+ CONSTRAINT "editable_artifact_live_tickets_modality_chk" CHECK (
38
+ "modality" IN ('spreadsheet', 'presentation', 'document')
39
+ ),
40
+ CONSTRAINT "editable_artifact_live_tickets_actor_chk" CHECK (
41
+ octet_length("actor_subject_id") BETWEEN 1 AND 1024
42
+ AND "actor_subject_id" = btrim("actor_subject_id")
43
+ AND (
44
+ ("actor_kind" = 'human'
45
+ AND "agent_session_id" IS NULL
46
+ AND "agent_turn_id" IS NULL
47
+ AND "agent_attempt_id" IS NULL
48
+ AND "agent_generation" IS NULL
49
+ AND "service_name" IS NULL)
50
+ OR
51
+ ("actor_kind" = 'agent'
52
+ AND "agent_session_id" IS NOT NULL
53
+ AND octet_length("agent_session_id") BETWEEN 1 AND 1024
54
+ AND "agent_session_id" = btrim("agent_session_id")
55
+ AND "agent_turn_id" IS NOT NULL
56
+ AND octet_length("agent_turn_id") BETWEEN 1 AND 1024
57
+ AND "agent_turn_id" = btrim("agent_turn_id")
58
+ AND "agent_attempt_id" IS NOT NULL
59
+ AND octet_length("agent_attempt_id") BETWEEN 1 AND 1024
60
+ AND "agent_attempt_id" = btrim("agent_attempt_id")
61
+ AND "agent_generation" IS NOT NULL
62
+ AND "agent_generation" BETWEEN 0 AND 2147483647
63
+ AND "service_name" IS NULL)
64
+ OR
65
+ ("actor_kind" = 'service'
66
+ AND "agent_session_id" IS NULL
67
+ AND "agent_turn_id" IS NULL
68
+ AND "agent_attempt_id" IS NULL
69
+ AND "agent_generation" IS NULL
70
+ AND "service_name" IS NOT NULL
71
+ AND octet_length("service_name") BETWEEN 1 AND 1024
72
+ AND "service_name" = btrim("service_name"))
73
+ )
74
+ ),
75
+ CONSTRAINT "editable_artifact_live_tickets_lifetime_chk" CHECK (
76
+ "protocol_version" BETWEEN 1 AND 2147483647
77
+ AND "expires_at" >= "issued_at" + interval '1 second'
78
+ AND "expires_at" <= "issued_at" + interval '60 seconds'
79
+ )
80
+ );
81
+
82
+ CREATE INDEX "editable_artifact_live_tickets_expiry_idx"
83
+ ON "editable_artifact_live_tickets" ("expires_at", "token_digest");
84
+
85
+ ALTER TABLE "editable_artifact_live_tickets" ENABLE ROW LEVEL SECURITY;
86
+ ALTER TABLE "editable_artifact_live_tickets" FORCE ROW LEVEL SECURITY;
87
+
88
+ DO $ticket_owner_policy$
89
+ DECLARE data_schema text := pg_catalog.current_schema();
90
+ DECLARE migration_owner text := current_user;
91
+ BEGIN
92
+ EXECUTE pg_catalog.format(
93
+ 'CREATE POLICY editable_artifact_live_ticket_owner ON %I.editable_artifact_live_tickets '
94
+ || 'FOR ALL USING (current_user = %L) WITH CHECK (current_user = %L)',
95
+ data_schema, migration_owner, migration_owner
96
+ );
97
+ END;
98
+ $ticket_owner_policy$;
99
+
100
+ CREATE OR REPLACE FUNCTION opengeni_private.resolve_editable_artifact_ticket_data_schema(
101
+ requested_schema name
102
+ ) RETURNS name
103
+ LANGUAGE plpgsql
104
+ SECURITY DEFINER
105
+ SET search_path = pg_catalog, pg_temp
106
+ AS $body$
107
+ DECLARE data_schema name;
108
+ BEGIN
109
+ data_schema := opengeni_private.resolve_editable_artifact_data_schema(requested_schema);
110
+ PERFORM 1
111
+ FROM pg_catalog.pg_namespace namespace
112
+ JOIN pg_catalog.pg_class ticket_relation
113
+ ON ticket_relation.relnamespace = namespace.oid
114
+ AND ticket_relation.relname = 'editable_artifact_live_tickets'
115
+ AND ticket_relation.relkind IN ('r', 'p')
116
+ JOIN pg_catalog.pg_class artifact_relation
117
+ ON artifact_relation.relnamespace = namespace.oid
118
+ AND artifact_relation.relname = 'editable_artifacts'
119
+ AND artifact_relation.relkind IN ('r', 'p')
120
+ AND artifact_relation.relowner = ticket_relation.relowner
121
+ JOIN pg_catalog.pg_roles owner_role
122
+ ON owner_role.oid = ticket_relation.relowner
123
+ AND owner_role.rolname = current_user
124
+ WHERE namespace.nspname = data_schema
125
+ AND ticket_relation.relrowsecurity
126
+ AND ticket_relation.relforcerowsecurity;
127
+ IF NOT FOUND THEN
128
+ RAISE EXCEPTION 'editable artifact live ticket data schema is unavailable or untrusted'
129
+ USING ERRCODE = '42501';
130
+ END IF;
131
+ RETURN data_schema;
132
+ END;
133
+ $body$;
134
+
135
+ CREATE OR REPLACE FUNCTION opengeni_private.put_editable_artifact_live_ticket(
136
+ p_token_digest text,
137
+ p_account_id uuid,
138
+ p_workspace_id uuid,
139
+ p_artifact_id text,
140
+ p_modality text,
141
+ p_actor_kind text,
142
+ p_actor_subject_id text,
143
+ p_replica_id text,
144
+ p_agent_session_id text,
145
+ p_agent_turn_id text,
146
+ p_agent_attempt_id text,
147
+ p_agent_generation integer,
148
+ p_service_name text,
149
+ p_allow_edit boolean,
150
+ p_protocol_version integer,
151
+ p_issued_at timestamptz,
152
+ p_expires_at timestamptz,
153
+ p_data_schema name DEFAULT pg_catalog.current_schema()
154
+ ) RETURNS void
155
+ LANGUAGE plpgsql
156
+ SECURITY DEFINER
157
+ SET search_path = pg_catalog, pg_temp
158
+ AS $body$
159
+ DECLARE data_schema name;
160
+ DECLARE stored boolean;
161
+ BEGIN
162
+ IF NOT opengeni_private.editable_artifact_scope_matches_context(
163
+ p_account_id, p_workspace_id
164
+ ) THEN
165
+ RAISE EXCEPTION 'editable artifact live ticket scope does not match context'
166
+ USING ERRCODE = '42501';
167
+ END IF;
168
+ IF p_issued_at < pg_catalog.clock_timestamp() - interval '60 seconds'
169
+ OR p_issued_at > pg_catalog.clock_timestamp() + interval '5 seconds'
170
+ OR p_expires_at <= pg_catalog.clock_timestamp()
171
+ OR p_expires_at > pg_catalog.clock_timestamp() + interval '60 seconds'
172
+ THEN
173
+ RAISE EXCEPTION 'editable artifact live ticket lifetime is outside database bounds'
174
+ USING ERRCODE = '22023';
175
+ END IF;
176
+ data_schema := opengeni_private.resolve_editable_artifact_ticket_data_schema(
177
+ p_data_schema
178
+ );
179
+ EXECUTE pg_catalog.format($query$
180
+ SELECT true
181
+ FROM %I.editable_artifacts
182
+ WHERE account_id = $1 AND workspace_id = $2 AND id = $3
183
+ AND modality = $4 AND lifecycle_state = 'active'
184
+ $query$, data_schema)
185
+ INTO stored
186
+ USING p_account_id, p_workspace_id, p_artifact_id, p_modality;
187
+ IF stored IS DISTINCT FROM true THEN
188
+ RAISE EXCEPTION 'editable artifact live ticket target is unavailable'
189
+ USING ERRCODE = '23503';
190
+ END IF;
191
+ EXECUTE pg_catalog.format($query$
192
+ INSERT INTO %I.editable_artifact_live_tickets (
193
+ token_digest, account_id, workspace_id, artifact_id, modality,
194
+ actor_kind, actor_subject_id, replica_id,
195
+ agent_session_id, agent_turn_id, agent_attempt_id, agent_generation,
196
+ service_name, allow_edit, protocol_version, issued_at, expires_at
197
+ ) VALUES (
198
+ $1, $2, $3, $4, $5, $6, $7, $8,
199
+ $9, $10, $11, $12, $13, $14, $15, $16, $17
200
+ )
201
+ $query$, data_schema) USING
202
+ p_token_digest, p_account_id, p_workspace_id, p_artifact_id, p_modality,
203
+ p_actor_kind, p_actor_subject_id, p_replica_id,
204
+ p_agent_session_id, p_agent_turn_id, p_agent_attempt_id, p_agent_generation,
205
+ p_service_name, p_allow_edit, p_protocol_version, p_issued_at, p_expires_at;
206
+ END;
207
+ $body$;
208
+
209
+ CREATE OR REPLACE FUNCTION opengeni_private.consume_editable_artifact_live_ticket(
210
+ p_token_digest text,
211
+ p_data_schema name DEFAULT pg_catalog.current_schema()
212
+ ) RETURNS TABLE (
213
+ token_digest text,
214
+ account_id uuid,
215
+ workspace_id uuid,
216
+ artifact_id text,
217
+ modality text,
218
+ actor_kind text,
219
+ actor_subject_id text,
220
+ replica_id text,
221
+ agent_session_id text,
222
+ agent_turn_id text,
223
+ agent_attempt_id text,
224
+ agent_generation integer,
225
+ service_name text,
226
+ allow_edit boolean,
227
+ protocol_version integer,
228
+ issued_at timestamptz,
229
+ expires_at timestamptz
230
+ )
231
+ LANGUAGE plpgsql
232
+ SECURITY DEFINER
233
+ SET search_path = pg_catalog, pg_temp
234
+ AS $body$
235
+ DECLARE data_schema name;
236
+ BEGIN
237
+ IF p_token_digest !~ '^sha256:[0-9a-f]{64}$' THEN
238
+ RAISE EXCEPTION 'editable artifact live ticket digest is malformed'
239
+ USING ERRCODE = '22023';
240
+ END IF;
241
+ data_schema := opengeni_private.resolve_editable_artifact_ticket_data_schema(
242
+ p_data_schema
243
+ );
244
+ RETURN QUERY EXECUTE pg_catalog.format($query$
245
+ WITH consumed AS (
246
+ DELETE FROM %I.editable_artifact_live_tickets ticket
247
+ WHERE ticket.token_digest = $1
248
+ RETURNING ticket.token_digest, ticket.account_id, ticket.workspace_id,
249
+ ticket.artifact_id, ticket.modality, ticket.actor_kind,
250
+ ticket.actor_subject_id, ticket.replica_id, ticket.agent_session_id,
251
+ ticket.agent_turn_id, ticket.agent_attempt_id, ticket.agent_generation,
252
+ ticket.service_name, ticket.allow_edit, ticket.protocol_version,
253
+ ticket.issued_at, ticket.expires_at
254
+ )
255
+ SELECT consumed.*
256
+ FROM consumed
257
+ WHERE consumed.expires_at > pg_catalog.clock_timestamp()
258
+ $query$, data_schema) USING p_token_digest;
259
+ END;
260
+ $body$;
261
+
262
+ CREATE OR REPLACE FUNCTION opengeni_private.cleanup_expired_editable_artifact_live_tickets(
263
+ p_limit integer,
264
+ p_data_schema name DEFAULT pg_catalog.current_schema()
265
+ ) RETURNS integer
266
+ LANGUAGE plpgsql
267
+ SECURITY DEFINER
268
+ SET search_path = pg_catalog, pg_temp
269
+ AS $body$
270
+ DECLARE data_schema name;
271
+ DECLARE removed integer;
272
+ BEGIN
273
+ IF p_limit IS NULL OR p_limit < 1 OR p_limit > 10000 THEN
274
+ RAISE EXCEPTION 'editable artifact live ticket cleanup limit is invalid'
275
+ USING ERRCODE = '22023';
276
+ END IF;
277
+ data_schema := opengeni_private.resolve_editable_artifact_ticket_data_schema(
278
+ p_data_schema
279
+ );
280
+ EXECUTE pg_catalog.format($query$
281
+ WITH expired AS (
282
+ SELECT ticket.token_digest
283
+ FROM %I.editable_artifact_live_tickets ticket
284
+ WHERE ticket.expires_at <= pg_catalog.clock_timestamp()
285
+ ORDER BY ticket.expires_at, ticket.token_digest
286
+ LIMIT $1
287
+ FOR UPDATE SKIP LOCKED
288
+ )
289
+ DELETE FROM %I.editable_artifact_live_tickets ticket
290
+ USING expired
291
+ WHERE ticket.token_digest = expired.token_digest
292
+ $query$, data_schema, data_schema) USING p_limit;
293
+ GET DIAGNOSTICS removed = ROW_COUNT;
294
+ RETURN removed;
295
+ END;
296
+ $body$;
297
+
298
+ REVOKE ALL ON TABLE "editable_artifact_live_tickets" FROM PUBLIC;
299
+ REVOKE ALL ON FUNCTION
300
+ opengeni_private.resolve_editable_artifact_ticket_data_schema(name) FROM PUBLIC;
301
+ REVOKE ALL ON FUNCTION
302
+ opengeni_private.put_editable_artifact_live_ticket(
303
+ text, uuid, uuid, text, text, text, text, text,
304
+ text, text, text, integer, text, boolean, integer, timestamptz, timestamptz, name
305
+ ) FROM PUBLIC;
306
+ REVOKE ALL ON FUNCTION
307
+ opengeni_private.consume_editable_artifact_live_ticket(text, name) FROM PUBLIC;
308
+ REVOKE ALL ON FUNCTION
309
+ opengeni_private.cleanup_expired_editable_artifact_live_tickets(integer, name)
310
+ FROM PUBLIC;
311
+
312
+ DO $runtime_ticket_grants$
313
+ DECLARE data_schema name := pg_catalog.current_schema();
314
+ BEGIN
315
+ IF EXISTS (
316
+ SELECT 1 FROM pg_catalog.pg_roles WHERE rolname = 'opengeni_app'
317
+ ) THEN
318
+ EXECUTE pg_catalog.format(
319
+ 'REVOKE ALL PRIVILEGES ON TABLE %I.editable_artifact_live_tickets FROM opengeni_app',
320
+ data_schema
321
+ );
322
+ GRANT EXECUTE ON FUNCTION
323
+ opengeni_private.put_editable_artifact_live_ticket(
324
+ text, uuid, uuid, text, text, text, text, text,
325
+ text, text, text, integer, text, boolean, integer, timestamptz, timestamptz, name
326
+ ) TO opengeni_app;
327
+ GRANT EXECUTE ON FUNCTION
328
+ opengeni_private.consume_editable_artifact_live_ticket(text, name)
329
+ TO opengeni_app;
330
+ GRANT EXECUTE ON FUNCTION
331
+ opengeni_private.cleanup_expired_editable_artifact_live_tickets(integer, name)
332
+ TO opengeni_app;
333
+ END IF;
334
+ END;
335
+ $runtime_ticket_grants$;
336
+
337
+ RESET statement_timeout;
338
+ RESET lock_timeout;
@@ -0,0 +1,190 @@
1
+ -- deployment-mode: rolling
2
+ -- Durable editable-artifact authorization projection and revocation fencing.
3
+
4
+ SET LOCAL lock_timeout = '5s';
5
+ SET LOCAL statement_timeout = '5min';
6
+
7
+ CREATE OR REPLACE FUNCTION opengeni_private.authorize_editable_artifact_actor(
8
+ p_account_id uuid,
9
+ p_workspace_id uuid,
10
+ p_artifact_id text,
11
+ p_actor_kind text,
12
+ p_actor_subject_id text,
13
+ p_agent_session_id text,
14
+ p_agent_turn_id text,
15
+ p_agent_attempt_id text,
16
+ p_agent_generation integer,
17
+ p_service_name text,
18
+ p_permission text,
19
+ p_data_schema name DEFAULT pg_catalog.current_schema()
20
+ ) RETURNS TABLE (allowed boolean, authorization_revision bigint)
21
+ LANGUAGE plpgsql
22
+ SECURITY DEFINER
23
+ SET search_path = pg_catalog, pg_temp
24
+ AS $body$
25
+ DECLARE data_schema name;
26
+ DECLARE required_permission text;
27
+ DECLARE current_revision bigint;
28
+ DECLARE principal_allowed boolean := false;
29
+ BEGIN
30
+ IF p_account_id IS NULL OR p_workspace_id IS NULL
31
+ OR p_artifact_id !~ '^[0-9a-f]{32}$' OR p_artifact_id ~ '^0+$'
32
+ OR p_actor_kind NOT IN ('human', 'agent', 'service')
33
+ OR p_actor_subject_id IS NULL
34
+ OR octet_length(p_actor_subject_id) NOT BETWEEN 1 AND 1024
35
+ OR p_actor_subject_id <> btrim(p_actor_subject_id)
36
+ OR p_permission NOT IN ('create', 'read', 'edit', 'import', 'export', 'manage')
37
+ OR NOT opengeni_private.editable_artifact_scope_matches_context(
38
+ p_account_id, p_workspace_id
39
+ )
40
+ THEN
41
+ RAISE EXCEPTION 'editable artifact authorization request is invalid'
42
+ USING ERRCODE = '22023';
43
+ END IF;
44
+ IF (p_actor_kind = 'human' AND (
45
+ p_agent_session_id IS NOT NULL OR p_agent_turn_id IS NOT NULL
46
+ OR p_agent_attempt_id IS NOT NULL OR p_agent_generation IS NOT NULL
47
+ OR p_service_name IS NOT NULL
48
+ )) OR (p_actor_kind = 'agent' AND (
49
+ p_agent_session_id IS NULL OR p_agent_turn_id IS NULL
50
+ OR p_agent_attempt_id IS NULL OR p_agent_generation IS NULL
51
+ OR p_agent_generation < 0 OR p_service_name IS NOT NULL
52
+ )) OR (p_actor_kind = 'service' AND (
53
+ p_agent_session_id IS NOT NULL OR p_agent_turn_id IS NOT NULL
54
+ OR p_agent_attempt_id IS NOT NULL OR p_agent_generation IS NOT NULL
55
+ OR p_service_name IS NULL
56
+ ))
57
+ THEN
58
+ RAISE EXCEPTION 'editable artifact authorization actor is invalid'
59
+ USING ERRCODE = '22023';
60
+ END IF;
61
+
62
+ data_schema := opengeni_private.resolve_editable_artifact_data_schema(p_data_schema);
63
+ required_permission := CASE
64
+ WHEN p_permission IN ('read', 'export') THEN 'artifacts:read'
65
+ ELSE 'artifacts:publish'
66
+ END;
67
+
68
+ IF p_permission = 'create' THEN
69
+ current_revision := opengeni_private.ensure_editable_artifact_scope_authorization_head(
70
+ p_account_id, p_workspace_id, data_schema
71
+ );
72
+ ELSE
73
+ EXECUTE pg_catalog.format($query$
74
+ SELECT artifact.authorization_revision
75
+ FROM %I.editable_artifacts artifact
76
+ WHERE artifact.account_id = $1 AND artifact.workspace_id = $2
77
+ AND artifact.id = $3
78
+ $query$, data_schema)
79
+ INTO current_revision
80
+ USING p_account_id, p_workspace_id, p_artifact_id;
81
+ current_revision := coalesce(current_revision, 1);
82
+ END IF;
83
+
84
+ IF p_actor_kind = 'agent' THEN
85
+ EXECUTE pg_catalog.format($query$
86
+ SELECT true
87
+ FROM %I.sessions session
88
+ JOIN %I.session_turns turn
89
+ ON turn.account_id = session.account_id
90
+ AND turn.workspace_id = session.workspace_id
91
+ AND turn.session_id = session.id
92
+ JOIN %I.session_turn_attempts attempt
93
+ ON attempt.account_id = turn.account_id
94
+ AND attempt.workspace_id = turn.workspace_id
95
+ AND attempt.session_id = turn.session_id
96
+ AND attempt.turn_id = turn.id
97
+ WHERE session.account_id = $1 AND session.workspace_id = $2
98
+ AND session.id::text = $3
99
+ AND turn.id::text = $4
100
+ AND attempt.id::text = $5
101
+ AND session.active_turn_id = turn.id
102
+ AND turn.active_attempt_id = attempt.id
103
+ AND turn.execution_generation = $6
104
+ AND attempt.execution_generation = $6
105
+ AND turn.status IN ('running', 'requires_action', 'recovering', 'waiting_capacity')
106
+ AND attempt.state IN ('claimed', 'running')
107
+ AND attempt.closed_at IS NULL AND attempt.quiesced_at IS NULL
108
+ AND NOT EXISTS (
109
+ SELECT 1
110
+ FROM %I.session_attempt_interruptions interruption
111
+ WHERE interruption.account_id = session.account_id
112
+ AND interruption.workspace_id = session.workspace_id
113
+ AND interruption.session_id = session.id
114
+ AND interruption.attempt_id = attempt.id
115
+ AND interruption.state IN ('pending', 'delivered', 'acknowledged')
116
+ )
117
+ AND (
118
+ session.first_party_mcp_permissions IS NULL
119
+ OR session.first_party_mcp_permissions ? $7
120
+ OR session.first_party_mcp_permissions ? 'workspace:admin'
121
+ )
122
+ LIMIT 1
123
+ FOR SHARE OF session, turn, attempt
124
+ $query$, data_schema, data_schema, data_schema, data_schema)
125
+ INTO principal_allowed
126
+ USING p_account_id, p_workspace_id, p_agent_session_id,
127
+ p_agent_turn_id, p_agent_attempt_id, p_agent_generation,
128
+ required_permission;
129
+ ELSIF p_actor_kind = 'service'
130
+ AND p_service_name = 'api_key'
131
+ AND p_actor_subject_id ~ '^api_key:[0-9a-fA-F-]{36}$'
132
+ THEN
133
+ EXECUTE pg_catalog.format($query$
134
+ SELECT true
135
+ FROM %I.api_keys api_key
136
+ WHERE api_key.account_id = $1 AND api_key.workspace_id = $2
137
+ AND api_key.id::text = substring($3 from 9)
138
+ AND api_key.revoked_at IS NULL
139
+ AND (api_key.expires_at IS NULL OR api_key.expires_at > pg_catalog.clock_timestamp())
140
+ AND (api_key.permissions ? $4 OR api_key.permissions ? 'workspace:admin')
141
+ LIMIT 1
142
+ FOR SHARE OF api_key
143
+ $query$, data_schema)
144
+ INTO principal_allowed
145
+ USING p_account_id, p_workspace_id, p_actor_subject_id, required_permission;
146
+ ELSIF p_actor_kind = 'human'
147
+ OR (p_actor_kind = 'service' AND p_service_name = 'configured_key')
148
+ THEN
149
+ -- Configured deployments authenticate the shared key at the HTTP boundary,
150
+ -- then persist its exact subject as a workspace membership. Revalidate that
151
+ -- durable tenant/permission grant here just as for a human session; key
152
+ -- possession itself is never accepted from caller-authored SQL inputs.
153
+ EXECUTE pg_catalog.format($query$
154
+ SELECT true
155
+ FROM %I.workspace_memberships membership
156
+ WHERE membership.account_id = $1 AND membership.workspace_id = $2
157
+ AND membership.subject_id = $3
158
+ AND (membership.permissions ? $4 OR membership.permissions ? 'workspace:admin')
159
+ LIMIT 1
160
+ FOR SHARE OF membership
161
+ $query$, data_schema)
162
+ INTO principal_allowed
163
+ USING p_account_id, p_workspace_id, p_actor_subject_id, required_permission;
164
+ ELSE
165
+ -- Delegated service grants have no independent transactionally
166
+ -- revalidatable authority source. Fail closed rather than accepting a
167
+ -- self-carried grant.
168
+ principal_allowed := false;
169
+ END IF;
170
+
171
+ RETURN QUERY SELECT coalesce(principal_allowed, false), current_revision;
172
+ END;
173
+ $body$;
174
+
175
+ REVOKE ALL ON FUNCTION opengeni_private.authorize_editable_artifact_actor(
176
+ uuid, uuid, text, text, text, text, text, text, integer, text, text, name
177
+ ) FROM PUBLIC;
178
+
179
+ DO $runtime_authorization_grant$
180
+ BEGIN
181
+ IF EXISTS (SELECT 1 FROM pg_catalog.pg_roles WHERE rolname = 'opengeni_app') THEN
182
+ GRANT EXECUTE ON FUNCTION opengeni_private.authorize_editable_artifact_actor(
183
+ uuid, uuid, text, text, text, text, text, text, integer, text, text, name
184
+ ) TO opengeni_app;
185
+ END IF;
186
+ END;
187
+ $runtime_authorization_grant$;
188
+
189
+ RESET statement_timeout;
190
+ RESET lock_timeout;
@@ -0,0 +1,160 @@
1
+ -- deployment-mode: rolling
2
+ -- Public immutable version pinning and materialization idempotency.
3
+
4
+ SET LOCAL lock_timeout = '5s';
5
+ SET LOCAL statement_timeout = '5min';
6
+
7
+ ALTER TABLE "editable_artifact_versions"
8
+ ALTER COLUMN "causal_frontier" DROP NOT NULL,
9
+ ADD COLUMN "native_revision" bigint;
10
+
11
+ ALTER TABLE "editable_artifact_versions"
12
+ DROP CONSTRAINT "editable_artifact_versions_facts_chk",
13
+ ADD CONSTRAINT "editable_artifact_versions_facts_chk" CHECK (
14
+ "head_sequence" BETWEEN 0 AND 9007199254740991
15
+ AND "state_hash" ~ '^sha256:[0-9a-f]{64}$'
16
+ AND octet_length(convert_to("name", 'UTF8')) BETWEEN 1 AND 256
17
+ AND "name" = btrim("name")
18
+ AND opengeni_private.editable_artifact_identity_valid("created_by_subject_id")
19
+ AND ((opengeni_private.editable_artifact_frontier_valid("causal_frontier")
20
+ AND "native_revision" IS NULL)
21
+ OR ("causal_frontier" IS NULL
22
+ AND "native_revision" BETWEEN 0 AND 9007199254740991))
23
+ );
24
+
25
+ DROP INDEX "editable_artifact_idempotency_receipts_artifact_request_uq";
26
+ CREATE UNIQUE INDEX "editable_artifact_idempotency_receipts_artifact_request_uq"
27
+ ON "editable_artifact_idempotency_receipts" (
28
+ "account_id", "workspace_id", "artifact_id", "operation_kind",
29
+ "authority_key_digest", "idempotency_key"
30
+ )
31
+ WHERE "operation_kind" IN ('edit', 'snapshot', 'version', 'materialize');
32
+
33
+ ALTER TABLE "editable_artifact_idempotency_receipts"
34
+ DROP CONSTRAINT "editable_artifact_idempotency_receipts_key_chk",
35
+ DROP CONSTRAINT "editable_artifact_idempotency_receipts_resource_chk",
36
+ ADD CONSTRAINT "editable_artifact_idempotency_receipts_key_chk" CHECK (
37
+ "operation_kind" IN ('create', 'import', 'edit', 'snapshot', 'version', 'materialize')
38
+ AND octet_length(convert_to("authority_key", 'UTF8')) BETWEEN 1 AND 8192
39
+ AND octet_length("idempotency_key") BETWEEN 1 AND 256
40
+ AND "idempotency_key" = btrim("idempotency_key")
41
+ ),
42
+ ADD CONSTRAINT "editable_artifact_idempotency_receipts_resource_chk" CHECK (
43
+ "resource_type" IN (
44
+ 'artifact', 'transaction', 'snapshot', 'artifact_version', 'materialization_job'
45
+ )
46
+ AND (
47
+ ("operation_kind" = 'create' AND "resource_type" = 'artifact'
48
+ AND "resource_id" = "artifact_id" AND "server_transaction_id" IS NULL)
49
+ OR ("operation_kind" = 'edit' AND "resource_type" = 'transaction'
50
+ AND "resource_id" = "server_transaction_id")
51
+ OR ("operation_kind" = 'snapshot' AND "resource_type" = 'snapshot'
52
+ AND "server_transaction_id" IS NULL)
53
+ OR ("operation_kind" = 'version' AND "resource_type" = 'artifact_version'
54
+ AND "server_transaction_id" IS NULL)
55
+ OR ("operation_kind" = 'materialize'
56
+ AND "resource_type" = 'materialization_job'
57
+ AND "server_transaction_id" IS NULL)
58
+ OR ("operation_kind" = 'import' AND "resource_type" = 'artifact'
59
+ AND "resource_id" = "artifact_id" AND "server_transaction_id" IS NULL)
60
+ )
61
+ );
62
+
63
+ DROP TRIGGER "editable_artifact_versions_projection_guard"
64
+ ON "editable_artifact_versions";
65
+
66
+ CREATE OR REPLACE FUNCTION opengeni_private.validate_editable_artifact_version_projection()
67
+ RETURNS trigger
68
+ LANGUAGE plpgsql
69
+ SET search_path = pg_catalog, pg_temp
70
+ AS $body$
71
+ DECLARE artifact_modality text;
72
+ DECLARE checkpoint record;
73
+ DECLARE snapshot record;
74
+ BEGIN
75
+ PERFORM set_config(
76
+ 'search_path', 'pg_catalog,' || quote_ident(TG_TABLE_SCHEMA) || ',pg_temp', true
77
+ );
78
+
79
+ SELECT artifact.modality INTO artifact_modality
80
+ FROM editable_artifacts artifact
81
+ WHERE artifact.account_id = NEW.account_id
82
+ AND artifact.workspace_id = NEW.workspace_id
83
+ AND artifact.id = NEW.artifact_id;
84
+
85
+ SELECT * INTO checkpoint
86
+ FROM editable_artifact_sequence_checkpoints candidate
87
+ WHERE candidate.account_id = NEW.account_id
88
+ AND candidate.workspace_id = NEW.workspace_id
89
+ AND candidate.artifact_id = NEW.artifact_id
90
+ AND candidate.head_sequence = NEW.head_sequence;
91
+
92
+ SELECT * INTO snapshot
93
+ FROM editable_artifact_snapshots candidate
94
+ WHERE candidate.account_id = NEW.account_id
95
+ AND candidate.workspace_id = NEW.workspace_id
96
+ AND candidate.artifact_id = NEW.artifact_id
97
+ AND candidate.id = NEW.snapshot_id;
98
+
99
+ IF artifact_modality IS NULL
100
+ OR checkpoint.head_sequence IS NULL
101
+ OR checkpoint.modality <> artifact_modality
102
+ OR checkpoint.state_hash <> NEW.state_hash
103
+ OR snapshot.id IS NULL
104
+ OR snapshot.modality <> artifact_modality
105
+ OR snapshot.covered_head_sequence <> NEW.head_sequence
106
+ OR snapshot.state_hash <> NEW.state_hash
107
+ OR (artifact_modality = 'spreadsheet' AND (
108
+ NEW.native_revision IS NOT NULL
109
+ OR checkpoint.causal_frontier IS DISTINCT FROM NEW.causal_frontier
110
+ OR snapshot.covered_causal_frontier IS DISTINCT FROM NEW.causal_frontier
111
+ ))
112
+ OR (artifact_modality IN ('document', 'presentation') AND (
113
+ NEW.causal_frontier IS NOT NULL
114
+ OR checkpoint.native_revision IS DISTINCT FROM NEW.native_revision
115
+ OR snapshot.native_revision IS DISTINCT FROM NEW.native_revision
116
+ ))
117
+ THEN
118
+ RAISE EXCEPTION 'artifact version does not exactly pin a verified snapshot checkpoint'
119
+ USING ERRCODE = '23514';
120
+ END IF;
121
+
122
+ IF NOT EXISTS (
123
+ SELECT 1
124
+ FROM editable_artifact_idempotency_receipts receipt
125
+ WHERE receipt.account_id = NEW.account_id
126
+ AND receipt.workspace_id = NEW.workspace_id
127
+ AND receipt.artifact_id = NEW.artifact_id
128
+ AND receipt.operation_kind = 'version'
129
+ AND receipt.resource_type = 'artifact_version'
130
+ AND receipt.resource_id = NEW.id
131
+ AND receipt.server_transaction_id IS NULL
132
+ AND opengeni_private.editable_artifact_object_has_exact_keys(
133
+ receipt.result, ARRAY['artifactId', 'schemaVersion', 'versionId']
134
+ )
135
+ AND jsonb_typeof(receipt.result->'schemaVersion') = 'number'
136
+ AND receipt.result->>'schemaVersion' = '1'
137
+ AND jsonb_typeof(receipt.result->'artifactId') = 'string'
138
+ AND receipt.result->>'artifactId' = NEW.artifact_id
139
+ AND jsonb_typeof(receipt.result->'versionId') = 'string'
140
+ AND receipt.result->>'versionId' = NEW.id
141
+ ) THEN
142
+ RAISE EXCEPTION 'artifact version idempotency receipt is missing'
143
+ USING ERRCODE = '23514';
144
+ END IF;
145
+
146
+ RETURN NULL;
147
+ END;
148
+ $body$;
149
+
150
+ REVOKE ALL ON FUNCTION
151
+ opengeni_private.validate_editable_artifact_version_projection()
152
+ FROM PUBLIC;
153
+
154
+ CREATE CONSTRAINT TRIGGER editable_artifact_versions_projection_guard
155
+ AFTER INSERT ON "editable_artifact_versions"
156
+ DEFERRABLE INITIALLY DEFERRED FOR EACH ROW
157
+ EXECUTE FUNCTION opengeni_private.validate_editable_artifact_version_projection();
158
+
159
+ RESET statement_timeout;
160
+ RESET lock_timeout;