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