@opengeni/db 0.28.1 → 0.31.1
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.
- package/dist/attached-browser-devices.d.ts +42 -0
- package/dist/attempt-tool-catalogs.d.ts +16 -0
- package/dist/browser-auth.d.ts +91 -0
- package/dist/browser-identities.d.ts +128 -0
- package/dist/browser-sessions.d.ts +272 -0
- package/dist/browser-state-artifacts.d.ts +17 -0
- package/dist/capability-components.d.ts +37 -0
- package/dist/capability-integrations.d.ts +192 -0
- package/dist/chunk-F5FT7BEZ.js +3535 -0
- package/dist/chunk-F5FT7BEZ.js.map +1 -0
- package/dist/chunk-ISI2TIUG.js +639 -0
- package/dist/chunk-ISI2TIUG.js.map +1 -0
- package/dist/chunk-MWDVXQOL.js +11639 -0
- package/dist/chunk-MWDVXQOL.js.map +1 -0
- package/dist/chunk-NBE6CHRI.js +955 -0
- package/dist/chunk-NBE6CHRI.js.map +1 -0
- package/dist/{chunk-DBS5HPEW.js → chunk-YHZXQ7HP.js} +760 -11
- package/dist/chunk-YHZXQ7HP.js.map +1 -0
- package/dist/codemode-operations.d.ts +117 -0
- package/dist/codex-token-resolver.d.ts +2 -2
- package/dist/company-profile-schema.d.ts +798 -0
- package/dist/company-profile.d.ts +268 -0
- package/dist/computer-sessions.d.ts +109 -0
- package/dist/connection-token-resolver.d.ts +9 -4
- package/dist/database.d.ts +52 -3
- package/dist/editable-artifact-durable-export.d.ts +153 -0
- package/dist/editable-artifact-durable-export.js +788 -0
- package/dist/editable-artifact-durable-export.js.map +1 -0
- package/dist/editable-artifact-materialization.d.ts +123 -0
- package/dist/editable-artifacts-schema.d.ts +4152 -0
- package/dist/editable-artifacts.d.ts +610 -0
- package/dist/editable-artifacts.js +26 -0
- package/dist/editable-artifacts.js.map +1 -0
- package/dist/environment-crypto.d.ts +8 -4
- package/dist/generated-images.d.ts +139 -0
- package/dist/index.d.ts +958 -126
- package/dist/index.js +31204 -12172
- package/dist/index.js.map +1 -1
- package/dist/insights.d.ts +51 -4
- package/dist/integration-bindings.d.ts +70 -0
- package/dist/integration-features.d.ts +99 -0
- package/dist/interaction-revisions.d.ts +3 -0
- package/dist/interaction-schema.d.ts +5372 -0
- package/dist/knowledge-source-sync-schema.d.ts +1679 -0
- package/dist/knowledge-source-sync.d.ts +274 -0
- package/dist/lossless-columns.d.ts +58 -0
- package/dist/lossless-json.d.ts +39 -0
- package/dist/memory-domain.d.ts +3 -6
- package/dist/memory-governance.d.ts +2 -2
- package/dist/memory-slack-delivery.d.ts +101 -0
- package/dist/new-session-drafts.d.ts +2 -2
- package/dist/pack-components.d.ts +76 -0
- package/dist/pack-installations.d.ts +86 -0
- package/dist/persistence-errors.d.ts +7 -14
- package/dist/plugin-packages.d.ts +126 -0
- package/dist/preference-registry.d.ts +1 -1
- package/dist/provision-roles.d.ts +9 -1
- package/dist/provision-roles.js +1 -1
- package/dist/runtime-posture.d.ts +18 -6
- package/dist/schema.d.ts +19122 -8731
- package/dist/schema.js +173 -1
- package/dist/scoped-knowledge.d.ts +97 -1
- package/dist/session-control.d.ts +4 -4
- package/dist/session-queue-commands.d.ts +19 -11
- package/dist/session-realtime-context.d.ts +2 -2
- package/dist/session-realtime-ledger.d.ts +4 -4
- package/dist/session-realtime-mirror.d.ts +2 -2
- package/dist/session-realtime-state.d.ts +1 -1
- package/dist/session-realtime-terminal.d.ts +8 -1
- package/dist/session-realtime.d.ts +7 -7
- package/dist/session-tool-call-settlement.d.ts +1 -1
- package/dist/slack-user-link-access.d.ts +54 -0
- package/dist/transcription-recordings-schema.d.ts +44 -6
- package/dist/transcription-recordings.d.ts +3 -3
- package/dist/turn-initiator.d.ts +2 -2
- package/dist/video-generation.d.ts +216 -0
- package/dist/video-generation.js +66 -0
- package/dist/video-generation.js.map +1 -0
- package/dist/workspace-artifacts.d.ts +1 -1
- package/dist/workspace-instruction-policies.d.ts +1 -1
- package/dist/workspace-learning-policy-schema.d.ts +932 -0
- package/dist/workspace-learning-policy.d.ts +161 -0
- package/drizzle/0065_enrollment_credential_generation.sql +10 -0
- package/drizzle/0140_retained_screenshot_artifacts.sql +192 -0
- package/drizzle/0176_lossless_canonical_json.sql +975 -0
- package/drizzle/0177_session_events_workspace_turn_type_index.sql +5 -0
- package/drizzle/0178_permissioned_secret_reads.sql +14 -0
- package/drizzle/0179_slack_private_shortcut_delivery_gate.sql +85 -0
- package/drizzle/0180_retained_screenshot_lifecycle_fences.sql +273 -0
- package/drizzle/0181_connected_machine_removal.sql +52 -0
- package/drizzle/0182_connected_machine_remove_session_default.sql +77 -0
- package/drizzle/0183_model_call_provider_cost_estimates.sql +39 -0
- package/drizzle/0184_sandbox_drain_teardown_fence.sql +682 -0
- package/drizzle/0185_temporal_schedule_cleanup_outbox.sql +168 -0
- package/drizzle/0186_sandbox_capture_provider_contract.sql +400 -0
- package/drizzle/0187_generated_image_artifacts.sql +173 -0
- package/drizzle/0188_image_generation_retention_failure.sql +25 -0
- package/drizzle/0189_retained_session_image_formats.sql +13 -0
- package/drizzle/0190_timeline_annotations.sql +31 -0
- package/drizzle/0191_editable_artifact_engine.sql +3853 -0
- package/drizzle/0192_editable_artifact_live_tickets.sql +338 -0
- package/drizzle/0193_editable_artifact_authorization.sql +190 -0
- package/drizzle/0194_editable_artifact_durable_exports.sql +160 -0
- package/drizzle/0195_editable_artifact_import_authorization.sql +203 -0
- package/drizzle/0196_rig_provider_images.sql +152 -0
- package/drizzle/0197_knowledge_source_sync_schedules.sql +523 -0
- package/drizzle/0198_memory_slack_publication_delivery.sql +393 -0
- package/drizzle/0199_workspace_learning_policy.sql +980 -0
- package/drizzle/0201_company_profile_authority.sql +849 -0
- package/drizzle/0202_document_index_checkpoints.sql +85 -0
- package/drizzle/0203_durable_video_generation.sql +353 -0
- package/drizzle/0204_video_generation_funding.sql +44 -0
- package/drizzle/0205_attempt_tool_catalogs.sql +230 -0
- package/drizzle/0206_browser_sessions.sql +646 -0
- package/drizzle/0207_browser_identities.sql +379 -0
- package/drizzle/0208_attached_browser_devices.sql +165 -0
- package/drizzle/0209_computer_sessions.sql +575 -0
- package/drizzle/0210_browser_auth_network_interventions.sql +441 -0
- package/drizzle/0211_editable_artifact_session_links.sql +58 -0
- package/drizzle/0212_slack_installation_bindings.sql +348 -0
- package/drizzle/0213_slack_user_link_access_requests.sql +177 -0
- package/drizzle/0214_session_activity_commit_gate.sql +262 -0
- package/drizzle/0215_capabilities_platform.sql +980 -0
- package/drizzle/0216_pack_component_ownership.sql +235 -0
- package/drizzle/0217_capability_definition_delete_authority.sql +32 -0
- package/package.json +18 -5
- package/src/attached-browser-devices.ts +371 -0
- package/src/attempt-tool-catalogs.ts +131 -0
- package/src/browser-auth.ts +1896 -0
- package/src/browser-identities.ts +1168 -0
- package/src/browser-sessions.ts +2005 -0
- package/src/browser-state-artifacts.ts +59 -0
- package/src/capability-components.ts +166 -0
- package/src/capability-integrations.ts +1663 -0
- package/src/codemode-operations.ts +674 -0
- package/src/company-profile-schema.ts +143 -0
- package/src/company-profile.ts +968 -0
- package/src/computer-sessions.ts +1130 -0
- package/src/connection-token-resolver.ts +270 -25
- package/src/database.ts +434 -24
- package/src/editable-artifact-durable-export.ts +1201 -0
- package/src/editable-artifact-materialization.ts +893 -0
- package/src/editable-artifacts-schema.ts +879 -0
- package/src/editable-artifacts.ts +5404 -0
- package/src/environment-crypto.ts +22 -9
- package/src/generated-images.ts +740 -0
- package/src/index.ts +10117 -2730
- package/src/insights.ts +160 -13
- package/src/integration-bindings.ts +455 -0
- package/src/integration-features.ts +859 -0
- package/src/interaction-revisions.ts +40 -0
- package/src/interaction-schema.ts +1303 -0
- package/src/knowledge-source-sync-schema.ts +192 -0
- package/src/knowledge-source-sync.ts +1922 -0
- package/src/lossless-columns.ts +35 -0
- package/src/lossless-json.ts +322 -0
- package/src/memory-domain.ts +5 -44
- package/src/memory-slack-delivery.ts +801 -0
- package/src/pack-components.ts +954 -0
- package/src/pack-installations.ts +929 -0
- package/src/persistence-errors.ts +26 -34
- package/src/plugin-packages.ts +1186 -0
- package/src/provision-roles.ts +302 -1
- package/src/runtime-posture-cli.ts +9 -0
- package/src/runtime-posture.ts +514 -5
- package/src/schema.ts +5177 -2957
- package/src/scoped-knowledge-schema.ts +1 -1
- package/src/scoped-knowledge.ts +655 -3
- package/src/session-control.ts +133 -81
- package/src/session-queue-commands.ts +511 -247
- package/src/session-realtime-context.ts +20 -7
- package/src/session-realtime-ledger.ts +32 -10
- package/src/session-realtime-mirror.ts +4 -2
- package/src/session-realtime-terminal.ts +89 -21
- package/src/session-realtime.ts +10 -9
- package/src/session-tool-call-settlement.ts +38 -15
- package/src/slack-user-link-access.ts +702 -0
- package/src/transcription-recordings-schema.ts +5 -2
- package/src/transcription-recordings.ts +70 -40
- package/src/video-generation.ts +1452 -0
- package/src/workspace-learning-policy-schema.ts +139 -0
- package/src/workspace-learning-policy.ts +595 -0
- package/dist/chunk-DBS5HPEW.js.map +0 -1
- package/dist/chunk-HT5T62CC.js +0 -6803
- package/dist/chunk-HT5T62CC.js.map +0 -1
- package/dist/event-payload-sanitizer.d.ts +0 -32
- package/src/event-payload-sanitizer.ts +0 -377
|
@@ -0,0 +1,3853 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- Additive durable authority for collaborative editable artifacts. Canonical
|
|
3
|
+
-- model bytes remain in object storage; Postgres owns scoped identity,
|
|
4
|
+
-- idempotency, causality, delivery order, immutable history, and job/outbox
|
|
5
|
+
-- state. No existing table or behavior is changed by this migration.
|
|
6
|
+
|
|
7
|
+
SET LOCAL lock_timeout = '5s';
|
|
8
|
+
SET LOCAL statement_timeout = '10min';
|
|
9
|
+
|
|
10
|
+
CREATE SCHEMA IF NOT EXISTS opengeni_private;
|
|
11
|
+
|
|
12
|
+
CREATE OR REPLACE FUNCTION opengeni_private.editable_artifact_frontier_valid(value jsonb)
|
|
13
|
+
RETURNS boolean
|
|
14
|
+
LANGUAGE plpgsql
|
|
15
|
+
IMMUTABLE
|
|
16
|
+
SET search_path = pg_catalog, pg_temp
|
|
17
|
+
AS $body$
|
|
18
|
+
DECLARE entry jsonb;
|
|
19
|
+
DECLARE replica text;
|
|
20
|
+
DECLARE counter_text text;
|
|
21
|
+
DECLARE previous_replica text;
|
|
22
|
+
BEGIN
|
|
23
|
+
IF value IS NULL
|
|
24
|
+
OR jsonb_typeof(value) IS DISTINCT FROM 'array'
|
|
25
|
+
OR jsonb_array_length(value) > 65536
|
|
26
|
+
OR pg_column_size(value) > 1048576
|
|
27
|
+
OR octet_length(value::text) > 1048576
|
|
28
|
+
THEN
|
|
29
|
+
RETURN false;
|
|
30
|
+
END IF;
|
|
31
|
+
FOR entry IN SELECT item FROM jsonb_array_elements(value) AS items(item) LOOP
|
|
32
|
+
IF jsonb_typeof(entry) <> 'object'
|
|
33
|
+
OR (SELECT count(*) FROM jsonb_object_keys(entry)) <> 2
|
|
34
|
+
OR NOT (entry ? 'replicaId' AND entry ? 'counter')
|
|
35
|
+
OR jsonb_typeof(entry->'replicaId') <> 'string'
|
|
36
|
+
OR jsonb_typeof(entry->'counter') <> 'number'
|
|
37
|
+
THEN
|
|
38
|
+
RETURN false;
|
|
39
|
+
END IF;
|
|
40
|
+
replica := entry->>'replicaId';
|
|
41
|
+
counter_text := entry->>'counter';
|
|
42
|
+
IF replica !~ '^[0-9a-f]{16}$' OR replica ~ '^0+$'
|
|
43
|
+
OR counter_text !~ '^[1-9][0-9]{0,15}$'
|
|
44
|
+
OR counter_text::numeric > 9007199254740991
|
|
45
|
+
OR (previous_replica IS NOT NULL
|
|
46
|
+
AND convert_to(replica, 'UTF8') <= convert_to(previous_replica, 'UTF8'))
|
|
47
|
+
THEN
|
|
48
|
+
RETURN false;
|
|
49
|
+
END IF;
|
|
50
|
+
previous_replica := replica;
|
|
51
|
+
END LOOP;
|
|
52
|
+
RETURN true;
|
|
53
|
+
EXCEPTION WHEN others THEN
|
|
54
|
+
RETURN false;
|
|
55
|
+
END;
|
|
56
|
+
$body$;
|
|
57
|
+
|
|
58
|
+
CREATE OR REPLACE FUNCTION opengeni_private.editable_artifact_id_array_valid(value jsonb)
|
|
59
|
+
RETURNS boolean
|
|
60
|
+
LANGUAGE sql
|
|
61
|
+
IMMUTABLE
|
|
62
|
+
SET search_path = pg_catalog, pg_temp
|
|
63
|
+
AS $body$
|
|
64
|
+
SELECT CASE
|
|
65
|
+
WHEN value IS NULL OR jsonb_typeof(value) IS DISTINCT FROM 'array' THEN false
|
|
66
|
+
ELSE jsonb_array_length(value) <= 10000
|
|
67
|
+
AND pg_column_size(value) <= 1048576
|
|
68
|
+
AND octet_length(value::text) <= 1048576
|
|
69
|
+
AND NOT EXISTS (
|
|
70
|
+
SELECT 1
|
|
71
|
+
FROM jsonb_array_elements(value) AS entries(entry)
|
|
72
|
+
WHERE jsonb_typeof(entry) <> 'string'
|
|
73
|
+
OR entry #>> '{}' !~ '^[0-9a-f]{32}$'
|
|
74
|
+
OR entry #>> '{}' ~ '^0+$'
|
|
75
|
+
)
|
|
76
|
+
AND (
|
|
77
|
+
SELECT count(*) = count(DISTINCT entry #>> '{}')
|
|
78
|
+
FROM jsonb_array_elements(value) AS entries(entry)
|
|
79
|
+
)
|
|
80
|
+
AND NOT EXISTS (
|
|
81
|
+
SELECT 1
|
|
82
|
+
FROM (
|
|
83
|
+
SELECT entry #>> '{}' AS current_value,
|
|
84
|
+
lag(entry #>> '{}') OVER (ORDER BY ordinal) AS previous_value
|
|
85
|
+
FROM jsonb_array_elements(value) WITH ORDINALITY AS entries(entry, ordinal)
|
|
86
|
+
) ordered
|
|
87
|
+
WHERE previous_value IS NOT NULL
|
|
88
|
+
AND convert_to(previous_value, 'UTF8') >= convert_to(current_value, 'UTF8')
|
|
89
|
+
)
|
|
90
|
+
END;
|
|
91
|
+
$body$;
|
|
92
|
+
|
|
93
|
+
CREATE OR REPLACE FUNCTION opengeni_private.editable_artifact_frontier_dominates(
|
|
94
|
+
candidate jsonb,
|
|
95
|
+
required jsonb
|
|
96
|
+
) RETURNS boolean
|
|
97
|
+
LANGUAGE plpgsql
|
|
98
|
+
IMMUTABLE
|
|
99
|
+
SET search_path = pg_catalog, pg_temp
|
|
100
|
+
AS $body$
|
|
101
|
+
BEGIN
|
|
102
|
+
IF NOT opengeni_private.editable_artifact_frontier_valid(candidate)
|
|
103
|
+
OR NOT opengeni_private.editable_artifact_frontier_valid(required)
|
|
104
|
+
THEN
|
|
105
|
+
RETURN false;
|
|
106
|
+
END IF;
|
|
107
|
+
RETURN NOT EXISTS (
|
|
108
|
+
SELECT 1
|
|
109
|
+
FROM jsonb_array_elements(required) AS required_entries(entry)
|
|
110
|
+
LEFT JOIN jsonb_array_elements(candidate) AS candidate_entries(candidate_entry)
|
|
111
|
+
ON candidate_entry->>'replicaId' = entry->>'replicaId'
|
|
112
|
+
WHERE candidate_entry IS NULL
|
|
113
|
+
OR (candidate_entry->>'counter')::bigint < (entry->>'counter')::bigint
|
|
114
|
+
);
|
|
115
|
+
END;
|
|
116
|
+
$body$;
|
|
117
|
+
|
|
118
|
+
CREATE OR REPLACE FUNCTION opengeni_private.editable_artifact_frontier_merge_dot_equals(
|
|
119
|
+
result_frontier jsonb,
|
|
120
|
+
left_frontier jsonb,
|
|
121
|
+
right_frontier jsonb,
|
|
122
|
+
dot_replica_id text,
|
|
123
|
+
dot_counter bigint
|
|
124
|
+
) RETURNS boolean
|
|
125
|
+
LANGUAGE plpgsql
|
|
126
|
+
IMMUTABLE
|
|
127
|
+
SET search_path = pg_catalog, pg_temp
|
|
128
|
+
AS $body$
|
|
129
|
+
DECLARE matches boolean;
|
|
130
|
+
BEGIN
|
|
131
|
+
IF NOT opengeni_private.editable_artifact_frontier_valid(result_frontier)
|
|
132
|
+
OR NOT opengeni_private.editable_artifact_frontier_valid(left_frontier)
|
|
133
|
+
OR NOT opengeni_private.editable_artifact_frontier_valid(right_frontier)
|
|
134
|
+
OR dot_replica_id !~ '^[0-9a-f]{16}$'
|
|
135
|
+
OR dot_replica_id ~ '^0+$'
|
|
136
|
+
OR dot_counter NOT BETWEEN 0 AND 9007199254740991
|
|
137
|
+
THEN
|
|
138
|
+
RETURN false;
|
|
139
|
+
END IF;
|
|
140
|
+
WITH entries AS (
|
|
141
|
+
SELECT entry->>'replicaId' AS replica_id, (entry->>'counter')::bigint AS counter
|
|
142
|
+
FROM jsonb_array_elements(left_frontier) AS left_entries(entry)
|
|
143
|
+
UNION ALL
|
|
144
|
+
SELECT entry->>'replicaId', (entry->>'counter')::bigint
|
|
145
|
+
FROM jsonb_array_elements(right_frontier) AS right_entries(entry)
|
|
146
|
+
UNION ALL
|
|
147
|
+
SELECT dot_replica_id, dot_counter WHERE dot_counter > 0
|
|
148
|
+
), merged AS (
|
|
149
|
+
SELECT replica_id, max(counter) AS counter
|
|
150
|
+
FROM entries
|
|
151
|
+
GROUP BY replica_id
|
|
152
|
+
), canonical AS (
|
|
153
|
+
SELECT coalesce(
|
|
154
|
+
jsonb_agg(
|
|
155
|
+
jsonb_build_object('replicaId', replica_id, 'counter', counter)
|
|
156
|
+
ORDER BY convert_to(replica_id, 'UTF8')
|
|
157
|
+
),
|
|
158
|
+
'[]'::jsonb
|
|
159
|
+
) AS value
|
|
160
|
+
FROM merged
|
|
161
|
+
)
|
|
162
|
+
SELECT result_frontier = canonical.value INTO matches FROM canonical;
|
|
163
|
+
RETURN matches;
|
|
164
|
+
END;
|
|
165
|
+
$body$;
|
|
166
|
+
|
|
167
|
+
CREATE OR REPLACE FUNCTION opengeni_private.editable_artifact_object_has_exact_keys(
|
|
168
|
+
value jsonb,
|
|
169
|
+
expected text[]
|
|
170
|
+
) RETURNS boolean
|
|
171
|
+
LANGUAGE sql
|
|
172
|
+
IMMUTABLE
|
|
173
|
+
SET search_path = pg_catalog, pg_temp
|
|
174
|
+
AS $body$
|
|
175
|
+
SELECT jsonb_typeof(value) = 'object'
|
|
176
|
+
AND (SELECT array_agg(key ORDER BY convert_to(key, 'UTF8'))
|
|
177
|
+
FROM jsonb_object_keys(value) AS keys(key))
|
|
178
|
+
= (SELECT array_agg(key ORDER BY convert_to(key, 'UTF8'))
|
|
179
|
+
FROM unnest(expected) AS keys(key));
|
|
180
|
+
$body$;
|
|
181
|
+
|
|
182
|
+
-- Match JavaScript boundedIdentity for PostgreSQL-representable text: 1..256
|
|
183
|
+
-- UTF-16 code units, no ECMAScript trim character at either edge, and the
|
|
184
|
+
-- redundant 1024-byte UTF-8 safety envelope. PostgreSQL UTF-8 text already
|
|
185
|
+
-- excludes unpaired UTF-16 surrogates.
|
|
186
|
+
CREATE OR REPLACE FUNCTION opengeni_private.editable_artifact_identity_valid(value text)
|
|
187
|
+
RETURNS boolean
|
|
188
|
+
LANGUAGE plpgsql
|
|
189
|
+
IMMUTABLE
|
|
190
|
+
SET search_path = pg_catalog, pg_temp
|
|
191
|
+
AS $body$
|
|
192
|
+
DECLARE code_units integer;
|
|
193
|
+
BEGIN
|
|
194
|
+
IF value IS NULL OR value = '' OR octet_length(convert_to(value, 'UTF8')) > 1024
|
|
195
|
+
OR value <> btrim(
|
|
196
|
+
value,
|
|
197
|
+
U&'\0009\000A\000B\000C\000D\0020\00A0\1680\2000\2001\2002\2003\2004\2005\2006\2007\2008\2009\200A\2028\2029\202F\205F\3000\FEFF'
|
|
198
|
+
)
|
|
199
|
+
THEN
|
|
200
|
+
RETURN false;
|
|
201
|
+
END IF;
|
|
202
|
+
SELECT sum(CASE WHEN ascii(character) > 65535 THEN 2 ELSE 1 END)::integer
|
|
203
|
+
INTO code_units
|
|
204
|
+
FROM unnest(string_to_array(value, NULL)) AS characters(character);
|
|
205
|
+
RETURN code_units BETWEEN 1 AND 256;
|
|
206
|
+
EXCEPTION WHEN others THEN
|
|
207
|
+
RETURN false;
|
|
208
|
+
END;
|
|
209
|
+
$body$;
|
|
210
|
+
|
|
211
|
+
-- PostgreSQL marks convert_to(text, name) STABLE because the target encoding is
|
|
212
|
+
-- an argument. This fixed-UTF8 wrapper is genuinely immutable and therefore
|
|
213
|
+
-- safe in stored generated digest columns.
|
|
214
|
+
CREATE OR REPLACE FUNCTION opengeni_private.editable_artifact_text_sha256(value text)
|
|
215
|
+
RETURNS bytea
|
|
216
|
+
LANGUAGE sql
|
|
217
|
+
IMMUTABLE
|
|
218
|
+
STRICT
|
|
219
|
+
SET search_path = pg_catalog, pg_temp
|
|
220
|
+
AS $body$
|
|
221
|
+
SELECT pg_catalog.sha256(pg_catalog.convert_to(value, 'UTF8'));
|
|
222
|
+
$body$;
|
|
223
|
+
|
|
224
|
+
CREATE OR REPLACE FUNCTION opengeni_private.editable_artifact_actor_key_matches(
|
|
225
|
+
value text,
|
|
226
|
+
actor_kind text,
|
|
227
|
+
actor_subject_id text,
|
|
228
|
+
agent_session_id text,
|
|
229
|
+
agent_turn_id text,
|
|
230
|
+
agent_attempt_id text,
|
|
231
|
+
agent_generation integer,
|
|
232
|
+
service_name text
|
|
233
|
+
) RETURNS boolean
|
|
234
|
+
LANGUAGE plpgsql
|
|
235
|
+
IMMUTABLE
|
|
236
|
+
SET search_path = pg_catalog, pg_temp
|
|
237
|
+
AS $body$
|
|
238
|
+
DECLARE parsed jsonb;
|
|
239
|
+
BEGIN
|
|
240
|
+
IF octet_length(convert_to(value, 'UTF8')) NOT BETWEEN 1 AND 8192 THEN
|
|
241
|
+
RETURN false;
|
|
242
|
+
END IF;
|
|
243
|
+
IF NOT opengeni_private.editable_artifact_identity_valid(actor_subject_id) THEN
|
|
244
|
+
RETURN false;
|
|
245
|
+
END IF;
|
|
246
|
+
parsed := value::jsonb;
|
|
247
|
+
IF actor_kind = 'human' THEN
|
|
248
|
+
RETURN parsed = jsonb_build_array('human', actor_subject_id)
|
|
249
|
+
AND value = '[' || to_json('human'::text)::text || ','
|
|
250
|
+
|| to_json(actor_subject_id)::text || ']';
|
|
251
|
+
ELSIF actor_kind = 'agent' THEN
|
|
252
|
+
IF NOT opengeni_private.editable_artifact_identity_valid(agent_session_id)
|
|
253
|
+
OR NOT opengeni_private.editable_artifact_identity_valid(agent_turn_id)
|
|
254
|
+
OR NOT opengeni_private.editable_artifact_identity_valid(agent_attempt_id)
|
|
255
|
+
THEN
|
|
256
|
+
RETURN false;
|
|
257
|
+
END IF;
|
|
258
|
+
RETURN parsed = jsonb_build_array(
|
|
259
|
+
'agent', actor_subject_id, agent_session_id, agent_turn_id,
|
|
260
|
+
agent_attempt_id, agent_generation
|
|
261
|
+
) AND value = '[' || to_json('agent'::text)::text || ','
|
|
262
|
+
|| to_json(actor_subject_id)::text || ',' || to_json(agent_session_id)::text
|
|
263
|
+
|| ',' || to_json(agent_turn_id)::text || ',' || to_json(agent_attempt_id)::text
|
|
264
|
+
|| ',' || agent_generation::text || ']';
|
|
265
|
+
ELSIF actor_kind = 'service' THEN
|
|
266
|
+
IF NOT opengeni_private.editable_artifact_identity_valid(service_name) THEN
|
|
267
|
+
RETURN false;
|
|
268
|
+
END IF;
|
|
269
|
+
RETURN parsed = jsonb_build_array('service', actor_subject_id, service_name)
|
|
270
|
+
AND value = '[' || to_json('service'::text)::text || ','
|
|
271
|
+
|| to_json(actor_subject_id)::text || ',' || to_json(service_name)::text || ']';
|
|
272
|
+
END IF;
|
|
273
|
+
RETURN false;
|
|
274
|
+
EXCEPTION WHEN others THEN
|
|
275
|
+
RETURN false;
|
|
276
|
+
END;
|
|
277
|
+
$body$;
|
|
278
|
+
|
|
279
|
+
CREATE OR REPLACE FUNCTION opengeni_private.reject_editable_artifact_history_mutation()
|
|
280
|
+
RETURNS trigger
|
|
281
|
+
LANGUAGE plpgsql
|
|
282
|
+
SET search_path = pg_catalog, pg_temp
|
|
283
|
+
AS $body$
|
|
284
|
+
BEGIN
|
|
285
|
+
RAISE EXCEPTION '% is immutable; use an explicit maintenance migration', TG_TABLE_NAME
|
|
286
|
+
USING ERRCODE = '55000';
|
|
287
|
+
END;
|
|
288
|
+
$body$;
|
|
289
|
+
|
|
290
|
+
CREATE TABLE "editable_artifact_scope_authorization_heads" (
|
|
291
|
+
"account_id" uuid NOT NULL,
|
|
292
|
+
"workspace_id" uuid NOT NULL,
|
|
293
|
+
"create_revision" bigint NOT NULL DEFAULT 1,
|
|
294
|
+
"updated_at" timestamptz NOT NULL DEFAULT now(),
|
|
295
|
+
CONSTRAINT "editable_artifact_scope_authorization_heads_pk"
|
|
296
|
+
PRIMARY KEY ("account_id", "workspace_id"),
|
|
297
|
+
CONSTRAINT "editable_artifact_scope_authorization_heads_workspace_fk"
|
|
298
|
+
FOREIGN KEY ("workspace_id", "account_id")
|
|
299
|
+
REFERENCES "workspaces"("id", "account_id") ON DELETE CASCADE,
|
|
300
|
+
CONSTRAINT "editable_artifact_scope_authorization_heads_revision_chk" CHECK (
|
|
301
|
+
"create_revision" BETWEEN 1 AND 9007199254740991
|
|
302
|
+
)
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
CREATE TABLE "editable_artifacts" (
|
|
306
|
+
"account_id" uuid NOT NULL,
|
|
307
|
+
"workspace_id" uuid NOT NULL,
|
|
308
|
+
"id" text NOT NULL,
|
|
309
|
+
"modality" text NOT NULL,
|
|
310
|
+
"title" text NOT NULL,
|
|
311
|
+
"lifecycle_state" text NOT NULL DEFAULT 'active',
|
|
312
|
+
"authorization_revision" bigint NOT NULL,
|
|
313
|
+
"head_sequence" bigint NOT NULL DEFAULT 0,
|
|
314
|
+
"causal_frontier" jsonb,
|
|
315
|
+
"state_hash" text NOT NULL,
|
|
316
|
+
"current_snapshot_id" text,
|
|
317
|
+
"created_by_subject_id" text NOT NULL,
|
|
318
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
319
|
+
"updated_at" timestamptz NOT NULL DEFAULT now(),
|
|
320
|
+
CONSTRAINT "editable_artifacts_pk" PRIMARY KEY ("account_id", "workspace_id", "id"),
|
|
321
|
+
CONSTRAINT "editable_artifacts_workspace_fk"
|
|
322
|
+
FOREIGN KEY ("workspace_id", "account_id")
|
|
323
|
+
REFERENCES "workspaces"("id", "account_id") ON DELETE RESTRICT,
|
|
324
|
+
CONSTRAINT "editable_artifacts_identity_chk" CHECK (
|
|
325
|
+
"id" ~ '^[0-9a-f]{32}$' AND "id" !~ '^0+$'
|
|
326
|
+
),
|
|
327
|
+
CONSTRAINT "editable_artifacts_modality_chk" CHECK (
|
|
328
|
+
"modality" IN ('spreadsheet', 'presentation', 'document')
|
|
329
|
+
),
|
|
330
|
+
CONSTRAINT "editable_artifacts_title_chk" CHECK (
|
|
331
|
+
octet_length(convert_to("title", 'UTF8')) BETWEEN 1 AND 512
|
|
332
|
+
AND "title" = btrim("title")
|
|
333
|
+
),
|
|
334
|
+
CONSTRAINT "editable_artifacts_lifecycle_chk" CHECK (
|
|
335
|
+
"lifecycle_state" IN ('active', 'archived')
|
|
336
|
+
),
|
|
337
|
+
CONSTRAINT "editable_artifacts_authorization_revision_chk" CHECK (
|
|
338
|
+
"authorization_revision" BETWEEN 1 AND 9007199254740991
|
|
339
|
+
),
|
|
340
|
+
CONSTRAINT "editable_artifacts_head_chk" CHECK (
|
|
341
|
+
"head_sequence" BETWEEN 0 AND 9007199254740991
|
|
342
|
+
),
|
|
343
|
+
CONSTRAINT "editable_artifacts_frontier_chk" CHECK (
|
|
344
|
+
("modality" = 'spreadsheet'
|
|
345
|
+
AND opengeni_private.editable_artifact_frontier_valid("causal_frontier"))
|
|
346
|
+
OR ("modality" IN ('document', 'presentation') AND "causal_frontier" IS NULL)
|
|
347
|
+
),
|
|
348
|
+
CONSTRAINT "editable_artifacts_hash_chk" CHECK (
|
|
349
|
+
"state_hash" ~ '^sha256:[0-9a-f]{64}$'
|
|
350
|
+
),
|
|
351
|
+
CONSTRAINT "editable_artifacts_snapshot_id_chk" CHECK (
|
|
352
|
+
"current_snapshot_id" IS NULL OR (
|
|
353
|
+
"current_snapshot_id" ~ '^[0-9a-f]{32}$'
|
|
354
|
+
AND "current_snapshot_id" !~ '^0+$'
|
|
355
|
+
)
|
|
356
|
+
),
|
|
357
|
+
CONSTRAINT "editable_artifacts_actor_chk" CHECK (
|
|
358
|
+
opengeni_private.editable_artifact_identity_valid("created_by_subject_id")
|
|
359
|
+
)
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
CREATE INDEX "editable_artifacts_workspace_timeline_idx"
|
|
363
|
+
ON "editable_artifacts" ("workspace_id", "lifecycle_state", "updated_at" DESC, "id");
|
|
364
|
+
|
|
365
|
+
CREATE TABLE "editable_artifact_transactions" (
|
|
366
|
+
"account_id" uuid NOT NULL,
|
|
367
|
+
"workspace_id" uuid NOT NULL,
|
|
368
|
+
"artifact_id" text NOT NULL,
|
|
369
|
+
"modality" text NOT NULL,
|
|
370
|
+
"id" text NOT NULL,
|
|
371
|
+
"client_transaction_id" text NOT NULL,
|
|
372
|
+
"previous_local_transaction_id" text,
|
|
373
|
+
"request_hash" text NOT NULL,
|
|
374
|
+
"intent_hash" text NOT NULL,
|
|
375
|
+
"intent_envelope_version" integer NOT NULL,
|
|
376
|
+
"intent_protocol_version" integer NOT NULL,
|
|
377
|
+
"command_protocol_version" integer NOT NULL,
|
|
378
|
+
"intent_byte_size" integer NOT NULL,
|
|
379
|
+
"intent_bytes" bytea NOT NULL,
|
|
380
|
+
"parent_head_sequence" bigint NOT NULL,
|
|
381
|
+
"sequence_start" bigint NOT NULL,
|
|
382
|
+
"sequence_end" bigint NOT NULL,
|
|
383
|
+
"prior_state_hash" text NOT NULL,
|
|
384
|
+
"causal_base" jsonb,
|
|
385
|
+
"resolved_causal_base" jsonb,
|
|
386
|
+
"resulting_causal_frontier" jsonb,
|
|
387
|
+
"selective_undo_targets" jsonb,
|
|
388
|
+
"state_hash" text NOT NULL,
|
|
389
|
+
"operation_count" integer,
|
|
390
|
+
"operation_ids" jsonb,
|
|
391
|
+
"actor_kind" text NOT NULL,
|
|
392
|
+
"actor_subject_id" text NOT NULL,
|
|
393
|
+
"actor_key" text NOT NULL,
|
|
394
|
+
"actor_key_digest" bytea GENERATED ALWAYS AS (
|
|
395
|
+
opengeni_private.editable_artifact_text_sha256("actor_key")
|
|
396
|
+
) STORED,
|
|
397
|
+
"replica_id" text NOT NULL,
|
|
398
|
+
"replica_counter" bigint NOT NULL,
|
|
399
|
+
"agent_session_id" text,
|
|
400
|
+
"agent_turn_id" text,
|
|
401
|
+
"agent_attempt_id" text,
|
|
402
|
+
"agent_generation" integer,
|
|
403
|
+
"service_name" text,
|
|
404
|
+
"kernel_version" text NOT NULL,
|
|
405
|
+
"model_schema_version" integer NOT NULL,
|
|
406
|
+
"operation_protocol_version" integer,
|
|
407
|
+
"commit_protocol_version" integer,
|
|
408
|
+
"prior_native_revision" bigint,
|
|
409
|
+
"native_revision" bigint,
|
|
410
|
+
"command_count" integer,
|
|
411
|
+
"native_receipt_byte_size" integer,
|
|
412
|
+
"native_receipt_hash" text,
|
|
413
|
+
"native_receipt_bytes" bytea,
|
|
414
|
+
"committed_transaction_byte_size" integer NOT NULL,
|
|
415
|
+
"committed_transaction_hash" text NOT NULL,
|
|
416
|
+
"committed_transaction_bytes" bytea NOT NULL,
|
|
417
|
+
"committed_at" timestamptz NOT NULL DEFAULT now(),
|
|
418
|
+
CONSTRAINT "editable_artifact_transactions_pk"
|
|
419
|
+
PRIMARY KEY ("account_id", "workspace_id", "artifact_id", "id"),
|
|
420
|
+
CONSTRAINT "editable_artifact_transactions_artifact_fk"
|
|
421
|
+
FOREIGN KEY ("account_id", "workspace_id", "artifact_id")
|
|
422
|
+
REFERENCES "editable_artifacts"("account_id", "workspace_id", "id") ON DELETE RESTRICT,
|
|
423
|
+
CONSTRAINT "editable_artifact_transactions_id_chk" CHECK (
|
|
424
|
+
"id" ~ '^[0-9a-f]{32}$' AND "id" !~ '^0+$'
|
|
425
|
+
),
|
|
426
|
+
CONSTRAINT "editable_artifact_transactions_modality_chk" CHECK (
|
|
427
|
+
"modality" IN ('spreadsheet', 'presentation', 'document')
|
|
428
|
+
),
|
|
429
|
+
CONSTRAINT "editable_artifact_transactions_client_id_chk" CHECK (
|
|
430
|
+
octet_length("client_transaction_id") BETWEEN 1 AND 200
|
|
431
|
+
AND "client_transaction_id" ~ '^[A-Za-z0-9._:-]+$'
|
|
432
|
+
AND ("previous_local_transaction_id" IS NULL OR (
|
|
433
|
+
octet_length("previous_local_transaction_id") BETWEEN 1 AND 200
|
|
434
|
+
AND "previous_local_transaction_id" ~ '^[A-Za-z0-9._:-]+$'
|
|
435
|
+
AND "previous_local_transaction_id" <> "client_transaction_id"
|
|
436
|
+
))
|
|
437
|
+
),
|
|
438
|
+
CONSTRAINT "editable_artifact_transactions_intent_chk" CHECK (
|
|
439
|
+
"request_hash" ~ '^sha256:[0-9a-f]{64}$'
|
|
440
|
+
AND "intent_hash" = "request_hash"
|
|
441
|
+
AND "intent_hash" = 'sha256:' || encode(sha256("intent_bytes"), 'hex')
|
|
442
|
+
AND "intent_envelope_version" = 1
|
|
443
|
+
AND "intent_protocol_version" = 1
|
|
444
|
+
AND "command_protocol_version" > 0
|
|
445
|
+
AND "intent_byte_size" BETWEEN 8 AND 5242880
|
|
446
|
+
AND octet_length("intent_bytes") = "intent_byte_size"
|
|
447
|
+
AND substring("intent_bytes" FROM 1 FOR 8) = convert_to('OGATX001', 'UTF8')
|
|
448
|
+
),
|
|
449
|
+
CONSTRAINT "editable_artifact_transactions_sequence_chk" CHECK (
|
|
450
|
+
"parent_head_sequence" BETWEEN 0 AND 9007199254740991
|
|
451
|
+
AND "sequence_start" = "parent_head_sequence" + 1
|
|
452
|
+
AND "sequence_end" <= 9007199254740991
|
|
453
|
+
AND (("modality" = 'spreadsheet'
|
|
454
|
+
AND "operation_count" BETWEEN 1 AND 4096
|
|
455
|
+
AND "sequence_end" = "parent_head_sequence" + "operation_count")
|
|
456
|
+
OR ("modality" IN ('document', 'presentation')
|
|
457
|
+
AND "operation_count" IS NULL
|
|
458
|
+
AND "sequence_end" = "sequence_start"))
|
|
459
|
+
),
|
|
460
|
+
CONSTRAINT "editable_artifact_transactions_frontiers_chk" CHECK (
|
|
461
|
+
("modality" = 'spreadsheet'
|
|
462
|
+
AND opengeni_private.editable_artifact_frontier_valid("causal_base")
|
|
463
|
+
AND opengeni_private.editable_artifact_frontier_valid("resolved_causal_base")
|
|
464
|
+
AND opengeni_private.editable_artifact_frontier_valid("resulting_causal_frontier")
|
|
465
|
+
AND opengeni_private.editable_artifact_id_array_valid("operation_ids")
|
|
466
|
+
AND jsonb_array_length("operation_ids") = "operation_count"
|
|
467
|
+
AND opengeni_private.editable_artifact_id_array_valid("selective_undo_targets"))
|
|
468
|
+
OR ("modality" IN ('document', 'presentation')
|
|
469
|
+
AND "causal_base" IS NULL AND "resolved_causal_base" IS NULL
|
|
470
|
+
AND "resulting_causal_frontier" IS NULL AND "operation_ids" IS NULL
|
|
471
|
+
AND "selective_undo_targets" IS NULL)
|
|
472
|
+
),
|
|
473
|
+
CONSTRAINT "editable_artifact_transactions_result_chk" CHECK (
|
|
474
|
+
"prior_state_hash" ~ '^sha256:[0-9a-f]{64}$'
|
|
475
|
+
AND "state_hash" ~ '^sha256:[0-9a-f]{64}$'
|
|
476
|
+
AND octet_length("kernel_version") BETWEEN 1 AND 512
|
|
477
|
+
AND "model_schema_version" > 0
|
|
478
|
+
AND "committed_transaction_byte_size" BETWEEN 1 AND 8388608
|
|
479
|
+
AND octet_length("committed_transaction_bytes") = "committed_transaction_byte_size"
|
|
480
|
+
AND "committed_transaction_hash" ~ '^sha256:[0-9a-f]{64}$'
|
|
481
|
+
AND "committed_transaction_hash" =
|
|
482
|
+
'sha256:' || encode(sha256("committed_transaction_bytes"), 'hex')
|
|
483
|
+
AND (("modality" = 'spreadsheet'
|
|
484
|
+
AND "operation_protocol_version" = 1
|
|
485
|
+
AND "commit_protocol_version" IS NULL
|
|
486
|
+
AND "prior_native_revision" IS NULL AND "native_revision" IS NULL
|
|
487
|
+
AND "command_count" IS NULL AND "native_receipt_byte_size" IS NULL
|
|
488
|
+
AND "native_receipt_hash" IS NULL AND "native_receipt_bytes" IS NULL
|
|
489
|
+
AND substring("committed_transaction_bytes" FROM 1 FOR 8) = convert_to('OGACO001', 'UTF8'))
|
|
490
|
+
OR ("modality" IN ('document', 'presentation')
|
|
491
|
+
AND "operation_protocol_version" IS NULL
|
|
492
|
+
AND "commit_protocol_version" = 1
|
|
493
|
+
AND "prior_native_revision" BETWEEN 0 AND 9007199254740991
|
|
494
|
+
AND (("modality" = 'document' AND (
|
|
495
|
+
("native_revision" = "prior_native_revision" AND "state_hash" = "prior_state_hash")
|
|
496
|
+
OR ("native_revision" = "prior_native_revision" + 1
|
|
497
|
+
AND "state_hash" <> "prior_state_hash")
|
|
498
|
+
)) OR ("modality" = 'presentation'
|
|
499
|
+
AND "native_revision" = "prior_native_revision" + 1
|
|
500
|
+
AND "state_hash" <> "prior_state_hash"))
|
|
501
|
+
AND "command_count" BETWEEN 1 AND 4096
|
|
502
|
+
AND "native_receipt_byte_size" BETWEEN 1 AND 524288
|
|
503
|
+
AND octet_length("native_receipt_bytes") = "native_receipt_byte_size"
|
|
504
|
+
AND "native_receipt_hash" ~ '^sha256:[0-9a-f]{64}$'
|
|
505
|
+
AND "native_receipt_hash" = 'sha256:' || encode(sha256("native_receipt_bytes"), 'hex')
|
|
506
|
+
AND substring("committed_transaction_bytes" FROM 1 FOR 8) = convert_to('OGAST001', 'UTF8')
|
|
507
|
+
AND get_byte("committed_transaction_bytes", 12) =
|
|
508
|
+
CASE "modality" WHEN 'document' THEN 1 ELSE 2 END
|
|
509
|
+
AND get_byte("committed_transaction_bytes", 13) = 0
|
|
510
|
+
AND get_byte("committed_transaction_bytes", 14) = 0
|
|
511
|
+
AND get_byte("committed_transaction_bytes", 15) = 0
|
|
512
|
+
AND substring("native_receipt_bytes" FROM 1 FOR 8) = convert_to(
|
|
513
|
+
CASE "modality" WHEN 'document' THEN 'OGADR001' ELSE 'OGAPR001' END,
|
|
514
|
+
'UTF8'
|
|
515
|
+
)))
|
|
516
|
+
),
|
|
517
|
+
CONSTRAINT "editable_artifact_transactions_replica_chk" CHECK (
|
|
518
|
+
"replica_id" ~ '^[0-9a-f]{16}$' AND "replica_id" !~ '^0+$'
|
|
519
|
+
AND "replica_counter" BETWEEN 1 AND 9007199254740991
|
|
520
|
+
AND (("replica_counter" = 1 AND "previous_local_transaction_id" IS NULL)
|
|
521
|
+
OR ("replica_counter" > 1 AND "previous_local_transaction_id" IS NOT NULL))
|
|
522
|
+
),
|
|
523
|
+
CONSTRAINT "editable_artifact_transactions_actor_chk" CHECK (
|
|
524
|
+
opengeni_private.editable_artifact_identity_valid("actor_subject_id")
|
|
525
|
+
AND (
|
|
526
|
+
("actor_kind" = 'human'
|
|
527
|
+
AND "agent_session_id" IS NULL AND "agent_turn_id" IS NULL
|
|
528
|
+
AND "agent_attempt_id" IS NULL AND "agent_generation" IS NULL
|
|
529
|
+
AND "service_name" IS NULL)
|
|
530
|
+
OR
|
|
531
|
+
("actor_kind" = 'agent'
|
|
532
|
+
AND "agent_session_id" IS NOT NULL
|
|
533
|
+
AND "agent_turn_id" IS NOT NULL
|
|
534
|
+
AND "agent_attempt_id" IS NOT NULL
|
|
535
|
+
AND "agent_generation" IS NOT NULL
|
|
536
|
+
AND opengeni_private.editable_artifact_identity_valid("agent_session_id")
|
|
537
|
+
AND opengeni_private.editable_artifact_identity_valid("agent_turn_id")
|
|
538
|
+
AND opengeni_private.editable_artifact_identity_valid("agent_attempt_id")
|
|
539
|
+
AND "agent_generation" BETWEEN 0 AND 2147483647
|
|
540
|
+
AND "service_name" IS NULL)
|
|
541
|
+
OR
|
|
542
|
+
("actor_kind" = 'service'
|
|
543
|
+
AND "agent_session_id" IS NULL AND "agent_turn_id" IS NULL
|
|
544
|
+
AND "agent_attempt_id" IS NULL AND "agent_generation" IS NULL
|
|
545
|
+
AND "service_name" IS NOT NULL
|
|
546
|
+
AND opengeni_private.editable_artifact_identity_valid("service_name"))
|
|
547
|
+
)
|
|
548
|
+
AND opengeni_private.editable_artifact_actor_key_matches(
|
|
549
|
+
"actor_key", "actor_kind", "actor_subject_id", "agent_session_id",
|
|
550
|
+
"agent_turn_id", "agent_attempt_id", "agent_generation", "service_name"
|
|
551
|
+
) IS TRUE
|
|
552
|
+
),
|
|
553
|
+
CONSTRAINT "editable_artifact_transactions_actor_client_uq"
|
|
554
|
+
UNIQUE (
|
|
555
|
+
"account_id", "workspace_id", "artifact_id", "actor_key_digest",
|
|
556
|
+
"client_transaction_id"
|
|
557
|
+
),
|
|
558
|
+
CONSTRAINT "editable_artifact_transactions_replica_counter_uq"
|
|
559
|
+
UNIQUE ("account_id", "workspace_id", "artifact_id", "replica_id", "replica_counter"),
|
|
560
|
+
CONSTRAINT "editable_artifact_transactions_predecessor_identity_uq"
|
|
561
|
+
UNIQUE (
|
|
562
|
+
"account_id", "workspace_id", "artifact_id", "actor_key_digest", "replica_id",
|
|
563
|
+
"client_transaction_id"
|
|
564
|
+
),
|
|
565
|
+
CONSTRAINT "editable_artifact_transactions_exact_receipt_authority_uq"
|
|
566
|
+
UNIQUE (
|
|
567
|
+
"account_id", "workspace_id", "artifact_id", "id", "actor_key_digest",
|
|
568
|
+
"client_transaction_id", "request_hash"
|
|
569
|
+
),
|
|
570
|
+
CONSTRAINT "editable_artifact_transactions_sequence_uq"
|
|
571
|
+
UNIQUE ("account_id", "workspace_id", "artifact_id", "sequence_start", "sequence_end")
|
|
572
|
+
);
|
|
573
|
+
|
|
574
|
+
ALTER TABLE "editable_artifact_transactions"
|
|
575
|
+
ADD CONSTRAINT "editable_artifact_transactions_predecessor_fk"
|
|
576
|
+
FOREIGN KEY (
|
|
577
|
+
"account_id", "workspace_id", "artifact_id", "actor_key_digest", "replica_id",
|
|
578
|
+
"previous_local_transaction_id"
|
|
579
|
+
) REFERENCES "editable_artifact_transactions" (
|
|
580
|
+
"account_id", "workspace_id", "artifact_id", "actor_key_digest", "replica_id",
|
|
581
|
+
"client_transaction_id"
|
|
582
|
+
) ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED;
|
|
583
|
+
|
|
584
|
+
CREATE INDEX "editable_artifact_transactions_replay_idx"
|
|
585
|
+
ON "editable_artifact_transactions" ("workspace_id", "artifact_id", "sequence_start");
|
|
586
|
+
|
|
587
|
+
CREATE TABLE "editable_artifact_operations" (
|
|
588
|
+
"account_id" uuid NOT NULL,
|
|
589
|
+
"workspace_id" uuid NOT NULL,
|
|
590
|
+
"artifact_id" text NOT NULL,
|
|
591
|
+
"transaction_id" text NOT NULL,
|
|
592
|
+
"operation_id" text NOT NULL,
|
|
593
|
+
"operation_index" integer NOT NULL,
|
|
594
|
+
"sequence" bigint NOT NULL,
|
|
595
|
+
"dot_replica_id" text NOT NULL,
|
|
596
|
+
"dot_counter" bigint NOT NULL,
|
|
597
|
+
"actor_key" text NOT NULL,
|
|
598
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
599
|
+
CONSTRAINT "editable_artifact_operations_pk" PRIMARY KEY (
|
|
600
|
+
"account_id", "workspace_id", "artifact_id", "transaction_id",
|
|
601
|
+
"operation_index"
|
|
602
|
+
),
|
|
603
|
+
CONSTRAINT "editable_artifact_operations_transaction_fk"
|
|
604
|
+
FOREIGN KEY ("account_id", "workspace_id", "artifact_id", "transaction_id")
|
|
605
|
+
REFERENCES "editable_artifact_transactions"(
|
|
606
|
+
"account_id", "workspace_id", "artifact_id", "id"
|
|
607
|
+
) ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED,
|
|
608
|
+
CONSTRAINT "editable_artifact_operations_ids_chk" CHECK (
|
|
609
|
+
"transaction_id" ~ '^[0-9a-f]{32}$' AND "transaction_id" !~ '^0+$'
|
|
610
|
+
AND "operation_id" ~ '^[0-9a-f]{32}$' AND "operation_id" !~ '^0+$'
|
|
611
|
+
),
|
|
612
|
+
CONSTRAINT "editable_artifact_operations_position_chk" CHECK (
|
|
613
|
+
"operation_index" BETWEEN 0 AND 4095
|
|
614
|
+
AND "sequence" BETWEEN 1 AND 9007199254740991
|
|
615
|
+
),
|
|
616
|
+
CONSTRAINT "editable_artifact_operations_dot_chk" CHECK (
|
|
617
|
+
"dot_replica_id" ~ '^[0-9a-f]{16}$' AND "dot_replica_id" !~ '^0+$'
|
|
618
|
+
AND "dot_counter" BETWEEN 1 AND 9007199254740991
|
|
619
|
+
AND octet_length(convert_to("actor_key", 'UTF8')) BETWEEN 1 AND 8192
|
|
620
|
+
)
|
|
621
|
+
);
|
|
622
|
+
|
|
623
|
+
CREATE UNIQUE INDEX "editable_artifact_operations_sequence_uq"
|
|
624
|
+
ON "editable_artifact_operations"
|
|
625
|
+
("account_id", "workspace_id", "artifact_id", "sequence");
|
|
626
|
+
CREATE UNIQUE INDEX "editable_artifact_operations_operation_uq"
|
|
627
|
+
ON "editable_artifact_operations"
|
|
628
|
+
("account_id", "workspace_id", "artifact_id", "operation_id");
|
|
629
|
+
CREATE UNIQUE INDEX "editable_artifact_operations_dot_operation_uq"
|
|
630
|
+
ON "editable_artifact_operations"
|
|
631
|
+
("account_id", "workspace_id", "artifact_id", "dot_replica_id", "dot_counter", "operation_index");
|
|
632
|
+
CREATE INDEX "editable_artifact_operations_replay_idx"
|
|
633
|
+
ON "editable_artifact_operations" ("workspace_id", "artifact_id", "sequence");
|
|
634
|
+
|
|
635
|
+
CREATE TABLE "editable_artifact_idempotency_receipts" (
|
|
636
|
+
"account_id" uuid NOT NULL,
|
|
637
|
+
"workspace_id" uuid NOT NULL,
|
|
638
|
+
"id" text NOT NULL,
|
|
639
|
+
"artifact_id" text NOT NULL,
|
|
640
|
+
"operation_kind" text NOT NULL,
|
|
641
|
+
"authority_key" text NOT NULL,
|
|
642
|
+
"authority_key_digest" bytea GENERATED ALWAYS AS (
|
|
643
|
+
opengeni_private.editable_artifact_text_sha256("authority_key")
|
|
644
|
+
) STORED,
|
|
645
|
+
"idempotency_key" text NOT NULL,
|
|
646
|
+
"request_hash" text NOT NULL,
|
|
647
|
+
"resource_type" text NOT NULL,
|
|
648
|
+
"resource_id" text NOT NULL,
|
|
649
|
+
"server_transaction_id" text,
|
|
650
|
+
"result" jsonb NOT NULL,
|
|
651
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
652
|
+
CONSTRAINT "editable_artifact_idempotency_receipts_pk"
|
|
653
|
+
PRIMARY KEY ("account_id", "workspace_id", "id"),
|
|
654
|
+
CONSTRAINT "editable_artifact_idempotency_receipts_artifact_fk"
|
|
655
|
+
FOREIGN KEY ("account_id", "workspace_id", "artifact_id")
|
|
656
|
+
REFERENCES "editable_artifacts"("account_id", "workspace_id", "id") ON DELETE RESTRICT,
|
|
657
|
+
CONSTRAINT "editable_artifact_idempotency_receipts_transaction_fk"
|
|
658
|
+
FOREIGN KEY ("account_id", "workspace_id", "artifact_id", "server_transaction_id")
|
|
659
|
+
REFERENCES "editable_artifact_transactions"(
|
|
660
|
+
"account_id", "workspace_id", "artifact_id", "id"
|
|
661
|
+
) ON DELETE RESTRICT,
|
|
662
|
+
CONSTRAINT "editable_artifact_idempotency_receipts_exact_transaction_fk"
|
|
663
|
+
FOREIGN KEY (
|
|
664
|
+
"account_id", "workspace_id", "artifact_id", "server_transaction_id",
|
|
665
|
+
"authority_key_digest", "idempotency_key", "request_hash"
|
|
666
|
+
) REFERENCES "editable_artifact_transactions"(
|
|
667
|
+
"account_id", "workspace_id", "artifact_id", "id", "actor_key_digest",
|
|
668
|
+
"client_transaction_id", "request_hash"
|
|
669
|
+
) ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED,
|
|
670
|
+
CONSTRAINT "editable_artifact_idempotency_receipts_identity_chk" CHECK (
|
|
671
|
+
"id" ~ '^[0-9a-f]{32}$' AND "id" !~ '^0+$'
|
|
672
|
+
AND "artifact_id" ~ '^[0-9a-f]{32}$' AND "artifact_id" !~ '^0+$'
|
|
673
|
+
AND "resource_id" ~ '^[0-9a-f]{32}$' AND "resource_id" !~ '^0+$'
|
|
674
|
+
AND "request_hash" ~ '^sha256:[0-9a-f]{64}$'
|
|
675
|
+
),
|
|
676
|
+
CONSTRAINT "editable_artifact_idempotency_receipts_key_chk" CHECK (
|
|
677
|
+
"operation_kind" IN ('create', 'import', 'edit', 'snapshot', 'materialize')
|
|
678
|
+
AND octet_length(convert_to("authority_key", 'UTF8')) BETWEEN 1 AND 8192
|
|
679
|
+
AND octet_length("idempotency_key") BETWEEN 1 AND 256
|
|
680
|
+
AND "idempotency_key" = btrim("idempotency_key")
|
|
681
|
+
),
|
|
682
|
+
CONSTRAINT "editable_artifact_idempotency_receipts_resource_chk" CHECK (
|
|
683
|
+
"resource_type" IN ('artifact', 'transaction', 'snapshot', 'materialization_job')
|
|
684
|
+
AND (
|
|
685
|
+
("operation_kind" = 'create' AND "resource_type" = 'artifact'
|
|
686
|
+
AND "resource_id" = "artifact_id" AND "server_transaction_id" IS NULL)
|
|
687
|
+
OR ("operation_kind" = 'edit' AND "resource_type" = 'transaction'
|
|
688
|
+
AND "resource_id" = "server_transaction_id")
|
|
689
|
+
OR ("operation_kind" = 'snapshot' AND "resource_type" = 'snapshot'
|
|
690
|
+
AND "server_transaction_id" IS NULL)
|
|
691
|
+
OR ("operation_kind" = 'materialize' AND "resource_type" = 'materialization_job'
|
|
692
|
+
AND "server_transaction_id" IS NULL)
|
|
693
|
+
OR ("operation_kind" = 'import' AND "resource_type" = 'artifact'
|
|
694
|
+
AND "resource_id" = "artifact_id" AND "server_transaction_id" IS NULL)
|
|
695
|
+
)
|
|
696
|
+
),
|
|
697
|
+
CONSTRAINT "editable_artifact_idempotency_receipts_result_chk" CHECK (
|
|
698
|
+
jsonb_typeof("result") = 'object'
|
|
699
|
+
AND octet_length("result"::text) <= 65536
|
|
700
|
+
)
|
|
701
|
+
);
|
|
702
|
+
|
|
703
|
+
CREATE UNIQUE INDEX "editable_artifact_idempotency_receipts_creation_request_uq"
|
|
704
|
+
ON "editable_artifact_idempotency_receipts"
|
|
705
|
+
(
|
|
706
|
+
"account_id", "workspace_id", "operation_kind", "authority_key_digest",
|
|
707
|
+
"idempotency_key"
|
|
708
|
+
)
|
|
709
|
+
WHERE "operation_kind" IN ('create', 'import');
|
|
710
|
+
CREATE UNIQUE INDEX "editable_artifact_idempotency_receipts_artifact_request_uq"
|
|
711
|
+
ON "editable_artifact_idempotency_receipts"
|
|
712
|
+
(
|
|
713
|
+
"account_id", "workspace_id", "artifact_id", "operation_kind",
|
|
714
|
+
"authority_key_digest", "idempotency_key"
|
|
715
|
+
)
|
|
716
|
+
WHERE "operation_kind" IN ('edit', 'snapshot', 'materialize');
|
|
717
|
+
CREATE UNIQUE INDEX "editable_artifact_idempotency_receipts_origin_uq"
|
|
718
|
+
ON "editable_artifact_idempotency_receipts"
|
|
719
|
+
("account_id", "workspace_id", "artifact_id")
|
|
720
|
+
WHERE "operation_kind" IN ('create', 'import') AND "resource_type" = 'artifact';
|
|
721
|
+
|
|
722
|
+
CREATE INDEX "editable_artifact_idempotency_receipts_transaction_idx"
|
|
723
|
+
ON "editable_artifact_idempotency_receipts"
|
|
724
|
+
("workspace_id", "artifact_id", "server_transaction_id");
|
|
725
|
+
|
|
726
|
+
CREATE TABLE "editable_artifact_undo_claims" (
|
|
727
|
+
"account_id" uuid NOT NULL,
|
|
728
|
+
"workspace_id" uuid NOT NULL,
|
|
729
|
+
"artifact_id" text NOT NULL,
|
|
730
|
+
"target_operation_id" text NOT NULL,
|
|
731
|
+
"claiming_transaction_id" text NOT NULL,
|
|
732
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
733
|
+
CONSTRAINT "editable_artifact_undo_claims_pk" PRIMARY KEY (
|
|
734
|
+
"account_id", "workspace_id", "artifact_id", "target_operation_id"
|
|
735
|
+
),
|
|
736
|
+
CONSTRAINT "editable_artifact_undo_claims_transaction_fk"
|
|
737
|
+
FOREIGN KEY ("account_id", "workspace_id", "artifact_id", "claiming_transaction_id")
|
|
738
|
+
REFERENCES "editable_artifact_transactions"(
|
|
739
|
+
"account_id", "workspace_id", "artifact_id", "id"
|
|
740
|
+
) ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED,
|
|
741
|
+
CONSTRAINT "editable_artifact_undo_claims_ids_chk" CHECK (
|
|
742
|
+
"target_operation_id" ~ '^[0-9a-f]{32}$' AND "target_operation_id" !~ '^0+$'
|
|
743
|
+
AND "claiming_transaction_id" ~ '^[0-9a-f]{32}$'
|
|
744
|
+
AND "claiming_transaction_id" !~ '^0+$'
|
|
745
|
+
)
|
|
746
|
+
);
|
|
747
|
+
|
|
748
|
+
CREATE INDEX "editable_artifact_undo_claims_transaction_idx"
|
|
749
|
+
ON "editable_artifact_undo_claims"
|
|
750
|
+
(
|
|
751
|
+
"account_id", "workspace_id", "artifact_id", "claiming_transaction_id",
|
|
752
|
+
"target_operation_id"
|
|
753
|
+
);
|
|
754
|
+
|
|
755
|
+
CREATE TABLE "editable_artifact_sequence_checkpoints" (
|
|
756
|
+
"account_id" uuid NOT NULL,
|
|
757
|
+
"workspace_id" uuid NOT NULL,
|
|
758
|
+
"artifact_id" text NOT NULL,
|
|
759
|
+
"modality" text NOT NULL,
|
|
760
|
+
"head_sequence" bigint NOT NULL,
|
|
761
|
+
"transaction_id" text,
|
|
762
|
+
"causal_frontier" jsonb,
|
|
763
|
+
"native_revision" bigint,
|
|
764
|
+
"state_hash" text NOT NULL,
|
|
765
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
766
|
+
CONSTRAINT "editable_artifact_sequence_checkpoints_pk" PRIMARY KEY (
|
|
767
|
+
"account_id", "workspace_id", "artifact_id", "head_sequence"
|
|
768
|
+
),
|
|
769
|
+
CONSTRAINT "editable_artifact_sequence_checkpoints_artifact_fk"
|
|
770
|
+
FOREIGN KEY ("account_id", "workspace_id", "artifact_id")
|
|
771
|
+
REFERENCES "editable_artifacts"("account_id", "workspace_id", "id") ON DELETE RESTRICT,
|
|
772
|
+
CONSTRAINT "editable_artifact_sequence_checkpoints_transaction_fk"
|
|
773
|
+
FOREIGN KEY ("account_id", "workspace_id", "artifact_id", "transaction_id")
|
|
774
|
+
REFERENCES "editable_artifact_transactions"(
|
|
775
|
+
"account_id", "workspace_id", "artifact_id", "id"
|
|
776
|
+
) ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED,
|
|
777
|
+
CONSTRAINT "editable_artifact_sequence_checkpoints_shape_chk" CHECK (
|
|
778
|
+
"head_sequence" BETWEEN 0 AND 9007199254740991
|
|
779
|
+
AND (("head_sequence" = 0 AND "transaction_id" IS NULL)
|
|
780
|
+
OR ("head_sequence" > 0 AND "transaction_id" IS NOT NULL))
|
|
781
|
+
AND (("modality" = 'spreadsheet'
|
|
782
|
+
AND opengeni_private.editable_artifact_frontier_valid("causal_frontier")
|
|
783
|
+
AND "native_revision" IS NULL)
|
|
784
|
+
OR ("modality" IN ('document', 'presentation')
|
|
785
|
+
AND "causal_frontier" IS NULL
|
|
786
|
+
AND "native_revision" BETWEEN 0 AND 9007199254740991))
|
|
787
|
+
AND "state_hash" ~ '^sha256:[0-9a-f]{64}$'
|
|
788
|
+
)
|
|
789
|
+
);
|
|
790
|
+
|
|
791
|
+
CREATE UNIQUE INDEX "editable_artifact_sequence_checkpoints_transaction_uq"
|
|
792
|
+
ON "editable_artifact_sequence_checkpoints"
|
|
793
|
+
("account_id", "workspace_id", "artifact_id", "transaction_id")
|
|
794
|
+
WHERE "transaction_id" IS NOT NULL;
|
|
795
|
+
|
|
796
|
+
ALTER TABLE "editable_artifact_transactions"
|
|
797
|
+
ADD CONSTRAINT "editable_artifact_transactions_parent_checkpoint_fk"
|
|
798
|
+
FOREIGN KEY ("account_id", "workspace_id", "artifact_id", "parent_head_sequence")
|
|
799
|
+
REFERENCES "editable_artifact_sequence_checkpoints"(
|
|
800
|
+
"account_id", "workspace_id", "artifact_id", "head_sequence"
|
|
801
|
+
) ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED;
|
|
802
|
+
|
|
803
|
+
CREATE TABLE "editable_artifact_blob_refs" (
|
|
804
|
+
"account_id" uuid NOT NULL,
|
|
805
|
+
"workspace_id" uuid NOT NULL,
|
|
806
|
+
"artifact_id" text NOT NULL,
|
|
807
|
+
"id" text NOT NULL,
|
|
808
|
+
"kind" text NOT NULL,
|
|
809
|
+
"object_reference" text NOT NULL,
|
|
810
|
+
"byte_size" bigint NOT NULL,
|
|
811
|
+
"content_hash" text NOT NULL,
|
|
812
|
+
"mime_type" text NOT NULL,
|
|
813
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
814
|
+
CONSTRAINT "editable_artifact_blob_refs_pk" PRIMARY KEY (
|
|
815
|
+
"account_id", "workspace_id", "artifact_id", "id"
|
|
816
|
+
),
|
|
817
|
+
CONSTRAINT "editable_artifact_blob_refs_artifact_fk"
|
|
818
|
+
FOREIGN KEY ("account_id", "workspace_id", "artifact_id")
|
|
819
|
+
REFERENCES "editable_artifacts"("account_id", "workspace_id", "id") ON DELETE RESTRICT,
|
|
820
|
+
CONSTRAINT "editable_artifact_blob_refs_identity_chk" CHECK (
|
|
821
|
+
"id" ~ '^[0-9a-f]{32}$' AND "id" !~ '^0+$'
|
|
822
|
+
AND "kind" IN (
|
|
823
|
+
'snapshot', 'original_import', 'media', 'loss_envelope', 'materialization'
|
|
824
|
+
)
|
|
825
|
+
),
|
|
826
|
+
CONSTRAINT "editable_artifact_blob_refs_facts_chk" CHECK (
|
|
827
|
+
octet_length("object_reference") BETWEEN 1 AND 1024
|
|
828
|
+
AND "object_reference" = btrim("object_reference")
|
|
829
|
+
AND "byte_size" BETWEEN 1 AND 9007199254740991
|
|
830
|
+
AND "content_hash" ~ '^sha256:[0-9a-f]{64}$'
|
|
831
|
+
AND octet_length("mime_type") BETWEEN 1 AND 256
|
|
832
|
+
AND "mime_type" = btrim("mime_type")
|
|
833
|
+
),
|
|
834
|
+
CONSTRAINT "editable_artifact_blob_refs_content_uq" UNIQUE (
|
|
835
|
+
"account_id", "workspace_id", "artifact_id", "kind", "content_hash"
|
|
836
|
+
),
|
|
837
|
+
CONSTRAINT "editable_artifact_blob_refs_exact_facts_uq" UNIQUE (
|
|
838
|
+
"account_id", "workspace_id", "artifact_id", "id", "byte_size",
|
|
839
|
+
"content_hash", "mime_type"
|
|
840
|
+
)
|
|
841
|
+
);
|
|
842
|
+
|
|
843
|
+
CREATE TABLE "editable_artifact_snapshots" (
|
|
844
|
+
"account_id" uuid NOT NULL,
|
|
845
|
+
"workspace_id" uuid NOT NULL,
|
|
846
|
+
"artifact_id" text NOT NULL,
|
|
847
|
+
"modality" text NOT NULL,
|
|
848
|
+
"id" text NOT NULL,
|
|
849
|
+
"blob_ref_id" text NOT NULL,
|
|
850
|
+
"byte_size" bigint NOT NULL,
|
|
851
|
+
"content_hash" text NOT NULL,
|
|
852
|
+
"mime_type" text NOT NULL DEFAULT 'application/vnd.opengeni.editable-artifact-snapshot',
|
|
853
|
+
"covered_head_sequence" bigint NOT NULL,
|
|
854
|
+
"covered_causal_frontier" jsonb,
|
|
855
|
+
"state_hash" text NOT NULL,
|
|
856
|
+
"model_schema_version" integer NOT NULL,
|
|
857
|
+
"operation_protocol_version" integer,
|
|
858
|
+
"kernel_version" text NOT NULL,
|
|
859
|
+
"crdt_state_version" integer,
|
|
860
|
+
"native_revision" bigint,
|
|
861
|
+
"verified_at" timestamptz NOT NULL,
|
|
862
|
+
"published_at" timestamptz NOT NULL DEFAULT now(),
|
|
863
|
+
CONSTRAINT "editable_artifact_snapshots_pk" PRIMARY KEY (
|
|
864
|
+
"account_id", "workspace_id", "artifact_id", "id"
|
|
865
|
+
),
|
|
866
|
+
CONSTRAINT "editable_artifact_snapshots_artifact_fk"
|
|
867
|
+
FOREIGN KEY ("account_id", "workspace_id", "artifact_id")
|
|
868
|
+
REFERENCES "editable_artifacts"("account_id", "workspace_id", "id") ON DELETE RESTRICT,
|
|
869
|
+
CONSTRAINT "editable_artifact_snapshots_blob_fk"
|
|
870
|
+
FOREIGN KEY ("account_id", "workspace_id", "artifact_id", "blob_ref_id")
|
|
871
|
+
REFERENCES "editable_artifact_blob_refs"(
|
|
872
|
+
"account_id", "workspace_id", "artifact_id", "id"
|
|
873
|
+
) ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED,
|
|
874
|
+
CONSTRAINT "editable_artifact_snapshots_blob_facts_fk"
|
|
875
|
+
FOREIGN KEY (
|
|
876
|
+
"account_id", "workspace_id", "artifact_id", "blob_ref_id", "byte_size",
|
|
877
|
+
"content_hash", "mime_type"
|
|
878
|
+
) REFERENCES "editable_artifact_blob_refs"(
|
|
879
|
+
"account_id", "workspace_id", "artifact_id", "id", "byte_size",
|
|
880
|
+
"content_hash", "mime_type"
|
|
881
|
+
) ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED,
|
|
882
|
+
CONSTRAINT "editable_artifact_snapshots_checkpoint_fk"
|
|
883
|
+
FOREIGN KEY ("account_id", "workspace_id", "artifact_id", "covered_head_sequence")
|
|
884
|
+
REFERENCES "editable_artifact_sequence_checkpoints"(
|
|
885
|
+
"account_id", "workspace_id", "artifact_id", "head_sequence"
|
|
886
|
+
) ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED,
|
|
887
|
+
CONSTRAINT "editable_artifact_snapshots_identity_chk" CHECK (
|
|
888
|
+
"id" ~ '^[0-9a-f]{32}$' AND "id" !~ '^0+$'
|
|
889
|
+
AND "blob_ref_id" ~ '^[0-9a-f]{32}$' AND "blob_ref_id" !~ '^0+$'
|
|
890
|
+
),
|
|
891
|
+
CONSTRAINT "editable_artifact_snapshots_facts_chk" CHECK (
|
|
892
|
+
"byte_size" BETWEEN 1 AND 67108864
|
|
893
|
+
AND "content_hash" ~ '^sha256:[0-9a-f]{64}$'
|
|
894
|
+
AND "mime_type" = 'application/vnd.opengeni.editable-artifact-snapshot'
|
|
895
|
+
AND "covered_head_sequence" BETWEEN 0 AND 9007199254740991
|
|
896
|
+
AND "state_hash" ~ '^sha256:[0-9a-f]{64}$'
|
|
897
|
+
AND "model_schema_version" > 0
|
|
898
|
+
AND (("modality" = 'spreadsheet'
|
|
899
|
+
AND opengeni_private.editable_artifact_frontier_valid("covered_causal_frontier")
|
|
900
|
+
AND "operation_protocol_version" > 0
|
|
901
|
+
AND "crdt_state_version" > 0
|
|
902
|
+
AND "native_revision" IS NULL)
|
|
903
|
+
OR ("modality" IN ('document', 'presentation')
|
|
904
|
+
AND "covered_causal_frontier" IS NULL
|
|
905
|
+
AND "operation_protocol_version" IS NULL
|
|
906
|
+
AND "crdt_state_version" IS NULL
|
|
907
|
+
AND "native_revision" BETWEEN 0 AND 9007199254740991))
|
|
908
|
+
AND octet_length("kernel_version") BETWEEN 1 AND 512
|
|
909
|
+
AND "verified_at" <= "published_at"
|
|
910
|
+
),
|
|
911
|
+
CONSTRAINT "editable_artifact_snapshots_content_uq" UNIQUE (
|
|
912
|
+
"account_id", "workspace_id", "artifact_id", "content_hash", "covered_head_sequence"
|
|
913
|
+
),
|
|
914
|
+
CONSTRAINT "editable_artifact_snapshots_source_manifest_uq" UNIQUE (
|
|
915
|
+
"account_id", "workspace_id", "artifact_id", "id", "covered_head_sequence", "state_hash"
|
|
916
|
+
)
|
|
917
|
+
);
|
|
918
|
+
|
|
919
|
+
CREATE INDEX "editable_artifact_snapshots_coverage_idx"
|
|
920
|
+
ON "editable_artifact_snapshots"
|
|
921
|
+
("workspace_id", "artifact_id", "covered_head_sequence" DESC);
|
|
922
|
+
|
|
923
|
+
ALTER TABLE "editable_artifacts"
|
|
924
|
+
ADD CONSTRAINT "editable_artifacts_current_snapshot_fk"
|
|
925
|
+
FOREIGN KEY ("account_id", "workspace_id", "id", "current_snapshot_id")
|
|
926
|
+
REFERENCES "editable_artifact_snapshots"(
|
|
927
|
+
"account_id", "workspace_id", "artifact_id", "id"
|
|
928
|
+
) ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED;
|
|
929
|
+
|
|
930
|
+
CREATE TABLE "editable_artifact_versions" (
|
|
931
|
+
"account_id" uuid NOT NULL,
|
|
932
|
+
"workspace_id" uuid NOT NULL,
|
|
933
|
+
"artifact_id" text NOT NULL,
|
|
934
|
+
"id" text NOT NULL,
|
|
935
|
+
"snapshot_id" text,
|
|
936
|
+
"head_sequence" bigint NOT NULL,
|
|
937
|
+
"causal_frontier" jsonb NOT NULL,
|
|
938
|
+
"state_hash" text NOT NULL,
|
|
939
|
+
"name" text NOT NULL,
|
|
940
|
+
"pinned" boolean NOT NULL DEFAULT true,
|
|
941
|
+
"created_by_subject_id" text NOT NULL,
|
|
942
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
943
|
+
CONSTRAINT "editable_artifact_versions_pk" PRIMARY KEY (
|
|
944
|
+
"account_id", "workspace_id", "artifact_id", "id"
|
|
945
|
+
),
|
|
946
|
+
CONSTRAINT "editable_artifact_versions_artifact_fk"
|
|
947
|
+
FOREIGN KEY ("account_id", "workspace_id", "artifact_id")
|
|
948
|
+
REFERENCES "editable_artifacts"("account_id", "workspace_id", "id") ON DELETE RESTRICT,
|
|
949
|
+
CONSTRAINT "editable_artifact_versions_snapshot_fk"
|
|
950
|
+
FOREIGN KEY ("account_id", "workspace_id", "artifact_id", "snapshot_id")
|
|
951
|
+
REFERENCES "editable_artifact_snapshots"(
|
|
952
|
+
"account_id", "workspace_id", "artifact_id", "id"
|
|
953
|
+
) ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED,
|
|
954
|
+
CONSTRAINT "editable_artifact_versions_checkpoint_fk"
|
|
955
|
+
FOREIGN KEY ("account_id", "workspace_id", "artifact_id", "head_sequence")
|
|
956
|
+
REFERENCES "editable_artifact_sequence_checkpoints"(
|
|
957
|
+
"account_id", "workspace_id", "artifact_id", "head_sequence"
|
|
958
|
+
) ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED,
|
|
959
|
+
CONSTRAINT "editable_artifact_versions_identity_chk" CHECK (
|
|
960
|
+
"id" ~ '^[0-9a-f]{32}$' AND "id" !~ '^0+$'
|
|
961
|
+
AND ("snapshot_id" IS NULL OR (
|
|
962
|
+
"snapshot_id" ~ '^[0-9a-f]{32}$' AND "snapshot_id" !~ '^0+$'
|
|
963
|
+
))
|
|
964
|
+
),
|
|
965
|
+
CONSTRAINT "editable_artifact_versions_facts_chk" CHECK (
|
|
966
|
+
"head_sequence" BETWEEN 0 AND 9007199254740991
|
|
967
|
+
AND opengeni_private.editable_artifact_frontier_valid("causal_frontier")
|
|
968
|
+
AND "state_hash" ~ '^sha256:[0-9a-f]{64}$'
|
|
969
|
+
AND octet_length(convert_to("name", 'UTF8')) BETWEEN 1 AND 256
|
|
970
|
+
AND "name" = btrim("name")
|
|
971
|
+
AND opengeni_private.editable_artifact_identity_valid("created_by_subject_id")
|
|
972
|
+
)
|
|
973
|
+
);
|
|
974
|
+
|
|
975
|
+
CREATE INDEX "editable_artifact_versions_timeline_idx"
|
|
976
|
+
ON "editable_artifact_versions" ("workspace_id", "artifact_id", "created_at" DESC, "id");
|
|
977
|
+
|
|
978
|
+
CREATE TABLE "editable_artifact_materialization_jobs" (
|
|
979
|
+
"account_id" uuid NOT NULL,
|
|
980
|
+
"workspace_id" uuid NOT NULL,
|
|
981
|
+
"artifact_id" text NOT NULL,
|
|
982
|
+
"id" text NOT NULL,
|
|
983
|
+
"version_id" text,
|
|
984
|
+
"input_snapshot_id" text NOT NULL,
|
|
985
|
+
"target_head_sequence" bigint NOT NULL,
|
|
986
|
+
"state_hash" text NOT NULL,
|
|
987
|
+
"format" text NOT NULL,
|
|
988
|
+
"normalized_options" text NOT NULL,
|
|
989
|
+
"options_hash" text GENERATED ALWAYS AS (
|
|
990
|
+
'sha256:' || encode(
|
|
991
|
+
opengeni_private.editable_artifact_text_sha256("normalized_options"), 'hex'
|
|
992
|
+
)
|
|
993
|
+
) STORED,
|
|
994
|
+
"codec_id" text NOT NULL,
|
|
995
|
+
"codec_version" text NOT NULL,
|
|
996
|
+
"kernel_version" text NOT NULL,
|
|
997
|
+
"font_registry_hash" text NOT NULL,
|
|
998
|
+
"policy_hash" text NOT NULL,
|
|
999
|
+
"state" text NOT NULL DEFAULT 'pending',
|
|
1000
|
+
"attempt_count" integer NOT NULL DEFAULT 0,
|
|
1001
|
+
"lease_owner" text,
|
|
1002
|
+
"lease_expires_at" timestamptz,
|
|
1003
|
+
"settled_by_owner" text,
|
|
1004
|
+
"error_code" text,
|
|
1005
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
1006
|
+
"started_at" timestamptz,
|
|
1007
|
+
"completed_at" timestamptz,
|
|
1008
|
+
CONSTRAINT "editable_artifact_materialization_jobs_pk" PRIMARY KEY (
|
|
1009
|
+
"account_id", "workspace_id", "artifact_id", "id"
|
|
1010
|
+
),
|
|
1011
|
+
CONSTRAINT "editable_artifact_materialization_jobs_artifact_fk"
|
|
1012
|
+
FOREIGN KEY ("account_id", "workspace_id", "artifact_id")
|
|
1013
|
+
REFERENCES "editable_artifacts"("account_id", "workspace_id", "id") ON DELETE RESTRICT,
|
|
1014
|
+
CONSTRAINT "editable_artifact_materialization_jobs_version_fk"
|
|
1015
|
+
FOREIGN KEY ("account_id", "workspace_id", "artifact_id", "version_id")
|
|
1016
|
+
REFERENCES "editable_artifact_versions"(
|
|
1017
|
+
"account_id", "workspace_id", "artifact_id", "id"
|
|
1018
|
+
) ON DELETE RESTRICT,
|
|
1019
|
+
CONSTRAINT "editable_artifact_materialization_jobs_checkpoint_fk"
|
|
1020
|
+
FOREIGN KEY ("account_id", "workspace_id", "artifact_id", "target_head_sequence")
|
|
1021
|
+
REFERENCES "editable_artifact_sequence_checkpoints"(
|
|
1022
|
+
"account_id", "workspace_id", "artifact_id", "head_sequence"
|
|
1023
|
+
) ON DELETE RESTRICT,
|
|
1024
|
+
CONSTRAINT "editable_artifact_materialization_jobs_source_snapshot_fk"
|
|
1025
|
+
FOREIGN KEY (
|
|
1026
|
+
"account_id", "workspace_id", "artifact_id", "input_snapshot_id",
|
|
1027
|
+
"target_head_sequence", "state_hash"
|
|
1028
|
+
) REFERENCES "editable_artifact_snapshots"(
|
|
1029
|
+
"account_id", "workspace_id", "artifact_id", "id",
|
|
1030
|
+
"covered_head_sequence", "state_hash"
|
|
1031
|
+
) ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED,
|
|
1032
|
+
CONSTRAINT "editable_artifact_materialization_jobs_identity_chk" CHECK (
|
|
1033
|
+
"id" ~ '^[0-9a-f]{32}$' AND "id" !~ '^0+$'
|
|
1034
|
+
AND "input_snapshot_id" ~ '^[0-9a-f]{32}$' AND "input_snapshot_id" !~ '^0+$'
|
|
1035
|
+
AND ("version_id" IS NULL OR (
|
|
1036
|
+
"version_id" ~ '^[0-9a-f]{32}$' AND "version_id" !~ '^0+$'
|
|
1037
|
+
))
|
|
1038
|
+
),
|
|
1039
|
+
CONSTRAINT "editable_artifact_materialization_jobs_cache_facts_chk" CHECK (
|
|
1040
|
+
"target_head_sequence" BETWEEN 0 AND 9007199254740991
|
|
1041
|
+
AND "state_hash" ~ '^sha256:[0-9a-f]{64}$'
|
|
1042
|
+
AND "format" IN ('xlsx', 'pptx', 'docx', 'pdf', 'png', 'webp')
|
|
1043
|
+
AND "options_hash" ~ '^sha256:[0-9a-f]{64}$'
|
|
1044
|
+
AND "font_registry_hash" ~ '^sha256:[0-9a-f]{64}$'
|
|
1045
|
+
AND "policy_hash" ~ '^sha256:[0-9a-f]{64}$'
|
|
1046
|
+
AND octet_length(convert_to("normalized_options", 'UTF8')) BETWEEN 2 AND 262144
|
|
1047
|
+
AND "normalized_options" = btrim("normalized_options")
|
|
1048
|
+
AND jsonb_typeof("normalized_options"::jsonb) = 'object'
|
|
1049
|
+
AND octet_length(convert_to("codec_id", 'UTF8')) BETWEEN 1 AND 128
|
|
1050
|
+
AND "codec_id" = btrim("codec_id")
|
|
1051
|
+
AND octet_length("codec_version") BETWEEN 1 AND 128
|
|
1052
|
+
AND "codec_version" = btrim("codec_version")
|
|
1053
|
+
AND octet_length("kernel_version") BETWEEN 1 AND 512
|
|
1054
|
+
AND "kernel_version" = btrim("kernel_version")
|
|
1055
|
+
),
|
|
1056
|
+
CONSTRAINT "editable_artifact_materialization_jobs_state_chk" CHECK (
|
|
1057
|
+
"state" IN ('pending', 'running', 'succeeded', 'failed')
|
|
1058
|
+
AND "attempt_count" BETWEEN 0 AND 1000
|
|
1059
|
+
AND ("lease_owner" IS NULL) = ("lease_expires_at" IS NULL)
|
|
1060
|
+
AND ("lease_owner" IS NULL OR octet_length("lease_owner") BETWEEN 1 AND 256)
|
|
1061
|
+
AND ("settled_by_owner" IS NULL
|
|
1062
|
+
OR octet_length("settled_by_owner") BETWEEN 1 AND 256)
|
|
1063
|
+
AND ("error_code" IS NULL OR octet_length("error_code") BETWEEN 1 AND 256)
|
|
1064
|
+
AND (("state" = 'pending' AND "attempt_count" = 0
|
|
1065
|
+
AND "lease_owner" IS NULL AND "settled_by_owner" IS NULL
|
|
1066
|
+
AND "started_at" IS NULL AND "completed_at" IS NULL)
|
|
1067
|
+
OR ("state" = 'running' AND "attempt_count" > 0
|
|
1068
|
+
AND "lease_owner" IS NOT NULL AND "started_at" IS NOT NULL
|
|
1069
|
+
AND "lease_expires_at" > "started_at" AND "settled_by_owner" IS NULL
|
|
1070
|
+
AND "completed_at" IS NULL)
|
|
1071
|
+
OR ("state" IN ('succeeded', 'failed')
|
|
1072
|
+
AND "attempt_count" > 0 AND "started_at" IS NOT NULL
|
|
1073
|
+
AND "completed_at" IS NOT NULL AND "completed_at" >= "started_at"
|
|
1074
|
+
AND "settled_by_owner" IS NOT NULL
|
|
1075
|
+
AND "lease_owner" IS NULL AND "lease_expires_at" IS NULL))
|
|
1076
|
+
AND (("state" = 'failed' AND "error_code" IS NOT NULL)
|
|
1077
|
+
OR ("state" <> 'failed' AND "error_code" IS NULL))
|
|
1078
|
+
),
|
|
1079
|
+
CONSTRAINT "editable_artifact_materialization_jobs_cache_uq" UNIQUE (
|
|
1080
|
+
"account_id", "workspace_id", "artifact_id", "input_snapshot_id", "state_hash", "format",
|
|
1081
|
+
"options_hash", "codec_id", "codec_version", "kernel_version",
|
|
1082
|
+
"font_registry_hash", "policy_hash"
|
|
1083
|
+
)
|
|
1084
|
+
);
|
|
1085
|
+
|
|
1086
|
+
CREATE INDEX "editable_artifact_materialization_jobs_claim_idx"
|
|
1087
|
+
ON "editable_artifact_materialization_jobs" (
|
|
1088
|
+
(coalesce("lease_expires_at", "created_at")), "created_at",
|
|
1089
|
+
"account_id", "workspace_id", "artifact_id", "id"
|
|
1090
|
+
)
|
|
1091
|
+
WHERE "state" IN ('pending', 'running');
|
|
1092
|
+
|
|
1093
|
+
CREATE TABLE "editable_artifact_materialization_results" (
|
|
1094
|
+
"account_id" uuid NOT NULL,
|
|
1095
|
+
"workspace_id" uuid NOT NULL,
|
|
1096
|
+
"artifact_id" text NOT NULL,
|
|
1097
|
+
"id" text NOT NULL,
|
|
1098
|
+
"job_id" text NOT NULL,
|
|
1099
|
+
"blob_ref_id" text NOT NULL,
|
|
1100
|
+
"byte_size" bigint NOT NULL,
|
|
1101
|
+
"content_hash" text NOT NULL,
|
|
1102
|
+
"mime_type" text NOT NULL,
|
|
1103
|
+
"verified_at" timestamptz NOT NULL,
|
|
1104
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
1105
|
+
CONSTRAINT "editable_artifact_materialization_results_pk" PRIMARY KEY (
|
|
1106
|
+
"account_id", "workspace_id", "artifact_id", "id"
|
|
1107
|
+
),
|
|
1108
|
+
CONSTRAINT "editable_artifact_materialization_results_job_fk"
|
|
1109
|
+
FOREIGN KEY ("account_id", "workspace_id", "artifact_id", "job_id")
|
|
1110
|
+
REFERENCES "editable_artifact_materialization_jobs"(
|
|
1111
|
+
"account_id", "workspace_id", "artifact_id", "id"
|
|
1112
|
+
) ON DELETE RESTRICT,
|
|
1113
|
+
CONSTRAINT "editable_artifact_materialization_results_blob_fk"
|
|
1114
|
+
FOREIGN KEY ("account_id", "workspace_id", "artifact_id", "blob_ref_id")
|
|
1115
|
+
REFERENCES "editable_artifact_blob_refs"(
|
|
1116
|
+
"account_id", "workspace_id", "artifact_id", "id"
|
|
1117
|
+
) ON DELETE RESTRICT,
|
|
1118
|
+
CONSTRAINT "editable_artifact_materialization_results_blob_facts_fk"
|
|
1119
|
+
FOREIGN KEY (
|
|
1120
|
+
"account_id", "workspace_id", "artifact_id", "blob_ref_id", "byte_size",
|
|
1121
|
+
"content_hash", "mime_type"
|
|
1122
|
+
) REFERENCES "editable_artifact_blob_refs"(
|
|
1123
|
+
"account_id", "workspace_id", "artifact_id", "id", "byte_size",
|
|
1124
|
+
"content_hash", "mime_type"
|
|
1125
|
+
) ON DELETE RESTRICT,
|
|
1126
|
+
CONSTRAINT "editable_artifact_materialization_results_identity_chk" CHECK (
|
|
1127
|
+
"id" ~ '^[0-9a-f]{32}$' AND "id" !~ '^0+$'
|
|
1128
|
+
AND "job_id" ~ '^[0-9a-f]{32}$' AND "job_id" !~ '^0+$'
|
|
1129
|
+
AND "blob_ref_id" ~ '^[0-9a-f]{32}$' AND "blob_ref_id" !~ '^0+$'
|
|
1130
|
+
),
|
|
1131
|
+
CONSTRAINT "editable_artifact_materialization_results_facts_chk" CHECK (
|
|
1132
|
+
"byte_size" BETWEEN 1 AND 9007199254740991
|
|
1133
|
+
AND "content_hash" ~ '^sha256:[0-9a-f]{64}$'
|
|
1134
|
+
AND octet_length("mime_type") BETWEEN 1 AND 256
|
|
1135
|
+
AND "mime_type" = btrim("mime_type")
|
|
1136
|
+
AND "verified_at" <= "created_at"
|
|
1137
|
+
),
|
|
1138
|
+
CONSTRAINT "editable_artifact_materialization_results_job_uq" UNIQUE (
|
|
1139
|
+
"account_id", "workspace_id", "artifact_id", "job_id"
|
|
1140
|
+
)
|
|
1141
|
+
);
|
|
1142
|
+
|
|
1143
|
+
CREATE TABLE "editable_artifact_live_outbox" (
|
|
1144
|
+
"account_id" uuid NOT NULL,
|
|
1145
|
+
"workspace_id" uuid NOT NULL,
|
|
1146
|
+
"artifact_id" text NOT NULL,
|
|
1147
|
+
"id" text NOT NULL,
|
|
1148
|
+
"transaction_id" text,
|
|
1149
|
+
"snapshot_id" text,
|
|
1150
|
+
"event_kind" text NOT NULL,
|
|
1151
|
+
"event" jsonb NOT NULL,
|
|
1152
|
+
"state" text NOT NULL DEFAULT 'pending',
|
|
1153
|
+
"attempt_count" integer NOT NULL DEFAULT 0,
|
|
1154
|
+
"lease_owner" text,
|
|
1155
|
+
"lease_expires_at" timestamptz,
|
|
1156
|
+
"next_attempt_at" timestamptz NOT NULL DEFAULT now(),
|
|
1157
|
+
"last_error_code" text,
|
|
1158
|
+
"published_at" timestamptz,
|
|
1159
|
+
"dead_lettered_at" timestamptz,
|
|
1160
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
1161
|
+
CONSTRAINT "editable_artifact_live_outbox_pk" PRIMARY KEY (
|
|
1162
|
+
"account_id", "workspace_id", "artifact_id", "id"
|
|
1163
|
+
),
|
|
1164
|
+
CONSTRAINT "editable_artifact_live_outbox_artifact_fk"
|
|
1165
|
+
FOREIGN KEY ("account_id", "workspace_id", "artifact_id")
|
|
1166
|
+
REFERENCES "editable_artifacts"("account_id", "workspace_id", "id") ON DELETE RESTRICT,
|
|
1167
|
+
CONSTRAINT "editable_artifact_live_outbox_transaction_fk"
|
|
1168
|
+
FOREIGN KEY ("account_id", "workspace_id", "artifact_id", "transaction_id")
|
|
1169
|
+
REFERENCES "editable_artifact_transactions"(
|
|
1170
|
+
"account_id", "workspace_id", "artifact_id", "id"
|
|
1171
|
+
) ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED,
|
|
1172
|
+
CONSTRAINT "editable_artifact_live_outbox_snapshot_fk"
|
|
1173
|
+
FOREIGN KEY ("account_id", "workspace_id", "artifact_id", "snapshot_id")
|
|
1174
|
+
REFERENCES "editable_artifact_snapshots"(
|
|
1175
|
+
"account_id", "workspace_id", "artifact_id", "id"
|
|
1176
|
+
) ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED,
|
|
1177
|
+
CONSTRAINT "editable_artifact_live_outbox_identity_chk" CHECK (
|
|
1178
|
+
"id" ~ '^[0-9a-f]{32}$' AND "id" !~ '^0+$'
|
|
1179
|
+
),
|
|
1180
|
+
CONSTRAINT "editable_artifact_live_outbox_event_chk" CHECK (
|
|
1181
|
+
"event_kind" IN ('transaction_committed', 'snapshot_published')
|
|
1182
|
+
AND jsonb_typeof("event") = 'object'
|
|
1183
|
+
AND octet_length("event"::text) <= 65536
|
|
1184
|
+
AND jsonb_typeof("event"->'kind') = 'string'
|
|
1185
|
+
AND jsonb_typeof("event"->'schemaVersion') = 'number'
|
|
1186
|
+
AND "event"->>'kind' = "event_kind"
|
|
1187
|
+
AND "event"->>'schemaVersion' = '1'
|
|
1188
|
+
AND opengeni_private.editable_artifact_object_has_exact_keys(
|
|
1189
|
+
"event"->'scope', ARRAY['accountId', 'workspaceId']
|
|
1190
|
+
)
|
|
1191
|
+
AND "event"#>>'{scope,accountId}' = "account_id"::text
|
|
1192
|
+
AND "event"#>>'{scope,workspaceId}' = "workspace_id"::text
|
|
1193
|
+
AND jsonb_typeof("event"->'artifactId') = 'string'
|
|
1194
|
+
AND "event"->>'artifactId' = "artifact_id"
|
|
1195
|
+
AND jsonb_typeof("event"->'modality') = 'string'
|
|
1196
|
+
AND "event"->>'modality' IN ('spreadsheet', 'document', 'presentation')
|
|
1197
|
+
AND (("event_kind" = 'transaction_committed'
|
|
1198
|
+
AND "transaction_id" IS NOT NULL AND "snapshot_id" IS NULL
|
|
1199
|
+
AND (("event"->>'modality' = 'spreadsheet'
|
|
1200
|
+
AND opengeni_private.editable_artifact_object_has_exact_keys(
|
|
1201
|
+
"event", ARRAY[
|
|
1202
|
+
'artifactId', 'committedAt', 'kind', 'modality', 'operationProtocolVersion',
|
|
1203
|
+
'schemaVersion', 'scope', 'sequenceEnd',
|
|
1204
|
+
'sequenceStart', 'serverTransactionId', 'stateHash'
|
|
1205
|
+
]
|
|
1206
|
+
)
|
|
1207
|
+
AND jsonb_typeof("event"->'operationProtocolVersion') = 'number'
|
|
1208
|
+
AND ("event"->>'operationProtocolVersion') ~ '^[1-9][0-9]{0,9}$')
|
|
1209
|
+
OR ("event"->>'modality' IN ('document', 'presentation')
|
|
1210
|
+
AND opengeni_private.editable_artifact_object_has_exact_keys(
|
|
1211
|
+
"event", ARRAY[
|
|
1212
|
+
'artifactId', 'commitProtocolVersion', 'committedAt', 'kind', 'modality',
|
|
1213
|
+
'schemaVersion', 'scope', 'sequenceEnd',
|
|
1214
|
+
'sequenceStart', 'serverTransactionId', 'stateHash'
|
|
1215
|
+
]
|
|
1216
|
+
)
|
|
1217
|
+
AND jsonb_typeof("event"->'commitProtocolVersion') = 'number'
|
|
1218
|
+
AND ("event"->>'commitProtocolVersion') ~ '^[1-9][0-9]{0,9}$'))
|
|
1219
|
+
AND jsonb_typeof("event"->'serverTransactionId') = 'string'
|
|
1220
|
+
AND "event"->>'serverTransactionId' = "transaction_id")
|
|
1221
|
+
OR ("event_kind" = 'snapshot_published'
|
|
1222
|
+
AND "transaction_id" IS NULL AND "snapshot_id" IS NOT NULL
|
|
1223
|
+
AND (("event"->>'modality' = 'spreadsheet'
|
|
1224
|
+
AND opengeni_private.editable_artifact_object_has_exact_keys(
|
|
1225
|
+
"event", ARRAY[
|
|
1226
|
+
'artifactId', 'coveredHeadSequence', 'kind', 'modality',
|
|
1227
|
+
'operationProtocolVersion', 'publishedAt', 'schemaVersion',
|
|
1228
|
+
'scope', 'snapshotId', 'stateHash'
|
|
1229
|
+
]
|
|
1230
|
+
)
|
|
1231
|
+
AND jsonb_typeof("event"->'operationProtocolVersion') = 'number'
|
|
1232
|
+
AND ("event"->>'operationProtocolVersion') ~ '^[1-9][0-9]{0,9}$')
|
|
1233
|
+
OR ("event"->>'modality' IN ('document', 'presentation')
|
|
1234
|
+
AND opengeni_private.editable_artifact_object_has_exact_keys(
|
|
1235
|
+
"event", ARRAY[
|
|
1236
|
+
'artifactId', 'coveredHeadSequence', 'kind', 'modality',
|
|
1237
|
+
'publishedAt', 'schemaVersion', 'scope', 'snapshotId', 'stateHash'
|
|
1238
|
+
]
|
|
1239
|
+
)))
|
|
1240
|
+
AND jsonb_typeof("event"->'snapshotId') = 'string'
|
|
1241
|
+
AND "event"->>'snapshotId' = "snapshot_id"))
|
|
1242
|
+
AND jsonb_typeof("event"->'stateHash') = 'string'
|
|
1243
|
+
AND "event"->>'stateHash' ~ '^sha256:[0-9a-f]{64}$'
|
|
1244
|
+
AND (("event_kind" = 'transaction_committed'
|
|
1245
|
+
AND jsonb_typeof("event"->'sequenceStart') = 'number'
|
|
1246
|
+
AND jsonb_typeof("event"->'sequenceEnd') = 'number'
|
|
1247
|
+
AND ("event"->>'sequenceStart') ~ '^[1-9][0-9]{0,15}$'
|
|
1248
|
+
AND ("event"->>'sequenceEnd') ~ '^[1-9][0-9]{0,15}$'
|
|
1249
|
+
AND jsonb_typeof("event"->'committedAt') = 'string'
|
|
1250
|
+
AND "event"->>'committedAt'
|
|
1251
|
+
~ '^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z$')
|
|
1252
|
+
OR ("event_kind" = 'snapshot_published'
|
|
1253
|
+
AND jsonb_typeof("event"->'coveredHeadSequence') = 'number'
|
|
1254
|
+
AND ("event"->>'coveredHeadSequence') ~ '^(0|[1-9][0-9]{0,15})$'
|
|
1255
|
+
AND jsonb_typeof("event"->'publishedAt') = 'string'
|
|
1256
|
+
AND "event"->>'publishedAt'
|
|
1257
|
+
~ '^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z$'))
|
|
1258
|
+
),
|
|
1259
|
+
CONSTRAINT "editable_artifact_live_outbox_state_chk" CHECK (
|
|
1260
|
+
"state" IN ('pending', 'publishing', 'published', 'dead_lettered')
|
|
1261
|
+
AND "attempt_count" BETWEEN 0 AND 1000000
|
|
1262
|
+
AND ("lease_owner" IS NULL) = ("lease_expires_at" IS NULL)
|
|
1263
|
+
AND ("lease_owner" IS NULL OR octet_length("lease_owner") BETWEEN 1 AND 256)
|
|
1264
|
+
AND ("last_error_code" IS NULL OR octet_length("last_error_code") BETWEEN 1 AND 256)
|
|
1265
|
+
AND (("state" = 'pending' AND "lease_owner" IS NULL
|
|
1266
|
+
AND "published_at" IS NULL AND "dead_lettered_at" IS NULL)
|
|
1267
|
+
OR ("state" = 'publishing' AND "lease_owner" IS NOT NULL
|
|
1268
|
+
AND "published_at" IS NULL AND "dead_lettered_at" IS NULL)
|
|
1269
|
+
OR ("state" = 'published' AND "lease_owner" IS NULL
|
|
1270
|
+
AND "published_at" IS NOT NULL AND "dead_lettered_at" IS NULL
|
|
1271
|
+
AND "last_error_code" IS NULL)
|
|
1272
|
+
OR ("state" = 'dead_lettered' AND "lease_owner" IS NULL
|
|
1273
|
+
AND "published_at" IS NULL AND "dead_lettered_at" IS NOT NULL
|
|
1274
|
+
AND "last_error_code" IN ('invalid_hint', 'oversized_hint', 'attempts_exhausted')))
|
|
1275
|
+
)
|
|
1276
|
+
);
|
|
1277
|
+
|
|
1278
|
+
CREATE UNIQUE INDEX "editable_artifact_live_outbox_id_uq"
|
|
1279
|
+
ON "editable_artifact_live_outbox" ("id");
|
|
1280
|
+
CREATE UNIQUE INDEX "editable_artifact_live_outbox_transaction_uq"
|
|
1281
|
+
ON "editable_artifact_live_outbox"
|
|
1282
|
+
("account_id", "workspace_id", "artifact_id", "transaction_id")
|
|
1283
|
+
WHERE "event_kind" = 'transaction_committed';
|
|
1284
|
+
CREATE UNIQUE INDEX "editable_artifact_live_outbox_snapshot_uq"
|
|
1285
|
+
ON "editable_artifact_live_outbox"
|
|
1286
|
+
("account_id", "workspace_id", "artifact_id", "snapshot_id")
|
|
1287
|
+
WHERE "event_kind" = 'snapshot_published';
|
|
1288
|
+
|
|
1289
|
+
CREATE INDEX "editable_artifact_live_outbox_pending_claim_idx"
|
|
1290
|
+
ON "editable_artifact_live_outbox" ("next_attempt_at", "created_at", "id")
|
|
1291
|
+
WHERE "state" = 'pending';
|
|
1292
|
+
CREATE INDEX "editable_artifact_live_outbox_publishing_claim_idx"
|
|
1293
|
+
ON "editable_artifact_live_outbox" ("lease_expires_at", "created_at", "id")
|
|
1294
|
+
WHERE "state" = 'publishing';
|
|
1295
|
+
|
|
1296
|
+
CREATE TABLE "editable_artifact_replica_leases" (
|
|
1297
|
+
"account_id" uuid NOT NULL,
|
|
1298
|
+
"workspace_id" uuid NOT NULL,
|
|
1299
|
+
"artifact_id" text NOT NULL,
|
|
1300
|
+
"modality" text NOT NULL,
|
|
1301
|
+
"replica_id" text NOT NULL,
|
|
1302
|
+
"actor_key" text NOT NULL,
|
|
1303
|
+
"applied_head_sequence" bigint NOT NULL,
|
|
1304
|
+
"causal_frontier" jsonb,
|
|
1305
|
+
"native_revision" bigint,
|
|
1306
|
+
"lease_expires_at" timestamptz NOT NULL,
|
|
1307
|
+
"last_seen_at" timestamptz NOT NULL DEFAULT now(),
|
|
1308
|
+
"revoked_at" timestamptz,
|
|
1309
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
1310
|
+
CONSTRAINT "editable_artifact_replica_leases_pk" PRIMARY KEY (
|
|
1311
|
+
"account_id", "workspace_id", "artifact_id", "replica_id"
|
|
1312
|
+
),
|
|
1313
|
+
CONSTRAINT "editable_artifact_replica_leases_artifact_fk"
|
|
1314
|
+
FOREIGN KEY ("account_id", "workspace_id", "artifact_id")
|
|
1315
|
+
REFERENCES "editable_artifacts"("account_id", "workspace_id", "id") ON DELETE RESTRICT,
|
|
1316
|
+
CONSTRAINT "editable_artifact_replica_leases_checkpoint_fk"
|
|
1317
|
+
FOREIGN KEY ("account_id", "workspace_id", "artifact_id", "applied_head_sequence")
|
|
1318
|
+
REFERENCES "editable_artifact_sequence_checkpoints"(
|
|
1319
|
+
"account_id", "workspace_id", "artifact_id", "head_sequence"
|
|
1320
|
+
) ON DELETE RESTRICT,
|
|
1321
|
+
CONSTRAINT "editable_artifact_replica_leases_replica_chk" CHECK (
|
|
1322
|
+
"replica_id" ~ '^[0-9a-f]{16}$' AND "replica_id" !~ '^0+$'
|
|
1323
|
+
AND octet_length(convert_to("actor_key", 'UTF8')) BETWEEN 1 AND 8192
|
|
1324
|
+
AND (("modality" = 'spreadsheet'
|
|
1325
|
+
AND opengeni_private.editable_artifact_frontier_valid("causal_frontier")
|
|
1326
|
+
AND "native_revision" IS NULL)
|
|
1327
|
+
OR ("modality" IN ('document', 'presentation')
|
|
1328
|
+
AND "causal_frontier" IS NULL
|
|
1329
|
+
AND "native_revision" BETWEEN 0 AND 9007199254740991))
|
|
1330
|
+
),
|
|
1331
|
+
CONSTRAINT "editable_artifact_replica_leases_cursor_chk" CHECK (
|
|
1332
|
+
"applied_head_sequence" BETWEEN 0 AND 9007199254740991
|
|
1333
|
+
AND "last_seen_at" >= "created_at"
|
|
1334
|
+
AND "lease_expires_at" > "last_seen_at"
|
|
1335
|
+
AND ("revoked_at" IS NULL OR "revoked_at" >= "created_at")
|
|
1336
|
+
)
|
|
1337
|
+
);
|
|
1338
|
+
|
|
1339
|
+
CREATE INDEX "editable_artifact_replica_leases_expiry_idx"
|
|
1340
|
+
ON "editable_artifact_replica_leases" ("lease_expires_at", "replica_id")
|
|
1341
|
+
WHERE "revoked_at" IS NULL;
|
|
1342
|
+
|
|
1343
|
+
CREATE OR REPLACE FUNCTION opengeni_private.validate_editable_artifact_transaction_commit()
|
|
1344
|
+
RETURNS trigger
|
|
1345
|
+
LANGUAGE plpgsql
|
|
1346
|
+
SET search_path = pg_catalog, pg_temp
|
|
1347
|
+
AS $body$
|
|
1348
|
+
DECLARE tx record;
|
|
1349
|
+
DECLARE parent_checkpoint record;
|
|
1350
|
+
DECLARE predecessor record;
|
|
1351
|
+
DECLARE predecessor_resolved_causal_base jsonb := '[]'::jsonb;
|
|
1352
|
+
DECLARE parent_replica_counter bigint;
|
|
1353
|
+
DECLARE resolved_replica_counter bigint;
|
|
1354
|
+
DECLARE result_replica_counter bigint;
|
|
1355
|
+
DECLARE operation_rows integer;
|
|
1356
|
+
DECLARE undo_rows integer;
|
|
1357
|
+
DECLARE artifact_modality text;
|
|
1358
|
+
BEGIN
|
|
1359
|
+
PERFORM set_config(
|
|
1360
|
+
'search_path', 'pg_catalog,' || quote_ident(TG_TABLE_SCHEMA) || ',pg_temp', true
|
|
1361
|
+
);
|
|
1362
|
+
SELECT * INTO tx FROM editable_artifact_transactions candidate
|
|
1363
|
+
WHERE candidate.account_id = NEW.account_id
|
|
1364
|
+
AND candidate.workspace_id = NEW.workspace_id
|
|
1365
|
+
AND candidate.artifact_id = NEW.artifact_id
|
|
1366
|
+
AND candidate.id = NEW.id;
|
|
1367
|
+
|
|
1368
|
+
IF NOT FOUND THEN
|
|
1369
|
+
RAISE EXCEPTION 'editable artifact transaction dependency is missing'
|
|
1370
|
+
USING ERRCODE = '23503';
|
|
1371
|
+
END IF;
|
|
1372
|
+
|
|
1373
|
+
SELECT artifact.modality INTO artifact_modality
|
|
1374
|
+
FROM editable_artifacts artifact
|
|
1375
|
+
WHERE artifact.account_id = tx.account_id
|
|
1376
|
+
AND artifact.workspace_id = tx.workspace_id
|
|
1377
|
+
AND artifact.id = tx.artifact_id;
|
|
1378
|
+
IF artifact_modality IS NULL OR artifact_modality <> tx.modality THEN
|
|
1379
|
+
RAISE EXCEPTION 'transaction modality differs from its durable artifact'
|
|
1380
|
+
USING ERRCODE = '23514';
|
|
1381
|
+
END IF;
|
|
1382
|
+
|
|
1383
|
+
IF tx.replica_counter > 1 THEN
|
|
1384
|
+
SELECT * INTO predecessor
|
|
1385
|
+
FROM editable_artifact_transactions candidate
|
|
1386
|
+
WHERE candidate.account_id = tx.account_id
|
|
1387
|
+
AND candidate.workspace_id = tx.workspace_id
|
|
1388
|
+
AND candidate.artifact_id = tx.artifact_id
|
|
1389
|
+
AND candidate.actor_key = tx.actor_key
|
|
1390
|
+
AND candidate.replica_id = tx.replica_id
|
|
1391
|
+
AND candidate.client_transaction_id = tx.previous_local_transaction_id;
|
|
1392
|
+
IF NOT FOUND
|
|
1393
|
+
OR predecessor.replica_counter <> tx.replica_counter - 1
|
|
1394
|
+
OR predecessor.sequence_end > tx.parent_head_sequence
|
|
1395
|
+
THEN
|
|
1396
|
+
RAISE EXCEPTION 'transaction predecessor is not the prior committed local replica counter'
|
|
1397
|
+
USING ERRCODE = '23514';
|
|
1398
|
+
END IF;
|
|
1399
|
+
predecessor_resolved_causal_base := predecessor.resolved_causal_base;
|
|
1400
|
+
END IF;
|
|
1401
|
+
|
|
1402
|
+
SELECT * INTO parent_checkpoint
|
|
1403
|
+
FROM editable_artifact_sequence_checkpoints checkpoint
|
|
1404
|
+
WHERE checkpoint.account_id = tx.account_id
|
|
1405
|
+
AND checkpoint.workspace_id = tx.workspace_id
|
|
1406
|
+
AND checkpoint.artifact_id = tx.artifact_id
|
|
1407
|
+
AND checkpoint.head_sequence = tx.parent_head_sequence;
|
|
1408
|
+
IF NOT FOUND THEN
|
|
1409
|
+
RAISE EXCEPTION 'transaction parent checkpoint is missing'
|
|
1410
|
+
USING ERRCODE = '23514';
|
|
1411
|
+
END IF;
|
|
1412
|
+
|
|
1413
|
+
IF parent_checkpoint.modality <> tx.modality
|
|
1414
|
+
OR parent_checkpoint.state_hash <> tx.prior_state_hash
|
|
1415
|
+
THEN
|
|
1416
|
+
RAISE EXCEPTION 'transaction parent checkpoint modality or state differs'
|
|
1417
|
+
USING ERRCODE = '23514';
|
|
1418
|
+
END IF;
|
|
1419
|
+
|
|
1420
|
+
IF tx.modality = 'spreadsheet' THEN
|
|
1421
|
+
SELECT (entry->>'counter')::bigint INTO resolved_replica_counter
|
|
1422
|
+
FROM jsonb_array_elements(tx.resolved_causal_base) AS entries(entry)
|
|
1423
|
+
WHERE entry->>'replicaId' = tx.replica_id;
|
|
1424
|
+
SELECT (entry->>'counter')::bigint INTO result_replica_counter
|
|
1425
|
+
FROM jsonb_array_elements(tx.resulting_causal_frontier) AS entries(entry)
|
|
1426
|
+
WHERE entry->>'replicaId' = tx.replica_id;
|
|
1427
|
+
SELECT (entry->>'counter')::bigint INTO parent_replica_counter
|
|
1428
|
+
FROM jsonb_array_elements(parent_checkpoint.causal_frontier) AS entries(entry)
|
|
1429
|
+
WHERE entry->>'replicaId' = tx.replica_id;
|
|
1430
|
+
IF coalesce(parent_replica_counter, 0) <> tx.replica_counter - 1
|
|
1431
|
+
OR coalesce(resolved_replica_counter, 0) <> tx.replica_counter - 1
|
|
1432
|
+
OR result_replica_counter IS DISTINCT FROM tx.replica_counter
|
|
1433
|
+
OR NOT opengeni_private.editable_artifact_frontier_merge_dot_equals(
|
|
1434
|
+
tx.resolved_causal_base, tx.causal_base, predecessor_resolved_causal_base,
|
|
1435
|
+
tx.replica_id, tx.replica_counter - 1
|
|
1436
|
+
)
|
|
1437
|
+
OR NOT opengeni_private.editable_artifact_frontier_merge_dot_equals(
|
|
1438
|
+
tx.resulting_causal_frontier, parent_checkpoint.causal_frontier,
|
|
1439
|
+
tx.resolved_causal_base, tx.replica_id, tx.replica_counter
|
|
1440
|
+
)
|
|
1441
|
+
OR NOT opengeni_private.editable_artifact_frontier_dominates(
|
|
1442
|
+
parent_checkpoint.causal_frontier, tx.resolved_causal_base
|
|
1443
|
+
)
|
|
1444
|
+
THEN
|
|
1445
|
+
RAISE EXCEPTION 'transaction causal frontiers do not match parent, predecessor, and dot'
|
|
1446
|
+
USING ERRCODE = '23514';
|
|
1447
|
+
END IF;
|
|
1448
|
+
ELSIF parent_checkpoint.native_revision <> tx.prior_native_revision THEN
|
|
1449
|
+
RAISE EXCEPTION 'serialized transaction native revision differs from its parent checkpoint'
|
|
1450
|
+
USING ERRCODE = '23514';
|
|
1451
|
+
END IF;
|
|
1452
|
+
|
|
1453
|
+
SELECT count(*) INTO operation_rows
|
|
1454
|
+
FROM editable_artifact_operations operation
|
|
1455
|
+
WHERE operation.account_id = tx.account_id
|
|
1456
|
+
AND operation.workspace_id = tx.workspace_id
|
|
1457
|
+
AND operation.artifact_id = tx.artifact_id
|
|
1458
|
+
AND operation.transaction_id = tx.id;
|
|
1459
|
+
IF (tx.modality = 'spreadsheet' AND operation_rows <> tx.operation_count)
|
|
1460
|
+
OR (tx.modality <> 'spreadsheet' AND operation_rows <> 0)
|
|
1461
|
+
THEN
|
|
1462
|
+
RAISE EXCEPTION 'transaction operation coverage is incomplete'
|
|
1463
|
+
USING ERRCODE = '23514';
|
|
1464
|
+
END IF;
|
|
1465
|
+
|
|
1466
|
+
IF tx.modality = 'spreadsheet' AND EXISTS (
|
|
1467
|
+
SELECT 1
|
|
1468
|
+
FROM editable_artifact_operations operation
|
|
1469
|
+
WHERE operation.account_id = tx.account_id
|
|
1470
|
+
AND operation.workspace_id = tx.workspace_id
|
|
1471
|
+
AND operation.artifact_id = tx.artifact_id
|
|
1472
|
+
AND operation.transaction_id = tx.id
|
|
1473
|
+
AND (
|
|
1474
|
+
operation.sequence <> tx.sequence_start + operation.operation_index
|
|
1475
|
+
OR operation.operation_index >= tx.operation_count
|
|
1476
|
+
OR operation.operation_id <> tx.operation_ids ->> operation.operation_index
|
|
1477
|
+
OR operation.dot_replica_id <> tx.replica_id
|
|
1478
|
+
OR operation.dot_counter <> tx.replica_counter
|
|
1479
|
+
OR operation.actor_key <> tx.actor_key
|
|
1480
|
+
OR operation.created_at <> tx.committed_at
|
|
1481
|
+
)
|
|
1482
|
+
) THEN
|
|
1483
|
+
RAISE EXCEPTION 'operation metadata differs from canonical transaction projection'
|
|
1484
|
+
USING ERRCODE = '23514';
|
|
1485
|
+
END IF;
|
|
1486
|
+
|
|
1487
|
+
SELECT count(*) INTO undo_rows
|
|
1488
|
+
FROM editable_artifact_undo_claims claim
|
|
1489
|
+
WHERE claim.account_id = tx.account_id
|
|
1490
|
+
AND claim.workspace_id = tx.workspace_id
|
|
1491
|
+
AND claim.artifact_id = tx.artifact_id
|
|
1492
|
+
AND claim.claiming_transaction_id = tx.id;
|
|
1493
|
+
IF (tx.modality <> 'spreadsheet' AND undo_rows <> 0)
|
|
1494
|
+
OR (tx.modality = 'spreadsheet' AND (
|
|
1495
|
+
undo_rows <> jsonb_array_length(tx.selective_undo_targets)
|
|
1496
|
+
OR EXISTS (
|
|
1497
|
+
WITH targets AS MATERIALIZED (
|
|
1498
|
+
SELECT operation_id
|
|
1499
|
+
FROM jsonb_array_elements_text(tx.selective_undo_targets) target(operation_id)
|
|
1500
|
+
), frontier AS MATERIALIZED (
|
|
1501
|
+
SELECT entry->>'replicaId' AS replica_id,
|
|
1502
|
+
(entry->>'counter')::bigint AS counter
|
|
1503
|
+
FROM jsonb_array_elements(tx.resolved_causal_base) AS entries(entry)
|
|
1504
|
+
)
|
|
1505
|
+
SELECT 1
|
|
1506
|
+
FROM targets target
|
|
1507
|
+
LEFT JOIN editable_artifact_undo_claims claim
|
|
1508
|
+
ON claim.account_id = tx.account_id
|
|
1509
|
+
AND claim.workspace_id = tx.workspace_id
|
|
1510
|
+
AND claim.artifact_id = tx.artifact_id
|
|
1511
|
+
AND claim.claiming_transaction_id = tx.id
|
|
1512
|
+
AND claim.target_operation_id = target.operation_id
|
|
1513
|
+
LEFT JOIN editable_artifact_operations operation
|
|
1514
|
+
ON operation.account_id = tx.account_id
|
|
1515
|
+
AND operation.workspace_id = tx.workspace_id
|
|
1516
|
+
AND operation.artifact_id = tx.artifact_id
|
|
1517
|
+
AND operation.operation_id = target.operation_id
|
|
1518
|
+
LEFT JOIN frontier
|
|
1519
|
+
ON frontier.replica_id = operation.dot_replica_id
|
|
1520
|
+
WHERE claim.target_operation_id IS NULL
|
|
1521
|
+
OR operation.operation_id IS NULL
|
|
1522
|
+
OR operation.actor_key <> tx.actor_key
|
|
1523
|
+
OR operation.sequence > tx.parent_head_sequence
|
|
1524
|
+
OR coalesce(frontier.counter, 0) < operation.dot_counter
|
|
1525
|
+
)))
|
|
1526
|
+
THEN
|
|
1527
|
+
RAISE EXCEPTION 'selective undo claims do not exactly match durable targets'
|
|
1528
|
+
USING ERRCODE = '23514';
|
|
1529
|
+
END IF;
|
|
1530
|
+
|
|
1531
|
+
IF NOT EXISTS (
|
|
1532
|
+
SELECT 1 FROM editable_artifact_sequence_checkpoints checkpoint
|
|
1533
|
+
WHERE checkpoint.account_id = tx.account_id
|
|
1534
|
+
AND checkpoint.workspace_id = tx.workspace_id
|
|
1535
|
+
AND checkpoint.artifact_id = tx.artifact_id
|
|
1536
|
+
AND checkpoint.head_sequence = tx.sequence_end
|
|
1537
|
+
AND checkpoint.transaction_id = tx.id
|
|
1538
|
+
AND checkpoint.modality = tx.modality
|
|
1539
|
+
AND ((tx.modality = 'spreadsheet'
|
|
1540
|
+
AND checkpoint.causal_frontier = tx.resulting_causal_frontier
|
|
1541
|
+
AND checkpoint.native_revision IS NULL)
|
|
1542
|
+
OR (tx.modality IN ('document', 'presentation')
|
|
1543
|
+
AND checkpoint.causal_frontier IS NULL
|
|
1544
|
+
AND checkpoint.native_revision = tx.native_revision))
|
|
1545
|
+
AND checkpoint.state_hash = tx.state_hash
|
|
1546
|
+
) THEN
|
|
1547
|
+
RAISE EXCEPTION 'transaction terminal checkpoint is missing or inconsistent'
|
|
1548
|
+
USING ERRCODE = '23514';
|
|
1549
|
+
END IF;
|
|
1550
|
+
|
|
1551
|
+
IF NOT EXISTS (
|
|
1552
|
+
SELECT 1 FROM editable_artifact_live_outbox outbox
|
|
1553
|
+
WHERE outbox.account_id = tx.account_id
|
|
1554
|
+
AND outbox.workspace_id = tx.workspace_id
|
|
1555
|
+
AND outbox.artifact_id = tx.artifact_id
|
|
1556
|
+
AND outbox.event_kind = 'transaction_committed'
|
|
1557
|
+
AND outbox.transaction_id = tx.id
|
|
1558
|
+
) THEN
|
|
1559
|
+
RAISE EXCEPTION 'transaction committed outbox event is missing'
|
|
1560
|
+
USING ERRCODE = '23514';
|
|
1561
|
+
END IF;
|
|
1562
|
+
|
|
1563
|
+
IF NOT EXISTS (
|
|
1564
|
+
SELECT 1 FROM editable_artifact_idempotency_receipts receipt
|
|
1565
|
+
WHERE receipt.account_id = tx.account_id
|
|
1566
|
+
AND receipt.workspace_id = tx.workspace_id
|
|
1567
|
+
AND receipt.artifact_id = tx.artifact_id
|
|
1568
|
+
AND receipt.operation_kind = 'edit'
|
|
1569
|
+
AND receipt.authority_key = tx.actor_key
|
|
1570
|
+
AND receipt.idempotency_key = tx.client_transaction_id
|
|
1571
|
+
AND receipt.request_hash = tx.request_hash
|
|
1572
|
+
AND receipt.resource_type = 'transaction'
|
|
1573
|
+
AND receipt.resource_id = tx.id
|
|
1574
|
+
AND receipt.server_transaction_id = tx.id
|
|
1575
|
+
) THEN
|
|
1576
|
+
RAISE EXCEPTION 'transaction idempotency receipt is missing or inconsistent'
|
|
1577
|
+
USING ERRCODE = '23514';
|
|
1578
|
+
END IF;
|
|
1579
|
+
|
|
1580
|
+
IF NOT EXISTS (
|
|
1581
|
+
SELECT 1 FROM editable_artifacts artifact
|
|
1582
|
+
WHERE artifact.account_id = tx.account_id
|
|
1583
|
+
AND artifact.workspace_id = tx.workspace_id
|
|
1584
|
+
AND artifact.id = tx.artifact_id
|
|
1585
|
+
AND artifact.lifecycle_state = 'active'
|
|
1586
|
+
AND artifact.head_sequence >= tx.sequence_end
|
|
1587
|
+
) THEN
|
|
1588
|
+
RAISE EXCEPTION 'artifact head was not advanced through committed transaction'
|
|
1589
|
+
USING ERRCODE = '23514';
|
|
1590
|
+
END IF;
|
|
1591
|
+
RETURN NULL;
|
|
1592
|
+
END;
|
|
1593
|
+
$body$;
|
|
1594
|
+
|
|
1595
|
+
CREATE CONSTRAINT TRIGGER editable_artifact_transactions_commit_guard
|
|
1596
|
+
AFTER INSERT ON "editable_artifact_transactions"
|
|
1597
|
+
DEFERRABLE INITIALLY DEFERRED FOR EACH ROW
|
|
1598
|
+
EXECUTE FUNCTION opengeni_private.validate_editable_artifact_transaction_commit();
|
|
1599
|
+
|
|
1600
|
+
-- The aggregate validator above runs exactly once per transaction header. These
|
|
1601
|
+
-- constant-cost guards prevent immutable children from being appended after the
|
|
1602
|
+
-- owning transaction has published its head, without rescanning the transaction
|
|
1603
|
+
-- once per operation projection row.
|
|
1604
|
+
CREATE OR REPLACE FUNCTION opengeni_private.guard_editable_artifact_transaction_child_insert()
|
|
1605
|
+
RETURNS trigger
|
|
1606
|
+
LANGUAGE plpgsql
|
|
1607
|
+
SET search_path = pg_catalog, pg_temp
|
|
1608
|
+
AS $body$
|
|
1609
|
+
DECLARE owner_transaction_id text;
|
|
1610
|
+
DECLARE owner_sequence_end bigint;
|
|
1611
|
+
DECLARE owner_modality text;
|
|
1612
|
+
DECLARE published_head bigint;
|
|
1613
|
+
BEGIN
|
|
1614
|
+
PERFORM set_config(
|
|
1615
|
+
'search_path', 'pg_catalog,' || quote_ident(TG_TABLE_SCHEMA) || ',pg_temp', true
|
|
1616
|
+
);
|
|
1617
|
+
IF TG_TABLE_NAME = 'editable_artifact_operations' THEN
|
|
1618
|
+
owner_transaction_id := NEW.transaction_id;
|
|
1619
|
+
ELSIF TG_TABLE_NAME = 'editable_artifact_idempotency_receipts' THEN
|
|
1620
|
+
IF NEW.operation_kind <> 'edit' THEN RETURN NEW; END IF;
|
|
1621
|
+
owner_transaction_id := NEW.server_transaction_id;
|
|
1622
|
+
ELSIF TG_TABLE_NAME = 'editable_artifact_undo_claims' THEN
|
|
1623
|
+
owner_transaction_id := NEW.claiming_transaction_id;
|
|
1624
|
+
ELSIF TG_TABLE_NAME = 'editable_artifact_sequence_checkpoints' THEN
|
|
1625
|
+
IF NEW.transaction_id IS NULL THEN RETURN NEW; END IF;
|
|
1626
|
+
owner_transaction_id := NEW.transaction_id;
|
|
1627
|
+
ELSIF TG_TABLE_NAME = 'editable_artifact_live_outbox' THEN
|
|
1628
|
+
IF NEW.event_kind <> 'transaction_committed' THEN RETURN NEW; END IF;
|
|
1629
|
+
owner_transaction_id := NEW.transaction_id;
|
|
1630
|
+
END IF;
|
|
1631
|
+
|
|
1632
|
+
SELECT transaction.sequence_end, transaction.modality
|
|
1633
|
+
INTO owner_sequence_end, owner_modality
|
|
1634
|
+
FROM editable_artifact_transactions transaction
|
|
1635
|
+
WHERE transaction.account_id = NEW.account_id
|
|
1636
|
+
AND transaction.workspace_id = NEW.workspace_id
|
|
1637
|
+
AND transaction.artifact_id = NEW.artifact_id
|
|
1638
|
+
AND transaction.id = owner_transaction_id;
|
|
1639
|
+
IF NOT FOUND THEN
|
|
1640
|
+
RAISE EXCEPTION 'transaction child must follow its transaction header'
|
|
1641
|
+
USING ERRCODE = '23503';
|
|
1642
|
+
END IF;
|
|
1643
|
+
IF TG_TABLE_NAME IN ('editable_artifact_operations', 'editable_artifact_undo_claims')
|
|
1644
|
+
AND owner_modality <> 'spreadsheet'
|
|
1645
|
+
THEN
|
|
1646
|
+
RAISE EXCEPTION 'serialized artifact transactions cannot receive CRDT children'
|
|
1647
|
+
USING ERRCODE = '23514';
|
|
1648
|
+
END IF;
|
|
1649
|
+
|
|
1650
|
+
SELECT artifact.head_sequence INTO published_head
|
|
1651
|
+
FROM editable_artifacts artifact
|
|
1652
|
+
WHERE artifact.account_id = NEW.account_id
|
|
1653
|
+
AND artifact.workspace_id = NEW.workspace_id
|
|
1654
|
+
AND artifact.id = NEW.artifact_id;
|
|
1655
|
+
IF NOT FOUND THEN
|
|
1656
|
+
RAISE EXCEPTION 'transaction child artifact is missing'
|
|
1657
|
+
USING ERRCODE = '23503';
|
|
1658
|
+
END IF;
|
|
1659
|
+
IF published_head >= owner_sequence_end THEN
|
|
1660
|
+
RAISE EXCEPTION 'committed transaction history cannot receive additional children'
|
|
1661
|
+
USING ERRCODE = '55000';
|
|
1662
|
+
END IF;
|
|
1663
|
+
RETURN NEW;
|
|
1664
|
+
END;
|
|
1665
|
+
$body$;
|
|
1666
|
+
|
|
1667
|
+
CREATE TRIGGER editable_artifact_operations_insert_guard
|
|
1668
|
+
BEFORE INSERT ON "editable_artifact_operations"
|
|
1669
|
+
FOR EACH ROW EXECUTE FUNCTION opengeni_private.guard_editable_artifact_transaction_child_insert();
|
|
1670
|
+
CREATE TRIGGER editable_artifact_receipts_insert_guard
|
|
1671
|
+
BEFORE INSERT ON "editable_artifact_idempotency_receipts"
|
|
1672
|
+
FOR EACH ROW EXECUTE FUNCTION opengeni_private.guard_editable_artifact_transaction_child_insert();
|
|
1673
|
+
CREATE TRIGGER editable_artifact_undo_claims_insert_guard
|
|
1674
|
+
BEFORE INSERT ON "editable_artifact_undo_claims"
|
|
1675
|
+
FOR EACH ROW EXECUTE FUNCTION opengeni_private.guard_editable_artifact_transaction_child_insert();
|
|
1676
|
+
CREATE TRIGGER editable_artifact_checkpoints_insert_guard
|
|
1677
|
+
BEFORE INSERT ON "editable_artifact_sequence_checkpoints"
|
|
1678
|
+
FOR EACH ROW EXECUTE FUNCTION opengeni_private.guard_editable_artifact_transaction_child_insert();
|
|
1679
|
+
CREATE TRIGGER editable_artifact_live_outbox_transaction_insert_guard
|
|
1680
|
+
BEFORE INSERT ON "editable_artifact_live_outbox"
|
|
1681
|
+
FOR EACH ROW EXECUTE FUNCTION opengeni_private.guard_editable_artifact_transaction_child_insert();
|
|
1682
|
+
|
|
1683
|
+
CREATE OR REPLACE FUNCTION opengeni_private.validate_editable_artifact_projection()
|
|
1684
|
+
RETURNS trigger
|
|
1685
|
+
LANGUAGE plpgsql
|
|
1686
|
+
SECURITY DEFINER
|
|
1687
|
+
SET search_path = pg_catalog, pg_temp
|
|
1688
|
+
AS $body$
|
|
1689
|
+
DECLARE checkpoint record;
|
|
1690
|
+
DECLARE blob record;
|
|
1691
|
+
DECLARE tx record;
|
|
1692
|
+
DECLARE snapshot record;
|
|
1693
|
+
DECLARE job record;
|
|
1694
|
+
DECLARE artifact_modality text;
|
|
1695
|
+
BEGIN
|
|
1696
|
+
PERFORM set_config(
|
|
1697
|
+
'search_path', 'pg_catalog,' || quote_ident(TG_TABLE_SCHEMA) || ',pg_temp', true
|
|
1698
|
+
);
|
|
1699
|
+
IF TG_TABLE_NAME = 'editable_artifacts' THEN
|
|
1700
|
+
SELECT * INTO checkpoint FROM editable_artifact_sequence_checkpoints candidate
|
|
1701
|
+
WHERE candidate.account_id = NEW.account_id
|
|
1702
|
+
AND candidate.workspace_id = NEW.workspace_id
|
|
1703
|
+
AND candidate.artifact_id = NEW.id
|
|
1704
|
+
AND candidate.head_sequence = NEW.head_sequence;
|
|
1705
|
+
IF NOT FOUND OR checkpoint.modality <> NEW.modality
|
|
1706
|
+
OR checkpoint.state_hash <> NEW.state_hash
|
|
1707
|
+
OR (NEW.modality = 'spreadsheet'
|
|
1708
|
+
AND checkpoint.causal_frontier IS DISTINCT FROM NEW.causal_frontier)
|
|
1709
|
+
OR (NEW.modality IN ('document', 'presentation')
|
|
1710
|
+
AND checkpoint.native_revision IS NULL)
|
|
1711
|
+
THEN
|
|
1712
|
+
RAISE EXCEPTION 'artifact head lacks an exact durable checkpoint'
|
|
1713
|
+
USING ERRCODE = '23514';
|
|
1714
|
+
END IF;
|
|
1715
|
+
IF TG_OP = 'INSERT' THEN
|
|
1716
|
+
IF NEW.head_sequence <> 0 OR NEW.current_snapshot_id IS NULL THEN
|
|
1717
|
+
RAISE EXCEPTION 'editable artifact must begin at its genesis snapshot'
|
|
1718
|
+
USING ERRCODE = '23514';
|
|
1719
|
+
END IF;
|
|
1720
|
+
IF NOT EXISTS (
|
|
1721
|
+
SELECT 1 FROM editable_artifact_idempotency_receipts receipt
|
|
1722
|
+
WHERE receipt.account_id = NEW.account_id
|
|
1723
|
+
AND receipt.workspace_id = NEW.workspace_id
|
|
1724
|
+
AND receipt.artifact_id = NEW.id
|
|
1725
|
+
AND receipt.operation_kind IN ('create', 'import')
|
|
1726
|
+
AND receipt.resource_type = 'artifact'
|
|
1727
|
+
AND receipt.resource_id = NEW.id
|
|
1728
|
+
AND opengeni_private.editable_artifact_object_has_exact_keys(
|
|
1729
|
+
receipt.result, ARRAY['artifactId', 'genesisSnapshotId', 'schemaVersion']
|
|
1730
|
+
)
|
|
1731
|
+
AND jsonb_typeof(receipt.result->'schemaVersion') = 'number'
|
|
1732
|
+
AND receipt.result->>'schemaVersion' = '1'
|
|
1733
|
+
AND jsonb_typeof(receipt.result->'artifactId') = 'string'
|
|
1734
|
+
AND receipt.result->>'artifactId' = NEW.id
|
|
1735
|
+
AND jsonb_typeof(receipt.result->'genesisSnapshotId') = 'string'
|
|
1736
|
+
AND receipt.result->>'genesisSnapshotId' = NEW.current_snapshot_id
|
|
1737
|
+
) THEN
|
|
1738
|
+
RAISE EXCEPTION 'editable artifact exact genesis receipt is missing'
|
|
1739
|
+
USING ERRCODE = '23514';
|
|
1740
|
+
END IF;
|
|
1741
|
+
END IF;
|
|
1742
|
+
IF NEW.current_snapshot_id IS NOT NULL AND NOT EXISTS (
|
|
1743
|
+
SELECT 1 FROM editable_artifact_snapshots current_snapshot
|
|
1744
|
+
WHERE current_snapshot.account_id = NEW.account_id
|
|
1745
|
+
AND current_snapshot.workspace_id = NEW.workspace_id
|
|
1746
|
+
AND current_snapshot.artifact_id = NEW.id
|
|
1747
|
+
AND current_snapshot.id = NEW.current_snapshot_id
|
|
1748
|
+
AND current_snapshot.modality = NEW.modality
|
|
1749
|
+
AND current_snapshot.covered_head_sequence <= NEW.head_sequence
|
|
1750
|
+
) THEN
|
|
1751
|
+
RAISE EXCEPTION 'artifact current snapshot is ahead of or outside its head'
|
|
1752
|
+
USING ERRCODE = '23514';
|
|
1753
|
+
END IF;
|
|
1754
|
+
ELSIF TG_TABLE_NAME = 'editable_artifact_snapshots' THEN
|
|
1755
|
+
SELECT * INTO checkpoint FROM editable_artifact_sequence_checkpoints candidate
|
|
1756
|
+
WHERE candidate.account_id = NEW.account_id
|
|
1757
|
+
AND candidate.workspace_id = NEW.workspace_id
|
|
1758
|
+
AND candidate.artifact_id = NEW.artifact_id
|
|
1759
|
+
AND candidate.head_sequence = NEW.covered_head_sequence;
|
|
1760
|
+
SELECT * INTO blob FROM editable_artifact_blob_refs candidate
|
|
1761
|
+
WHERE candidate.account_id = NEW.account_id
|
|
1762
|
+
AND candidate.workspace_id = NEW.workspace_id
|
|
1763
|
+
AND candidate.artifact_id = NEW.artifact_id
|
|
1764
|
+
AND candidate.id = NEW.blob_ref_id;
|
|
1765
|
+
IF checkpoint.head_sequence IS NULL
|
|
1766
|
+
OR checkpoint.modality <> NEW.modality
|
|
1767
|
+
OR (NEW.modality = 'spreadsheet'
|
|
1768
|
+
AND checkpoint.causal_frontier IS DISTINCT FROM NEW.covered_causal_frontier)
|
|
1769
|
+
OR (NEW.modality IN ('document', 'presentation')
|
|
1770
|
+
AND checkpoint.native_revision IS DISTINCT FROM NEW.native_revision)
|
|
1771
|
+
OR checkpoint.state_hash <> NEW.state_hash
|
|
1772
|
+
OR blob.id IS NULL OR blob.kind <> 'snapshot'
|
|
1773
|
+
OR blob.byte_size <> NEW.byte_size OR blob.content_hash <> NEW.content_hash
|
|
1774
|
+
OR blob.mime_type <> NEW.mime_type
|
|
1775
|
+
THEN
|
|
1776
|
+
RAISE EXCEPTION 'snapshot does not exactly match its checkpoint and blob facts'
|
|
1777
|
+
USING ERRCODE = '23514';
|
|
1778
|
+
END IF;
|
|
1779
|
+
IF NOT EXISTS (
|
|
1780
|
+
SELECT 1 FROM editable_artifact_live_outbox outbox
|
|
1781
|
+
WHERE outbox.account_id = NEW.account_id
|
|
1782
|
+
AND outbox.workspace_id = NEW.workspace_id
|
|
1783
|
+
AND outbox.artifact_id = NEW.artifact_id
|
|
1784
|
+
AND outbox.event_kind = 'snapshot_published'
|
|
1785
|
+
AND outbox.snapshot_id = NEW.id
|
|
1786
|
+
) THEN
|
|
1787
|
+
RAISE EXCEPTION 'snapshot published outbox event is missing'
|
|
1788
|
+
USING ERRCODE = '23514';
|
|
1789
|
+
END IF;
|
|
1790
|
+
IF NOT EXISTS (
|
|
1791
|
+
SELECT 1 FROM editable_artifacts artifact
|
|
1792
|
+
WHERE artifact.account_id = NEW.account_id
|
|
1793
|
+
AND artifact.workspace_id = NEW.workspace_id
|
|
1794
|
+
AND artifact.id = NEW.artifact_id
|
|
1795
|
+
AND artifact.modality = NEW.modality
|
|
1796
|
+
AND artifact.current_snapshot_id = NEW.id
|
|
1797
|
+
) THEN
|
|
1798
|
+
RAISE EXCEPTION 'published snapshot is not the artifact current snapshot'
|
|
1799
|
+
USING ERRCODE = '23514';
|
|
1800
|
+
END IF;
|
|
1801
|
+
ELSIF TG_TABLE_NAME = 'editable_artifact_versions' THEN
|
|
1802
|
+
SELECT artifact.modality INTO artifact_modality
|
|
1803
|
+
FROM editable_artifacts artifact
|
|
1804
|
+
WHERE artifact.account_id = NEW.account_id
|
|
1805
|
+
AND artifact.workspace_id = NEW.workspace_id
|
|
1806
|
+
AND artifact.id = NEW.artifact_id;
|
|
1807
|
+
SELECT * INTO checkpoint FROM editable_artifact_sequence_checkpoints candidate
|
|
1808
|
+
WHERE candidate.account_id = NEW.account_id
|
|
1809
|
+
AND candidate.workspace_id = NEW.workspace_id
|
|
1810
|
+
AND candidate.artifact_id = NEW.artifact_id
|
|
1811
|
+
AND candidate.head_sequence = NEW.head_sequence;
|
|
1812
|
+
IF artifact_modality <> 'spreadsheet'
|
|
1813
|
+
OR checkpoint.head_sequence IS NULL
|
|
1814
|
+
OR checkpoint.causal_frontier <> NEW.causal_frontier
|
|
1815
|
+
OR checkpoint.state_hash <> NEW.state_hash
|
|
1816
|
+
OR (NEW.snapshot_id IS NOT NULL AND NOT EXISTS (
|
|
1817
|
+
SELECT 1 FROM editable_artifact_snapshots version_snapshot
|
|
1818
|
+
WHERE version_snapshot.account_id = NEW.account_id
|
|
1819
|
+
AND version_snapshot.workspace_id = NEW.workspace_id
|
|
1820
|
+
AND version_snapshot.artifact_id = NEW.artifact_id
|
|
1821
|
+
AND version_snapshot.id = NEW.snapshot_id
|
|
1822
|
+
AND version_snapshot.covered_head_sequence <= NEW.head_sequence
|
|
1823
|
+
AND opengeni_private.editable_artifact_frontier_dominates(
|
|
1824
|
+
NEW.causal_frontier, version_snapshot.covered_causal_frontier
|
|
1825
|
+
)
|
|
1826
|
+
AND (version_snapshot.covered_head_sequence < NEW.head_sequence OR (
|
|
1827
|
+
version_snapshot.covered_causal_frontier = NEW.causal_frontier
|
|
1828
|
+
AND version_snapshot.state_hash = NEW.state_hash
|
|
1829
|
+
))
|
|
1830
|
+
))
|
|
1831
|
+
THEN
|
|
1832
|
+
RAISE EXCEPTION 'artifact version does not exactly identify a durable checkpoint'
|
|
1833
|
+
USING ERRCODE = '23514';
|
|
1834
|
+
END IF;
|
|
1835
|
+
ELSIF TG_TABLE_NAME = 'editable_artifact_materialization_results' THEN
|
|
1836
|
+
SELECT * INTO blob FROM editable_artifact_blob_refs candidate
|
|
1837
|
+
WHERE candidate.account_id = NEW.account_id
|
|
1838
|
+
AND candidate.workspace_id = NEW.workspace_id
|
|
1839
|
+
AND candidate.artifact_id = NEW.artifact_id
|
|
1840
|
+
AND candidate.id = NEW.blob_ref_id;
|
|
1841
|
+
SELECT * INTO job FROM editable_artifact_materialization_jobs candidate
|
|
1842
|
+
WHERE candidate.account_id = NEW.account_id
|
|
1843
|
+
AND candidate.workspace_id = NEW.workspace_id
|
|
1844
|
+
AND candidate.artifact_id = NEW.artifact_id
|
|
1845
|
+
AND candidate.id = NEW.job_id;
|
|
1846
|
+
IF blob.id IS NULL OR blob.kind <> 'materialization'
|
|
1847
|
+
OR blob.byte_size <> NEW.byte_size OR blob.content_hash <> NEW.content_hash
|
|
1848
|
+
OR blob.mime_type <> NEW.mime_type
|
|
1849
|
+
OR job.id IS NULL OR job.state <> 'succeeded'
|
|
1850
|
+
OR NEW.mime_type <> (CASE job.format
|
|
1851
|
+
WHEN 'xlsx' THEN 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
1852
|
+
WHEN 'pptx' THEN 'application/vnd.openxmlformats-officedocument.presentationml.presentation'
|
|
1853
|
+
WHEN 'docx' THEN 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
|
1854
|
+
WHEN 'pdf' THEN 'application/pdf'
|
|
1855
|
+
WHEN 'png' THEN 'image/png'
|
|
1856
|
+
WHEN 'webp' THEN 'image/webp'
|
|
1857
|
+
END)
|
|
1858
|
+
THEN
|
|
1859
|
+
RAISE EXCEPTION 'materialization result does not match a succeeded job and blob facts'
|
|
1860
|
+
USING ERRCODE = '23514';
|
|
1861
|
+
END IF;
|
|
1862
|
+
ELSIF TG_TABLE_NAME = 'editable_artifact_materialization_jobs' THEN
|
|
1863
|
+
SELECT * INTO checkpoint FROM editable_artifact_sequence_checkpoints candidate
|
|
1864
|
+
WHERE candidate.account_id = NEW.account_id
|
|
1865
|
+
AND candidate.workspace_id = NEW.workspace_id
|
|
1866
|
+
AND candidate.artifact_id = NEW.artifact_id
|
|
1867
|
+
AND candidate.head_sequence = NEW.target_head_sequence;
|
|
1868
|
+
SELECT * INTO snapshot FROM editable_artifact_snapshots candidate
|
|
1869
|
+
WHERE candidate.account_id = NEW.account_id
|
|
1870
|
+
AND candidate.workspace_id = NEW.workspace_id
|
|
1871
|
+
AND candidate.artifact_id = NEW.artifact_id
|
|
1872
|
+
AND candidate.id = NEW.input_snapshot_id;
|
|
1873
|
+
IF snapshot.id IS NOT NULL THEN
|
|
1874
|
+
SELECT * INTO blob FROM editable_artifact_blob_refs candidate
|
|
1875
|
+
WHERE candidate.account_id = snapshot.account_id
|
|
1876
|
+
AND candidate.workspace_id = snapshot.workspace_id
|
|
1877
|
+
AND candidate.artifact_id = snapshot.artifact_id
|
|
1878
|
+
AND candidate.id = snapshot.blob_ref_id;
|
|
1879
|
+
END IF;
|
|
1880
|
+
SELECT artifact.modality INTO artifact_modality
|
|
1881
|
+
FROM editable_artifacts artifact
|
|
1882
|
+
WHERE artifact.account_id = NEW.account_id
|
|
1883
|
+
AND artifact.workspace_id = NEW.workspace_id
|
|
1884
|
+
AND artifact.id = NEW.artifact_id;
|
|
1885
|
+
IF checkpoint.head_sequence IS NULL OR checkpoint.state_hash <> NEW.state_hash
|
|
1886
|
+
OR snapshot.id IS NULL
|
|
1887
|
+
OR snapshot.covered_head_sequence <> NEW.target_head_sequence
|
|
1888
|
+
OR snapshot.state_hash <> NEW.state_hash
|
|
1889
|
+
OR blob.id IS NULL OR blob.kind <> 'snapshot'
|
|
1890
|
+
OR blob.id <> snapshot.blob_ref_id
|
|
1891
|
+
OR blob.byte_size <> snapshot.byte_size
|
|
1892
|
+
OR blob.content_hash <> snapshot.content_hash
|
|
1893
|
+
OR blob.mime_type <> snapshot.mime_type
|
|
1894
|
+
OR artifact_modality IS NULL
|
|
1895
|
+
OR NOT ((artifact_modality = 'spreadsheet' AND NEW.format IN ('xlsx', 'pdf', 'png', 'webp'))
|
|
1896
|
+
OR (artifact_modality = 'presentation' AND NEW.format IN ('pptx', 'pdf', 'png', 'webp'))
|
|
1897
|
+
OR (artifact_modality = 'document' AND NEW.format IN ('docx', 'pdf', 'png', 'webp')))
|
|
1898
|
+
OR (NEW.version_id IS NOT NULL AND NOT EXISTS (
|
|
1899
|
+
SELECT 1 FROM editable_artifact_versions version
|
|
1900
|
+
WHERE version.account_id = NEW.account_id
|
|
1901
|
+
AND version.workspace_id = NEW.workspace_id
|
|
1902
|
+
AND version.artifact_id = NEW.artifact_id
|
|
1903
|
+
AND version.id = NEW.version_id
|
|
1904
|
+
AND version.head_sequence = NEW.target_head_sequence
|
|
1905
|
+
AND version.state_hash = NEW.state_hash
|
|
1906
|
+
))
|
|
1907
|
+
THEN
|
|
1908
|
+
RAISE EXCEPTION 'materialization job manifest differs from its pinned source/checkpoint/version'
|
|
1909
|
+
USING ERRCODE = '23514';
|
|
1910
|
+
END IF;
|
|
1911
|
+
IF NOT EXISTS (
|
|
1912
|
+
SELECT 1 FROM editable_artifact_idempotency_receipts receipt
|
|
1913
|
+
WHERE receipt.account_id = NEW.account_id
|
|
1914
|
+
AND receipt.workspace_id = NEW.workspace_id
|
|
1915
|
+
AND receipt.artifact_id = NEW.artifact_id
|
|
1916
|
+
AND receipt.operation_kind = 'materialize'
|
|
1917
|
+
AND receipt.resource_type = 'materialization_job'
|
|
1918
|
+
AND receipt.resource_id = NEW.id
|
|
1919
|
+
AND receipt.server_transaction_id IS NULL
|
|
1920
|
+
) THEN
|
|
1921
|
+
RAISE EXCEPTION 'materialization job idempotency receipt is missing'
|
|
1922
|
+
USING ERRCODE = '23514';
|
|
1923
|
+
END IF;
|
|
1924
|
+
IF NEW.state = 'succeeded' AND NOT EXISTS (
|
|
1925
|
+
SELECT 1 FROM editable_artifact_materialization_results result
|
|
1926
|
+
WHERE result.account_id = NEW.account_id
|
|
1927
|
+
AND result.workspace_id = NEW.workspace_id
|
|
1928
|
+
AND result.artifact_id = NEW.artifact_id
|
|
1929
|
+
AND result.job_id = NEW.id
|
|
1930
|
+
) THEN
|
|
1931
|
+
RAISE EXCEPTION 'succeeded materialization job lacks its immutable result'
|
|
1932
|
+
USING ERRCODE = '23514';
|
|
1933
|
+
END IF;
|
|
1934
|
+
ELSIF TG_TABLE_NAME = 'editable_artifact_replica_leases' THEN
|
|
1935
|
+
SELECT artifact.modality INTO artifact_modality
|
|
1936
|
+
FROM editable_artifacts artifact
|
|
1937
|
+
WHERE artifact.account_id = NEW.account_id
|
|
1938
|
+
AND artifact.workspace_id = NEW.workspace_id
|
|
1939
|
+
AND artifact.id = NEW.artifact_id;
|
|
1940
|
+
SELECT * INTO checkpoint FROM editable_artifact_sequence_checkpoints candidate
|
|
1941
|
+
WHERE candidate.account_id = NEW.account_id
|
|
1942
|
+
AND candidate.workspace_id = NEW.workspace_id
|
|
1943
|
+
AND candidate.artifact_id = NEW.artifact_id
|
|
1944
|
+
AND candidate.head_sequence = NEW.applied_head_sequence;
|
|
1945
|
+
IF artifact_modality IS NULL OR NEW.modality <> artifact_modality
|
|
1946
|
+
OR checkpoint.head_sequence IS NULL OR checkpoint.modality <> NEW.modality
|
|
1947
|
+
OR (NEW.modality = 'spreadsheet'
|
|
1948
|
+
AND checkpoint.causal_frontier IS DISTINCT FROM NEW.causal_frontier)
|
|
1949
|
+
OR (NEW.modality IN ('document', 'presentation')
|
|
1950
|
+
AND checkpoint.native_revision IS DISTINCT FROM NEW.native_revision)
|
|
1951
|
+
THEN
|
|
1952
|
+
RAISE EXCEPTION 'replica lease cursor differs from its durable checkpoint'
|
|
1953
|
+
USING ERRCODE = '23514';
|
|
1954
|
+
END IF;
|
|
1955
|
+
ELSIF TG_TABLE_NAME = 'editable_artifact_idempotency_receipts' THEN
|
|
1956
|
+
IF (NEW.resource_type = 'artifact' AND NOT EXISTS (
|
|
1957
|
+
SELECT 1 FROM editable_artifacts artifact
|
|
1958
|
+
WHERE artifact.account_id = NEW.account_id
|
|
1959
|
+
AND artifact.workspace_id = NEW.workspace_id
|
|
1960
|
+
AND artifact.id = NEW.artifact_id
|
|
1961
|
+
AND artifact.id = NEW.resource_id
|
|
1962
|
+
)) OR (NEW.resource_type = 'snapshot' AND NOT EXISTS (
|
|
1963
|
+
SELECT 1 FROM editable_artifact_snapshots receipt_snapshot
|
|
1964
|
+
WHERE receipt_snapshot.account_id = NEW.account_id
|
|
1965
|
+
AND receipt_snapshot.workspace_id = NEW.workspace_id
|
|
1966
|
+
AND receipt_snapshot.artifact_id = NEW.artifact_id
|
|
1967
|
+
AND receipt_snapshot.id = NEW.resource_id
|
|
1968
|
+
)) OR (NEW.resource_type = 'materialization_job' AND NOT EXISTS (
|
|
1969
|
+
SELECT 1 FROM editable_artifact_materialization_jobs receipt_job
|
|
1970
|
+
WHERE receipt_job.account_id = NEW.account_id
|
|
1971
|
+
AND receipt_job.workspace_id = NEW.workspace_id
|
|
1972
|
+
AND receipt_job.artifact_id = NEW.artifact_id
|
|
1973
|
+
AND receipt_job.id = NEW.resource_id
|
|
1974
|
+
))
|
|
1975
|
+
THEN
|
|
1976
|
+
RAISE EXCEPTION 'idempotency receipt resource does not exist in its exact scope'
|
|
1977
|
+
USING ERRCODE = '23514';
|
|
1978
|
+
END IF;
|
|
1979
|
+
IF NEW.operation_kind IN ('create', 'import') AND NOT EXISTS (
|
|
1980
|
+
SELECT 1 FROM editable_artifacts artifact
|
|
1981
|
+
WHERE artifact.account_id = NEW.account_id
|
|
1982
|
+
AND artifact.workspace_id = NEW.workspace_id
|
|
1983
|
+
AND artifact.id = NEW.artifact_id
|
|
1984
|
+
AND artifact.head_sequence = 0
|
|
1985
|
+
AND artifact.current_snapshot_id IS NOT NULL
|
|
1986
|
+
AND opengeni_private.editable_artifact_object_has_exact_keys(
|
|
1987
|
+
NEW.result, ARRAY['artifactId', 'genesisSnapshotId', 'schemaVersion']
|
|
1988
|
+
)
|
|
1989
|
+
AND jsonb_typeof(NEW.result->'schemaVersion') = 'number'
|
|
1990
|
+
AND NEW.result->>'schemaVersion' = '1'
|
|
1991
|
+
AND jsonb_typeof(NEW.result->'artifactId') = 'string'
|
|
1992
|
+
AND NEW.result->>'artifactId' = artifact.id
|
|
1993
|
+
AND jsonb_typeof(NEW.result->'genesisSnapshotId') = 'string'
|
|
1994
|
+
AND NEW.result->>'genesisSnapshotId' = artifact.current_snapshot_id
|
|
1995
|
+
) THEN
|
|
1996
|
+
RAISE EXCEPTION 'creation/import receipt differs from exact artifact genesis'
|
|
1997
|
+
USING ERRCODE = '23514';
|
|
1998
|
+
END IF;
|
|
1999
|
+
ELSIF TG_TABLE_NAME = 'editable_artifact_live_outbox' THEN
|
|
2000
|
+
IF NEW.event_kind = 'transaction_committed' THEN
|
|
2001
|
+
SELECT * INTO tx FROM editable_artifact_transactions candidate
|
|
2002
|
+
WHERE candidate.account_id = NEW.account_id
|
|
2003
|
+
AND candidate.workspace_id = NEW.workspace_id
|
|
2004
|
+
AND candidate.artifact_id = NEW.artifact_id
|
|
2005
|
+
AND candidate.id = NEW.transaction_id;
|
|
2006
|
+
IF tx.id IS NULL
|
|
2007
|
+
OR NEW.event->>'modality' <> tx.modality
|
|
2008
|
+
OR NEW.event->>'sequenceStart' <> tx.sequence_start::text
|
|
2009
|
+
OR NEW.event->>'sequenceEnd' <> tx.sequence_end::text
|
|
2010
|
+
OR NEW.event->>'stateHash' <> tx.state_hash
|
|
2011
|
+
OR (tx.modality = 'spreadsheet'
|
|
2012
|
+
AND NEW.event->>'operationProtocolVersion' <> tx.operation_protocol_version::text)
|
|
2013
|
+
OR (tx.modality IN ('document', 'presentation')
|
|
2014
|
+
AND NEW.event->>'commitProtocolVersion' <> tx.commit_protocol_version::text)
|
|
2015
|
+
OR (NEW.event->>'committedAt')::timestamptz <> tx.committed_at
|
|
2016
|
+
OR NEW.created_at <> tx.committed_at
|
|
2017
|
+
THEN
|
|
2018
|
+
RAISE EXCEPTION 'transaction outbox event differs from durable transaction'
|
|
2019
|
+
USING ERRCODE = '23514';
|
|
2020
|
+
END IF;
|
|
2021
|
+
ELSE
|
|
2022
|
+
SELECT * INTO snapshot FROM editable_artifact_snapshots candidate
|
|
2023
|
+
WHERE candidate.account_id = NEW.account_id
|
|
2024
|
+
AND candidate.workspace_id = NEW.workspace_id
|
|
2025
|
+
AND candidate.artifact_id = NEW.artifact_id
|
|
2026
|
+
AND candidate.id = NEW.snapshot_id;
|
|
2027
|
+
IF snapshot.id IS NULL
|
|
2028
|
+
OR NEW.event->>'modality' <> snapshot.modality
|
|
2029
|
+
OR NEW.event->>'coveredHeadSequence' <> snapshot.covered_head_sequence::text
|
|
2030
|
+
OR NEW.event->>'stateHash' <> snapshot.state_hash
|
|
2031
|
+
OR (snapshot.modality = 'spreadsheet'
|
|
2032
|
+
AND NEW.event->>'operationProtocolVersion'
|
|
2033
|
+
<> snapshot.operation_protocol_version::text)
|
|
2034
|
+
OR (NEW.event->>'publishedAt')::timestamptz <> snapshot.published_at
|
|
2035
|
+
OR NEW.created_at <> snapshot.published_at
|
|
2036
|
+
THEN
|
|
2037
|
+
RAISE EXCEPTION 'snapshot outbox event differs from durable snapshot'
|
|
2038
|
+
USING ERRCODE = '23514';
|
|
2039
|
+
END IF;
|
|
2040
|
+
END IF;
|
|
2041
|
+
END IF;
|
|
2042
|
+
RETURN NULL;
|
|
2043
|
+
END;
|
|
2044
|
+
$body$;
|
|
2045
|
+
|
|
2046
|
+
CREATE CONSTRAINT TRIGGER editable_artifacts_projection_guard
|
|
2047
|
+
AFTER INSERT OR UPDATE ON "editable_artifacts"
|
|
2048
|
+
DEFERRABLE INITIALLY DEFERRED FOR EACH ROW
|
|
2049
|
+
EXECUTE FUNCTION opengeni_private.validate_editable_artifact_projection();
|
|
2050
|
+
CREATE CONSTRAINT TRIGGER editable_artifact_snapshots_projection_guard
|
|
2051
|
+
AFTER INSERT ON "editable_artifact_snapshots"
|
|
2052
|
+
DEFERRABLE INITIALLY DEFERRED FOR EACH ROW
|
|
2053
|
+
EXECUTE FUNCTION opengeni_private.validate_editable_artifact_projection();
|
|
2054
|
+
CREATE CONSTRAINT TRIGGER editable_artifact_versions_projection_guard
|
|
2055
|
+
AFTER INSERT ON "editable_artifact_versions"
|
|
2056
|
+
DEFERRABLE INITIALLY DEFERRED FOR EACH ROW
|
|
2057
|
+
EXECUTE FUNCTION opengeni_private.validate_editable_artifact_projection();
|
|
2058
|
+
CREATE CONSTRAINT TRIGGER editable_artifact_materialization_results_projection_guard
|
|
2059
|
+
AFTER INSERT ON "editable_artifact_materialization_results"
|
|
2060
|
+
DEFERRABLE INITIALLY DEFERRED FOR EACH ROW
|
|
2061
|
+
EXECUTE FUNCTION opengeni_private.validate_editable_artifact_projection();
|
|
2062
|
+
CREATE CONSTRAINT TRIGGER editable_artifact_materialization_jobs_projection_guard
|
|
2063
|
+
AFTER INSERT OR UPDATE ON "editable_artifact_materialization_jobs"
|
|
2064
|
+
DEFERRABLE INITIALLY DEFERRED FOR EACH ROW
|
|
2065
|
+
EXECUTE FUNCTION opengeni_private.validate_editable_artifact_projection();
|
|
2066
|
+
CREATE CONSTRAINT TRIGGER editable_artifact_replica_leases_projection_guard
|
|
2067
|
+
AFTER INSERT OR UPDATE ON "editable_artifact_replica_leases"
|
|
2068
|
+
DEFERRABLE INITIALLY DEFERRED FOR EACH ROW
|
|
2069
|
+
EXECUTE FUNCTION opengeni_private.validate_editable_artifact_projection();
|
|
2070
|
+
CREATE CONSTRAINT TRIGGER editable_artifact_idempotency_receipts_projection_guard
|
|
2071
|
+
AFTER INSERT ON "editable_artifact_idempotency_receipts"
|
|
2072
|
+
DEFERRABLE INITIALLY DEFERRED FOR EACH ROW
|
|
2073
|
+
EXECUTE FUNCTION opengeni_private.validate_editable_artifact_projection();
|
|
2074
|
+
CREATE CONSTRAINT TRIGGER editable_artifact_live_outbox_projection_guard
|
|
2075
|
+
AFTER INSERT ON "editable_artifact_live_outbox"
|
|
2076
|
+
DEFERRABLE INITIALLY DEFERRED FOR EACH ROW
|
|
2077
|
+
EXECUTE FUNCTION opengeni_private.validate_editable_artifact_projection();
|
|
2078
|
+
|
|
2079
|
+
CREATE OR REPLACE FUNCTION opengeni_private.guard_editable_artifact_update()
|
|
2080
|
+
RETURNS trigger
|
|
2081
|
+
LANGUAGE plpgsql
|
|
2082
|
+
SET search_path = pg_catalog, pg_temp
|
|
2083
|
+
AS $body$
|
|
2084
|
+
DECLARE old_snapshot_coverage bigint;
|
|
2085
|
+
DECLARE new_snapshot_coverage bigint;
|
|
2086
|
+
DECLARE table_owner name;
|
|
2087
|
+
BEGIN
|
|
2088
|
+
PERFORM set_config(
|
|
2089
|
+
'search_path', 'pg_catalog,' || quote_ident(TG_TABLE_SCHEMA) || ',pg_temp', true
|
|
2090
|
+
);
|
|
2091
|
+
IF NEW.account_id <> OLD.account_id OR NEW.workspace_id <> OLD.workspace_id
|
|
2092
|
+
OR NEW.id <> OLD.id OR NEW.modality <> OLD.modality
|
|
2093
|
+
OR NEW.created_by_subject_id <> OLD.created_by_subject_id
|
|
2094
|
+
OR NEW.created_at <> OLD.created_at
|
|
2095
|
+
OR NEW.head_sequence < OLD.head_sequence
|
|
2096
|
+
OR NEW.updated_at < OLD.updated_at
|
|
2097
|
+
OR (NEW.head_sequence = OLD.head_sequence AND (
|
|
2098
|
+
NEW.causal_frontier IS DISTINCT FROM OLD.causal_frontier
|
|
2099
|
+
OR NEW.state_hash <> OLD.state_hash
|
|
2100
|
+
))
|
|
2101
|
+
THEN
|
|
2102
|
+
RAISE EXCEPTION 'invalid editable artifact head or identity mutation'
|
|
2103
|
+
USING ERRCODE = '23514';
|
|
2104
|
+
END IF;
|
|
2105
|
+
IF NEW.authorization_revision < OLD.authorization_revision THEN
|
|
2106
|
+
RAISE EXCEPTION 'editable artifact authorization revision cannot regress'
|
|
2107
|
+
USING ERRCODE = '23514';
|
|
2108
|
+
END IF;
|
|
2109
|
+
IF NEW.authorization_revision > OLD.authorization_revision THEN
|
|
2110
|
+
SELECT pg_catalog.pg_get_userbyid(relation.relowner) INTO table_owner
|
|
2111
|
+
FROM pg_catalog.pg_class relation
|
|
2112
|
+
WHERE relation.oid = TG_RELID;
|
|
2113
|
+
IF table_owner IS NULL OR current_user <> table_owner THEN
|
|
2114
|
+
RAISE EXCEPTION 'editable artifact authorization revision requires its owner capability'
|
|
2115
|
+
USING ERRCODE = '42501';
|
|
2116
|
+
END IF;
|
|
2117
|
+
IF NEW.title <> OLD.title
|
|
2118
|
+
OR NEW.lifecycle_state <> OLD.lifecycle_state
|
|
2119
|
+
OR NEW.head_sequence <> OLD.head_sequence
|
|
2120
|
+
OR NEW.causal_frontier IS DISTINCT FROM OLD.causal_frontier
|
|
2121
|
+
OR NEW.state_hash <> OLD.state_hash
|
|
2122
|
+
OR NEW.current_snapshot_id IS DISTINCT FROM OLD.current_snapshot_id
|
|
2123
|
+
THEN
|
|
2124
|
+
RAISE EXCEPTION 'editable artifact authorization revision must advance independently'
|
|
2125
|
+
USING ERRCODE = '23514';
|
|
2126
|
+
END IF;
|
|
2127
|
+
END IF;
|
|
2128
|
+
IF OLD.lifecycle_state = 'archived' AND NEW.lifecycle_state <> 'archived' THEN
|
|
2129
|
+
RAISE EXCEPTION 'archived editable artifact cannot be reactivated implicitly'
|
|
2130
|
+
USING ERRCODE = '23514';
|
|
2131
|
+
END IF;
|
|
2132
|
+
IF OLD.lifecycle_state = 'archived' AND (
|
|
2133
|
+
NEW.head_sequence <> OLD.head_sequence
|
|
2134
|
+
OR NEW.causal_frontier IS DISTINCT FROM OLD.causal_frontier
|
|
2135
|
+
OR NEW.state_hash <> OLD.state_hash
|
|
2136
|
+
) THEN
|
|
2137
|
+
RAISE EXCEPTION 'archived editable artifact authority cannot advance'
|
|
2138
|
+
USING ERRCODE = '23514';
|
|
2139
|
+
END IF;
|
|
2140
|
+
IF NEW.current_snapshot_id IS DISTINCT FROM OLD.current_snapshot_id THEN
|
|
2141
|
+
IF NEW.current_snapshot_id IS NULL THEN
|
|
2142
|
+
RAISE EXCEPTION 'editable artifact current snapshot cannot be cleared'
|
|
2143
|
+
USING ERRCODE = '23514';
|
|
2144
|
+
END IF;
|
|
2145
|
+
SELECT snapshot.covered_head_sequence INTO new_snapshot_coverage
|
|
2146
|
+
FROM editable_artifact_snapshots snapshot
|
|
2147
|
+
WHERE snapshot.account_id = NEW.account_id
|
|
2148
|
+
AND snapshot.workspace_id = NEW.workspace_id
|
|
2149
|
+
AND snapshot.artifact_id = NEW.id
|
|
2150
|
+
AND snapshot.id = NEW.current_snapshot_id;
|
|
2151
|
+
IF NOT FOUND THEN
|
|
2152
|
+
RAISE EXCEPTION 'editable artifact current snapshot is missing'
|
|
2153
|
+
USING ERRCODE = '23503';
|
|
2154
|
+
END IF;
|
|
2155
|
+
IF OLD.current_snapshot_id IS NOT NULL THEN
|
|
2156
|
+
SELECT snapshot.covered_head_sequence INTO old_snapshot_coverage
|
|
2157
|
+
FROM editable_artifact_snapshots snapshot
|
|
2158
|
+
WHERE snapshot.account_id = OLD.account_id
|
|
2159
|
+
AND snapshot.workspace_id = OLD.workspace_id
|
|
2160
|
+
AND snapshot.artifact_id = OLD.id
|
|
2161
|
+
AND snapshot.id = OLD.current_snapshot_id;
|
|
2162
|
+
IF NOT FOUND OR new_snapshot_coverage <= old_snapshot_coverage THEN
|
|
2163
|
+
RAISE EXCEPTION 'editable artifact current snapshot coverage must advance'
|
|
2164
|
+
USING ERRCODE = '23514';
|
|
2165
|
+
END IF;
|
|
2166
|
+
END IF;
|
|
2167
|
+
END IF;
|
|
2168
|
+
RETURN NEW;
|
|
2169
|
+
END;
|
|
2170
|
+
$body$;
|
|
2171
|
+
|
|
2172
|
+
CREATE TRIGGER editable_artifacts_update_guard
|
|
2173
|
+
BEFORE UPDATE ON "editable_artifacts"
|
|
2174
|
+
FOR EACH ROW EXECUTE FUNCTION opengeni_private.guard_editable_artifact_update();
|
|
2175
|
+
|
|
2176
|
+
CREATE OR REPLACE FUNCTION opengeni_private.guard_editable_artifact_mutable_state()
|
|
2177
|
+
RETURNS trigger
|
|
2178
|
+
LANGUAGE plpgsql
|
|
2179
|
+
SET search_path = pg_catalog, pg_temp
|
|
2180
|
+
AS $body$
|
|
2181
|
+
BEGIN
|
|
2182
|
+
PERFORM set_config(
|
|
2183
|
+
'search_path', 'pg_catalog,' || quote_ident(TG_TABLE_SCHEMA) || ',pg_temp', true
|
|
2184
|
+
);
|
|
2185
|
+
IF TG_TABLE_NAME = 'editable_artifact_materialization_jobs' THEN
|
|
2186
|
+
IF TG_OP = 'INSERT' THEN
|
|
2187
|
+
IF NEW.state <> 'pending' OR NEW.attempt_count <> 0
|
|
2188
|
+
OR NEW.lease_owner IS NOT NULL OR NEW.lease_expires_at IS NOT NULL
|
|
2189
|
+
OR NEW.settled_by_owner IS NOT NULL OR NEW.error_code IS NOT NULL
|
|
2190
|
+
OR NEW.started_at IS NOT NULL
|
|
2191
|
+
OR NEW.completed_at IS NOT NULL
|
|
2192
|
+
THEN
|
|
2193
|
+
RAISE EXCEPTION 'materialization jobs must begin pending and unleased'
|
|
2194
|
+
USING ERRCODE = '23514';
|
|
2195
|
+
END IF;
|
|
2196
|
+
RETURN NEW;
|
|
2197
|
+
END IF;
|
|
2198
|
+
IF NEW.account_id <> OLD.account_id OR NEW.workspace_id <> OLD.workspace_id
|
|
2199
|
+
OR NEW.artifact_id <> OLD.artifact_id OR NEW.id <> OLD.id
|
|
2200
|
+
OR NEW.version_id IS DISTINCT FROM OLD.version_id
|
|
2201
|
+
OR NEW.input_snapshot_id <> OLD.input_snapshot_id
|
|
2202
|
+
OR NEW.target_head_sequence <> OLD.target_head_sequence
|
|
2203
|
+
OR NEW.state_hash <> OLD.state_hash OR NEW.format <> OLD.format
|
|
2204
|
+
OR NEW.normalized_options <> OLD.normalized_options
|
|
2205
|
+
OR NEW.options_hash <> OLD.options_hash OR NEW.codec_id <> OLD.codec_id
|
|
2206
|
+
OR NEW.codec_version <> OLD.codec_version
|
|
2207
|
+
OR NEW.kernel_version <> OLD.kernel_version
|
|
2208
|
+
OR NEW.font_registry_hash <> OLD.font_registry_hash OR NEW.policy_hash <> OLD.policy_hash
|
|
2209
|
+
OR NEW.created_at <> OLD.created_at
|
|
2210
|
+
THEN
|
|
2211
|
+
RAISE EXCEPTION 'invalid materialization job mutation' USING ERRCODE = '23514';
|
|
2212
|
+
END IF;
|
|
2213
|
+
IF OLD.state IN ('succeeded', 'failed') THEN
|
|
2214
|
+
RAISE EXCEPTION 'terminal materialization job is immutable' USING ERRCODE = '23514';
|
|
2215
|
+
ELSIF OLD.state = 'pending' THEN
|
|
2216
|
+
IF NEW.state <> 'running' OR NEW.attempt_count <> OLD.attempt_count + 1
|
|
2217
|
+
OR NEW.settled_by_owner IS NOT NULL
|
|
2218
|
+
THEN
|
|
2219
|
+
RAISE EXCEPTION 'pending materialization job must be claimed exactly once'
|
|
2220
|
+
USING ERRCODE = '23514';
|
|
2221
|
+
END IF;
|
|
2222
|
+
ELSIF OLD.state = 'running' AND NEW.state = 'running' THEN
|
|
2223
|
+
IF NOT (
|
|
2224
|
+
-- Heartbeats extend one still-live claim without changing its fencing
|
|
2225
|
+
-- token. They cannot resurrect an expired lease or move ownership.
|
|
2226
|
+
(NEW.attempt_count = OLD.attempt_count
|
|
2227
|
+
AND NEW.lease_owner IS NOT DISTINCT FROM OLD.lease_owner
|
|
2228
|
+
AND NEW.started_at IS NOT DISTINCT FROM OLD.started_at
|
|
2229
|
+
AND OLD.lease_expires_at > pg_catalog.statement_timestamp()
|
|
2230
|
+
AND NEW.lease_expires_at > OLD.lease_expires_at
|
|
2231
|
+
AND NEW.settled_by_owner IS NULL)
|
|
2232
|
+
OR
|
|
2233
|
+
-- Reclaim is a distinct transition: only an expired claim may advance
|
|
2234
|
+
-- the attempt-count fence and reset its owner/start/expiration.
|
|
2235
|
+
(NEW.attempt_count = OLD.attempt_count + 1
|
|
2236
|
+
AND OLD.lease_expires_at <= pg_catalog.statement_timestamp()
|
|
2237
|
+
AND NEW.started_at >= OLD.started_at
|
|
2238
|
+
AND NEW.settled_by_owner IS NULL)
|
|
2239
|
+
)
|
|
2240
|
+
THEN
|
|
2241
|
+
RAISE EXCEPTION 'invalid materialization renewal or reclaim transition'
|
|
2242
|
+
USING ERRCODE = '23514';
|
|
2243
|
+
END IF;
|
|
2244
|
+
ELSIF OLD.state = 'running' AND NEW.state IN ('succeeded', 'failed') THEN
|
|
2245
|
+
IF NOT (
|
|
2246
|
+
(NEW.attempt_count = OLD.attempt_count
|
|
2247
|
+
AND NEW.started_at IS NOT DISTINCT FROM OLD.started_at
|
|
2248
|
+
AND NEW.settled_by_owner IS NOT DISTINCT FROM OLD.lease_owner
|
|
2249
|
+
AND OLD.lease_expires_at > pg_catalog.statement_timestamp())
|
|
2250
|
+
OR
|
|
2251
|
+
(NEW.state = 'failed' AND NEW.error_code = 'attempts_exhausted'
|
|
2252
|
+
AND NEW.attempt_count = 1000
|
|
2253
|
+
AND NEW.started_at IS NOT DISTINCT FROM OLD.started_at
|
|
2254
|
+
AND NEW.settled_by_owner IS NOT DISTINCT FROM OLD.lease_owner
|
|
2255
|
+
AND OLD.lease_expires_at <= pg_catalog.statement_timestamp())
|
|
2256
|
+
) THEN
|
|
2257
|
+
RAISE EXCEPTION 'materialization settlement attempt is inconsistent'
|
|
2258
|
+
USING ERRCODE = '23514';
|
|
2259
|
+
END IF;
|
|
2260
|
+
ELSE
|
|
2261
|
+
RAISE EXCEPTION 'invalid materialization job state transition' USING ERRCODE = '23514';
|
|
2262
|
+
END IF;
|
|
2263
|
+
ELSIF TG_TABLE_NAME = 'editable_artifact_live_outbox' THEN
|
|
2264
|
+
IF TG_OP = 'INSERT' THEN
|
|
2265
|
+
IF NEW.state <> 'pending' OR NEW.attempt_count <> 0
|
|
2266
|
+
OR NEW.lease_owner IS NOT NULL OR NEW.lease_expires_at IS NOT NULL
|
|
2267
|
+
OR NEW.next_attempt_at IS DISTINCT FROM NEW.created_at
|
|
2268
|
+
OR NEW.last_error_code IS NOT NULL OR NEW.published_at IS NOT NULL
|
|
2269
|
+
OR NEW.dead_lettered_at IS NOT NULL
|
|
2270
|
+
THEN
|
|
2271
|
+
RAISE EXCEPTION 'outbox events must begin pending and unleased'
|
|
2272
|
+
USING ERRCODE = '23514';
|
|
2273
|
+
END IF;
|
|
2274
|
+
RETURN NEW;
|
|
2275
|
+
END IF;
|
|
2276
|
+
IF NEW.account_id <> OLD.account_id OR NEW.workspace_id <> OLD.workspace_id
|
|
2277
|
+
OR NEW.artifact_id <> OLD.artifact_id OR NEW.id <> OLD.id
|
|
2278
|
+
OR NEW.transaction_id IS DISTINCT FROM OLD.transaction_id
|
|
2279
|
+
OR NEW.snapshot_id IS DISTINCT FROM OLD.snapshot_id
|
|
2280
|
+
OR NEW.event_kind <> OLD.event_kind OR NEW.event <> OLD.event
|
|
2281
|
+
OR NEW.created_at <> OLD.created_at OR NEW.attempt_count < OLD.attempt_count
|
|
2282
|
+
OR NEW.attempt_count > OLD.attempt_count + 1
|
|
2283
|
+
OR OLD.state IN ('published', 'dead_lettered')
|
|
2284
|
+
THEN
|
|
2285
|
+
RAISE EXCEPTION 'invalid editable artifact outbox mutation' USING ERRCODE = '23514';
|
|
2286
|
+
END IF;
|
|
2287
|
+
ELSIF TG_TABLE_NAME = 'editable_artifact_replica_leases' THEN
|
|
2288
|
+
IF NEW.account_id <> OLD.account_id OR NEW.workspace_id <> OLD.workspace_id
|
|
2289
|
+
OR NEW.artifact_id <> OLD.artifact_id OR NEW.replica_id <> OLD.replica_id
|
|
2290
|
+
OR NEW.modality <> OLD.modality
|
|
2291
|
+
OR NEW.actor_key <> OLD.actor_key OR NEW.created_at <> OLD.created_at
|
|
2292
|
+
OR NEW.applied_head_sequence < OLD.applied_head_sequence
|
|
2293
|
+
OR NEW.last_seen_at < OLD.last_seen_at
|
|
2294
|
+
OR (OLD.revoked_at IS NOT NULL AND NEW.revoked_at IS DISTINCT FROM OLD.revoked_at)
|
|
2295
|
+
THEN
|
|
2296
|
+
RAISE EXCEPTION 'invalid editable artifact replica lease mutation'
|
|
2297
|
+
USING ERRCODE = '23514';
|
|
2298
|
+
END IF;
|
|
2299
|
+
END IF;
|
|
2300
|
+
RETURN NEW;
|
|
2301
|
+
END;
|
|
2302
|
+
$body$;
|
|
2303
|
+
|
|
2304
|
+
CREATE TRIGGER editable_artifact_materialization_jobs_update_guard
|
|
2305
|
+
BEFORE INSERT OR UPDATE ON "editable_artifact_materialization_jobs"
|
|
2306
|
+
FOR EACH ROW EXECUTE FUNCTION opengeni_private.guard_editable_artifact_mutable_state();
|
|
2307
|
+
CREATE TRIGGER editable_artifact_live_outbox_update_guard
|
|
2308
|
+
BEFORE INSERT OR UPDATE ON "editable_artifact_live_outbox"
|
|
2309
|
+
FOR EACH ROW EXECUTE FUNCTION opengeni_private.guard_editable_artifact_mutable_state();
|
|
2310
|
+
CREATE TRIGGER editable_artifact_replica_leases_update_guard
|
|
2311
|
+
BEFORE UPDATE ON "editable_artifact_replica_leases"
|
|
2312
|
+
FOR EACH ROW EXECUTE FUNCTION opengeni_private.guard_editable_artifact_mutable_state();
|
|
2313
|
+
|
|
2314
|
+
DO $immutable_history$
|
|
2315
|
+
DECLARE table_name text;
|
|
2316
|
+
BEGIN
|
|
2317
|
+
FOREACH table_name IN ARRAY ARRAY[
|
|
2318
|
+
'editable_artifact_transactions',
|
|
2319
|
+
'editable_artifact_operations',
|
|
2320
|
+
'editable_artifact_idempotency_receipts',
|
|
2321
|
+
'editable_artifact_undo_claims',
|
|
2322
|
+
'editable_artifact_sequence_checkpoints',
|
|
2323
|
+
'editable_artifact_blob_refs',
|
|
2324
|
+
'editable_artifact_snapshots',
|
|
2325
|
+
'editable_artifact_versions',
|
|
2326
|
+
'editable_artifact_materialization_results'
|
|
2327
|
+
] LOOP
|
|
2328
|
+
EXECUTE format(
|
|
2329
|
+
'CREATE TRIGGER %I BEFORE UPDATE OR DELETE ON %I '
|
|
2330
|
+
|| 'FOR EACH ROW EXECUTE FUNCTION '
|
|
2331
|
+
|| 'opengeni_private.reject_editable_artifact_history_mutation()',
|
|
2332
|
+
table_name || '_immutable_guard', table_name
|
|
2333
|
+
);
|
|
2334
|
+
END LOOP;
|
|
2335
|
+
END;
|
|
2336
|
+
$immutable_history$;
|
|
2337
|
+
|
|
2338
|
+
-- Validate the explicit/defaulted caller schema before privileged access. The
|
|
2339
|
+
-- public functions default this final argument from current_schema() at their
|
|
2340
|
+
-- call site, before their hardened SECURITY DEFINER search_path takes effect.
|
|
2341
|
+
CREATE OR REPLACE FUNCTION opengeni_private.editable_artifact_scope_matches_context(
|
|
2342
|
+
p_account_id uuid,
|
|
2343
|
+
p_workspace_id uuid
|
|
2344
|
+
) RETURNS boolean
|
|
2345
|
+
LANGUAGE plpgsql
|
|
2346
|
+
STABLE
|
|
2347
|
+
SET search_path = pg_catalog, pg_temp
|
|
2348
|
+
AS $body$
|
|
2349
|
+
BEGIN
|
|
2350
|
+
IF p_account_id IS NULL OR p_workspace_id IS NULL THEN RETURN false; END IF;
|
|
2351
|
+
RETURN p_account_id = nullif(
|
|
2352
|
+
pg_catalog.current_setting('opengeni.account_id', true), ''
|
|
2353
|
+
)::uuid
|
|
2354
|
+
AND p_workspace_id = nullif(
|
|
2355
|
+
pg_catalog.current_setting('opengeni.workspace_id', true), ''
|
|
2356
|
+
)::uuid;
|
|
2357
|
+
EXCEPTION WHEN others THEN
|
|
2358
|
+
RETURN false;
|
|
2359
|
+
END;
|
|
2360
|
+
$body$;
|
|
2361
|
+
|
|
2362
|
+
CREATE OR REPLACE FUNCTION opengeni_private.resolve_editable_artifact_data_schema(
|
|
2363
|
+
requested_schema name
|
|
2364
|
+
)
|
|
2365
|
+
RETURNS name
|
|
2366
|
+
LANGUAGE plpgsql
|
|
2367
|
+
SECURITY DEFINER
|
|
2368
|
+
SET search_path = pg_catalog, pg_temp
|
|
2369
|
+
AS $body$
|
|
2370
|
+
DECLARE data_schema name;
|
|
2371
|
+
BEGIN
|
|
2372
|
+
IF requested_schema IS NULL THEN
|
|
2373
|
+
RAISE EXCEPTION 'editable artifact data schema is unavailable or untrusted'
|
|
2374
|
+
USING ERRCODE = '42501';
|
|
2375
|
+
END IF;
|
|
2376
|
+
SELECT namespace.nspname INTO data_schema
|
|
2377
|
+
FROM pg_catalog.pg_namespace namespace
|
|
2378
|
+
JOIN pg_catalog.pg_class relation
|
|
2379
|
+
ON relation.relnamespace = namespace.oid
|
|
2380
|
+
AND relation.relname = 'editable_artifact_live_outbox'
|
|
2381
|
+
AND relation.relkind IN ('r', 'p')
|
|
2382
|
+
JOIN pg_catalog.pg_class artifact_relation
|
|
2383
|
+
ON artifact_relation.relnamespace = namespace.oid
|
|
2384
|
+
AND artifact_relation.relname = 'editable_artifacts'
|
|
2385
|
+
AND artifact_relation.relkind IN ('r', 'p')
|
|
2386
|
+
AND artifact_relation.relowner = relation.relowner
|
|
2387
|
+
JOIN pg_catalog.pg_class scope_authority_relation
|
|
2388
|
+
ON scope_authority_relation.relnamespace = namespace.oid
|
|
2389
|
+
AND scope_authority_relation.relname = 'editable_artifact_scope_authorization_heads'
|
|
2390
|
+
AND scope_authority_relation.relkind IN ('r', 'p')
|
|
2391
|
+
AND scope_authority_relation.relowner = relation.relowner
|
|
2392
|
+
JOIN pg_catalog.pg_class job_relation
|
|
2393
|
+
ON job_relation.relnamespace = namespace.oid
|
|
2394
|
+
AND job_relation.relname = 'editable_artifact_materialization_jobs'
|
|
2395
|
+
AND job_relation.relkind IN ('r', 'p')
|
|
2396
|
+
AND job_relation.relowner = relation.relowner
|
|
2397
|
+
JOIN pg_catalog.pg_class result_relation
|
|
2398
|
+
ON result_relation.relnamespace = namespace.oid
|
|
2399
|
+
AND result_relation.relname = 'editable_artifact_materialization_results'
|
|
2400
|
+
AND result_relation.relkind IN ('r', 'p')
|
|
2401
|
+
AND result_relation.relowner = relation.relowner
|
|
2402
|
+
JOIN pg_catalog.pg_class blob_relation
|
|
2403
|
+
ON blob_relation.relnamespace = namespace.oid
|
|
2404
|
+
AND blob_relation.relname = 'editable_artifact_blob_refs'
|
|
2405
|
+
AND blob_relation.relkind IN ('r', 'p')
|
|
2406
|
+
AND blob_relation.relowner = relation.relowner
|
|
2407
|
+
JOIN pg_catalog.pg_roles owner_role
|
|
2408
|
+
ON owner_role.oid = relation.relowner
|
|
2409
|
+
AND owner_role.rolname = current_user
|
|
2410
|
+
WHERE namespace.nspname = requested_schema
|
|
2411
|
+
AND namespace.nspname !~ '^pg_'
|
|
2412
|
+
AND namespace.nspname <> 'information_schema'
|
|
2413
|
+
AND relation.relrowsecurity
|
|
2414
|
+
AND relation.relforcerowsecurity
|
|
2415
|
+
AND artifact_relation.relrowsecurity
|
|
2416
|
+
AND artifact_relation.relforcerowsecurity
|
|
2417
|
+
AND scope_authority_relation.relrowsecurity
|
|
2418
|
+
AND scope_authority_relation.relforcerowsecurity
|
|
2419
|
+
AND job_relation.relrowsecurity
|
|
2420
|
+
AND job_relation.relforcerowsecurity
|
|
2421
|
+
AND result_relation.relrowsecurity
|
|
2422
|
+
AND result_relation.relforcerowsecurity
|
|
2423
|
+
AND blob_relation.relrowsecurity
|
|
2424
|
+
AND blob_relation.relforcerowsecurity
|
|
2425
|
+
AND pg_catalog.has_schema_privilege(session_user, namespace.oid, 'USAGE');
|
|
2426
|
+
IF NOT FOUND THEN
|
|
2427
|
+
RAISE EXCEPTION 'editable artifact data schema is unavailable or untrusted'
|
|
2428
|
+
USING ERRCODE = '42501';
|
|
2429
|
+
END IF;
|
|
2430
|
+
RETURN data_schema;
|
|
2431
|
+
END;
|
|
2432
|
+
$body$;
|
|
2433
|
+
|
|
2434
|
+
CREATE OR REPLACE FUNCTION opengeni_private.ensure_editable_artifact_scope_authorization_head(
|
|
2435
|
+
p_account_id uuid,
|
|
2436
|
+
p_workspace_id uuid,
|
|
2437
|
+
p_data_schema name DEFAULT pg_catalog.current_schema()
|
|
2438
|
+
) RETURNS bigint
|
|
2439
|
+
LANGUAGE plpgsql
|
|
2440
|
+
SECURITY DEFINER
|
|
2441
|
+
SET search_path = pg_catalog, pg_temp
|
|
2442
|
+
AS $body$
|
|
2443
|
+
DECLARE data_schema name;
|
|
2444
|
+
DECLARE current_revision bigint;
|
|
2445
|
+
BEGIN
|
|
2446
|
+
IF p_account_id IS NULL OR p_workspace_id IS NULL OR p_data_schema IS NULL
|
|
2447
|
+
OR NOT opengeni_private.editable_artifact_scope_matches_context(
|
|
2448
|
+
p_account_id, p_workspace_id
|
|
2449
|
+
)
|
|
2450
|
+
THEN
|
|
2451
|
+
RAISE EXCEPTION 'editable artifact scope authorization context is unavailable'
|
|
2452
|
+
USING ERRCODE = '42501';
|
|
2453
|
+
END IF;
|
|
2454
|
+
data_schema := opengeni_private.resolve_editable_artifact_data_schema(p_data_schema);
|
|
2455
|
+
EXECUTE pg_catalog.format($query$
|
|
2456
|
+
INSERT INTO %I.editable_artifact_scope_authorization_heads (
|
|
2457
|
+
account_id, workspace_id, create_revision
|
|
2458
|
+
) VALUES ($1, $2, 1)
|
|
2459
|
+
ON CONFLICT (account_id, workspace_id) DO NOTHING
|
|
2460
|
+
$query$, data_schema) USING p_account_id, p_workspace_id;
|
|
2461
|
+
EXECUTE pg_catalog.format($query$
|
|
2462
|
+
SELECT create_revision
|
|
2463
|
+
FROM %I.editable_artifact_scope_authorization_heads
|
|
2464
|
+
WHERE account_id = $1 AND workspace_id = $2
|
|
2465
|
+
FOR SHARE
|
|
2466
|
+
$query$, data_schema) INTO current_revision USING p_account_id, p_workspace_id;
|
|
2467
|
+
IF current_revision IS NULL THEN
|
|
2468
|
+
RAISE EXCEPTION 'editable artifact scope authorization head is unavailable'
|
|
2469
|
+
USING ERRCODE = '55000';
|
|
2470
|
+
END IF;
|
|
2471
|
+
RETURN current_revision;
|
|
2472
|
+
END;
|
|
2473
|
+
$body$;
|
|
2474
|
+
|
|
2475
|
+
CREATE OR REPLACE FUNCTION opengeni_private.advance_editable_artifact_scope_authorization_revision(
|
|
2476
|
+
p_account_id uuid,
|
|
2477
|
+
p_workspace_id uuid,
|
|
2478
|
+
p_expected_revision bigint,
|
|
2479
|
+
p_next_revision bigint,
|
|
2480
|
+
p_data_schema name DEFAULT pg_catalog.current_schema()
|
|
2481
|
+
) RETURNS TABLE (
|
|
2482
|
+
applied boolean,
|
|
2483
|
+
authorization_revision bigint
|
|
2484
|
+
)
|
|
2485
|
+
LANGUAGE plpgsql
|
|
2486
|
+
SECURITY DEFINER
|
|
2487
|
+
SET search_path = pg_catalog, pg_temp
|
|
2488
|
+
AS $body$
|
|
2489
|
+
DECLARE data_schema name;
|
|
2490
|
+
DECLARE current_revision bigint;
|
|
2491
|
+
BEGIN
|
|
2492
|
+
IF p_account_id IS NULL OR p_workspace_id IS NULL OR p_data_schema IS NULL
|
|
2493
|
+
OR p_expected_revision IS NULL
|
|
2494
|
+
OR p_expected_revision NOT BETWEEN 1 AND 9007199254740991
|
|
2495
|
+
OR p_next_revision IS NULL
|
|
2496
|
+
OR p_next_revision NOT BETWEEN 1 AND 9007199254740991
|
|
2497
|
+
OR p_next_revision <= p_expected_revision
|
|
2498
|
+
OR NOT opengeni_private.editable_artifact_scope_matches_context(
|
|
2499
|
+
p_account_id, p_workspace_id
|
|
2500
|
+
)
|
|
2501
|
+
THEN
|
|
2502
|
+
RAISE EXCEPTION 'invalid editable artifact scope authorization revision bounds'
|
|
2503
|
+
USING ERRCODE = '22023';
|
|
2504
|
+
END IF;
|
|
2505
|
+
data_schema := opengeni_private.resolve_editable_artifact_data_schema(p_data_schema);
|
|
2506
|
+
-- Ensure the default head exists before the compare-and-swap. Both statements
|
|
2507
|
+
-- remain in the caller transaction, so create's FOR SHARE and this UPDATE
|
|
2508
|
+
-- serialize revocation against genesis publication.
|
|
2509
|
+
PERFORM opengeni_private.ensure_editable_artifact_scope_authorization_head(
|
|
2510
|
+
p_account_id, p_workspace_id, data_schema
|
|
2511
|
+
);
|
|
2512
|
+
EXECUTE pg_catalog.format($query$
|
|
2513
|
+
UPDATE %I.editable_artifact_scope_authorization_heads target
|
|
2514
|
+
SET create_revision = $3,
|
|
2515
|
+
updated_at = GREATEST(target.updated_at, pg_catalog.statement_timestamp())
|
|
2516
|
+
WHERE target.account_id = $1
|
|
2517
|
+
AND target.workspace_id = $2
|
|
2518
|
+
AND target.create_revision = $4
|
|
2519
|
+
RETURNING target.create_revision
|
|
2520
|
+
$query$, data_schema)
|
|
2521
|
+
INTO current_revision
|
|
2522
|
+
USING p_account_id, p_workspace_id, p_next_revision, p_expected_revision;
|
|
2523
|
+
IF current_revision IS NOT NULL THEN
|
|
2524
|
+
RETURN QUERY SELECT true, current_revision;
|
|
2525
|
+
RETURN;
|
|
2526
|
+
END IF;
|
|
2527
|
+
EXECUTE pg_catalog.format($query$
|
|
2528
|
+
SELECT target.create_revision
|
|
2529
|
+
FROM %I.editable_artifact_scope_authorization_heads target
|
|
2530
|
+
WHERE target.account_id = $1 AND target.workspace_id = $2
|
|
2531
|
+
$query$, data_schema) INTO current_revision USING p_account_id, p_workspace_id;
|
|
2532
|
+
RETURN QUERY SELECT false, current_revision;
|
|
2533
|
+
END;
|
|
2534
|
+
$body$;
|
|
2535
|
+
|
|
2536
|
+
-- Authorization policy projection is a separate owner capability. Ordinary
|
|
2537
|
+
-- artifact-head UPDATEs retain their existing ACL but the row trigger rejects
|
|
2538
|
+
-- any authorization revision change unless this SECURITY DEFINER owner path is
|
|
2539
|
+
-- used. Exact expected-revision matching is the CAS fence consumed by edit and
|
|
2540
|
+
-- snapshot authorization checks.
|
|
2541
|
+
CREATE OR REPLACE FUNCTION opengeni_private.advance_editable_artifact_authorization_revision(
|
|
2542
|
+
p_account_id uuid,
|
|
2543
|
+
p_workspace_id uuid,
|
|
2544
|
+
p_artifact_id text,
|
|
2545
|
+
p_expected_revision bigint,
|
|
2546
|
+
p_next_revision bigint,
|
|
2547
|
+
p_data_schema name DEFAULT pg_catalog.current_schema()
|
|
2548
|
+
) RETURNS TABLE (
|
|
2549
|
+
applied boolean,
|
|
2550
|
+
authorization_revision bigint
|
|
2551
|
+
)
|
|
2552
|
+
LANGUAGE plpgsql
|
|
2553
|
+
SECURITY DEFINER
|
|
2554
|
+
SET search_path = pg_catalog, pg_temp
|
|
2555
|
+
AS $body$
|
|
2556
|
+
DECLARE affected integer;
|
|
2557
|
+
DECLARE current_revision bigint;
|
|
2558
|
+
DECLARE data_schema name;
|
|
2559
|
+
BEGIN
|
|
2560
|
+
IF p_account_id IS NULL OR p_workspace_id IS NULL OR p_artifact_id IS NULL
|
|
2561
|
+
OR p_data_schema IS NULL
|
|
2562
|
+
OR p_artifact_id !~ '^[0-9a-f]{32}$' OR p_artifact_id ~ '^0+$'
|
|
2563
|
+
OR p_expected_revision IS NULL
|
|
2564
|
+
OR p_expected_revision NOT BETWEEN 0 AND 9007199254740991
|
|
2565
|
+
OR p_next_revision IS NULL
|
|
2566
|
+
OR p_next_revision NOT BETWEEN 1 AND 9007199254740991
|
|
2567
|
+
OR p_next_revision <= p_expected_revision
|
|
2568
|
+
THEN
|
|
2569
|
+
RAISE EXCEPTION 'invalid editable artifact authorization revision bounds'
|
|
2570
|
+
USING ERRCODE = '22023';
|
|
2571
|
+
END IF;
|
|
2572
|
+
IF NOT opengeni_private.editable_artifact_scope_matches_context(
|
|
2573
|
+
p_account_id, p_workspace_id
|
|
2574
|
+
) THEN
|
|
2575
|
+
RAISE EXCEPTION 'editable artifact authorization scope is unavailable'
|
|
2576
|
+
USING ERRCODE = '42501';
|
|
2577
|
+
END IF;
|
|
2578
|
+
data_schema := opengeni_private.resolve_editable_artifact_data_schema(p_data_schema);
|
|
2579
|
+
EXECUTE pg_catalog.format($query$
|
|
2580
|
+
UPDATE %I.editable_artifacts target
|
|
2581
|
+
SET authorization_revision = $4,
|
|
2582
|
+
updated_at = GREATEST(
|
|
2583
|
+
target.updated_at, pg_catalog.statement_timestamp()
|
|
2584
|
+
)
|
|
2585
|
+
WHERE target.account_id = $1
|
|
2586
|
+
AND target.workspace_id = $2
|
|
2587
|
+
AND target.id = $3
|
|
2588
|
+
AND target.authorization_revision = $5
|
|
2589
|
+
RETURNING target.authorization_revision
|
|
2590
|
+
$query$, data_schema)
|
|
2591
|
+
INTO current_revision
|
|
2592
|
+
USING p_account_id, p_workspace_id, p_artifact_id, p_next_revision, p_expected_revision;
|
|
2593
|
+
GET DIAGNOSTICS affected = ROW_COUNT;
|
|
2594
|
+
IF affected = 1 THEN
|
|
2595
|
+
RETURN QUERY SELECT true, current_revision;
|
|
2596
|
+
RETURN;
|
|
2597
|
+
END IF;
|
|
2598
|
+
EXECUTE pg_catalog.format($query$
|
|
2599
|
+
SELECT target.authorization_revision
|
|
2600
|
+
FROM %I.editable_artifacts target
|
|
2601
|
+
WHERE target.account_id = $1
|
|
2602
|
+
AND target.workspace_id = $2
|
|
2603
|
+
AND target.id = $3
|
|
2604
|
+
$query$, data_schema)
|
|
2605
|
+
INTO current_revision
|
|
2606
|
+
USING p_account_id, p_workspace_id, p_artifact_id;
|
|
2607
|
+
RETURN QUERY SELECT false, current_revision;
|
|
2608
|
+
END;
|
|
2609
|
+
$body$;
|
|
2610
|
+
|
|
2611
|
+
CREATE OR REPLACE FUNCTION opengeni_private.claim_editable_artifact_live_outbox(
|
|
2612
|
+
p_owner text,
|
|
2613
|
+
p_lease_duration_ms integer,
|
|
2614
|
+
p_limit integer,
|
|
2615
|
+
p_data_schema name
|
|
2616
|
+
) RETURNS TABLE (
|
|
2617
|
+
outbox_id text,
|
|
2618
|
+
account_id uuid,
|
|
2619
|
+
workspace_id uuid,
|
|
2620
|
+
artifact_id text,
|
|
2621
|
+
transaction_id text,
|
|
2622
|
+
snapshot_id text,
|
|
2623
|
+
event_kind text,
|
|
2624
|
+
event jsonb,
|
|
2625
|
+
state text,
|
|
2626
|
+
attempt_count integer,
|
|
2627
|
+
lease_owner text,
|
|
2628
|
+
lease_expires_at timestamptz,
|
|
2629
|
+
next_attempt_at timestamptz,
|
|
2630
|
+
last_error_code text,
|
|
2631
|
+
published_at timestamptz,
|
|
2632
|
+
dead_lettered_at timestamptz,
|
|
2633
|
+
created_at timestamptz
|
|
2634
|
+
)
|
|
2635
|
+
LANGUAGE plpgsql
|
|
2636
|
+
SECURITY DEFINER
|
|
2637
|
+
SET search_path = pg_catalog, pg_temp
|
|
2638
|
+
AS $body$
|
|
2639
|
+
DECLARE data_schema name;
|
|
2640
|
+
BEGIN
|
|
2641
|
+
IF p_owner IS NULL OR pg_catalog.octet_length(p_owner) NOT BETWEEN 1 AND 256
|
|
2642
|
+
OR p_lease_duration_ms IS NULL
|
|
2643
|
+
OR p_lease_duration_ms NOT BETWEEN 1 AND 86400000
|
|
2644
|
+
OR p_limit IS NULL OR p_limit NOT BETWEEN 1 AND 1000
|
|
2645
|
+
OR p_data_schema IS NULL
|
|
2646
|
+
THEN
|
|
2647
|
+
RAISE EXCEPTION 'invalid editable artifact outbox claim bounds'
|
|
2648
|
+
USING ERRCODE = '22023';
|
|
2649
|
+
END IF;
|
|
2650
|
+
data_schema := opengeni_private.resolve_editable_artifact_data_schema(p_data_schema);
|
|
2651
|
+
RETURN QUERY EXECUTE pg_catalog.format($query$
|
|
2652
|
+
WITH exhausted_candidates AS MATERIALIZED (
|
|
2653
|
+
SELECT candidate.id
|
|
2654
|
+
FROM %1$I.editable_artifact_live_outbox candidate
|
|
2655
|
+
WHERE candidate.attempt_count >= 1000000
|
|
2656
|
+
AND ((candidate.state = 'pending'
|
|
2657
|
+
AND candidate.next_attempt_at <= pg_catalog.statement_timestamp())
|
|
2658
|
+
OR (candidate.state = 'publishing'
|
|
2659
|
+
AND candidate.lease_expires_at <= pg_catalog.statement_timestamp()))
|
|
2660
|
+
ORDER BY candidate.next_attempt_at, candidate.created_at, candidate.id
|
|
2661
|
+
FOR UPDATE SKIP LOCKED
|
|
2662
|
+
LIMIT $1
|
|
2663
|
+
), exhausted AS (
|
|
2664
|
+
UPDATE %1$I.editable_artifact_live_outbox target
|
|
2665
|
+
SET state = 'dead_lettered', lease_owner = NULL, lease_expires_at = NULL,
|
|
2666
|
+
last_error_code = 'attempts_exhausted',
|
|
2667
|
+
dead_lettered_at = pg_catalog.statement_timestamp()
|
|
2668
|
+
FROM exhausted_candidates
|
|
2669
|
+
WHERE target.id = exhausted_candidates.id
|
|
2670
|
+
RETURNING target.id
|
|
2671
|
+
), candidates AS (
|
|
2672
|
+
SELECT candidate.id
|
|
2673
|
+
FROM %1$I.editable_artifact_live_outbox candidate
|
|
2674
|
+
WHERE candidate.attempt_count < 1000000
|
|
2675
|
+
AND ((candidate.state = 'pending'
|
|
2676
|
+
AND candidate.next_attempt_at <= pg_catalog.statement_timestamp())
|
|
2677
|
+
OR (candidate.state = 'publishing'
|
|
2678
|
+
AND candidate.lease_expires_at <= pg_catalog.statement_timestamp()))
|
|
2679
|
+
ORDER BY candidate.next_attempt_at, candidate.created_at, candidate.id
|
|
2680
|
+
FOR UPDATE SKIP LOCKED
|
|
2681
|
+
LIMIT $1
|
|
2682
|
+
), claimed AS (
|
|
2683
|
+
UPDATE %1$I.editable_artifact_live_outbox target
|
|
2684
|
+
SET state = 'publishing',
|
|
2685
|
+
attempt_count = target.attempt_count + 1,
|
|
2686
|
+
lease_owner = $2,
|
|
2687
|
+
lease_expires_at = pg_catalog.statement_timestamp()
|
|
2688
|
+
+ pg_catalog.make_interval(secs => $3 / 1000.0),
|
|
2689
|
+
last_error_code = NULL
|
|
2690
|
+
FROM candidates
|
|
2691
|
+
WHERE target.id = candidates.id
|
|
2692
|
+
RETURNING target.*
|
|
2693
|
+
)
|
|
2694
|
+
SELECT claimed.id, claimed.account_id, claimed.workspace_id,
|
|
2695
|
+
claimed.artifact_id, claimed.transaction_id, claimed.snapshot_id,
|
|
2696
|
+
claimed.event_kind, claimed.event, claimed.state, claimed.attempt_count,
|
|
2697
|
+
claimed.lease_owner, claimed.lease_expires_at, claimed.next_attempt_at,
|
|
2698
|
+
claimed.last_error_code, claimed.published_at, claimed.dead_lettered_at,
|
|
2699
|
+
claimed.created_at
|
|
2700
|
+
FROM claimed
|
|
2701
|
+
CROSS JOIN (SELECT count(*) FROM exhausted) exhausted_barrier
|
|
2702
|
+
ORDER BY claimed.created_at, claimed.id
|
|
2703
|
+
$query$, data_schema) USING p_limit, p_owner, p_lease_duration_ms;
|
|
2704
|
+
END;
|
|
2705
|
+
$body$;
|
|
2706
|
+
|
|
2707
|
+
CREATE OR REPLACE FUNCTION opengeni_private.renew_editable_artifact_live_outbox(
|
|
2708
|
+
p_outbox_id text,
|
|
2709
|
+
p_owner text,
|
|
2710
|
+
p_attempt_count integer,
|
|
2711
|
+
p_lease_duration_ms integer,
|
|
2712
|
+
p_data_schema name
|
|
2713
|
+
) RETURNS boolean
|
|
2714
|
+
LANGUAGE plpgsql
|
|
2715
|
+
SECURITY DEFINER
|
|
2716
|
+
SET search_path = pg_catalog, pg_temp
|
|
2717
|
+
AS $body$
|
|
2718
|
+
DECLARE affected integer;
|
|
2719
|
+
DECLARE data_schema name;
|
|
2720
|
+
BEGIN
|
|
2721
|
+
IF p_outbox_id IS NULL OR p_outbox_id !~ '^[0-9a-f]{32}$' OR p_outbox_id ~ '^0+$'
|
|
2722
|
+
OR p_owner IS NULL OR pg_catalog.octet_length(p_owner) NOT BETWEEN 1 AND 256
|
|
2723
|
+
OR p_attempt_count IS NULL OR p_attempt_count NOT BETWEEN 1 AND 1000000
|
|
2724
|
+
OR p_lease_duration_ms IS NULL OR p_lease_duration_ms NOT BETWEEN 1 AND 86400000
|
|
2725
|
+
OR p_data_schema IS NULL
|
|
2726
|
+
THEN
|
|
2727
|
+
RAISE EXCEPTION 'invalid editable artifact outbox renewal bounds'
|
|
2728
|
+
USING ERRCODE = '22023';
|
|
2729
|
+
END IF;
|
|
2730
|
+
data_schema := opengeni_private.resolve_editable_artifact_data_schema(p_data_schema);
|
|
2731
|
+
EXECUTE pg_catalog.format($query$
|
|
2732
|
+
UPDATE %I.editable_artifact_live_outbox target
|
|
2733
|
+
SET lease_expires_at = pg_catalog.statement_timestamp()
|
|
2734
|
+
+ pg_catalog.make_interval(secs => $4 / 1000.0)
|
|
2735
|
+
WHERE target.id = $1
|
|
2736
|
+
AND target.state = 'publishing'
|
|
2737
|
+
AND target.lease_owner = $2
|
|
2738
|
+
AND target.attempt_count = $3
|
|
2739
|
+
AND target.lease_expires_at > pg_catalog.statement_timestamp()
|
|
2740
|
+
$query$, data_schema)
|
|
2741
|
+
USING p_outbox_id, p_owner, p_attempt_count, p_lease_duration_ms;
|
|
2742
|
+
GET DIAGNOSTICS affected = ROW_COUNT;
|
|
2743
|
+
RETURN affected = 1;
|
|
2744
|
+
END;
|
|
2745
|
+
$body$;
|
|
2746
|
+
|
|
2747
|
+
CREATE OR REPLACE FUNCTION opengeni_private.retry_editable_artifact_live_outbox(
|
|
2748
|
+
p_outbox_id text,
|
|
2749
|
+
p_owner text,
|
|
2750
|
+
p_attempt_count integer,
|
|
2751
|
+
p_retry_delay_ms integer,
|
|
2752
|
+
p_error_code text,
|
|
2753
|
+
p_data_schema name
|
|
2754
|
+
) RETURNS boolean
|
|
2755
|
+
LANGUAGE plpgsql
|
|
2756
|
+
SECURITY DEFINER
|
|
2757
|
+
SET search_path = pg_catalog, pg_temp
|
|
2758
|
+
AS $body$
|
|
2759
|
+
DECLARE affected integer;
|
|
2760
|
+
DECLARE data_schema name;
|
|
2761
|
+
BEGIN
|
|
2762
|
+
IF p_outbox_id IS NULL OR p_outbox_id !~ '^[0-9a-f]{32}$' OR p_outbox_id ~ '^0+$'
|
|
2763
|
+
OR p_owner IS NULL OR pg_catalog.octet_length(p_owner) NOT BETWEEN 1 AND 256
|
|
2764
|
+
OR p_attempt_count IS NULL OR p_attempt_count NOT BETWEEN 1 AND 1000000
|
|
2765
|
+
OR p_retry_delay_ms IS NULL OR p_retry_delay_ms NOT BETWEEN 1 AND 86400000
|
|
2766
|
+
OR p_error_code NOT IN ('broker_unavailable', 'broker_backpressure', 'publish_timeout')
|
|
2767
|
+
OR p_data_schema IS NULL
|
|
2768
|
+
THEN
|
|
2769
|
+
RAISE EXCEPTION 'invalid editable artifact outbox retry bounds'
|
|
2770
|
+
USING ERRCODE = '22023';
|
|
2771
|
+
END IF;
|
|
2772
|
+
data_schema := opengeni_private.resolve_editable_artifact_data_schema(p_data_schema);
|
|
2773
|
+
EXECUTE pg_catalog.format($query$
|
|
2774
|
+
UPDATE %I.editable_artifact_live_outbox target
|
|
2775
|
+
SET state = 'pending', lease_owner = NULL, lease_expires_at = NULL,
|
|
2776
|
+
next_attempt_at = pg_catalog.statement_timestamp()
|
|
2777
|
+
+ pg_catalog.make_interval(secs => $4 / 1000.0),
|
|
2778
|
+
last_error_code = $5
|
|
2779
|
+
WHERE target.id = $1
|
|
2780
|
+
AND target.state = 'publishing'
|
|
2781
|
+
AND target.lease_owner = $2
|
|
2782
|
+
AND target.attempt_count = $3
|
|
2783
|
+
AND target.lease_expires_at > pg_catalog.statement_timestamp()
|
|
2784
|
+
AND target.lease_expires_at > pg_catalog.statement_timestamp()
|
|
2785
|
+
$query$, data_schema)
|
|
2786
|
+
USING p_outbox_id, p_owner, p_attempt_count, p_retry_delay_ms, p_error_code;
|
|
2787
|
+
GET DIAGNOSTICS affected = ROW_COUNT;
|
|
2788
|
+
IF affected = 1 THEN RETURN true; END IF;
|
|
2789
|
+
EXECUTE pg_catalog.format($query$
|
|
2790
|
+
SELECT count(*)::integer
|
|
2791
|
+
FROM %I.editable_artifact_live_outbox target
|
|
2792
|
+
WHERE target.id = $1 AND target.state = 'pending'
|
|
2793
|
+
AND target.attempt_count = $2 AND target.last_error_code = $3
|
|
2794
|
+
$query$, data_schema) INTO affected
|
|
2795
|
+
USING p_outbox_id, p_attempt_count, p_error_code;
|
|
2796
|
+
RETURN affected = 1;
|
|
2797
|
+
END;
|
|
2798
|
+
$body$;
|
|
2799
|
+
|
|
2800
|
+
CREATE OR REPLACE FUNCTION opengeni_private.dead_letter_editable_artifact_live_outbox(
|
|
2801
|
+
p_outbox_id text,
|
|
2802
|
+
p_owner text,
|
|
2803
|
+
p_attempt_count integer,
|
|
2804
|
+
p_error_code text,
|
|
2805
|
+
p_data_schema name
|
|
2806
|
+
) RETURNS boolean
|
|
2807
|
+
LANGUAGE plpgsql
|
|
2808
|
+
SECURITY DEFINER
|
|
2809
|
+
SET search_path = pg_catalog, pg_temp
|
|
2810
|
+
AS $body$
|
|
2811
|
+
DECLARE affected integer;
|
|
2812
|
+
DECLARE data_schema name;
|
|
2813
|
+
BEGIN
|
|
2814
|
+
IF p_outbox_id IS NULL OR p_outbox_id !~ '^[0-9a-f]{32}$' OR p_outbox_id ~ '^0+$'
|
|
2815
|
+
OR p_owner IS NULL OR pg_catalog.octet_length(p_owner) NOT BETWEEN 1 AND 256
|
|
2816
|
+
OR p_attempt_count IS NULL OR p_attempt_count NOT BETWEEN 1 AND 1000000
|
|
2817
|
+
OR p_error_code NOT IN ('invalid_hint', 'oversized_hint')
|
|
2818
|
+
OR p_data_schema IS NULL
|
|
2819
|
+
THEN
|
|
2820
|
+
RAISE EXCEPTION 'invalid editable artifact outbox dead-letter bounds'
|
|
2821
|
+
USING ERRCODE = '22023';
|
|
2822
|
+
END IF;
|
|
2823
|
+
data_schema := opengeni_private.resolve_editable_artifact_data_schema(p_data_schema);
|
|
2824
|
+
EXECUTE pg_catalog.format($query$
|
|
2825
|
+
UPDATE %I.editable_artifact_live_outbox target
|
|
2826
|
+
SET state = 'dead_lettered', lease_owner = NULL, lease_expires_at = NULL,
|
|
2827
|
+
last_error_code = $4, dead_lettered_at = pg_catalog.statement_timestamp()
|
|
2828
|
+
WHERE target.id = $1
|
|
2829
|
+
AND target.state = 'publishing'
|
|
2830
|
+
AND target.lease_owner = $2
|
|
2831
|
+
AND target.attempt_count = $3
|
|
2832
|
+
AND target.lease_expires_at > pg_catalog.statement_timestamp()
|
|
2833
|
+
$query$, data_schema)
|
|
2834
|
+
USING p_outbox_id, p_owner, p_attempt_count, p_error_code;
|
|
2835
|
+
GET DIAGNOSTICS affected = ROW_COUNT;
|
|
2836
|
+
IF affected = 1 THEN RETURN true; END IF;
|
|
2837
|
+
EXECUTE pg_catalog.format($query$
|
|
2838
|
+
SELECT count(*)::integer
|
|
2839
|
+
FROM %I.editable_artifact_live_outbox target
|
|
2840
|
+
WHERE target.id = $1 AND target.state = 'dead_lettered'
|
|
2841
|
+
AND target.attempt_count = $2 AND target.last_error_code = $3
|
|
2842
|
+
$query$, data_schema) INTO affected
|
|
2843
|
+
USING p_outbox_id, p_attempt_count, p_error_code;
|
|
2844
|
+
RETURN affected = 1;
|
|
2845
|
+
END;
|
|
2846
|
+
$body$;
|
|
2847
|
+
|
|
2848
|
+
CREATE OR REPLACE FUNCTION opengeni_private.mark_editable_artifact_live_outbox_published(
|
|
2849
|
+
p_outbox_id text,
|
|
2850
|
+
p_owner text,
|
|
2851
|
+
p_attempt_count integer,
|
|
2852
|
+
p_data_schema name
|
|
2853
|
+
) RETURNS boolean
|
|
2854
|
+
LANGUAGE plpgsql
|
|
2855
|
+
SECURITY DEFINER
|
|
2856
|
+
SET search_path = pg_catalog, pg_temp
|
|
2857
|
+
AS $body$
|
|
2858
|
+
DECLARE affected integer;
|
|
2859
|
+
DECLARE data_schema name;
|
|
2860
|
+
BEGIN
|
|
2861
|
+
IF p_outbox_id IS NULL
|
|
2862
|
+
OR p_outbox_id !~ '^[0-9a-f]{32}$' OR p_outbox_id ~ '^0+$'
|
|
2863
|
+
OR p_owner IS NULL OR pg_catalog.octet_length(p_owner) NOT BETWEEN 1 AND 256
|
|
2864
|
+
OR p_attempt_count IS NULL OR p_attempt_count NOT BETWEEN 1 AND 1000000
|
|
2865
|
+
OR p_data_schema IS NULL
|
|
2866
|
+
THEN
|
|
2867
|
+
RAISE EXCEPTION 'invalid editable artifact outbox settlement bounds'
|
|
2868
|
+
USING ERRCODE = '22023';
|
|
2869
|
+
END IF;
|
|
2870
|
+
data_schema := opengeni_private.resolve_editable_artifact_data_schema(p_data_schema);
|
|
2871
|
+
EXECUTE pg_catalog.format($query$
|
|
2872
|
+
UPDATE %I.editable_artifact_live_outbox target
|
|
2873
|
+
SET state = 'published', published_at = pg_catalog.statement_timestamp(),
|
|
2874
|
+
lease_owner = NULL, lease_expires_at = NULL, last_error_code = NULL
|
|
2875
|
+
WHERE target.id = $1
|
|
2876
|
+
AND target.state = 'publishing'
|
|
2877
|
+
AND target.lease_owner = $2
|
|
2878
|
+
AND target.attempt_count = $3
|
|
2879
|
+
AND target.lease_expires_at > pg_catalog.statement_timestamp()
|
|
2880
|
+
$query$, data_schema) USING p_outbox_id, p_owner, p_attempt_count;
|
|
2881
|
+
GET DIAGNOSTICS affected = ROW_COUNT;
|
|
2882
|
+
IF affected = 1 THEN RETURN true; END IF;
|
|
2883
|
+
EXECUTE pg_catalog.format($query$
|
|
2884
|
+
SELECT count(*)::integer
|
|
2885
|
+
FROM %I.editable_artifact_live_outbox target
|
|
2886
|
+
WHERE target.id = $1
|
|
2887
|
+
AND target.attempt_count = $2
|
|
2888
|
+
AND target.state = 'published'
|
|
2889
|
+
$query$, data_schema) INTO affected USING p_outbox_id, p_attempt_count;
|
|
2890
|
+
RETURN affected = 1;
|
|
2891
|
+
END;
|
|
2892
|
+
$body$;
|
|
2893
|
+
|
|
2894
|
+
CREATE OR REPLACE FUNCTION opengeni_private.release_editable_artifact_live_outbox(
|
|
2895
|
+
p_outbox_id text,
|
|
2896
|
+
p_owner text,
|
|
2897
|
+
p_attempt_count integer,
|
|
2898
|
+
p_data_schema name
|
|
2899
|
+
) RETURNS boolean
|
|
2900
|
+
LANGUAGE plpgsql
|
|
2901
|
+
SECURITY DEFINER
|
|
2902
|
+
SET search_path = pg_catalog, pg_temp
|
|
2903
|
+
AS $body$
|
|
2904
|
+
DECLARE affected integer;
|
|
2905
|
+
DECLARE data_schema name;
|
|
2906
|
+
BEGIN
|
|
2907
|
+
IF p_outbox_id IS NULL
|
|
2908
|
+
OR p_outbox_id !~ '^[0-9a-f]{32}$' OR p_outbox_id ~ '^0+$'
|
|
2909
|
+
OR p_owner IS NULL OR pg_catalog.octet_length(p_owner) NOT BETWEEN 1 AND 256
|
|
2910
|
+
OR p_attempt_count IS NULL OR p_attempt_count NOT BETWEEN 1 AND 1000000
|
|
2911
|
+
OR p_data_schema IS NULL
|
|
2912
|
+
THEN
|
|
2913
|
+
RAISE EXCEPTION 'invalid editable artifact outbox release bounds'
|
|
2914
|
+
USING ERRCODE = '22023';
|
|
2915
|
+
END IF;
|
|
2916
|
+
data_schema := opengeni_private.resolve_editable_artifact_data_schema(p_data_schema);
|
|
2917
|
+
EXECUTE pg_catalog.format($query$
|
|
2918
|
+
UPDATE %I.editable_artifact_live_outbox target
|
|
2919
|
+
SET state = 'pending', lease_owner = NULL, lease_expires_at = NULL,
|
|
2920
|
+
next_attempt_at = pg_catalog.statement_timestamp()
|
|
2921
|
+
WHERE target.id = $1
|
|
2922
|
+
AND target.state = 'publishing'
|
|
2923
|
+
AND target.lease_owner = $2
|
|
2924
|
+
AND target.attempt_count = $3
|
|
2925
|
+
AND target.lease_expires_at > pg_catalog.statement_timestamp()
|
|
2926
|
+
$query$, data_schema) USING p_outbox_id, p_owner, p_attempt_count;
|
|
2927
|
+
GET DIAGNOSTICS affected = ROW_COUNT;
|
|
2928
|
+
IF affected = 1 THEN RETURN true; END IF;
|
|
2929
|
+
EXECUTE pg_catalog.format($query$
|
|
2930
|
+
SELECT count(*)::integer
|
|
2931
|
+
FROM %I.editable_artifact_live_outbox target
|
|
2932
|
+
WHERE target.id = $1
|
|
2933
|
+
AND target.attempt_count = $2
|
|
2934
|
+
AND ((target.state = 'pending' AND target.lease_owner IS NULL)
|
|
2935
|
+
OR target.state = 'published')
|
|
2936
|
+
$query$, data_schema) INTO affected USING p_outbox_id, p_attempt_count;
|
|
2937
|
+
RETURN affected = 1;
|
|
2938
|
+
END;
|
|
2939
|
+
$body$;
|
|
2940
|
+
|
|
2941
|
+
-- Cross-workspace materialization is a dedicated database capability. The
|
|
2942
|
+
-- worker owns no table privileges: these SECURITY DEFINER routines are the only
|
|
2943
|
+
-- path from a global claim to an exact, lease-fenced terminal projection.
|
|
2944
|
+
CREATE OR REPLACE FUNCTION opengeni_private.claim_editable_artifact_materializations(
|
|
2945
|
+
p_owner text,
|
|
2946
|
+
p_lease_duration_ms integer,
|
|
2947
|
+
p_limit integer,
|
|
2948
|
+
p_data_schema name
|
|
2949
|
+
) RETURNS TABLE (
|
|
2950
|
+
account_id uuid,
|
|
2951
|
+
workspace_id uuid,
|
|
2952
|
+
artifact_id text,
|
|
2953
|
+
job_id text,
|
|
2954
|
+
version_id text,
|
|
2955
|
+
modality text,
|
|
2956
|
+
input_snapshot_id text,
|
|
2957
|
+
target_head_sequence bigint,
|
|
2958
|
+
state_hash text,
|
|
2959
|
+
source_object_reference text,
|
|
2960
|
+
source_byte_size bigint,
|
|
2961
|
+
source_content_hash text,
|
|
2962
|
+
source_mime_type text,
|
|
2963
|
+
model_schema_version integer,
|
|
2964
|
+
operation_protocol_version integer,
|
|
2965
|
+
snapshot_protocol_version integer,
|
|
2966
|
+
format text,
|
|
2967
|
+
codec_id text,
|
|
2968
|
+
normalized_options text,
|
|
2969
|
+
options_hash text,
|
|
2970
|
+
codec_version text,
|
|
2971
|
+
kernel_version text,
|
|
2972
|
+
font_registry_hash text,
|
|
2973
|
+
policy_hash text,
|
|
2974
|
+
attempt_count integer,
|
|
2975
|
+
lease_owner text,
|
|
2976
|
+
lease_expires_at timestamptz
|
|
2977
|
+
)
|
|
2978
|
+
LANGUAGE plpgsql
|
|
2979
|
+
SECURITY DEFINER
|
|
2980
|
+
SET search_path = pg_catalog, pg_temp
|
|
2981
|
+
AS $body$
|
|
2982
|
+
DECLARE data_schema name;
|
|
2983
|
+
BEGIN
|
|
2984
|
+
IF p_owner IS NULL OR pg_catalog.octet_length(p_owner) NOT BETWEEN 1 AND 256
|
|
2985
|
+
OR p_owner <> pg_catalog.btrim(p_owner)
|
|
2986
|
+
OR p_lease_duration_ms IS NULL
|
|
2987
|
+
OR p_lease_duration_ms NOT BETWEEN 1 AND 86400000
|
|
2988
|
+
OR p_limit IS NULL OR p_limit NOT BETWEEN 1 AND 1000
|
|
2989
|
+
OR p_data_schema IS NULL
|
|
2990
|
+
THEN
|
|
2991
|
+
RAISE EXCEPTION 'invalid editable artifact materialization claim bounds'
|
|
2992
|
+
USING ERRCODE = '22023';
|
|
2993
|
+
END IF;
|
|
2994
|
+
data_schema := opengeni_private.resolve_editable_artifact_data_schema(p_data_schema);
|
|
2995
|
+
RETURN QUERY EXECUTE pg_catalog.format($query$
|
|
2996
|
+
WITH exhausted_candidates AS MATERIALIZED (
|
|
2997
|
+
SELECT candidate.account_id, candidate.workspace_id,
|
|
2998
|
+
candidate.artifact_id, candidate.id
|
|
2999
|
+
FROM %1$I.editable_artifact_materialization_jobs candidate
|
|
3000
|
+
WHERE candidate.state = 'running'
|
|
3001
|
+
AND candidate.attempt_count >= 1000
|
|
3002
|
+
AND candidate.lease_expires_at <= pg_catalog.statement_timestamp()
|
|
3003
|
+
ORDER BY candidate.lease_expires_at, candidate.created_at,
|
|
3004
|
+
candidate.account_id, candidate.workspace_id, candidate.artifact_id, candidate.id
|
|
3005
|
+
FOR UPDATE OF candidate SKIP LOCKED
|
|
3006
|
+
LIMIT $1
|
|
3007
|
+
), exhausted AS (
|
|
3008
|
+
UPDATE %1$I.editable_artifact_materialization_jobs target
|
|
3009
|
+
SET state = 'failed', settled_by_owner = target.lease_owner,
|
|
3010
|
+
lease_owner = NULL, lease_expires_at = NULL,
|
|
3011
|
+
error_code = 'attempts_exhausted',
|
|
3012
|
+
completed_at = pg_catalog.statement_timestamp()
|
|
3013
|
+
FROM exhausted_candidates
|
|
3014
|
+
WHERE target.account_id = exhausted_candidates.account_id
|
|
3015
|
+
AND target.workspace_id = exhausted_candidates.workspace_id
|
|
3016
|
+
AND target.artifact_id = exhausted_candidates.artifact_id
|
|
3017
|
+
AND target.id = exhausted_candidates.id
|
|
3018
|
+
RETURNING target.id
|
|
3019
|
+
), candidates AS (
|
|
3020
|
+
SELECT candidate.account_id, candidate.workspace_id,
|
|
3021
|
+
candidate.artifact_id, candidate.id
|
|
3022
|
+
FROM %1$I.editable_artifact_materialization_jobs candidate
|
|
3023
|
+
WHERE candidate.attempt_count < 1000
|
|
3024
|
+
AND (candidate.state = 'pending' OR (
|
|
3025
|
+
candidate.state = 'running'
|
|
3026
|
+
AND candidate.lease_expires_at <= pg_catalog.statement_timestamp()
|
|
3027
|
+
))
|
|
3028
|
+
ORDER BY COALESCE(candidate.lease_expires_at, candidate.created_at),
|
|
3029
|
+
candidate.created_at, candidate.account_id, candidate.workspace_id,
|
|
3030
|
+
candidate.artifact_id, candidate.id
|
|
3031
|
+
FOR UPDATE OF candidate SKIP LOCKED
|
|
3032
|
+
LIMIT $1
|
|
3033
|
+
), claimed AS (
|
|
3034
|
+
UPDATE %1$I.editable_artifact_materialization_jobs target
|
|
3035
|
+
SET state = 'running', attempt_count = target.attempt_count + 1,
|
|
3036
|
+
lease_owner = $2,
|
|
3037
|
+
lease_expires_at = pg_catalog.statement_timestamp()
|
|
3038
|
+
+ pg_catalog.make_interval(secs => $3 / 1000.0),
|
|
3039
|
+
settled_by_owner = NULL, error_code = NULL,
|
|
3040
|
+
started_at = pg_catalog.statement_timestamp(), completed_at = NULL
|
|
3041
|
+
FROM candidates
|
|
3042
|
+
WHERE target.account_id = candidates.account_id
|
|
3043
|
+
AND target.workspace_id = candidates.workspace_id
|
|
3044
|
+
AND target.artifact_id = candidates.artifact_id
|
|
3045
|
+
AND target.id = candidates.id
|
|
3046
|
+
RETURNING target.*
|
|
3047
|
+
)
|
|
3048
|
+
SELECT claimed.account_id, claimed.workspace_id, claimed.artifact_id,
|
|
3049
|
+
claimed.id, claimed.version_id, artifact.modality, claimed.input_snapshot_id,
|
|
3050
|
+
claimed.target_head_sequence, claimed.state_hash,
|
|
3051
|
+
source_blob.object_reference, source_blob.byte_size,
|
|
3052
|
+
source_blob.content_hash, source_blob.mime_type,
|
|
3053
|
+
source_snapshot.model_schema_version, source_snapshot.operation_protocol_version,
|
|
3054
|
+
source_snapshot.crdt_state_version,
|
|
3055
|
+
claimed.format, claimed.codec_id, claimed.normalized_options,
|
|
3056
|
+
claimed.options_hash, claimed.codec_version, claimed.kernel_version,
|
|
3057
|
+
claimed.font_registry_hash, claimed.policy_hash, claimed.attempt_count,
|
|
3058
|
+
claimed.lease_owner, claimed.lease_expires_at
|
|
3059
|
+
FROM claimed
|
|
3060
|
+
CROSS JOIN (SELECT count(*) FROM exhausted) exhausted_barrier
|
|
3061
|
+
JOIN %1$I.editable_artifacts artifact
|
|
3062
|
+
ON artifact.account_id = claimed.account_id
|
|
3063
|
+
AND artifact.workspace_id = claimed.workspace_id
|
|
3064
|
+
AND artifact.id = claimed.artifact_id
|
|
3065
|
+
JOIN %1$I.editable_artifact_snapshots source_snapshot
|
|
3066
|
+
ON source_snapshot.account_id = claimed.account_id
|
|
3067
|
+
AND source_snapshot.workspace_id = claimed.workspace_id
|
|
3068
|
+
AND source_snapshot.artifact_id = claimed.artifact_id
|
|
3069
|
+
AND source_snapshot.id = claimed.input_snapshot_id
|
|
3070
|
+
AND source_snapshot.covered_head_sequence = claimed.target_head_sequence
|
|
3071
|
+
AND source_snapshot.state_hash = claimed.state_hash
|
|
3072
|
+
JOIN %1$I.editable_artifact_blob_refs source_blob
|
|
3073
|
+
ON source_blob.account_id = source_snapshot.account_id
|
|
3074
|
+
AND source_blob.workspace_id = source_snapshot.workspace_id
|
|
3075
|
+
AND source_blob.artifact_id = source_snapshot.artifact_id
|
|
3076
|
+
AND source_blob.id = source_snapshot.blob_ref_id
|
|
3077
|
+
AND source_blob.kind = 'snapshot'
|
|
3078
|
+
AND source_blob.byte_size = source_snapshot.byte_size
|
|
3079
|
+
AND source_blob.content_hash = source_snapshot.content_hash
|
|
3080
|
+
AND source_blob.mime_type = source_snapshot.mime_type
|
|
3081
|
+
ORDER BY claimed.created_at, claimed.account_id, claimed.workspace_id,
|
|
3082
|
+
claimed.artifact_id, claimed.id
|
|
3083
|
+
$query$, data_schema) USING p_limit, p_owner, p_lease_duration_ms;
|
|
3084
|
+
END;
|
|
3085
|
+
$body$;
|
|
3086
|
+
|
|
3087
|
+
CREATE OR REPLACE FUNCTION opengeni_private.renew_editable_artifact_materialization(
|
|
3088
|
+
p_account_id uuid,
|
|
3089
|
+
p_workspace_id uuid,
|
|
3090
|
+
p_artifact_id text,
|
|
3091
|
+
p_job_id text,
|
|
3092
|
+
p_owner text,
|
|
3093
|
+
p_attempt_count integer,
|
|
3094
|
+
p_lease_duration_ms integer,
|
|
3095
|
+
p_data_schema name
|
|
3096
|
+
) RETURNS TABLE (
|
|
3097
|
+
account_id uuid,
|
|
3098
|
+
workspace_id uuid,
|
|
3099
|
+
artifact_id text,
|
|
3100
|
+
job_id text,
|
|
3101
|
+
lease_owner text,
|
|
3102
|
+
attempt_count integer,
|
|
3103
|
+
lease_expires_at timestamptz
|
|
3104
|
+
)
|
|
3105
|
+
LANGUAGE plpgsql
|
|
3106
|
+
SECURITY DEFINER
|
|
3107
|
+
SET search_path = pg_catalog, pg_temp
|
|
3108
|
+
AS $body$
|
|
3109
|
+
DECLARE data_schema name;
|
|
3110
|
+
BEGIN
|
|
3111
|
+
IF p_account_id IS NULL OR p_workspace_id IS NULL
|
|
3112
|
+
OR p_artifact_id IS NULL OR p_artifact_id !~ '^[0-9a-f]{32}$'
|
|
3113
|
+
OR p_artifact_id ~ '^0+$'
|
|
3114
|
+
OR p_job_id IS NULL OR p_job_id !~ '^[0-9a-f]{32}$' OR p_job_id ~ '^0+$'
|
|
3115
|
+
OR p_owner IS NULL OR pg_catalog.octet_length(p_owner) NOT BETWEEN 1 AND 256
|
|
3116
|
+
OR p_owner <> pg_catalog.btrim(p_owner)
|
|
3117
|
+
OR p_attempt_count IS NULL OR p_attempt_count NOT BETWEEN 1 AND 1000
|
|
3118
|
+
OR p_lease_duration_ms IS NULL
|
|
3119
|
+
OR p_lease_duration_ms NOT BETWEEN 1 AND 86400000
|
|
3120
|
+
OR p_data_schema IS NULL
|
|
3121
|
+
THEN
|
|
3122
|
+
RAISE EXCEPTION 'invalid editable artifact materialization renewal bounds'
|
|
3123
|
+
USING ERRCODE = '22023';
|
|
3124
|
+
END IF;
|
|
3125
|
+
data_schema := opengeni_private.resolve_editable_artifact_data_schema(p_data_schema);
|
|
3126
|
+
RETURN QUERY EXECUTE pg_catalog.format($query$
|
|
3127
|
+
UPDATE %I.editable_artifact_materialization_jobs target
|
|
3128
|
+
SET lease_expires_at = GREATEST(
|
|
3129
|
+
target.lease_expires_at + interval '1 microsecond',
|
|
3130
|
+
pg_catalog.statement_timestamp()
|
|
3131
|
+
+ pg_catalog.make_interval(secs => $7 / 1000.0)
|
|
3132
|
+
)
|
|
3133
|
+
WHERE target.account_id = $1 AND target.workspace_id = $2
|
|
3134
|
+
AND target.artifact_id = $3 AND target.id = $4
|
|
3135
|
+
AND target.state = 'running' AND target.lease_owner = $5
|
|
3136
|
+
AND target.attempt_count = $6
|
|
3137
|
+
AND target.lease_expires_at > pg_catalog.statement_timestamp()
|
|
3138
|
+
RETURNING target.account_id, target.workspace_id, target.artifact_id,
|
|
3139
|
+
target.id, target.lease_owner, target.attempt_count,
|
|
3140
|
+
target.lease_expires_at
|
|
3141
|
+
$query$, data_schema)
|
|
3142
|
+
USING p_account_id, p_workspace_id, p_artifact_id, p_job_id, p_owner,
|
|
3143
|
+
p_attempt_count, p_lease_duration_ms;
|
|
3144
|
+
END;
|
|
3145
|
+
$body$;
|
|
3146
|
+
|
|
3147
|
+
CREATE OR REPLACE FUNCTION opengeni_private.succeed_editable_artifact_materialization(
|
|
3148
|
+
p_account_id uuid,
|
|
3149
|
+
p_workspace_id uuid,
|
|
3150
|
+
p_artifact_id text,
|
|
3151
|
+
p_job_id text,
|
|
3152
|
+
p_owner text,
|
|
3153
|
+
p_attempt_count integer,
|
|
3154
|
+
p_result_id text,
|
|
3155
|
+
p_blob_ref_id text,
|
|
3156
|
+
p_object_reference text,
|
|
3157
|
+
p_byte_size bigint,
|
|
3158
|
+
p_content_hash text,
|
|
3159
|
+
p_mime_type text,
|
|
3160
|
+
p_verified_at timestamptz,
|
|
3161
|
+
p_data_schema name
|
|
3162
|
+
) RETURNS TABLE (
|
|
3163
|
+
outcome text,
|
|
3164
|
+
account_id uuid,
|
|
3165
|
+
workspace_id uuid,
|
|
3166
|
+
artifact_id text,
|
|
3167
|
+
job_id text,
|
|
3168
|
+
result_id text,
|
|
3169
|
+
blob_ref_id text,
|
|
3170
|
+
object_reference text,
|
|
3171
|
+
byte_size bigint,
|
|
3172
|
+
content_hash text,
|
|
3173
|
+
mime_type text,
|
|
3174
|
+
verified_at timestamptz,
|
|
3175
|
+
created_at timestamptz,
|
|
3176
|
+
settled_by_owner text,
|
|
3177
|
+
attempt_count integer
|
|
3178
|
+
)
|
|
3179
|
+
LANGUAGE plpgsql
|
|
3180
|
+
SECURITY DEFINER
|
|
3181
|
+
SET search_path = pg_catalog, pg_temp
|
|
3182
|
+
AS $body$
|
|
3183
|
+
DECLARE data_schema name;
|
|
3184
|
+
DECLARE affected integer := 0;
|
|
3185
|
+
DECLARE job_state text;
|
|
3186
|
+
DECLARE job_attempt integer;
|
|
3187
|
+
DECLARE job_owner text;
|
|
3188
|
+
DECLARE job_lease_expires_at timestamptz;
|
|
3189
|
+
DECLARE job_format text;
|
|
3190
|
+
BEGIN
|
|
3191
|
+
IF p_account_id IS NULL OR p_workspace_id IS NULL
|
|
3192
|
+
OR p_artifact_id IS NULL OR p_artifact_id !~ '^[0-9a-f]{32}$'
|
|
3193
|
+
OR p_artifact_id ~ '^0+$'
|
|
3194
|
+
OR p_job_id IS NULL OR p_job_id !~ '^[0-9a-f]{32}$' OR p_job_id ~ '^0+$'
|
|
3195
|
+
OR p_owner IS NULL OR pg_catalog.octet_length(p_owner) NOT BETWEEN 1 AND 256
|
|
3196
|
+
OR p_owner <> pg_catalog.btrim(p_owner)
|
|
3197
|
+
OR p_attempt_count IS NULL OR p_attempt_count NOT BETWEEN 1 AND 1000
|
|
3198
|
+
OR p_result_id IS NULL OR p_result_id !~ '^[0-9a-f]{32}$'
|
|
3199
|
+
OR p_result_id ~ '^0+$'
|
|
3200
|
+
OR p_blob_ref_id IS NULL OR p_blob_ref_id !~ '^[0-9a-f]{32}$'
|
|
3201
|
+
OR p_blob_ref_id ~ '^0+$'
|
|
3202
|
+
OR p_object_reference IS NULL
|
|
3203
|
+
OR pg_catalog.octet_length(p_object_reference) NOT BETWEEN 1 AND 1024
|
|
3204
|
+
OR p_object_reference <> pg_catalog.btrim(p_object_reference)
|
|
3205
|
+
OR p_byte_size IS NULL OR p_byte_size NOT BETWEEN 1 AND 9007199254740991
|
|
3206
|
+
OR p_content_hash IS NULL OR p_content_hash !~ '^sha256:[0-9a-f]{64}$'
|
|
3207
|
+
OR p_mime_type NOT IN (
|
|
3208
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
3209
|
+
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
3210
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
3211
|
+
'application/pdf', 'image/png', 'image/webp'
|
|
3212
|
+
)
|
|
3213
|
+
OR p_verified_at IS NULL
|
|
3214
|
+
OR p_verified_at > pg_catalog.clock_timestamp() + interval '5 minutes'
|
|
3215
|
+
OR p_data_schema IS NULL
|
|
3216
|
+
THEN
|
|
3217
|
+
RAISE EXCEPTION 'invalid editable artifact materialization success bounds'
|
|
3218
|
+
USING ERRCODE = '22023';
|
|
3219
|
+
END IF;
|
|
3220
|
+
data_schema := opengeni_private.resolve_editable_artifact_data_schema(p_data_schema);
|
|
3221
|
+
|
|
3222
|
+
job_state := NULL;
|
|
3223
|
+
EXECUTE pg_catalog.format($query$
|
|
3224
|
+
SELECT target.state, target.attempt_count, target.lease_owner,
|
|
3225
|
+
target.lease_expires_at, target.format
|
|
3226
|
+
FROM %I.editable_artifact_materialization_jobs target
|
|
3227
|
+
WHERE target.account_id = $1 AND target.workspace_id = $2
|
|
3228
|
+
AND target.artifact_id = $3 AND target.id = $4
|
|
3229
|
+
FOR UPDATE
|
|
3230
|
+
$query$, data_schema)
|
|
3231
|
+
INTO job_state, job_attempt, job_owner, job_lease_expires_at, job_format
|
|
3232
|
+
USING p_account_id, p_workspace_id, p_artifact_id, p_job_id;
|
|
3233
|
+
IF job_state IS NULL THEN RETURN; END IF;
|
|
3234
|
+
|
|
3235
|
+
-- A lost successful response is replayable only when every immutable output
|
|
3236
|
+
-- fact and the exact settled lease fence match. A stale/different caller gets
|
|
3237
|
+
-- no row and is surfaced as lease_fenced by the repository.
|
|
3238
|
+
IF job_state = 'succeeded' THEN
|
|
3239
|
+
RETURN QUERY EXECUTE pg_catalog.format($query$
|
|
3240
|
+
SELECT 'replayed'::text, job.account_id, job.workspace_id,
|
|
3241
|
+
job.artifact_id, job.id, result.id, result.blob_ref_id,
|
|
3242
|
+
blob.object_reference, result.byte_size, result.content_hash,
|
|
3243
|
+
result.mime_type, result.verified_at, result.created_at,
|
|
3244
|
+
job.settled_by_owner, job.attempt_count
|
|
3245
|
+
FROM %1$I.editable_artifact_materialization_jobs job
|
|
3246
|
+
JOIN %1$I.editable_artifact_materialization_results result
|
|
3247
|
+
ON result.account_id = job.account_id
|
|
3248
|
+
AND result.workspace_id = job.workspace_id
|
|
3249
|
+
AND result.artifact_id = job.artifact_id AND result.job_id = job.id
|
|
3250
|
+
JOIN %1$I.editable_artifact_blob_refs blob
|
|
3251
|
+
ON blob.account_id = result.account_id
|
|
3252
|
+
AND blob.workspace_id = result.workspace_id
|
|
3253
|
+
AND blob.artifact_id = result.artifact_id AND blob.id = result.blob_ref_id
|
|
3254
|
+
WHERE job.account_id = $1 AND job.workspace_id = $2
|
|
3255
|
+
AND job.artifact_id = $3 AND job.id = $4
|
|
3256
|
+
AND job.state = 'succeeded' AND job.settled_by_owner = $5
|
|
3257
|
+
AND job.attempt_count = $6 AND result.id = $7
|
|
3258
|
+
AND result.blob_ref_id = $8 AND blob.kind = 'materialization'
|
|
3259
|
+
AND blob.object_reference = $9 AND result.byte_size = $10
|
|
3260
|
+
AND result.content_hash = $11 AND result.mime_type = $12
|
|
3261
|
+
AND result.verified_at = $13
|
|
3262
|
+
AND blob.byte_size = result.byte_size
|
|
3263
|
+
AND blob.content_hash = result.content_hash
|
|
3264
|
+
AND blob.mime_type = result.mime_type
|
|
3265
|
+
$query$, data_schema)
|
|
3266
|
+
USING p_account_id, p_workspace_id, p_artifact_id, p_job_id, p_owner,
|
|
3267
|
+
p_attempt_count, p_result_id, p_blob_ref_id, p_object_reference,
|
|
3268
|
+
p_byte_size, p_content_hash, p_mime_type, p_verified_at;
|
|
3269
|
+
RETURN;
|
|
3270
|
+
END IF;
|
|
3271
|
+
|
|
3272
|
+
IF job_state <> 'running' OR job_owner IS DISTINCT FROM p_owner
|
|
3273
|
+
OR job_attempt IS DISTINCT FROM p_attempt_count
|
|
3274
|
+
OR job_lease_expires_at <= pg_catalog.statement_timestamp()
|
|
3275
|
+
THEN
|
|
3276
|
+
RETURN;
|
|
3277
|
+
END IF;
|
|
3278
|
+
IF p_mime_type <> (CASE job_format
|
|
3279
|
+
WHEN 'xlsx' THEN 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
3280
|
+
WHEN 'pptx' THEN 'application/vnd.openxmlformats-officedocument.presentationml.presentation'
|
|
3281
|
+
WHEN 'docx' THEN 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
|
3282
|
+
WHEN 'pdf' THEN 'application/pdf'
|
|
3283
|
+
WHEN 'png' THEN 'image/png'
|
|
3284
|
+
WHEN 'webp' THEN 'image/webp'
|
|
3285
|
+
END) THEN
|
|
3286
|
+
RAISE EXCEPTION 'materialization MIME type differs from the claimed format'
|
|
3287
|
+
USING ERRCODE = '22023';
|
|
3288
|
+
END IF;
|
|
3289
|
+
|
|
3290
|
+
EXECUTE pg_catalog.format($query$
|
|
3291
|
+
INSERT INTO %I.editable_artifact_blob_refs (
|
|
3292
|
+
account_id, workspace_id, artifact_id, id, kind, object_reference,
|
|
3293
|
+
byte_size, content_hash, mime_type, created_at
|
|
3294
|
+
) VALUES ($1, $2, $3, $4, 'materialization', $5, $6, $7, $8,
|
|
3295
|
+
pg_catalog.statement_timestamp())
|
|
3296
|
+
ON CONFLICT DO NOTHING
|
|
3297
|
+
$query$, data_schema)
|
|
3298
|
+
USING p_account_id, p_workspace_id, p_artifact_id, p_blob_ref_id,
|
|
3299
|
+
p_object_reference, p_byte_size, p_content_hash, p_mime_type;
|
|
3300
|
+
EXECUTE pg_catalog.format($query$
|
|
3301
|
+
SELECT pg_catalog.count(*)::integer
|
|
3302
|
+
FROM %I.editable_artifact_blob_refs blob
|
|
3303
|
+
WHERE blob.account_id = $1 AND blob.workspace_id = $2
|
|
3304
|
+
AND blob.artifact_id = $3 AND blob.id = $4
|
|
3305
|
+
AND blob.kind = 'materialization' AND blob.object_reference = $5
|
|
3306
|
+
AND blob.byte_size = $6 AND blob.content_hash = $7 AND blob.mime_type = $8
|
|
3307
|
+
$query$, data_schema) INTO affected
|
|
3308
|
+
USING p_account_id, p_workspace_id, p_artifact_id, p_blob_ref_id,
|
|
3309
|
+
p_object_reference, p_byte_size, p_content_hash, p_mime_type;
|
|
3310
|
+
IF affected <> 1 THEN
|
|
3311
|
+
RAISE EXCEPTION 'materialization blob identity conflicts with immutable facts'
|
|
3312
|
+
USING ERRCODE = '23505';
|
|
3313
|
+
END IF;
|
|
3314
|
+
|
|
3315
|
+
EXECUTE pg_catalog.format($query$
|
|
3316
|
+
UPDATE %I.editable_artifact_materialization_jobs target
|
|
3317
|
+
SET state = 'succeeded', settled_by_owner = target.lease_owner,
|
|
3318
|
+
lease_owner = NULL, lease_expires_at = NULL, error_code = NULL,
|
|
3319
|
+
completed_at = pg_catalog.statement_timestamp()
|
|
3320
|
+
WHERE target.account_id = $1 AND target.workspace_id = $2
|
|
3321
|
+
AND target.artifact_id = $3 AND target.id = $4
|
|
3322
|
+
AND target.state = 'running' AND target.lease_owner = $5
|
|
3323
|
+
AND target.attempt_count = $6
|
|
3324
|
+
AND target.lease_expires_at > pg_catalog.statement_timestamp()
|
|
3325
|
+
$query$, data_schema)
|
|
3326
|
+
USING p_account_id, p_workspace_id, p_artifact_id, p_job_id, p_owner,
|
|
3327
|
+
p_attempt_count;
|
|
3328
|
+
GET DIAGNOSTICS affected = ROW_COUNT;
|
|
3329
|
+
IF affected <> 1 THEN RETURN; END IF;
|
|
3330
|
+
|
|
3331
|
+
EXECUTE pg_catalog.format($query$
|
|
3332
|
+
INSERT INTO %I.editable_artifact_materialization_results (
|
|
3333
|
+
account_id, workspace_id, artifact_id, id, job_id, blob_ref_id,
|
|
3334
|
+
byte_size, content_hash, mime_type, verified_at, created_at
|
|
3335
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10,
|
|
3336
|
+
GREATEST(pg_catalog.statement_timestamp(), $10))
|
|
3337
|
+
$query$, data_schema)
|
|
3338
|
+
USING p_account_id, p_workspace_id, p_artifact_id, p_result_id, p_job_id,
|
|
3339
|
+
p_blob_ref_id, p_byte_size, p_content_hash, p_mime_type, p_verified_at;
|
|
3340
|
+
|
|
3341
|
+
RETURN QUERY EXECUTE pg_catalog.format($query$
|
|
3342
|
+
SELECT 'committed'::text, job.account_id, job.workspace_id,
|
|
3343
|
+
job.artifact_id, job.id, result.id, result.blob_ref_id,
|
|
3344
|
+
blob.object_reference, result.byte_size, result.content_hash,
|
|
3345
|
+
result.mime_type, result.verified_at, result.created_at,
|
|
3346
|
+
job.settled_by_owner, job.attempt_count
|
|
3347
|
+
FROM %1$I.editable_artifact_materialization_jobs job
|
|
3348
|
+
JOIN %1$I.editable_artifact_materialization_results result
|
|
3349
|
+
ON result.account_id = job.account_id AND result.workspace_id = job.workspace_id
|
|
3350
|
+
AND result.artifact_id = job.artifact_id AND result.job_id = job.id
|
|
3351
|
+
JOIN %1$I.editable_artifact_blob_refs blob
|
|
3352
|
+
ON blob.account_id = result.account_id AND blob.workspace_id = result.workspace_id
|
|
3353
|
+
AND blob.artifact_id = result.artifact_id AND blob.id = result.blob_ref_id
|
|
3354
|
+
WHERE job.account_id = $1 AND job.workspace_id = $2
|
|
3355
|
+
AND job.artifact_id = $3 AND job.id = $4 AND result.id = $5
|
|
3356
|
+
$query$, data_schema)
|
|
3357
|
+
USING p_account_id, p_workspace_id, p_artifact_id, p_job_id, p_result_id;
|
|
3358
|
+
END;
|
|
3359
|
+
$body$;
|
|
3360
|
+
|
|
3361
|
+
CREATE OR REPLACE FUNCTION opengeni_private.fail_editable_artifact_materialization(
|
|
3362
|
+
p_account_id uuid,
|
|
3363
|
+
p_workspace_id uuid,
|
|
3364
|
+
p_artifact_id text,
|
|
3365
|
+
p_job_id text,
|
|
3366
|
+
p_owner text,
|
|
3367
|
+
p_attempt_count integer,
|
|
3368
|
+
p_error_code text,
|
|
3369
|
+
p_data_schema name
|
|
3370
|
+
) RETURNS TABLE (
|
|
3371
|
+
outcome text,
|
|
3372
|
+
account_id uuid,
|
|
3373
|
+
workspace_id uuid,
|
|
3374
|
+
artifact_id text,
|
|
3375
|
+
job_id text,
|
|
3376
|
+
settled_by_owner text,
|
|
3377
|
+
attempt_count integer,
|
|
3378
|
+
error_code text
|
|
3379
|
+
)
|
|
3380
|
+
LANGUAGE plpgsql
|
|
3381
|
+
SECURITY DEFINER
|
|
3382
|
+
SET search_path = pg_catalog, pg_temp
|
|
3383
|
+
AS $body$
|
|
3384
|
+
DECLARE data_schema name;
|
|
3385
|
+
DECLARE affected integer := 0;
|
|
3386
|
+
DECLARE job_state text;
|
|
3387
|
+
DECLARE job_attempt integer;
|
|
3388
|
+
DECLARE job_owner text;
|
|
3389
|
+
DECLARE job_lease_expires_at timestamptz;
|
|
3390
|
+
DECLARE job_settled_by_owner text;
|
|
3391
|
+
DECLARE job_error_code text;
|
|
3392
|
+
BEGIN
|
|
3393
|
+
IF p_account_id IS NULL OR p_workspace_id IS NULL
|
|
3394
|
+
OR p_artifact_id IS NULL OR p_artifact_id !~ '^[0-9a-f]{32}$'
|
|
3395
|
+
OR p_artifact_id ~ '^0+$'
|
|
3396
|
+
OR p_job_id IS NULL OR p_job_id !~ '^[0-9a-f]{32}$' OR p_job_id ~ '^0+$'
|
|
3397
|
+
OR p_owner IS NULL OR pg_catalog.octet_length(p_owner) NOT BETWEEN 1 AND 256
|
|
3398
|
+
OR p_owner <> pg_catalog.btrim(p_owner)
|
|
3399
|
+
OR p_attempt_count IS NULL OR p_attempt_count NOT BETWEEN 1 AND 1000
|
|
3400
|
+
OR p_error_code IS NULL OR p_error_code !~ '^[a-z][a-z0-9_.-]{0,127}$'
|
|
3401
|
+
OR p_data_schema IS NULL
|
|
3402
|
+
THEN
|
|
3403
|
+
RAISE EXCEPTION 'invalid editable artifact materialization failure bounds'
|
|
3404
|
+
USING ERRCODE = '22023';
|
|
3405
|
+
END IF;
|
|
3406
|
+
data_schema := opengeni_private.resolve_editable_artifact_data_schema(p_data_schema);
|
|
3407
|
+
job_state := NULL;
|
|
3408
|
+
EXECUTE pg_catalog.format($query$
|
|
3409
|
+
SELECT target.state, target.attempt_count, target.lease_owner,
|
|
3410
|
+
target.lease_expires_at, target.settled_by_owner, target.error_code
|
|
3411
|
+
FROM %I.editable_artifact_materialization_jobs target
|
|
3412
|
+
WHERE target.account_id = $1 AND target.workspace_id = $2
|
|
3413
|
+
AND target.artifact_id = $3 AND target.id = $4
|
|
3414
|
+
FOR UPDATE
|
|
3415
|
+
$query$, data_schema)
|
|
3416
|
+
INTO job_state, job_attempt, job_owner, job_lease_expires_at,
|
|
3417
|
+
job_settled_by_owner, job_error_code
|
|
3418
|
+
USING p_account_id, p_workspace_id, p_artifact_id, p_job_id;
|
|
3419
|
+
|
|
3420
|
+
IF job_state = 'failed' AND job_attempt = p_attempt_count
|
|
3421
|
+
AND job_settled_by_owner = p_owner AND job_error_code = p_error_code
|
|
3422
|
+
THEN
|
|
3423
|
+
RETURN QUERY SELECT 'replayed'::text, p_account_id, p_workspace_id,
|
|
3424
|
+
p_artifact_id, p_job_id, job_settled_by_owner, job_attempt, job_error_code;
|
|
3425
|
+
RETURN;
|
|
3426
|
+
END IF;
|
|
3427
|
+
IF job_state <> 'running' OR job_owner IS DISTINCT FROM p_owner
|
|
3428
|
+
OR job_attempt IS DISTINCT FROM p_attempt_count
|
|
3429
|
+
OR job_lease_expires_at <= pg_catalog.statement_timestamp()
|
|
3430
|
+
THEN
|
|
3431
|
+
RETURN QUERY SELECT 'fenced'::text, p_account_id, p_workspace_id,
|
|
3432
|
+
p_artifact_id, p_job_id, p_owner, p_attempt_count, p_error_code;
|
|
3433
|
+
RETURN;
|
|
3434
|
+
END IF;
|
|
3435
|
+
|
|
3436
|
+
EXECUTE pg_catalog.format($query$
|
|
3437
|
+
UPDATE %I.editable_artifact_materialization_jobs target
|
|
3438
|
+
SET state = 'failed', settled_by_owner = target.lease_owner,
|
|
3439
|
+
lease_owner = NULL, lease_expires_at = NULL, error_code = $7,
|
|
3440
|
+
completed_at = pg_catalog.statement_timestamp()
|
|
3441
|
+
WHERE target.account_id = $1 AND target.workspace_id = $2
|
|
3442
|
+
AND target.artifact_id = $3 AND target.id = $4
|
|
3443
|
+
AND target.state = 'running' AND target.lease_owner = $5
|
|
3444
|
+
AND target.attempt_count = $6
|
|
3445
|
+
AND target.lease_expires_at > pg_catalog.statement_timestamp()
|
|
3446
|
+
$query$, data_schema)
|
|
3447
|
+
USING p_account_id, p_workspace_id, p_artifact_id, p_job_id, p_owner,
|
|
3448
|
+
p_attempt_count, p_error_code;
|
|
3449
|
+
GET DIAGNOSTICS affected = ROW_COUNT;
|
|
3450
|
+
IF affected <> 1 THEN
|
|
3451
|
+
RETURN QUERY SELECT 'fenced'::text, p_account_id, p_workspace_id,
|
|
3452
|
+
p_artifact_id, p_job_id, p_owner, p_attempt_count, p_error_code;
|
|
3453
|
+
RETURN;
|
|
3454
|
+
END IF;
|
|
3455
|
+
RETURN QUERY SELECT 'failed'::text, p_account_id, p_workspace_id,
|
|
3456
|
+
p_artifact_id, p_job_id, p_owner, p_attempt_count, p_error_code;
|
|
3457
|
+
END;
|
|
3458
|
+
$body$;
|
|
3459
|
+
|
|
3460
|
+
REVOKE ALL ON FUNCTION
|
|
3461
|
+
opengeni_private.claim_editable_artifact_materializations(text, integer, integer, name)
|
|
3462
|
+
FROM PUBLIC;
|
|
3463
|
+
REVOKE ALL ON FUNCTION
|
|
3464
|
+
opengeni_private.renew_editable_artifact_materialization(
|
|
3465
|
+
uuid, uuid, text, text, text, integer, integer, name
|
|
3466
|
+
) FROM PUBLIC;
|
|
3467
|
+
REVOKE ALL ON FUNCTION
|
|
3468
|
+
opengeni_private.succeed_editable_artifact_materialization(
|
|
3469
|
+
uuid, uuid, text, text, text, integer, text, text, text, bigint,
|
|
3470
|
+
text, text, timestamptz, name
|
|
3471
|
+
) FROM PUBLIC;
|
|
3472
|
+
REVOKE ALL ON FUNCTION
|
|
3473
|
+
opengeni_private.fail_editable_artifact_materialization(
|
|
3474
|
+
uuid, uuid, text, text, text, integer, text, name
|
|
3475
|
+
) FROM PUBLIC;
|
|
3476
|
+
REVOKE ALL ON FUNCTION
|
|
3477
|
+
opengeni_private.ensure_editable_artifact_scope_authorization_head(uuid, uuid, name)
|
|
3478
|
+
FROM PUBLIC;
|
|
3479
|
+
REVOKE ALL ON FUNCTION
|
|
3480
|
+
opengeni_private.advance_editable_artifact_scope_authorization_revision(
|
|
3481
|
+
uuid, uuid, bigint, bigint, name
|
|
3482
|
+
) FROM PUBLIC;
|
|
3483
|
+
REVOKE ALL ON FUNCTION
|
|
3484
|
+
opengeni_private.claim_editable_artifact_live_outbox(text, integer, integer, name)
|
|
3485
|
+
FROM PUBLIC;
|
|
3486
|
+
REVOKE ALL ON FUNCTION
|
|
3487
|
+
opengeni_private.mark_editable_artifact_live_outbox_published(text, text, integer, name)
|
|
3488
|
+
FROM PUBLIC;
|
|
3489
|
+
REVOKE ALL ON FUNCTION
|
|
3490
|
+
opengeni_private.renew_editable_artifact_live_outbox(text, text, integer, integer, name)
|
|
3491
|
+
FROM PUBLIC;
|
|
3492
|
+
REVOKE ALL ON FUNCTION
|
|
3493
|
+
opengeni_private.retry_editable_artifact_live_outbox(
|
|
3494
|
+
text, text, integer, integer, text, name
|
|
3495
|
+
) FROM PUBLIC;
|
|
3496
|
+
REVOKE ALL ON FUNCTION
|
|
3497
|
+
opengeni_private.dead_letter_editable_artifact_live_outbox(
|
|
3498
|
+
text, text, integer, text, name
|
|
3499
|
+
) FROM PUBLIC;
|
|
3500
|
+
REVOKE ALL ON FUNCTION
|
|
3501
|
+
opengeni_private.release_editable_artifact_live_outbox(text, text, integer, name)
|
|
3502
|
+
FROM PUBLIC;
|
|
3503
|
+
REVOKE ALL ON FUNCTION
|
|
3504
|
+
opengeni_private.advance_editable_artifact_authorization_revision(
|
|
3505
|
+
uuid, uuid, text, bigint, bigint, name
|
|
3506
|
+
) FROM PUBLIC;
|
|
3507
|
+
REVOKE ALL ON FUNCTION
|
|
3508
|
+
opengeni_private.resolve_editable_artifact_data_schema(name) FROM PUBLIC;
|
|
3509
|
+
REVOKE ALL ON FUNCTION
|
|
3510
|
+
opengeni_private.editable_artifact_frontier_valid(jsonb) FROM PUBLIC;
|
|
3511
|
+
REVOKE ALL ON FUNCTION
|
|
3512
|
+
opengeni_private.editable_artifact_id_array_valid(jsonb) FROM PUBLIC;
|
|
3513
|
+
REVOKE ALL ON FUNCTION
|
|
3514
|
+
opengeni_private.editable_artifact_frontier_dominates(jsonb, jsonb) FROM PUBLIC;
|
|
3515
|
+
REVOKE ALL ON FUNCTION
|
|
3516
|
+
opengeni_private.editable_artifact_frontier_merge_dot_equals(
|
|
3517
|
+
jsonb, jsonb, jsonb, text, bigint
|
|
3518
|
+
) FROM PUBLIC;
|
|
3519
|
+
REVOKE ALL ON FUNCTION
|
|
3520
|
+
opengeni_private.editable_artifact_object_has_exact_keys(jsonb, text[]) FROM PUBLIC;
|
|
3521
|
+
REVOKE ALL ON FUNCTION
|
|
3522
|
+
opengeni_private.editable_artifact_actor_key_matches(
|
|
3523
|
+
text, text, text, text, text, text, integer, text
|
|
3524
|
+
) FROM PUBLIC;
|
|
3525
|
+
|
|
3526
|
+
DO $rls$
|
|
3527
|
+
DECLARE table_name text;
|
|
3528
|
+
BEGIN
|
|
3529
|
+
FOREACH table_name IN ARRAY ARRAY[
|
|
3530
|
+
'editable_artifact_scope_authorization_heads',
|
|
3531
|
+
'editable_artifacts',
|
|
3532
|
+
'editable_artifact_transactions',
|
|
3533
|
+
'editable_artifact_operations',
|
|
3534
|
+
'editable_artifact_idempotency_receipts',
|
|
3535
|
+
'editable_artifact_undo_claims',
|
|
3536
|
+
'editable_artifact_sequence_checkpoints',
|
|
3537
|
+
'editable_artifact_blob_refs',
|
|
3538
|
+
'editable_artifact_snapshots',
|
|
3539
|
+
'editable_artifact_versions',
|
|
3540
|
+
'editable_artifact_materialization_jobs',
|
|
3541
|
+
'editable_artifact_materialization_results',
|
|
3542
|
+
'editable_artifact_live_outbox',
|
|
3543
|
+
'editable_artifact_replica_leases'
|
|
3544
|
+
] LOOP
|
|
3545
|
+
EXECUTE format('ALTER TABLE %I ENABLE ROW LEVEL SECURITY', table_name);
|
|
3546
|
+
EXECUTE format('ALTER TABLE %I FORCE ROW LEVEL SECURITY', table_name);
|
|
3547
|
+
EXECUTE format(
|
|
3548
|
+
'CREATE POLICY workspace_isolation ON %I '
|
|
3549
|
+
|| 'USING (opengeni_private.workspace_rls_visible(account_id, workspace_id)) '
|
|
3550
|
+
|| 'WITH CHECK (opengeni_private.workspace_rls_visible(account_id, workspace_id))',
|
|
3551
|
+
table_name
|
|
3552
|
+
);
|
|
3553
|
+
END LOOP;
|
|
3554
|
+
END;
|
|
3555
|
+
$rls$;
|
|
3556
|
+
|
|
3557
|
+
-- FORCE RLS also applies to a table owner. This narrow owner-only policy lets
|
|
3558
|
+
-- the audited SECURITY DEFINER outbox capability claim across workspaces while
|
|
3559
|
+
-- ordinary runtime SQL remains constrained by workspace_isolation.
|
|
3560
|
+
DO $outbox_dispatch_policy$
|
|
3561
|
+
DECLARE data_schema text := current_schema();
|
|
3562
|
+
DECLARE migration_owner text := current_user;
|
|
3563
|
+
BEGIN
|
|
3564
|
+
EXECUTE format(
|
|
3565
|
+
'CREATE POLICY editable_artifact_outbox_dispatcher ON %I.editable_artifact_live_outbox '
|
|
3566
|
+
|| 'FOR ALL USING (current_user = %L) WITH CHECK (current_user = %L)',
|
|
3567
|
+
data_schema, migration_owner, migration_owner
|
|
3568
|
+
);
|
|
3569
|
+
END;
|
|
3570
|
+
$outbox_dispatch_policy$;
|
|
3571
|
+
|
|
3572
|
+
-- The materializer's definer owner needs a deliberately narrow FORCE-RLS path
|
|
3573
|
+
-- for its global claim and atomic projection. The login role itself receives
|
|
3574
|
+
-- no table privilege and therefore cannot exercise these policies directly.
|
|
3575
|
+
DO $artifact_materializer_policy$
|
|
3576
|
+
DECLARE data_schema text := current_schema();
|
|
3577
|
+
DECLARE migration_owner text := current_user;
|
|
3578
|
+
DECLARE table_name text;
|
|
3579
|
+
BEGIN
|
|
3580
|
+
FOREACH table_name IN ARRAY ARRAY[
|
|
3581
|
+
'editable_artifact_materialization_jobs',
|
|
3582
|
+
'editable_artifact_materialization_results',
|
|
3583
|
+
'editable_artifact_blob_refs'
|
|
3584
|
+
] LOOP
|
|
3585
|
+
EXECUTE format(
|
|
3586
|
+
'CREATE POLICY editable_artifact_materializer_owner ON %I.%I FOR ALL '
|
|
3587
|
+
|| 'USING (current_user = %L) WITH CHECK (current_user = %L)',
|
|
3588
|
+
data_schema, table_name, migration_owner, migration_owner
|
|
3589
|
+
);
|
|
3590
|
+
END LOOP;
|
|
3591
|
+
FOREACH table_name IN ARRAY ARRAY[
|
|
3592
|
+
'editable_artifact_sequence_checkpoints',
|
|
3593
|
+
'editable_artifact_versions',
|
|
3594
|
+
'editable_artifact_idempotency_receipts'
|
|
3595
|
+
] LOOP
|
|
3596
|
+
EXECUTE format(
|
|
3597
|
+
'CREATE POLICY editable_artifact_materializer_owner ON %I.%I FOR SELECT '
|
|
3598
|
+
|| 'USING (current_user = %L)',
|
|
3599
|
+
data_schema, table_name, migration_owner
|
|
3600
|
+
);
|
|
3601
|
+
END LOOP;
|
|
3602
|
+
END;
|
|
3603
|
+
$artifact_materializer_policy$;
|
|
3604
|
+
|
|
3605
|
+
-- The runtime may read the local create-policy fence, but only the narrow
|
|
3606
|
+
-- owner functions may initialize or advance it across FORCE RLS.
|
|
3607
|
+
DO $artifact_scope_authority_policy$
|
|
3608
|
+
DECLARE data_schema text := current_schema();
|
|
3609
|
+
DECLARE migration_owner text := current_user;
|
|
3610
|
+
BEGIN
|
|
3611
|
+
EXECUTE format(
|
|
3612
|
+
'CREATE POLICY editable_artifact_scope_authority_owner '
|
|
3613
|
+
|| 'ON %I.editable_artifact_scope_authorization_heads FOR ALL '
|
|
3614
|
+
|| 'USING (current_user = %L) WITH CHECK (current_user = %L)',
|
|
3615
|
+
data_schema, migration_owner, migration_owner
|
|
3616
|
+
);
|
|
3617
|
+
END;
|
|
3618
|
+
$artifact_scope_authority_policy$;
|
|
3619
|
+
|
|
3620
|
+
-- PostgreSQL grants EXECUTE to PUBLIC on new functions by default. The private
|
|
3621
|
+
-- schema is an explicit capability boundary: callers receive named grants,
|
|
3622
|
+
-- never ambient PUBLIC execution. Preserve that invariant for future helpers
|
|
3623
|
+
-- created by this migration owner as well.
|
|
3624
|
+
REVOKE EXECUTE ON ALL FUNCTIONS IN SCHEMA opengeni_private FROM PUBLIC;
|
|
3625
|
+
ALTER DEFAULT PRIVILEGES IN SCHEMA opengeni_private
|
|
3626
|
+
REVOKE EXECUTE ON FUNCTIONS FROM PUBLIC;
|
|
3627
|
+
|
|
3628
|
+
DO $runtime_grants$
|
|
3629
|
+
DECLARE data_schema text := current_schema();
|
|
3630
|
+
DECLARE table_name text;
|
|
3631
|
+
BEGIN
|
|
3632
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
|
|
3633
|
+
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA opengeni_private TO opengeni_app;
|
|
3634
|
+
FOREACH table_name IN ARRAY ARRAY[
|
|
3635
|
+
'editable_artifact_scope_authorization_heads',
|
|
3636
|
+
'editable_artifacts',
|
|
3637
|
+
'editable_artifact_transactions',
|
|
3638
|
+
'editable_artifact_operations',
|
|
3639
|
+
'editable_artifact_idempotency_receipts',
|
|
3640
|
+
'editable_artifact_undo_claims',
|
|
3641
|
+
'editable_artifact_sequence_checkpoints',
|
|
3642
|
+
'editable_artifact_blob_refs',
|
|
3643
|
+
'editable_artifact_snapshots',
|
|
3644
|
+
'editable_artifact_versions',
|
|
3645
|
+
'editable_artifact_materialization_jobs',
|
|
3646
|
+
'editable_artifact_materialization_results',
|
|
3647
|
+
'editable_artifact_live_outbox',
|
|
3648
|
+
'editable_artifact_replica_leases'
|
|
3649
|
+
] LOOP
|
|
3650
|
+
EXECUTE format(
|
|
3651
|
+
'REVOKE ALL PRIVILEGES ON TABLE %I.%I FROM opengeni_app',
|
|
3652
|
+
data_schema, table_name
|
|
3653
|
+
);
|
|
3654
|
+
END LOOP;
|
|
3655
|
+
EXECUTE format(
|
|
3656
|
+
'GRANT SELECT, INSERT, UPDATE ON TABLE %I.editable_artifacts TO opengeni_app',
|
|
3657
|
+
data_schema
|
|
3658
|
+
);
|
|
3659
|
+
FOREACH table_name IN ARRAY ARRAY[
|
|
3660
|
+
'editable_artifact_transactions',
|
|
3661
|
+
'editable_artifact_operations',
|
|
3662
|
+
'editable_artifact_idempotency_receipts',
|
|
3663
|
+
'editable_artifact_undo_claims',
|
|
3664
|
+
'editable_artifact_sequence_checkpoints',
|
|
3665
|
+
'editable_artifact_blob_refs',
|
|
3666
|
+
'editable_artifact_snapshots',
|
|
3667
|
+
'editable_artifact_versions',
|
|
3668
|
+
'editable_artifact_materialization_results'
|
|
3669
|
+
] LOOP
|
|
3670
|
+
EXECUTE format(
|
|
3671
|
+
'GRANT SELECT, INSERT ON TABLE %I.%I TO opengeni_app', data_schema, table_name
|
|
3672
|
+
);
|
|
3673
|
+
END LOOP;
|
|
3674
|
+
EXECUTE format(
|
|
3675
|
+
'GRANT SELECT, INSERT ON TABLE %I.editable_artifact_materialization_jobs '
|
|
3676
|
+
|| 'TO opengeni_app', data_schema
|
|
3677
|
+
);
|
|
3678
|
+
EXECUTE format(
|
|
3679
|
+
'GRANT SELECT, INSERT ON TABLE %I.editable_artifact_live_outbox TO opengeni_app',
|
|
3680
|
+
data_schema
|
|
3681
|
+
);
|
|
3682
|
+
EXECUTE format(
|
|
3683
|
+
'GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE %I.editable_artifact_replica_leases '
|
|
3684
|
+
|| 'TO opengeni_app', data_schema
|
|
3685
|
+
);
|
|
3686
|
+
GRANT EXECUTE ON FUNCTION
|
|
3687
|
+
opengeni_private.ensure_editable_artifact_scope_authorization_head(
|
|
3688
|
+
uuid, uuid, name
|
|
3689
|
+
) TO opengeni_app;
|
|
3690
|
+
GRANT EXECUTE ON FUNCTION
|
|
3691
|
+
opengeni_private.advance_editable_artifact_scope_authorization_revision(
|
|
3692
|
+
uuid, uuid, bigint, bigint, name
|
|
3693
|
+
) TO opengeni_app;
|
|
3694
|
+
GRANT EXECUTE ON FUNCTION
|
|
3695
|
+
opengeni_private.advance_editable_artifact_authorization_revision(
|
|
3696
|
+
uuid, uuid, text, bigint, bigint, name
|
|
3697
|
+
) TO opengeni_app;
|
|
3698
|
+
GRANT EXECUTE ON FUNCTION
|
|
3699
|
+
opengeni_private.editable_artifact_frontier_valid(jsonb)
|
|
3700
|
+
TO opengeni_app;
|
|
3701
|
+
GRANT EXECUTE ON FUNCTION
|
|
3702
|
+
opengeni_private.editable_artifact_id_array_valid(jsonb)
|
|
3703
|
+
TO opengeni_app;
|
|
3704
|
+
GRANT EXECUTE ON FUNCTION
|
|
3705
|
+
opengeni_private.editable_artifact_object_has_exact_keys(jsonb, text[])
|
|
3706
|
+
TO opengeni_app;
|
|
3707
|
+
GRANT EXECUTE ON FUNCTION
|
|
3708
|
+
opengeni_private.editable_artifact_actor_key_matches(
|
|
3709
|
+
text, text, text, text, text, text, integer, text
|
|
3710
|
+
) TO opengeni_app;
|
|
3711
|
+
GRANT EXECUTE ON FUNCTION
|
|
3712
|
+
opengeni_private.editable_artifact_frontier_dominates(jsonb, jsonb)
|
|
3713
|
+
TO opengeni_app;
|
|
3714
|
+
GRANT EXECUTE ON FUNCTION
|
|
3715
|
+
opengeni_private.editable_artifact_frontier_merge_dot_equals(
|
|
3716
|
+
jsonb, jsonb, jsonb, text, bigint
|
|
3717
|
+
) TO opengeni_app;
|
|
3718
|
+
REVOKE EXECUTE ON FUNCTION
|
|
3719
|
+
opengeni_private.claim_editable_artifact_live_outbox(
|
|
3720
|
+
text, integer, integer, name
|
|
3721
|
+
) FROM opengeni_app;
|
|
3722
|
+
REVOKE EXECUTE ON FUNCTION
|
|
3723
|
+
opengeni_private.mark_editable_artifact_live_outbox_published(
|
|
3724
|
+
text, text, integer, name
|
|
3725
|
+
) FROM opengeni_app;
|
|
3726
|
+
REVOKE EXECUTE ON FUNCTION
|
|
3727
|
+
opengeni_private.renew_editable_artifact_live_outbox(
|
|
3728
|
+
text, text, integer, integer, name
|
|
3729
|
+
) FROM opengeni_app;
|
|
3730
|
+
REVOKE EXECUTE ON FUNCTION
|
|
3731
|
+
opengeni_private.retry_editable_artifact_live_outbox(
|
|
3732
|
+
text, text, integer, integer, text, name
|
|
3733
|
+
) FROM opengeni_app;
|
|
3734
|
+
REVOKE EXECUTE ON FUNCTION
|
|
3735
|
+
opengeni_private.dead_letter_editable_artifact_live_outbox(
|
|
3736
|
+
text, text, integer, text, name
|
|
3737
|
+
) FROM opengeni_app;
|
|
3738
|
+
REVOKE EXECUTE ON FUNCTION
|
|
3739
|
+
opengeni_private.release_editable_artifact_live_outbox(
|
|
3740
|
+
text, text, integer, name
|
|
3741
|
+
) FROM opengeni_app;
|
|
3742
|
+
REVOKE EXECUTE ON FUNCTION
|
|
3743
|
+
opengeni_private.claim_editable_artifact_materializations(
|
|
3744
|
+
text, integer, integer, name
|
|
3745
|
+
) FROM opengeni_app;
|
|
3746
|
+
REVOKE EXECUTE ON FUNCTION
|
|
3747
|
+
opengeni_private.renew_editable_artifact_materialization(
|
|
3748
|
+
uuid, uuid, text, text, text, integer, integer, name
|
|
3749
|
+
) FROM opengeni_app;
|
|
3750
|
+
REVOKE EXECUTE ON FUNCTION
|
|
3751
|
+
opengeni_private.succeed_editable_artifact_materialization(
|
|
3752
|
+
uuid, uuid, text, text, text, integer, text, text, text, bigint,
|
|
3753
|
+
text, text, timestamptz, name
|
|
3754
|
+
) FROM opengeni_app;
|
|
3755
|
+
REVOKE EXECUTE ON FUNCTION
|
|
3756
|
+
opengeni_private.fail_editable_artifact_materialization(
|
|
3757
|
+
uuid, uuid, text, text, text, integer, text, name
|
|
3758
|
+
) FROM opengeni_app;
|
|
3759
|
+
END IF;
|
|
3760
|
+
END;
|
|
3761
|
+
$runtime_grants$;
|
|
3762
|
+
|
|
3763
|
+
DO $artifact_outbox_dispatcher_grants$
|
|
3764
|
+
DECLARE data_schema text := current_schema();
|
|
3765
|
+
BEGIN
|
|
3766
|
+
IF EXISTS (
|
|
3767
|
+
SELECT 1 FROM pg_catalog.pg_roles
|
|
3768
|
+
WHERE rolname = 'opengeni_artifact_outbox_dispatcher'
|
|
3769
|
+
) THEN
|
|
3770
|
+
GRANT USAGE ON SCHEMA opengeni_private TO opengeni_artifact_outbox_dispatcher;
|
|
3771
|
+
EXECUTE format(
|
|
3772
|
+
'GRANT USAGE ON SCHEMA %I TO opengeni_artifact_outbox_dispatcher', data_schema
|
|
3773
|
+
);
|
|
3774
|
+
EXECUTE format(
|
|
3775
|
+
'REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA %I '
|
|
3776
|
+
|| 'FROM opengeni_artifact_outbox_dispatcher', data_schema
|
|
3777
|
+
);
|
|
3778
|
+
EXECUTE format(
|
|
3779
|
+
'REVOKE ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA %I '
|
|
3780
|
+
|| 'FROM opengeni_artifact_outbox_dispatcher', data_schema
|
|
3781
|
+
);
|
|
3782
|
+
REVOKE ALL PRIVILEGES ON ALL FUNCTIONS IN SCHEMA opengeni_private
|
|
3783
|
+
FROM opengeni_artifact_outbox_dispatcher;
|
|
3784
|
+
GRANT EXECUTE ON FUNCTION
|
|
3785
|
+
opengeni_private.claim_editable_artifact_live_outbox(text, integer, integer, name)
|
|
3786
|
+
TO opengeni_artifact_outbox_dispatcher;
|
|
3787
|
+
GRANT EXECUTE ON FUNCTION
|
|
3788
|
+
opengeni_private.mark_editable_artifact_live_outbox_published(
|
|
3789
|
+
text, text, integer, name
|
|
3790
|
+
) TO opengeni_artifact_outbox_dispatcher;
|
|
3791
|
+
GRANT EXECUTE ON FUNCTION
|
|
3792
|
+
opengeni_private.renew_editable_artifact_live_outbox(
|
|
3793
|
+
text, text, integer, integer, name
|
|
3794
|
+
) TO opengeni_artifact_outbox_dispatcher;
|
|
3795
|
+
GRANT EXECUTE ON FUNCTION
|
|
3796
|
+
opengeni_private.retry_editable_artifact_live_outbox(
|
|
3797
|
+
text, text, integer, integer, text, name
|
|
3798
|
+
) TO opengeni_artifact_outbox_dispatcher;
|
|
3799
|
+
GRANT EXECUTE ON FUNCTION
|
|
3800
|
+
opengeni_private.dead_letter_editable_artifact_live_outbox(
|
|
3801
|
+
text, text, integer, text, name
|
|
3802
|
+
) TO opengeni_artifact_outbox_dispatcher;
|
|
3803
|
+
GRANT EXECUTE ON FUNCTION
|
|
3804
|
+
opengeni_private.release_editable_artifact_live_outbox(text, text, integer, name)
|
|
3805
|
+
TO opengeni_artifact_outbox_dispatcher;
|
|
3806
|
+
END IF;
|
|
3807
|
+
END;
|
|
3808
|
+
$artifact_outbox_dispatcher_grants$;
|
|
3809
|
+
|
|
3810
|
+
DO $artifact_materializer_grants$
|
|
3811
|
+
DECLARE data_schema text := current_schema();
|
|
3812
|
+
BEGIN
|
|
3813
|
+
IF EXISTS (
|
|
3814
|
+
SELECT 1 FROM pg_catalog.pg_roles
|
|
3815
|
+
WHERE rolname = 'opengeni_artifact_materializer'
|
|
3816
|
+
) THEN
|
|
3817
|
+
GRANT USAGE ON SCHEMA opengeni_private TO opengeni_artifact_materializer;
|
|
3818
|
+
EXECUTE format(
|
|
3819
|
+
'GRANT USAGE ON SCHEMA %I TO opengeni_artifact_materializer', data_schema
|
|
3820
|
+
);
|
|
3821
|
+
EXECUTE format(
|
|
3822
|
+
'REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA %I '
|
|
3823
|
+
|| 'FROM opengeni_artifact_materializer', data_schema
|
|
3824
|
+
);
|
|
3825
|
+
EXECUTE format(
|
|
3826
|
+
'REVOKE ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA %I '
|
|
3827
|
+
|| 'FROM opengeni_artifact_materializer', data_schema
|
|
3828
|
+
);
|
|
3829
|
+
REVOKE ALL PRIVILEGES ON ALL FUNCTIONS IN SCHEMA opengeni_private
|
|
3830
|
+
FROM opengeni_artifact_materializer;
|
|
3831
|
+
GRANT EXECUTE ON FUNCTION
|
|
3832
|
+
opengeni_private.claim_editable_artifact_materializations(
|
|
3833
|
+
text, integer, integer, name
|
|
3834
|
+
) TO opengeni_artifact_materializer;
|
|
3835
|
+
GRANT EXECUTE ON FUNCTION
|
|
3836
|
+
opengeni_private.renew_editable_artifact_materialization(
|
|
3837
|
+
uuid, uuid, text, text, text, integer, integer, name
|
|
3838
|
+
) TO opengeni_artifact_materializer;
|
|
3839
|
+
GRANT EXECUTE ON FUNCTION
|
|
3840
|
+
opengeni_private.succeed_editable_artifact_materialization(
|
|
3841
|
+
uuid, uuid, text, text, text, integer, text, text, text, bigint,
|
|
3842
|
+
text, text, timestamptz, name
|
|
3843
|
+
) TO opengeni_artifact_materializer;
|
|
3844
|
+
GRANT EXECUTE ON FUNCTION
|
|
3845
|
+
opengeni_private.fail_editable_artifact_materialization(
|
|
3846
|
+
uuid, uuid, text, text, text, integer, text, name
|
|
3847
|
+
) TO opengeni_artifact_materializer;
|
|
3848
|
+
END IF;
|
|
3849
|
+
END;
|
|
3850
|
+
$artifact_materializer_grants$;
|
|
3851
|
+
|
|
3852
|
+
RESET statement_timeout;
|
|
3853
|
+
RESET lock_timeout;
|