@opengeni/db 0.19.0 → 0.22.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/{chunk-XOXEQ7HG.js → chunk-BNGEN5QZ.js} +85 -1
- package/dist/chunk-BNGEN5QZ.js.map +1 -0
- package/dist/{chunk-SZP6KRLC.js → chunk-CYGFLLMN.js} +2698 -1918
- package/dist/chunk-CYGFLLMN.js.map +1 -0
- package/dist/index.d.ts +108 -1
- package/dist/index.js +5940 -2907
- package/dist/index.js.map +1 -1
- package/dist/provision-roles.js +1 -1
- package/dist/runtime-posture.d.ts +4 -4
- package/dist/schema.d.ts +1147 -101
- package/dist/schema.js +37 -1
- package/dist/scoped-knowledge-schema.d.ts +5341 -0
- package/dist/scoped-knowledge.d.ts +208 -0
- package/dist/session-control.d.ts +1 -0
- package/dist/session-queue-commands.d.ts +2 -1
- package/drizzle/0153_mcp_personal_connection_delegations.sql +181 -0
- package/drizzle/0154_scoped_knowledge_foundation.sql +2292 -0
- package/drizzle/0155_connector_action_policies.sql +218 -0
- package/package.json +3 -3
- package/src/connection-token-resolver.ts +3 -0
- package/src/index.ts +1211 -7
- package/src/provision-roles.ts +48 -0
- package/src/runtime-posture.ts +36 -0
- package/src/schema.ts +228 -2
- package/src/scoped-knowledge-schema.ts +603 -0
- package/src/scoped-knowledge.ts +2891 -0
- package/src/session-control.ts +3 -0
- package/src/session-queue-commands.ts +48 -0
- package/dist/chunk-SZP6KRLC.js.map +0 -1
- package/dist/chunk-XOXEQ7HG.js.map +0 -1
|
@@ -0,0 +1,2292 @@
|
|
|
1
|
+
-- deployment-mode: maintenance
|
|
2
|
+
-- Additive provider-neutral scoped-knowledge foundation. This migration creates
|
|
3
|
+
-- no connector, route, SDK/MCP/UI surface, prompt composition, policy head, or
|
|
4
|
+
-- active preference/memory write path.
|
|
5
|
+
|
|
6
|
+
SET lock_timeout = '5s';
|
|
7
|
+
SET statement_timeout = '10min';
|
|
8
|
+
|
|
9
|
+
CREATE SEQUENCE "knowledge_claim_review_revision_seq" AS bigint;
|
|
10
|
+
|
|
11
|
+
CREATE OR REPLACE FUNCTION opengeni_private.scoped_knowledge_scope_valid(
|
|
12
|
+
row_scope text,
|
|
13
|
+
row_workspace_id uuid,
|
|
14
|
+
row_subject_id text
|
|
15
|
+
) RETURNS boolean
|
|
16
|
+
LANGUAGE sql IMMUTABLE AS $$
|
|
17
|
+
SELECT
|
|
18
|
+
(row_scope = 'organization' AND row_workspace_id IS NULL AND row_subject_id IS NULL)
|
|
19
|
+
OR (row_scope = 'workspace' AND row_workspace_id IS NOT NULL AND row_subject_id IS NULL)
|
|
20
|
+
OR (
|
|
21
|
+
row_scope = 'personal'
|
|
22
|
+
AND nullif(btrim(row_subject_id), '') IS NOT NULL
|
|
23
|
+
AND octet_length(convert_to(row_subject_id, 'UTF8')) <= 1024
|
|
24
|
+
);
|
|
25
|
+
$$;
|
|
26
|
+
|
|
27
|
+
CREATE OR REPLACE FUNCTION opengeni_private.scoped_knowledge_scope_key(
|
|
28
|
+
row_scope text,
|
|
29
|
+
row_workspace_id uuid,
|
|
30
|
+
row_subject_id text
|
|
31
|
+
) RETURNS text
|
|
32
|
+
LANGUAGE sql IMMUTABLE AS $$
|
|
33
|
+
SELECT row_scope || ':' || coalesce(row_workspace_id::text, '-') || ':' ||
|
|
34
|
+
coalesce(row_subject_id, '-');
|
|
35
|
+
$$;
|
|
36
|
+
|
|
37
|
+
CREATE OR REPLACE FUNCTION opengeni_private.scoped_knowledge_scope_visible(
|
|
38
|
+
row_account_id uuid,
|
|
39
|
+
row_scope text,
|
|
40
|
+
row_workspace_id uuid,
|
|
41
|
+
row_subject_id text
|
|
42
|
+
) RETURNS boolean
|
|
43
|
+
LANGUAGE sql STABLE AS $$
|
|
44
|
+
SELECT row_account_id = opengeni_private.current_account_id()
|
|
45
|
+
AND (
|
|
46
|
+
row_scope = 'organization'
|
|
47
|
+
OR (
|
|
48
|
+
row_scope = 'workspace'
|
|
49
|
+
AND row_workspace_id = opengeni_private.current_workspace_id()
|
|
50
|
+
)
|
|
51
|
+
OR (
|
|
52
|
+
row_scope = 'personal'
|
|
53
|
+
AND row_subject_id = opengeni_private.current_subject_id()
|
|
54
|
+
AND (
|
|
55
|
+
row_workspace_id IS NULL
|
|
56
|
+
OR row_workspace_id = opengeni_private.current_workspace_id()
|
|
57
|
+
)
|
|
58
|
+
)
|
|
59
|
+
);
|
|
60
|
+
$$;
|
|
61
|
+
|
|
62
|
+
CREATE OR REPLACE FUNCTION opengeni_private.scoped_knowledge_actor_valid(
|
|
63
|
+
row_actor_kind text,
|
|
64
|
+
row_actor_subject_id text,
|
|
65
|
+
row_initiating_human_subject_id text
|
|
66
|
+
) RETURNS boolean
|
|
67
|
+
LANGUAGE sql IMMUTABLE AS $$
|
|
68
|
+
SELECT length(btrim(row_actor_subject_id)) BETWEEN 1 AND 1024
|
|
69
|
+
AND (
|
|
70
|
+
(
|
|
71
|
+
row_actor_kind = 'human'
|
|
72
|
+
AND row_initiating_human_subject_id = row_actor_subject_id
|
|
73
|
+
)
|
|
74
|
+
OR (
|
|
75
|
+
row_actor_kind = 'service'
|
|
76
|
+
AND (
|
|
77
|
+
row_initiating_human_subject_id IS NULL
|
|
78
|
+
OR length(btrim(row_initiating_human_subject_id)) BETWEEN 1 AND 1024
|
|
79
|
+
)
|
|
80
|
+
)
|
|
81
|
+
);
|
|
82
|
+
$$;
|
|
83
|
+
|
|
84
|
+
CREATE OR REPLACE FUNCTION opengeni_private.scoped_knowledge_actor_authorized(
|
|
85
|
+
row_actor_kind text,
|
|
86
|
+
row_actor_subject_id text,
|
|
87
|
+
row_initiating_human_subject_id text
|
|
88
|
+
) RETURNS boolean
|
|
89
|
+
LANGUAGE sql STABLE AS $$
|
|
90
|
+
SELECT opengeni_private.scoped_knowledge_actor_valid(
|
|
91
|
+
row_actor_kind, row_actor_subject_id, row_initiating_human_subject_id
|
|
92
|
+
)
|
|
93
|
+
AND (
|
|
94
|
+
(
|
|
95
|
+
row_actor_kind = 'human'
|
|
96
|
+
AND row_actor_subject_id = opengeni_private.current_subject_id()
|
|
97
|
+
AND row_initiating_human_subject_id = opengeni_private.current_subject_id()
|
|
98
|
+
)
|
|
99
|
+
OR (
|
|
100
|
+
row_actor_kind = 'service'
|
|
101
|
+
AND row_initiating_human_subject_id
|
|
102
|
+
IS NOT DISTINCT FROM opengeni_private.current_subject_id()
|
|
103
|
+
)
|
|
104
|
+
);
|
|
105
|
+
$$;
|
|
106
|
+
|
|
107
|
+
CREATE TABLE "knowledge_operation_receipts" (
|
|
108
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
109
|
+
"account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE RESTRICT,
|
|
110
|
+
"scope_kind" text NOT NULL,
|
|
111
|
+
"scope_workspace_id" uuid REFERENCES "workspaces"("id") ON DELETE RESTRICT,
|
|
112
|
+
"scope_subject_id" text,
|
|
113
|
+
"scope_key" text NOT NULL,
|
|
114
|
+
"operation_kind" text NOT NULL,
|
|
115
|
+
"operation_namespace" text NOT NULL,
|
|
116
|
+
"operation_id" text NOT NULL,
|
|
117
|
+
"input_hash" text NOT NULL,
|
|
118
|
+
"result_id" uuid NOT NULL,
|
|
119
|
+
"actor_kind" text NOT NULL,
|
|
120
|
+
"actor_subject_id" text NOT NULL,
|
|
121
|
+
"initiating_human_subject_id" text,
|
|
122
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
123
|
+
CONSTRAINT "knowledge_operation_receipts_scope_chk" CHECK (
|
|
124
|
+
opengeni_private.scoped_knowledge_scope_valid(
|
|
125
|
+
"scope_kind", "scope_workspace_id", "scope_subject_id"
|
|
126
|
+
)
|
|
127
|
+
AND "scope_key" = opengeni_private.scoped_knowledge_scope_key(
|
|
128
|
+
"scope_kind", "scope_workspace_id", "scope_subject_id"
|
|
129
|
+
)
|
|
130
|
+
),
|
|
131
|
+
CONSTRAINT "knowledge_operation_receipts_identity_chk" CHECK (
|
|
132
|
+
"operation_kind" IN (
|
|
133
|
+
'provider', 'source', 'source_object', 'document_version',
|
|
134
|
+
'entity', 'entity_alias', 'fact', 'claim_relation'
|
|
135
|
+
)
|
|
136
|
+
AND length(btrim("operation_namespace")) BETWEEN 1 AND 256
|
|
137
|
+
AND length(btrim("operation_id")) BETWEEN 1 AND 256
|
|
138
|
+
AND "input_hash" ~ '^[0-9a-f]{64}$'
|
|
139
|
+
),
|
|
140
|
+
CONSTRAINT "knowledge_operation_receipts_actor_chk" CHECK (
|
|
141
|
+
opengeni_private.scoped_knowledge_actor_valid(
|
|
142
|
+
"actor_kind", "actor_subject_id", "initiating_human_subject_id"
|
|
143
|
+
)
|
|
144
|
+
),
|
|
145
|
+
CONSTRAINT "knowledge_operation_receipts_operation_uq"
|
|
146
|
+
UNIQUE ("account_id", "operation_kind", "operation_namespace", "operation_id")
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
CREATE INDEX "knowledge_operation_receipts_result_idx"
|
|
150
|
+
ON "knowledge_operation_receipts" ("operation_kind", "result_id");
|
|
151
|
+
|
|
152
|
+
CREATE TABLE "knowledge_providers" (
|
|
153
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
154
|
+
"account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE RESTRICT,
|
|
155
|
+
"scope_kind" text NOT NULL,
|
|
156
|
+
"scope_workspace_id" uuid REFERENCES "workspaces"("id") ON DELETE RESTRICT,
|
|
157
|
+
"scope_subject_id" text,
|
|
158
|
+
"scope_key" text NOT NULL,
|
|
159
|
+
"provider_key" text NOT NULL,
|
|
160
|
+
"external_tenant_id" text NOT NULL,
|
|
161
|
+
"lifecycle_state" text NOT NULL DEFAULT 'active',
|
|
162
|
+
"lifecycle_generation" bigint NOT NULL DEFAULT 1,
|
|
163
|
+
"operation_id" text NOT NULL,
|
|
164
|
+
"input_hash" text NOT NULL,
|
|
165
|
+
"actor_kind" text NOT NULL,
|
|
166
|
+
"actor_subject_id" text NOT NULL,
|
|
167
|
+
"initiating_human_subject_id" text,
|
|
168
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
169
|
+
"updated_at" timestamptz NOT NULL DEFAULT now(),
|
|
170
|
+
CONSTRAINT "knowledge_providers_scope_chk" CHECK (
|
|
171
|
+
opengeni_private.scoped_knowledge_scope_valid(
|
|
172
|
+
"scope_kind", "scope_workspace_id", "scope_subject_id"
|
|
173
|
+
)
|
|
174
|
+
AND "scope_key" = opengeni_private.scoped_knowledge_scope_key(
|
|
175
|
+
"scope_kind", "scope_workspace_id", "scope_subject_id"
|
|
176
|
+
)
|
|
177
|
+
),
|
|
178
|
+
CONSTRAINT "knowledge_providers_provider_key_chk" CHECK (
|
|
179
|
+
"provider_key" = lower(btrim("provider_key"))
|
|
180
|
+
AND length("provider_key") BETWEEN 1 AND 96
|
|
181
|
+
AND "provider_key" ~ '^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$'
|
|
182
|
+
AND "provider_key" !~ '--'
|
|
183
|
+
),
|
|
184
|
+
CONSTRAINT "knowledge_providers_external_identity_chk" CHECK (
|
|
185
|
+
length(btrim("external_tenant_id")) BETWEEN 1 AND 1024
|
|
186
|
+
),
|
|
187
|
+
CONSTRAINT "knowledge_providers_lifecycle_chk" CHECK (
|
|
188
|
+
"lifecycle_state" IN ('active', 'deleted', 'revoked')
|
|
189
|
+
AND "lifecycle_generation" > 0
|
|
190
|
+
),
|
|
191
|
+
CONSTRAINT "knowledge_providers_idempotency_chk" CHECK (
|
|
192
|
+
length(btrim("operation_id")) BETWEEN 1 AND 256
|
|
193
|
+
AND "input_hash" ~ '^[0-9a-f]{64}$'
|
|
194
|
+
),
|
|
195
|
+
CONSTRAINT "knowledge_providers_actor_chk" CHECK (
|
|
196
|
+
opengeni_private.scoped_knowledge_actor_valid(
|
|
197
|
+
"actor_kind", "actor_subject_id", "initiating_human_subject_id"
|
|
198
|
+
)
|
|
199
|
+
),
|
|
200
|
+
CONSTRAINT "knowledge_providers_external_identity_uq"
|
|
201
|
+
UNIQUE ("account_id", "provider_key", "external_tenant_id"),
|
|
202
|
+
CONSTRAINT "knowledge_providers_operation_uq" UNIQUE ("account_id", "operation_id"),
|
|
203
|
+
CONSTRAINT "knowledge_providers_scope_identity_uq"
|
|
204
|
+
UNIQUE ("account_id", "id", "scope_key")
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
CREATE INDEX "knowledge_providers_scope_lifecycle_idx"
|
|
208
|
+
ON "knowledge_providers" ("account_id", "scope_key", "lifecycle_state");
|
|
209
|
+
|
|
210
|
+
CREATE TABLE "knowledge_sources" (
|
|
211
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
212
|
+
"account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE RESTRICT,
|
|
213
|
+
"scope_kind" text NOT NULL,
|
|
214
|
+
"scope_workspace_id" uuid REFERENCES "workspaces"("id") ON DELETE RESTRICT,
|
|
215
|
+
"scope_subject_id" text,
|
|
216
|
+
"scope_key" text NOT NULL,
|
|
217
|
+
"provider_id" uuid NOT NULL,
|
|
218
|
+
"external_source_id" text NOT NULL,
|
|
219
|
+
"source_kind" text NOT NULL,
|
|
220
|
+
"source_uri" text,
|
|
221
|
+
"current_acl_generation" bigint,
|
|
222
|
+
"sync_generation" bigint NOT NULL DEFAULT 0,
|
|
223
|
+
"sync_cursor" text,
|
|
224
|
+
"lifecycle_state" text NOT NULL DEFAULT 'active',
|
|
225
|
+
"lifecycle_generation" bigint NOT NULL DEFAULT 1,
|
|
226
|
+
"operation_id" text NOT NULL,
|
|
227
|
+
"input_hash" text NOT NULL,
|
|
228
|
+
"actor_kind" text NOT NULL,
|
|
229
|
+
"actor_subject_id" text NOT NULL,
|
|
230
|
+
"initiating_human_subject_id" text,
|
|
231
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
232
|
+
"updated_at" timestamptz NOT NULL DEFAULT now(),
|
|
233
|
+
CONSTRAINT "knowledge_sources_provider_fk"
|
|
234
|
+
FOREIGN KEY ("account_id", "provider_id", "scope_key")
|
|
235
|
+
REFERENCES "knowledge_providers"("account_id", "id", "scope_key") ON DELETE RESTRICT,
|
|
236
|
+
CONSTRAINT "knowledge_sources_scope_chk" CHECK (
|
|
237
|
+
opengeni_private.scoped_knowledge_scope_valid(
|
|
238
|
+
"scope_kind", "scope_workspace_id", "scope_subject_id"
|
|
239
|
+
)
|
|
240
|
+
AND "scope_key" = opengeni_private.scoped_knowledge_scope_key(
|
|
241
|
+
"scope_kind", "scope_workspace_id", "scope_subject_id"
|
|
242
|
+
)
|
|
243
|
+
),
|
|
244
|
+
CONSTRAINT "knowledge_sources_identity_chk" CHECK (
|
|
245
|
+
length(btrim("external_source_id")) BETWEEN 1 AND 1024
|
|
246
|
+
AND length(btrim("source_kind")) BETWEEN 1 AND 96
|
|
247
|
+
AND ("source_uri" IS NULL OR length("source_uri") BETWEEN 1 AND 4096)
|
|
248
|
+
),
|
|
249
|
+
CONSTRAINT "knowledge_sources_generations_chk" CHECK (
|
|
250
|
+
("current_acl_generation" IS NULL OR "current_acl_generation" > 0)
|
|
251
|
+
AND "sync_generation" >= 0
|
|
252
|
+
AND "lifecycle_generation" > 0
|
|
253
|
+
AND "lifecycle_state" IN ('active', 'deleted', 'revoked')
|
|
254
|
+
),
|
|
255
|
+
CONSTRAINT "knowledge_sources_idempotency_chk" CHECK (
|
|
256
|
+
length(btrim("operation_id")) BETWEEN 1 AND 256
|
|
257
|
+
AND "input_hash" ~ '^[0-9a-f]{64}$'
|
|
258
|
+
AND ("sync_cursor" IS NULL OR length("sync_cursor") <= 4096)
|
|
259
|
+
),
|
|
260
|
+
CONSTRAINT "knowledge_sources_actor_chk" CHECK (
|
|
261
|
+
opengeni_private.scoped_knowledge_actor_valid(
|
|
262
|
+
"actor_kind", "actor_subject_id", "initiating_human_subject_id"
|
|
263
|
+
)
|
|
264
|
+
),
|
|
265
|
+
CONSTRAINT "knowledge_sources_external_identity_uq"
|
|
266
|
+
UNIQUE ("provider_id", "external_source_id"),
|
|
267
|
+
CONSTRAINT "knowledge_sources_operation_uq" UNIQUE ("account_id", "operation_id"),
|
|
268
|
+
CONSTRAINT "knowledge_sources_scope_identity_uq"
|
|
269
|
+
UNIQUE ("account_id", "id", "scope_key"),
|
|
270
|
+
CONSTRAINT "knowledge_sources_current_acl_identity_uq"
|
|
271
|
+
UNIQUE ("account_id", "id", "current_acl_generation")
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
CREATE INDEX "knowledge_sources_provider_idx"
|
|
275
|
+
ON "knowledge_sources" ("provider_id", "external_source_id");
|
|
276
|
+
|
|
277
|
+
CREATE TABLE "knowledge_source_acl_versions" (
|
|
278
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
279
|
+
"account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE RESTRICT,
|
|
280
|
+
"scope_kind" text NOT NULL,
|
|
281
|
+
"scope_workspace_id" uuid REFERENCES "workspaces"("id") ON DELETE RESTRICT,
|
|
282
|
+
"scope_subject_id" text,
|
|
283
|
+
"scope_key" text NOT NULL,
|
|
284
|
+
"source_id" uuid NOT NULL,
|
|
285
|
+
"source_scope_key" text NOT NULL,
|
|
286
|
+
"generation" bigint NOT NULL,
|
|
287
|
+
"acl_version" text,
|
|
288
|
+
"acl_hash" text NOT NULL,
|
|
289
|
+
"agent_access" boolean NOT NULL DEFAULT true,
|
|
290
|
+
"operation_id" text NOT NULL,
|
|
291
|
+
"input_hash" text NOT NULL,
|
|
292
|
+
"actor_kind" text NOT NULL,
|
|
293
|
+
"actor_subject_id" text NOT NULL,
|
|
294
|
+
"initiating_human_subject_id" text,
|
|
295
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
296
|
+
CONSTRAINT "knowledge_source_acl_versions_source_fk"
|
|
297
|
+
FOREIGN KEY ("account_id", "source_id", "source_scope_key")
|
|
298
|
+
REFERENCES "knowledge_sources"("account_id", "id", "scope_key") ON DELETE RESTRICT,
|
|
299
|
+
CONSTRAINT "knowledge_source_acl_versions_scope_chk" CHECK (
|
|
300
|
+
opengeni_private.scoped_knowledge_scope_valid(
|
|
301
|
+
"scope_kind", "scope_workspace_id", "scope_subject_id"
|
|
302
|
+
)
|
|
303
|
+
AND "scope_key" = opengeni_private.scoped_knowledge_scope_key(
|
|
304
|
+
"scope_kind", "scope_workspace_id", "scope_subject_id"
|
|
305
|
+
)
|
|
306
|
+
),
|
|
307
|
+
CONSTRAINT "knowledge_source_acl_versions_generation_chk" CHECK ("generation" > 0),
|
|
308
|
+
CONSTRAINT "knowledge_source_acl_versions_hash_chk" CHECK (
|
|
309
|
+
"acl_hash" ~ '^[0-9a-f]{64}$'
|
|
310
|
+
AND "input_hash" ~ '^[0-9a-f]{64}$'
|
|
311
|
+
),
|
|
312
|
+
CONSTRAINT "knowledge_source_acl_versions_bounds_chk" CHECK (
|
|
313
|
+
("acl_version" IS NULL OR length("acl_version") BETWEEN 1 AND 512)
|
|
314
|
+
AND length(btrim("operation_id")) BETWEEN 1 AND 256
|
|
315
|
+
),
|
|
316
|
+
CONSTRAINT "knowledge_source_acl_versions_actor_chk" CHECK (
|
|
317
|
+
opengeni_private.scoped_knowledge_actor_valid(
|
|
318
|
+
"actor_kind", "actor_subject_id", "initiating_human_subject_id"
|
|
319
|
+
)
|
|
320
|
+
),
|
|
321
|
+
CONSTRAINT "knowledge_source_acl_versions_source_generation_uq"
|
|
322
|
+
UNIQUE ("account_id", "source_id", "generation"),
|
|
323
|
+
CONSTRAINT "knowledge_source_acl_versions_source_operation_uq"
|
|
324
|
+
UNIQUE ("source_id", "operation_id")
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
CREATE INDEX "knowledge_source_acl_versions_source_timeline_idx"
|
|
328
|
+
ON "knowledge_source_acl_versions" ("source_id", "generation" DESC);
|
|
329
|
+
|
|
330
|
+
ALTER TABLE "knowledge_sources"
|
|
331
|
+
ADD CONSTRAINT "knowledge_sources_current_acl_fk"
|
|
332
|
+
FOREIGN KEY ("account_id", "id", "current_acl_generation")
|
|
333
|
+
REFERENCES "knowledge_source_acl_versions"("account_id", "source_id", "generation")
|
|
334
|
+
ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED;
|
|
335
|
+
|
|
336
|
+
CREATE TABLE "knowledge_sync_runs" (
|
|
337
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
338
|
+
"account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE RESTRICT,
|
|
339
|
+
"scope_kind" text NOT NULL,
|
|
340
|
+
"scope_workspace_id" uuid REFERENCES "workspaces"("id") ON DELETE RESTRICT,
|
|
341
|
+
"scope_subject_id" text,
|
|
342
|
+
"scope_key" text NOT NULL,
|
|
343
|
+
"source_id" uuid NOT NULL,
|
|
344
|
+
"input_sync_generation" bigint NOT NULL,
|
|
345
|
+
"input_lifecycle_generation" bigint NOT NULL,
|
|
346
|
+
"input_cursor" text,
|
|
347
|
+
"input_hash" text NOT NULL,
|
|
348
|
+
"operation_id" text NOT NULL,
|
|
349
|
+
"state" text NOT NULL DEFAULT 'started',
|
|
350
|
+
"output_cursor" text,
|
|
351
|
+
"watermark" timestamptz,
|
|
352
|
+
"metadata" jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
353
|
+
"error_code" text,
|
|
354
|
+
"completion_hash" text,
|
|
355
|
+
"actor_kind" text NOT NULL,
|
|
356
|
+
"actor_subject_id" text NOT NULL,
|
|
357
|
+
"initiating_human_subject_id" text,
|
|
358
|
+
"started_at" timestamptz NOT NULL DEFAULT now(),
|
|
359
|
+
"completed_at" timestamptz,
|
|
360
|
+
CONSTRAINT "knowledge_sync_runs_source_fk"
|
|
361
|
+
FOREIGN KEY ("account_id", "source_id", "scope_key")
|
|
362
|
+
REFERENCES "knowledge_sources"("account_id", "id", "scope_key") ON DELETE RESTRICT,
|
|
363
|
+
CONSTRAINT "knowledge_sync_runs_scope_chk" CHECK (
|
|
364
|
+
opengeni_private.scoped_knowledge_scope_valid(
|
|
365
|
+
"scope_kind", "scope_workspace_id", "scope_subject_id"
|
|
366
|
+
)
|
|
367
|
+
AND "scope_key" = opengeni_private.scoped_knowledge_scope_key(
|
|
368
|
+
"scope_kind", "scope_workspace_id", "scope_subject_id"
|
|
369
|
+
)
|
|
370
|
+
),
|
|
371
|
+
CONSTRAINT "knowledge_sync_runs_input_chk" CHECK (
|
|
372
|
+
"input_sync_generation" >= 0
|
|
373
|
+
AND "input_lifecycle_generation" > 0
|
|
374
|
+
AND "input_hash" ~ '^[0-9a-f]{64}$'
|
|
375
|
+
AND length(btrim("operation_id")) BETWEEN 1 AND 256
|
|
376
|
+
AND ("input_cursor" IS NULL OR length("input_cursor") <= 4096)
|
|
377
|
+
),
|
|
378
|
+
CONSTRAINT "knowledge_sync_runs_state_chk" CHECK (
|
|
379
|
+
(
|
|
380
|
+
"state" = 'started'
|
|
381
|
+
AND "completed_at" IS NULL
|
|
382
|
+
AND "completion_hash" IS NULL
|
|
383
|
+
AND "output_cursor" IS NULL
|
|
384
|
+
AND "error_code" IS NULL
|
|
385
|
+
) OR (
|
|
386
|
+
"state" = 'succeeded'
|
|
387
|
+
AND "completed_at" IS NOT NULL
|
|
388
|
+
AND "completion_hash" ~ '^[0-9a-f]{64}$'
|
|
389
|
+
AND "error_code" IS NULL
|
|
390
|
+
) OR (
|
|
391
|
+
"state" = 'failed'
|
|
392
|
+
AND "completed_at" IS NOT NULL
|
|
393
|
+
AND "completion_hash" ~ '^[0-9a-f]{64}$'
|
|
394
|
+
AND length(btrim("error_code")) BETWEEN 1 AND 128
|
|
395
|
+
AND "output_cursor" IS NULL
|
|
396
|
+
)
|
|
397
|
+
),
|
|
398
|
+
CONSTRAINT "knowledge_sync_runs_metadata_chk" CHECK (
|
|
399
|
+
jsonb_typeof("metadata") = 'object'
|
|
400
|
+
AND octet_length(convert_to("metadata"::text, 'UTF8')) <= 16384
|
|
401
|
+
AND ("output_cursor" IS NULL OR length("output_cursor") <= 4096)
|
|
402
|
+
),
|
|
403
|
+
CONSTRAINT "knowledge_sync_runs_actor_chk" CHECK (
|
|
404
|
+
opengeni_private.scoped_knowledge_actor_valid(
|
|
405
|
+
"actor_kind", "actor_subject_id", "initiating_human_subject_id"
|
|
406
|
+
)
|
|
407
|
+
),
|
|
408
|
+
CONSTRAINT "knowledge_sync_runs_source_operation_uq" UNIQUE ("source_id", "operation_id")
|
|
409
|
+
);
|
|
410
|
+
|
|
411
|
+
CREATE INDEX "knowledge_sync_runs_source_state_idx"
|
|
412
|
+
ON "knowledge_sync_runs" ("source_id", "state", "started_at" DESC);
|
|
413
|
+
|
|
414
|
+
CREATE TABLE "knowledge_source_objects" (
|
|
415
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
416
|
+
"account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE RESTRICT,
|
|
417
|
+
"scope_kind" text NOT NULL,
|
|
418
|
+
"scope_workspace_id" uuid REFERENCES "workspaces"("id") ON DELETE RESTRICT,
|
|
419
|
+
"scope_subject_id" text,
|
|
420
|
+
"scope_key" text NOT NULL,
|
|
421
|
+
"source_id" uuid NOT NULL,
|
|
422
|
+
"external_object_id" text NOT NULL,
|
|
423
|
+
"document_id" uuid,
|
|
424
|
+
"lifecycle_state" text NOT NULL DEFAULT 'active',
|
|
425
|
+
"lifecycle_generation" bigint NOT NULL DEFAULT 1,
|
|
426
|
+
"version_generation" bigint NOT NULL DEFAULT 0,
|
|
427
|
+
"current_version_id" uuid,
|
|
428
|
+
"operation_id" text NOT NULL,
|
|
429
|
+
"input_hash" text NOT NULL,
|
|
430
|
+
"actor_kind" text NOT NULL,
|
|
431
|
+
"actor_subject_id" text NOT NULL,
|
|
432
|
+
"initiating_human_subject_id" text,
|
|
433
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
434
|
+
"updated_at" timestamptz NOT NULL DEFAULT now(),
|
|
435
|
+
CONSTRAINT "knowledge_source_objects_source_fk"
|
|
436
|
+
FOREIGN KEY ("account_id", "source_id", "scope_key")
|
|
437
|
+
REFERENCES "knowledge_sources"("account_id", "id", "scope_key") ON DELETE RESTRICT,
|
|
438
|
+
CONSTRAINT "knowledge_source_objects_scope_chk" CHECK (
|
|
439
|
+
opengeni_private.scoped_knowledge_scope_valid(
|
|
440
|
+
"scope_kind", "scope_workspace_id", "scope_subject_id"
|
|
441
|
+
)
|
|
442
|
+
AND "scope_key" = opengeni_private.scoped_knowledge_scope_key(
|
|
443
|
+
"scope_kind", "scope_workspace_id", "scope_subject_id"
|
|
444
|
+
)
|
|
445
|
+
),
|
|
446
|
+
CONSTRAINT "knowledge_source_objects_identity_chk" CHECK (
|
|
447
|
+
length(btrim("external_object_id")) BETWEEN 1 AND 1024
|
|
448
|
+
),
|
|
449
|
+
CONSTRAINT "knowledge_source_objects_generation_chk" CHECK (
|
|
450
|
+
"lifecycle_state" IN ('active', 'deleted', 'revoked')
|
|
451
|
+
AND "lifecycle_generation" > 0
|
|
452
|
+
AND "version_generation" >= 0
|
|
453
|
+
AND (("current_version_id" IS NULL) = ("version_generation" = 0))
|
|
454
|
+
),
|
|
455
|
+
CONSTRAINT "knowledge_source_objects_idempotency_chk" CHECK (
|
|
456
|
+
length(btrim("operation_id")) BETWEEN 1 AND 256
|
|
457
|
+
AND "input_hash" ~ '^[0-9a-f]{64}$'
|
|
458
|
+
),
|
|
459
|
+
CONSTRAINT "knowledge_source_objects_actor_chk" CHECK (
|
|
460
|
+
opengeni_private.scoped_knowledge_actor_valid(
|
|
461
|
+
"actor_kind", "actor_subject_id", "initiating_human_subject_id"
|
|
462
|
+
)
|
|
463
|
+
),
|
|
464
|
+
CONSTRAINT "knowledge_source_objects_external_identity_uq"
|
|
465
|
+
UNIQUE ("source_id", "external_object_id"),
|
|
466
|
+
CONSTRAINT "knowledge_source_objects_source_operation_uq"
|
|
467
|
+
UNIQUE ("source_id", "operation_id"),
|
|
468
|
+
CONSTRAINT "knowledge_source_objects_scope_identity_uq"
|
|
469
|
+
UNIQUE ("account_id", "id", "scope_key"),
|
|
470
|
+
CONSTRAINT "knowledge_source_objects_source_identity_uq"
|
|
471
|
+
UNIQUE ("account_id", "id", "source_id", "scope_key"),
|
|
472
|
+
CONSTRAINT "knowledge_source_objects_current_version_identity_uq"
|
|
473
|
+
UNIQUE ("account_id", "id", "current_version_id")
|
|
474
|
+
);
|
|
475
|
+
|
|
476
|
+
CREATE TABLE "knowledge_document_versions" (
|
|
477
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
478
|
+
"account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE RESTRICT,
|
|
479
|
+
"scope_kind" text NOT NULL,
|
|
480
|
+
"scope_workspace_id" uuid REFERENCES "workspaces"("id") ON DELETE RESTRICT,
|
|
481
|
+
"scope_subject_id" text,
|
|
482
|
+
"scope_key" text NOT NULL,
|
|
483
|
+
"source_id" uuid NOT NULL,
|
|
484
|
+
"object_id" uuid NOT NULL,
|
|
485
|
+
"version_generation" bigint NOT NULL,
|
|
486
|
+
"external_version_id" text NOT NULL,
|
|
487
|
+
"content_sha256" text NOT NULL,
|
|
488
|
+
"ingestion_key" text NOT NULL,
|
|
489
|
+
"source_cursor" text,
|
|
490
|
+
"source_metadata" jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
491
|
+
"source_created_at" timestamptz,
|
|
492
|
+
"source_updated_at" timestamptz,
|
|
493
|
+
"observed_at" timestamptz NOT NULL DEFAULT now(),
|
|
494
|
+
"acl_version_id" uuid NOT NULL,
|
|
495
|
+
"acl_generation" bigint NOT NULL,
|
|
496
|
+
"document_id" uuid,
|
|
497
|
+
"file_id" uuid,
|
|
498
|
+
"location_metadata" jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
499
|
+
"operation_id" text NOT NULL,
|
|
500
|
+
"input_hash" text NOT NULL,
|
|
501
|
+
"actor_kind" text NOT NULL,
|
|
502
|
+
"actor_subject_id" text NOT NULL,
|
|
503
|
+
"initiating_human_subject_id" text,
|
|
504
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
505
|
+
CONSTRAINT "knowledge_document_versions_source_fk"
|
|
506
|
+
FOREIGN KEY ("account_id", "source_id", "scope_key")
|
|
507
|
+
REFERENCES "knowledge_sources"("account_id", "id", "scope_key") ON DELETE RESTRICT,
|
|
508
|
+
CONSTRAINT "knowledge_document_versions_object_fk"
|
|
509
|
+
FOREIGN KEY ("account_id", "object_id", "source_id", "scope_key")
|
|
510
|
+
REFERENCES "knowledge_source_objects"("account_id", "id", "source_id", "scope_key")
|
|
511
|
+
ON DELETE RESTRICT,
|
|
512
|
+
CONSTRAINT "knowledge_document_versions_acl_fk"
|
|
513
|
+
FOREIGN KEY ("account_id", "source_id", "acl_generation")
|
|
514
|
+
REFERENCES "knowledge_source_acl_versions"("account_id", "source_id", "generation")
|
|
515
|
+
ON DELETE RESTRICT,
|
|
516
|
+
CONSTRAINT "knowledge_document_versions_scope_chk" CHECK (
|
|
517
|
+
opengeni_private.scoped_knowledge_scope_valid(
|
|
518
|
+
"scope_kind", "scope_workspace_id", "scope_subject_id"
|
|
519
|
+
)
|
|
520
|
+
AND "scope_key" = opengeni_private.scoped_knowledge_scope_key(
|
|
521
|
+
"scope_kind", "scope_workspace_id", "scope_subject_id"
|
|
522
|
+
)
|
|
523
|
+
),
|
|
524
|
+
CONSTRAINT "knowledge_document_versions_identity_chk" CHECK (
|
|
525
|
+
"version_generation" > 0
|
|
526
|
+
AND length(btrim("external_version_id")) BETWEEN 1 AND 1024
|
|
527
|
+
AND length(btrim("ingestion_key")) BETWEEN 1 AND 512
|
|
528
|
+
AND "content_sha256" ~ '^[0-9a-f]{64}$'
|
|
529
|
+
AND "acl_generation" > 0
|
|
530
|
+
AND ("source_cursor" IS NULL OR length("source_cursor") <= 4096)
|
|
531
|
+
),
|
|
532
|
+
CONSTRAINT "knowledge_document_versions_metadata_chk" CHECK (
|
|
533
|
+
jsonb_typeof("source_metadata") = 'object'
|
|
534
|
+
AND jsonb_typeof("location_metadata") = 'object'
|
|
535
|
+
AND octet_length(convert_to("source_metadata"::text, 'UTF8')) <= 16384
|
|
536
|
+
AND octet_length(convert_to("location_metadata"::text, 'UTF8')) <= 16384
|
|
537
|
+
),
|
|
538
|
+
CONSTRAINT "knowledge_document_versions_idempotency_chk" CHECK (
|
|
539
|
+
length(btrim("operation_id")) BETWEEN 1 AND 256
|
|
540
|
+
AND "input_hash" ~ '^[0-9a-f]{64}$'
|
|
541
|
+
),
|
|
542
|
+
CONSTRAINT "knowledge_document_versions_actor_chk" CHECK (
|
|
543
|
+
opengeni_private.scoped_knowledge_actor_valid(
|
|
544
|
+
"actor_kind", "actor_subject_id", "initiating_human_subject_id"
|
|
545
|
+
)
|
|
546
|
+
),
|
|
547
|
+
CONSTRAINT "knowledge_document_versions_object_generation_uq"
|
|
548
|
+
UNIQUE ("account_id", "object_id", "version_generation"),
|
|
549
|
+
CONSTRAINT "knowledge_document_versions_object_external_version_uq"
|
|
550
|
+
UNIQUE ("object_id", "external_version_id"),
|
|
551
|
+
CONSTRAINT "knowledge_document_versions_object_ingestion_uq"
|
|
552
|
+
UNIQUE ("object_id", "ingestion_key"),
|
|
553
|
+
CONSTRAINT "knowledge_document_versions_object_operation_uq"
|
|
554
|
+
UNIQUE ("object_id", "operation_id"),
|
|
555
|
+
CONSTRAINT "knowledge_document_versions_object_identity_uq"
|
|
556
|
+
UNIQUE ("account_id", "object_id", "id"),
|
|
557
|
+
CONSTRAINT "knowledge_document_versions_scope_identity_uq"
|
|
558
|
+
UNIQUE ("account_id", "id", "scope_key")
|
|
559
|
+
);
|
|
560
|
+
|
|
561
|
+
ALTER TABLE "knowledge_source_objects"
|
|
562
|
+
ADD CONSTRAINT "knowledge_source_objects_current_version_fk"
|
|
563
|
+
FOREIGN KEY ("account_id", "id", "current_version_id")
|
|
564
|
+
REFERENCES "knowledge_document_versions"("account_id", "object_id", "id")
|
|
565
|
+
ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED;
|
|
566
|
+
|
|
567
|
+
CREATE TABLE "knowledge_lifecycle_events" (
|
|
568
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
569
|
+
"account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE RESTRICT,
|
|
570
|
+
"scope_kind" text NOT NULL,
|
|
571
|
+
"scope_workspace_id" uuid REFERENCES "workspaces"("id") ON DELETE RESTRICT,
|
|
572
|
+
"scope_subject_id" text,
|
|
573
|
+
"scope_key" text NOT NULL,
|
|
574
|
+
"target_kind" text NOT NULL,
|
|
575
|
+
"provider_id" uuid,
|
|
576
|
+
"source_id" uuid,
|
|
577
|
+
"object_id" uuid,
|
|
578
|
+
"event_type" text NOT NULL,
|
|
579
|
+
"old_state" text NOT NULL,
|
|
580
|
+
"new_state" text NOT NULL,
|
|
581
|
+
"old_generation" bigint NOT NULL,
|
|
582
|
+
"new_generation" bigint NOT NULL,
|
|
583
|
+
"operation_id" text NOT NULL,
|
|
584
|
+
"input_hash" text NOT NULL,
|
|
585
|
+
"reason_code" text NOT NULL,
|
|
586
|
+
"actor_kind" text NOT NULL,
|
|
587
|
+
"actor_subject_id" text NOT NULL,
|
|
588
|
+
"initiating_human_subject_id" text,
|
|
589
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
590
|
+
CONSTRAINT "knowledge_lifecycle_events_provider_fk"
|
|
591
|
+
FOREIGN KEY ("account_id", "provider_id", "scope_key")
|
|
592
|
+
REFERENCES "knowledge_providers"("account_id", "id", "scope_key") ON DELETE RESTRICT,
|
|
593
|
+
CONSTRAINT "knowledge_lifecycle_events_source_fk"
|
|
594
|
+
FOREIGN KEY ("account_id", "source_id", "scope_key")
|
|
595
|
+
REFERENCES "knowledge_sources"("account_id", "id", "scope_key") ON DELETE RESTRICT,
|
|
596
|
+
CONSTRAINT "knowledge_lifecycle_events_object_fk"
|
|
597
|
+
FOREIGN KEY ("account_id", "object_id", "scope_key")
|
|
598
|
+
REFERENCES "knowledge_source_objects"("account_id", "id", "scope_key") ON DELETE RESTRICT,
|
|
599
|
+
CONSTRAINT "knowledge_lifecycle_events_scope_chk" CHECK (
|
|
600
|
+
opengeni_private.scoped_knowledge_scope_valid(
|
|
601
|
+
"scope_kind", "scope_workspace_id", "scope_subject_id"
|
|
602
|
+
)
|
|
603
|
+
AND "scope_key" = opengeni_private.scoped_knowledge_scope_key(
|
|
604
|
+
"scope_kind", "scope_workspace_id", "scope_subject_id"
|
|
605
|
+
)
|
|
606
|
+
),
|
|
607
|
+
CONSTRAINT "knowledge_lifecycle_events_target_chk" CHECK (
|
|
608
|
+
("target_kind" = 'provider' AND "provider_id" IS NOT NULL
|
|
609
|
+
AND "source_id" IS NULL AND "object_id" IS NULL)
|
|
610
|
+
OR ("target_kind" = 'source' AND "provider_id" IS NULL
|
|
611
|
+
AND "source_id" IS NOT NULL AND "object_id" IS NULL)
|
|
612
|
+
OR ("target_kind" = 'object' AND "provider_id" IS NULL
|
|
613
|
+
AND "source_id" IS NULL AND "object_id" IS NOT NULL)
|
|
614
|
+
),
|
|
615
|
+
CONSTRAINT "knowledge_lifecycle_events_transition_chk" CHECK (
|
|
616
|
+
"event_type" IN (
|
|
617
|
+
'deleted', 'revoked', 'restored', 'acl_changed',
|
|
618
|
+
'sync_succeeded', 'sync_failed', 'object_version_added'
|
|
619
|
+
)
|
|
620
|
+
AND "old_state" IN ('active', 'deleted', 'revoked')
|
|
621
|
+
AND "new_state" IN ('active', 'deleted', 'revoked')
|
|
622
|
+
AND "old_generation" > 0
|
|
623
|
+
AND "new_generation" >= "old_generation"
|
|
624
|
+
AND (
|
|
625
|
+
("event_type" IN ('deleted', 'revoked', 'restored')
|
|
626
|
+
AND "new_generation" = "old_generation" + 1)
|
|
627
|
+
OR ("event_type" NOT IN ('deleted', 'revoked', 'restored')
|
|
628
|
+
AND "new_generation" = "old_generation"
|
|
629
|
+
AND "new_state" = "old_state")
|
|
630
|
+
)
|
|
631
|
+
),
|
|
632
|
+
CONSTRAINT "knowledge_lifecycle_events_audit_chk" CHECK (
|
|
633
|
+
length(btrim("operation_id")) BETWEEN 1 AND 256
|
|
634
|
+
AND "input_hash" ~ '^[0-9a-f]{64}$'
|
|
635
|
+
AND length(btrim("reason_code")) BETWEEN 1 AND 128
|
|
636
|
+
AND opengeni_private.scoped_knowledge_actor_valid(
|
|
637
|
+
"actor_kind", "actor_subject_id", "initiating_human_subject_id"
|
|
638
|
+
)
|
|
639
|
+
)
|
|
640
|
+
);
|
|
641
|
+
|
|
642
|
+
CREATE UNIQUE INDEX "knowledge_lifecycle_events_target_operation_uq"
|
|
643
|
+
ON "knowledge_lifecycle_events" (
|
|
644
|
+
"account_id", "target_kind", "provider_id", "source_id", "object_id", "operation_id"
|
|
645
|
+
) NULLS NOT DISTINCT;
|
|
646
|
+
CREATE INDEX "knowledge_lifecycle_events_target_timeline_idx"
|
|
647
|
+
ON "knowledge_lifecycle_events" (
|
|
648
|
+
"target_kind", "provider_id", "source_id", "object_id", "created_at" DESC
|
|
649
|
+
);
|
|
650
|
+
|
|
651
|
+
CREATE TABLE "knowledge_entities" (
|
|
652
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
653
|
+
"account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE RESTRICT,
|
|
654
|
+
"scope_kind" text NOT NULL,
|
|
655
|
+
"scope_workspace_id" uuid REFERENCES "workspaces"("id") ON DELETE RESTRICT,
|
|
656
|
+
"scope_subject_id" text,
|
|
657
|
+
"scope_key" text NOT NULL,
|
|
658
|
+
"entity_type" text NOT NULL,
|
|
659
|
+
"normalized_key" text NOT NULL,
|
|
660
|
+
"display_name" text NOT NULL,
|
|
661
|
+
"operation_id" text NOT NULL,
|
|
662
|
+
"input_hash" text NOT NULL,
|
|
663
|
+
"actor_kind" text NOT NULL,
|
|
664
|
+
"actor_subject_id" text NOT NULL,
|
|
665
|
+
"initiating_human_subject_id" text,
|
|
666
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
667
|
+
CONSTRAINT "knowledge_entities_scope_chk" CHECK (
|
|
668
|
+
opengeni_private.scoped_knowledge_scope_valid(
|
|
669
|
+
"scope_kind", "scope_workspace_id", "scope_subject_id"
|
|
670
|
+
)
|
|
671
|
+
AND "scope_key" = opengeni_private.scoped_knowledge_scope_key(
|
|
672
|
+
"scope_kind", "scope_workspace_id", "scope_subject_id"
|
|
673
|
+
)
|
|
674
|
+
),
|
|
675
|
+
CONSTRAINT "knowledge_entities_key_chk" CHECK (
|
|
676
|
+
"entity_type" = lower(btrim("entity_type"))
|
|
677
|
+
AND length("entity_type") BETWEEN 1 AND 96
|
|
678
|
+
AND "entity_type" ~ '^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$'
|
|
679
|
+
AND "normalized_key" = lower(btrim("normalized_key"))
|
|
680
|
+
AND length("normalized_key") BETWEEN 1 AND 512
|
|
681
|
+
AND length(btrim("display_name")) BETWEEN 1 AND 512
|
|
682
|
+
),
|
|
683
|
+
CONSTRAINT "knowledge_entities_audit_chk" CHECK (
|
|
684
|
+
length(btrim("operation_id")) BETWEEN 1 AND 256
|
|
685
|
+
AND "input_hash" ~ '^[0-9a-f]{64}$'
|
|
686
|
+
AND opengeni_private.scoped_knowledge_actor_valid(
|
|
687
|
+
"actor_kind", "actor_subject_id", "initiating_human_subject_id"
|
|
688
|
+
)
|
|
689
|
+
),
|
|
690
|
+
CONSTRAINT "knowledge_entities_natural_identity_uq"
|
|
691
|
+
UNIQUE ("account_id", "scope_key", "entity_type", "normalized_key"),
|
|
692
|
+
CONSTRAINT "knowledge_entities_operation_uq" UNIQUE ("account_id", "operation_id"),
|
|
693
|
+
CONSTRAINT "knowledge_entities_scope_identity_uq"
|
|
694
|
+
UNIQUE ("account_id", "id", "scope_key"),
|
|
695
|
+
CONSTRAINT "knowledge_entities_scope_type_identity_uq"
|
|
696
|
+
UNIQUE ("account_id", "id", "scope_key", "entity_type")
|
|
697
|
+
);
|
|
698
|
+
|
|
699
|
+
CREATE TABLE "knowledge_entity_aliases" (
|
|
700
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
701
|
+
"account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE RESTRICT,
|
|
702
|
+
"scope_kind" text NOT NULL,
|
|
703
|
+
"scope_workspace_id" uuid REFERENCES "workspaces"("id") ON DELETE RESTRICT,
|
|
704
|
+
"scope_subject_id" text,
|
|
705
|
+
"scope_key" text NOT NULL,
|
|
706
|
+
"entity_id" uuid NOT NULL,
|
|
707
|
+
"entity_type" text NOT NULL,
|
|
708
|
+
"alias" text NOT NULL,
|
|
709
|
+
"normalized_alias" text NOT NULL,
|
|
710
|
+
"operation_id" text NOT NULL,
|
|
711
|
+
"input_hash" text NOT NULL,
|
|
712
|
+
"actor_kind" text NOT NULL,
|
|
713
|
+
"actor_subject_id" text NOT NULL,
|
|
714
|
+
"initiating_human_subject_id" text,
|
|
715
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
716
|
+
CONSTRAINT "knowledge_entity_aliases_entity_fk"
|
|
717
|
+
FOREIGN KEY ("account_id", "entity_id", "scope_key", "entity_type")
|
|
718
|
+
REFERENCES "knowledge_entities"("account_id", "id", "scope_key", "entity_type")
|
|
719
|
+
ON DELETE RESTRICT,
|
|
720
|
+
CONSTRAINT "knowledge_entity_aliases_scope_chk" CHECK (
|
|
721
|
+
opengeni_private.scoped_knowledge_scope_valid(
|
|
722
|
+
"scope_kind", "scope_workspace_id", "scope_subject_id"
|
|
723
|
+
)
|
|
724
|
+
AND "scope_key" = opengeni_private.scoped_knowledge_scope_key(
|
|
725
|
+
"scope_kind", "scope_workspace_id", "scope_subject_id"
|
|
726
|
+
)
|
|
727
|
+
),
|
|
728
|
+
CONSTRAINT "knowledge_entity_aliases_alias_chk" CHECK (
|
|
729
|
+
length(btrim("alias")) BETWEEN 1 AND 512
|
|
730
|
+
AND "normalized_alias" = lower(btrim("normalized_alias"))
|
|
731
|
+
AND length("normalized_alias") BETWEEN 1 AND 512
|
|
732
|
+
),
|
|
733
|
+
CONSTRAINT "knowledge_entity_aliases_audit_chk" CHECK (
|
|
734
|
+
length(btrim("operation_id")) BETWEEN 1 AND 256
|
|
735
|
+
AND "input_hash" ~ '^[0-9a-f]{64}$'
|
|
736
|
+
AND opengeni_private.scoped_knowledge_actor_valid(
|
|
737
|
+
"actor_kind", "actor_subject_id", "initiating_human_subject_id"
|
|
738
|
+
)
|
|
739
|
+
),
|
|
740
|
+
CONSTRAINT "knowledge_entity_aliases_natural_identity_uq"
|
|
741
|
+
UNIQUE ("account_id", "scope_key", "entity_type", "normalized_alias"),
|
|
742
|
+
CONSTRAINT "knowledge_entity_aliases_operation_uq" UNIQUE ("account_id", "operation_id")
|
|
743
|
+
);
|
|
744
|
+
|
|
745
|
+
CREATE INDEX "knowledge_entity_aliases_entity_idx"
|
|
746
|
+
ON "knowledge_entity_aliases" ("entity_id", "created_at");
|
|
747
|
+
|
|
748
|
+
CREATE TABLE "knowledge_facts" (
|
|
749
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
750
|
+
"account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE RESTRICT,
|
|
751
|
+
"scope_kind" text NOT NULL,
|
|
752
|
+
"scope_workspace_id" uuid REFERENCES "workspaces"("id") ON DELETE RESTRICT,
|
|
753
|
+
"scope_subject_id" text,
|
|
754
|
+
"scope_key" text NOT NULL,
|
|
755
|
+
"subject_entity_id" uuid NOT NULL,
|
|
756
|
+
"predicate_key" text NOT NULL,
|
|
757
|
+
"object_kind" text NOT NULL,
|
|
758
|
+
"object_entity_id" uuid,
|
|
759
|
+
"object_value" jsonb,
|
|
760
|
+
"object_hash" text NOT NULL,
|
|
761
|
+
"operation_id" text NOT NULL,
|
|
762
|
+
"input_hash" text NOT NULL,
|
|
763
|
+
"actor_kind" text NOT NULL,
|
|
764
|
+
"actor_subject_id" text NOT NULL,
|
|
765
|
+
"initiating_human_subject_id" text,
|
|
766
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
767
|
+
CONSTRAINT "knowledge_facts_subject_fk"
|
|
768
|
+
FOREIGN KEY ("account_id", "subject_entity_id", "scope_key")
|
|
769
|
+
REFERENCES "knowledge_entities"("account_id", "id", "scope_key") ON DELETE RESTRICT,
|
|
770
|
+
CONSTRAINT "knowledge_facts_object_entity_fk"
|
|
771
|
+
FOREIGN KEY ("account_id", "object_entity_id", "scope_key")
|
|
772
|
+
REFERENCES "knowledge_entities"("account_id", "id", "scope_key") ON DELETE RESTRICT,
|
|
773
|
+
CONSTRAINT "knowledge_facts_scope_chk" CHECK (
|
|
774
|
+
opengeni_private.scoped_knowledge_scope_valid(
|
|
775
|
+
"scope_kind", "scope_workspace_id", "scope_subject_id"
|
|
776
|
+
)
|
|
777
|
+
AND "scope_key" = opengeni_private.scoped_knowledge_scope_key(
|
|
778
|
+
"scope_kind", "scope_workspace_id", "scope_subject_id"
|
|
779
|
+
)
|
|
780
|
+
),
|
|
781
|
+
CONSTRAINT "knowledge_facts_predicate_chk" CHECK (
|
|
782
|
+
"predicate_key" = lower(btrim("predicate_key"))
|
|
783
|
+
AND length("predicate_key") BETWEEN 1 AND 128
|
|
784
|
+
AND "predicate_key" ~ '^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$'
|
|
785
|
+
),
|
|
786
|
+
CONSTRAINT "knowledge_facts_object_shape_chk" CHECK (
|
|
787
|
+
("object_kind" = 'entity' AND "object_entity_id" IS NOT NULL AND "object_value" IS NULL)
|
|
788
|
+
OR ("object_kind" IN ('text', 'number', 'boolean', 'json', 'timestamp')
|
|
789
|
+
AND "object_entity_id" IS NULL AND "object_value" IS NOT NULL)
|
|
790
|
+
),
|
|
791
|
+
CONSTRAINT "knowledge_facts_hash_chk" CHECK (
|
|
792
|
+
"object_hash" ~ '^[0-9a-f]{64}$'
|
|
793
|
+
AND "input_hash" ~ '^[0-9a-f]{64}$'
|
|
794
|
+
),
|
|
795
|
+
CONSTRAINT "knowledge_facts_audit_chk" CHECK (
|
|
796
|
+
length(btrim("operation_id")) BETWEEN 1 AND 256
|
|
797
|
+
AND opengeni_private.scoped_knowledge_actor_valid(
|
|
798
|
+
"actor_kind", "actor_subject_id", "initiating_human_subject_id"
|
|
799
|
+
)
|
|
800
|
+
),
|
|
801
|
+
CONSTRAINT "knowledge_facts_natural_identity_uq"
|
|
802
|
+
UNIQUE ("account_id", "scope_key", "subject_entity_id", "predicate_key", "object_hash"),
|
|
803
|
+
CONSTRAINT "knowledge_facts_operation_uq" UNIQUE ("account_id", "operation_id"),
|
|
804
|
+
CONSTRAINT "knowledge_facts_scope_identity_uq"
|
|
805
|
+
UNIQUE ("account_id", "id", "scope_key")
|
|
806
|
+
);
|
|
807
|
+
|
|
808
|
+
CREATE TABLE "knowledge_claims" (
|
|
809
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
810
|
+
"account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE RESTRICT,
|
|
811
|
+
"scope_kind" text NOT NULL,
|
|
812
|
+
"scope_workspace_id" uuid REFERENCES "workspaces"("id") ON DELETE RESTRICT,
|
|
813
|
+
"scope_subject_id" text,
|
|
814
|
+
"scope_key" text NOT NULL,
|
|
815
|
+
"fact_id" uuid NOT NULL,
|
|
816
|
+
"origin" text NOT NULL,
|
|
817
|
+
"confidence_bps" integer NOT NULL,
|
|
818
|
+
"effective_at" timestamptz NOT NULL,
|
|
819
|
+
"expires_at" timestamptz,
|
|
820
|
+
"extraction_method" text NOT NULL,
|
|
821
|
+
"extraction_metadata" jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
822
|
+
"model_provider" text,
|
|
823
|
+
"model_name" text,
|
|
824
|
+
"model_version" text,
|
|
825
|
+
"operation_id" text NOT NULL,
|
|
826
|
+
"input_hash" text NOT NULL,
|
|
827
|
+
"actor_kind" text NOT NULL,
|
|
828
|
+
"actor_subject_id" text NOT NULL,
|
|
829
|
+
"initiating_human_subject_id" text,
|
|
830
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
831
|
+
CONSTRAINT "knowledge_claims_fact_fk"
|
|
832
|
+
FOREIGN KEY ("account_id", "fact_id", "scope_key")
|
|
833
|
+
REFERENCES "knowledge_facts"("account_id", "id", "scope_key") ON DELETE RESTRICT,
|
|
834
|
+
CONSTRAINT "knowledge_claims_scope_chk" CHECK (
|
|
835
|
+
opengeni_private.scoped_knowledge_scope_valid(
|
|
836
|
+
"scope_kind", "scope_workspace_id", "scope_subject_id"
|
|
837
|
+
)
|
|
838
|
+
AND "scope_key" = opengeni_private.scoped_knowledge_scope_key(
|
|
839
|
+
"scope_kind", "scope_workspace_id", "scope_subject_id"
|
|
840
|
+
)
|
|
841
|
+
),
|
|
842
|
+
CONSTRAINT "knowledge_claims_assertion_chk" CHECK (
|
|
843
|
+
"origin" IN ('explicit', 'inferred')
|
|
844
|
+
AND "confidence_bps" BETWEEN 0 AND 10000
|
|
845
|
+
AND ("expires_at" IS NULL OR "effective_at" < "expires_at")
|
|
846
|
+
AND length(btrim("extraction_method")) BETWEEN 1 AND 128
|
|
847
|
+
),
|
|
848
|
+
CONSTRAINT "knowledge_claims_metadata_chk" CHECK (
|
|
849
|
+
jsonb_typeof("extraction_metadata") = 'object'
|
|
850
|
+
AND octet_length(convert_to("extraction_metadata"::text, 'UTF8')) <= 16384
|
|
851
|
+
AND ("model_provider" IS NULL OR length("model_provider") BETWEEN 1 AND 128)
|
|
852
|
+
AND ("model_name" IS NULL OR length("model_name") BETWEEN 1 AND 256)
|
|
853
|
+
AND ("model_version" IS NULL OR length("model_version") BETWEEN 1 AND 256)
|
|
854
|
+
),
|
|
855
|
+
CONSTRAINT "knowledge_claims_audit_chk" CHECK (
|
|
856
|
+
length(btrim("operation_id")) BETWEEN 1 AND 256
|
|
857
|
+
AND "input_hash" ~ '^[0-9a-f]{64}$'
|
|
858
|
+
AND opengeni_private.scoped_knowledge_actor_valid(
|
|
859
|
+
"actor_kind", "actor_subject_id", "initiating_human_subject_id"
|
|
860
|
+
)
|
|
861
|
+
),
|
|
862
|
+
CONSTRAINT "knowledge_claims_operation_uq" UNIQUE ("account_id", "operation_id"),
|
|
863
|
+
CONSTRAINT "knowledge_claims_scope_identity_uq"
|
|
864
|
+
UNIQUE ("account_id", "id", "scope_key")
|
|
865
|
+
);
|
|
866
|
+
|
|
867
|
+
CREATE INDEX "knowledge_claims_fact_timeline_idx"
|
|
868
|
+
ON "knowledge_claims" ("fact_id", "effective_at" DESC, "created_at" DESC);
|
|
869
|
+
|
|
870
|
+
CREATE TABLE "knowledge_claim_relations" (
|
|
871
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
872
|
+
"account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE RESTRICT,
|
|
873
|
+
"scope_kind" text NOT NULL,
|
|
874
|
+
"scope_workspace_id" uuid REFERENCES "workspaces"("id") ON DELETE RESTRICT,
|
|
875
|
+
"scope_subject_id" text,
|
|
876
|
+
"scope_key" text NOT NULL,
|
|
877
|
+
"relation_type" text NOT NULL,
|
|
878
|
+
"from_claim_id" uuid NOT NULL,
|
|
879
|
+
"to_claim_id" uuid NOT NULL,
|
|
880
|
+
"operation_id" text NOT NULL,
|
|
881
|
+
"input_hash" text NOT NULL,
|
|
882
|
+
"actor_kind" text NOT NULL,
|
|
883
|
+
"actor_subject_id" text NOT NULL,
|
|
884
|
+
"initiating_human_subject_id" text,
|
|
885
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
886
|
+
CONSTRAINT "knowledge_claim_relations_from_fk"
|
|
887
|
+
FOREIGN KEY ("account_id", "from_claim_id", "scope_key")
|
|
888
|
+
REFERENCES "knowledge_claims"("account_id", "id", "scope_key") ON DELETE RESTRICT,
|
|
889
|
+
CONSTRAINT "knowledge_claim_relations_to_fk"
|
|
890
|
+
FOREIGN KEY ("account_id", "to_claim_id", "scope_key")
|
|
891
|
+
REFERENCES "knowledge_claims"("account_id", "id", "scope_key") ON DELETE RESTRICT,
|
|
892
|
+
CONSTRAINT "knowledge_claim_relations_scope_chk" CHECK (
|
|
893
|
+
opengeni_private.scoped_knowledge_scope_valid(
|
|
894
|
+
"scope_kind", "scope_workspace_id", "scope_subject_id"
|
|
895
|
+
)
|
|
896
|
+
AND "scope_key" = opengeni_private.scoped_knowledge_scope_key(
|
|
897
|
+
"scope_kind", "scope_workspace_id", "scope_subject_id"
|
|
898
|
+
)
|
|
899
|
+
),
|
|
900
|
+
CONSTRAINT "knowledge_claim_relations_shape_chk" CHECK (
|
|
901
|
+
"relation_type" IN ('supersedes', 'conflicts_with')
|
|
902
|
+
AND "from_claim_id" <> "to_claim_id"
|
|
903
|
+
AND (
|
|
904
|
+
"relation_type" <> 'conflicts_with'
|
|
905
|
+
OR "from_claim_id"::text < "to_claim_id"::text
|
|
906
|
+
)
|
|
907
|
+
),
|
|
908
|
+
CONSTRAINT "knowledge_claim_relations_audit_chk" CHECK (
|
|
909
|
+
length(btrim("operation_id")) BETWEEN 1 AND 256
|
|
910
|
+
AND "input_hash" ~ '^[0-9a-f]{64}$'
|
|
911
|
+
AND opengeni_private.scoped_knowledge_actor_valid(
|
|
912
|
+
"actor_kind", "actor_subject_id", "initiating_human_subject_id"
|
|
913
|
+
)
|
|
914
|
+
),
|
|
915
|
+
CONSTRAINT "knowledge_claim_relations_natural_identity_uq"
|
|
916
|
+
UNIQUE ("relation_type", "from_claim_id", "to_claim_id"),
|
|
917
|
+
CONSTRAINT "knowledge_claim_relations_operation_uq" UNIQUE ("account_id", "operation_id")
|
|
918
|
+
);
|
|
919
|
+
|
|
920
|
+
CREATE TABLE "knowledge_claim_evidence" (
|
|
921
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
922
|
+
"account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE RESTRICT,
|
|
923
|
+
"scope_kind" text NOT NULL,
|
|
924
|
+
"scope_workspace_id" uuid REFERENCES "workspaces"("id") ON DELETE RESTRICT,
|
|
925
|
+
"scope_subject_id" text,
|
|
926
|
+
"scope_key" text NOT NULL,
|
|
927
|
+
"claim_id" uuid NOT NULL,
|
|
928
|
+
"document_version_id" uuid NOT NULL,
|
|
929
|
+
"polarity" text NOT NULL,
|
|
930
|
+
"document_chunk_id" uuid,
|
|
931
|
+
"chunk_index" integer,
|
|
932
|
+
"locator" text,
|
|
933
|
+
"quote_hash" text,
|
|
934
|
+
"content_hash" text NOT NULL,
|
|
935
|
+
"operation_id" text NOT NULL,
|
|
936
|
+
"input_hash" text NOT NULL,
|
|
937
|
+
"actor_kind" text NOT NULL,
|
|
938
|
+
"actor_subject_id" text NOT NULL,
|
|
939
|
+
"initiating_human_subject_id" text,
|
|
940
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
941
|
+
CONSTRAINT "knowledge_claim_evidence_claim_fk"
|
|
942
|
+
FOREIGN KEY ("account_id", "claim_id", "scope_key")
|
|
943
|
+
REFERENCES "knowledge_claims"("account_id", "id", "scope_key") ON DELETE RESTRICT,
|
|
944
|
+
CONSTRAINT "knowledge_claim_evidence_version_fk"
|
|
945
|
+
FOREIGN KEY ("account_id", "document_version_id", "scope_key")
|
|
946
|
+
REFERENCES "knowledge_document_versions"("account_id", "id", "scope_key") ON DELETE RESTRICT,
|
|
947
|
+
CONSTRAINT "knowledge_claim_evidence_scope_chk" CHECK (
|
|
948
|
+
opengeni_private.scoped_knowledge_scope_valid(
|
|
949
|
+
"scope_kind", "scope_workspace_id", "scope_subject_id"
|
|
950
|
+
)
|
|
951
|
+
AND "scope_key" = opengeni_private.scoped_knowledge_scope_key(
|
|
952
|
+
"scope_kind", "scope_workspace_id", "scope_subject_id"
|
|
953
|
+
)
|
|
954
|
+
),
|
|
955
|
+
CONSTRAINT "knowledge_claim_evidence_location_chk" CHECK (
|
|
956
|
+
"polarity" IN ('supports', 'contradicts')
|
|
957
|
+
AND ("chunk_index" IS NULL OR "chunk_index" >= 0)
|
|
958
|
+
AND ("locator" IS NULL OR length("locator") BETWEEN 1 AND 2048)
|
|
959
|
+
AND ("quote_hash" IS NULL OR "quote_hash" ~ '^[0-9a-f]{64}$')
|
|
960
|
+
AND "content_hash" ~ '^[0-9a-f]{64}$'
|
|
961
|
+
),
|
|
962
|
+
CONSTRAINT "knowledge_claim_evidence_audit_chk" CHECK (
|
|
963
|
+
length(btrim("operation_id")) BETWEEN 1 AND 256
|
|
964
|
+
AND "input_hash" ~ '^[0-9a-f]{64}$'
|
|
965
|
+
AND opengeni_private.scoped_knowledge_actor_valid(
|
|
966
|
+
"actor_kind", "actor_subject_id", "initiating_human_subject_id"
|
|
967
|
+
)
|
|
968
|
+
),
|
|
969
|
+
CONSTRAINT "knowledge_claim_evidence_operation_uq" UNIQUE ("account_id", "operation_id"),
|
|
970
|
+
CONSTRAINT "knowledge_claim_evidence_scope_identity_uq"
|
|
971
|
+
UNIQUE ("account_id", "id", "scope_key"),
|
|
972
|
+
CONSTRAINT "knowledge_claim_evidence_claim_identity_uq"
|
|
973
|
+
UNIQUE ("account_id", "id", "claim_id", "scope_key")
|
|
974
|
+
);
|
|
975
|
+
|
|
976
|
+
CREATE UNIQUE INDEX "knowledge_claim_evidence_natural_identity_uq"
|
|
977
|
+
ON "knowledge_claim_evidence" (
|
|
978
|
+
"claim_id", "document_version_id", "polarity",
|
|
979
|
+
coalesce("document_chunk_id"::text, ''), coalesce("locator", '')
|
|
980
|
+
);
|
|
981
|
+
CREATE INDEX "knowledge_claim_evidence_claim_polarity_idx"
|
|
982
|
+
ON "knowledge_claim_evidence" ("claim_id", "polarity", "created_at");
|
|
983
|
+
|
|
984
|
+
CREATE TABLE "knowledge_claim_reviews" (
|
|
985
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
986
|
+
"account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE RESTRICT,
|
|
987
|
+
"scope_kind" text NOT NULL,
|
|
988
|
+
"scope_workspace_id" uuid REFERENCES "workspaces"("id") ON DELETE RESTRICT,
|
|
989
|
+
"scope_subject_id" text,
|
|
990
|
+
"scope_key" text NOT NULL,
|
|
991
|
+
"claim_id" uuid NOT NULL,
|
|
992
|
+
"review_revision" bigint NOT NULL DEFAULT nextval('knowledge_claim_review_revision_seq'),
|
|
993
|
+
"state" text NOT NULL,
|
|
994
|
+
"reason" text NOT NULL,
|
|
995
|
+
"operation_id" text NOT NULL,
|
|
996
|
+
"input_hash" text NOT NULL,
|
|
997
|
+
"actor_kind" text NOT NULL,
|
|
998
|
+
"actor_subject_id" text NOT NULL,
|
|
999
|
+
"initiating_human_subject_id" text,
|
|
1000
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
1001
|
+
CONSTRAINT "knowledge_claim_reviews_claim_fk"
|
|
1002
|
+
FOREIGN KEY ("account_id", "claim_id", "scope_key")
|
|
1003
|
+
REFERENCES "knowledge_claims"("account_id", "id", "scope_key") ON DELETE RESTRICT,
|
|
1004
|
+
CONSTRAINT "knowledge_claim_reviews_scope_chk" CHECK (
|
|
1005
|
+
opengeni_private.scoped_knowledge_scope_valid(
|
|
1006
|
+
"scope_kind", "scope_workspace_id", "scope_subject_id"
|
|
1007
|
+
)
|
|
1008
|
+
AND "scope_key" = opengeni_private.scoped_knowledge_scope_key(
|
|
1009
|
+
"scope_kind", "scope_workspace_id", "scope_subject_id"
|
|
1010
|
+
)
|
|
1011
|
+
),
|
|
1012
|
+
CONSTRAINT "knowledge_claim_reviews_review_chk" CHECK (
|
|
1013
|
+
"state" IN ('proposed', 'approved', 'rejected', 'revoked')
|
|
1014
|
+
AND "review_revision" > 0
|
|
1015
|
+
AND length(btrim("reason")) BETWEEN 1 AND 4096
|
|
1016
|
+
AND (
|
|
1017
|
+
"state" = 'proposed'
|
|
1018
|
+
OR ("actor_kind" = 'human' AND "actor_subject_id" = "initiating_human_subject_id")
|
|
1019
|
+
)
|
|
1020
|
+
),
|
|
1021
|
+
CONSTRAINT "knowledge_claim_reviews_audit_chk" CHECK (
|
|
1022
|
+
length(btrim("operation_id")) BETWEEN 1 AND 256
|
|
1023
|
+
AND "input_hash" ~ '^[0-9a-f]{64}$'
|
|
1024
|
+
AND opengeni_private.scoped_knowledge_actor_valid(
|
|
1025
|
+
"actor_kind", "actor_subject_id", "initiating_human_subject_id"
|
|
1026
|
+
)
|
|
1027
|
+
),
|
|
1028
|
+
CONSTRAINT "knowledge_claim_reviews_claim_revision_uq"
|
|
1029
|
+
UNIQUE ("claim_id", "review_revision"),
|
|
1030
|
+
CONSTRAINT "knowledge_claim_reviews_operation_uq" UNIQUE ("account_id", "operation_id")
|
|
1031
|
+
);
|
|
1032
|
+
|
|
1033
|
+
CREATE INDEX "knowledge_claim_reviews_claim_timeline_idx"
|
|
1034
|
+
ON "knowledge_claim_reviews" ("claim_id", "review_revision" DESC);
|
|
1035
|
+
|
|
1036
|
+
CREATE TABLE "knowledge_change_proposals" (
|
|
1037
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
1038
|
+
"account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE RESTRICT,
|
|
1039
|
+
"scope_kind" text NOT NULL,
|
|
1040
|
+
"scope_workspace_id" uuid REFERENCES "workspaces"("id") ON DELETE RESTRICT,
|
|
1041
|
+
"scope_subject_id" text,
|
|
1042
|
+
"scope_key" text NOT NULL,
|
|
1043
|
+
"target_kind" text NOT NULL,
|
|
1044
|
+
"target_scope" text NOT NULL,
|
|
1045
|
+
"target_key" text,
|
|
1046
|
+
"content" text NOT NULL,
|
|
1047
|
+
"content_hash" text NOT NULL,
|
|
1048
|
+
"claim_id" uuid NOT NULL,
|
|
1049
|
+
"evidence_id" uuid NOT NULL,
|
|
1050
|
+
"status" text NOT NULL DEFAULT 'proposed',
|
|
1051
|
+
"operation_id" text NOT NULL,
|
|
1052
|
+
"input_hash" text NOT NULL,
|
|
1053
|
+
"actor_kind" text NOT NULL,
|
|
1054
|
+
"actor_subject_id" text NOT NULL,
|
|
1055
|
+
"initiating_human_subject_id" text,
|
|
1056
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
1057
|
+
CONSTRAINT "knowledge_change_proposals_claim_fk"
|
|
1058
|
+
FOREIGN KEY ("account_id", "claim_id", "scope_key")
|
|
1059
|
+
REFERENCES "knowledge_claims"("account_id", "id", "scope_key") ON DELETE RESTRICT,
|
|
1060
|
+
CONSTRAINT "knowledge_change_proposals_evidence_fk"
|
|
1061
|
+
FOREIGN KEY ("account_id", "evidence_id", "claim_id", "scope_key")
|
|
1062
|
+
REFERENCES "knowledge_claim_evidence"("account_id", "id", "claim_id", "scope_key")
|
|
1063
|
+
ON DELETE RESTRICT,
|
|
1064
|
+
CONSTRAINT "knowledge_change_proposals_scope_chk" CHECK (
|
|
1065
|
+
opengeni_private.scoped_knowledge_scope_valid(
|
|
1066
|
+
"scope_kind", "scope_workspace_id", "scope_subject_id"
|
|
1067
|
+
)
|
|
1068
|
+
AND "scope_key" = opengeni_private.scoped_knowledge_scope_key(
|
|
1069
|
+
"scope_kind", "scope_workspace_id", "scope_subject_id"
|
|
1070
|
+
)
|
|
1071
|
+
),
|
|
1072
|
+
CONSTRAINT "knowledge_change_proposals_target_chk" CHECK (
|
|
1073
|
+
(
|
|
1074
|
+
"target_kind" = 'instruction_policy'
|
|
1075
|
+
AND (
|
|
1076
|
+
("target_scope" = 'global' AND "target_key" IS NULL)
|
|
1077
|
+
OR ("target_scope" = 'role' AND length(btrim("target_key")) BETWEEN 1 AND 96)
|
|
1078
|
+
)
|
|
1079
|
+
) OR (
|
|
1080
|
+
"target_kind" = 'preference'
|
|
1081
|
+
AND "target_scope" IN ('organization', 'workspace', 'personal')
|
|
1082
|
+
AND "target_key" = lower(btrim("target_key"))
|
|
1083
|
+
AND length("target_key") BETWEEN 1 AND 96
|
|
1084
|
+
AND "target_key" ~ '^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$'
|
|
1085
|
+
)
|
|
1086
|
+
),
|
|
1087
|
+
CONSTRAINT "knowledge_change_proposals_content_chk" CHECK (
|
|
1088
|
+
"status" = 'proposed'
|
|
1089
|
+
AND length(btrim("content")) > 0
|
|
1090
|
+
AND octet_length("content") <= 1048576
|
|
1091
|
+
AND "content_hash" ~ '^[0-9a-f]{64}$'
|
|
1092
|
+
AND "content_hash" = encode(sha256(convert_to("content", 'UTF8')), 'hex')
|
|
1093
|
+
),
|
|
1094
|
+
CONSTRAINT "knowledge_change_proposals_audit_chk" CHECK (
|
|
1095
|
+
length(btrim("operation_id")) BETWEEN 1 AND 256
|
|
1096
|
+
AND "input_hash" ~ '^[0-9a-f]{64}$'
|
|
1097
|
+
AND opengeni_private.scoped_knowledge_actor_valid(
|
|
1098
|
+
"actor_kind", "actor_subject_id", "initiating_human_subject_id"
|
|
1099
|
+
)
|
|
1100
|
+
),
|
|
1101
|
+
CONSTRAINT "knowledge_change_proposals_operation_uq" UNIQUE ("account_id", "operation_id")
|
|
1102
|
+
);
|
|
1103
|
+
|
|
1104
|
+
CREATE INDEX "knowledge_change_proposals_claim_timeline_idx"
|
|
1105
|
+
ON "knowledge_change_proposals" ("claim_id", "created_at" DESC);
|
|
1106
|
+
|
|
1107
|
+
DO $scope_workspace_fks$
|
|
1108
|
+
DECLARE table_name text;
|
|
1109
|
+
BEGIN
|
|
1110
|
+
FOREACH table_name IN ARRAY ARRAY[
|
|
1111
|
+
'knowledge_operation_receipts',
|
|
1112
|
+
'knowledge_providers',
|
|
1113
|
+
'knowledge_sources',
|
|
1114
|
+
'knowledge_source_acl_versions',
|
|
1115
|
+
'knowledge_sync_runs',
|
|
1116
|
+
'knowledge_source_objects',
|
|
1117
|
+
'knowledge_document_versions',
|
|
1118
|
+
'knowledge_lifecycle_events',
|
|
1119
|
+
'knowledge_entities',
|
|
1120
|
+
'knowledge_entity_aliases',
|
|
1121
|
+
'knowledge_facts',
|
|
1122
|
+
'knowledge_claims',
|
|
1123
|
+
'knowledge_claim_relations',
|
|
1124
|
+
'knowledge_claim_evidence',
|
|
1125
|
+
'knowledge_claim_reviews',
|
|
1126
|
+
'knowledge_change_proposals'
|
|
1127
|
+
] LOOP
|
|
1128
|
+
EXECUTE format(
|
|
1129
|
+
'ALTER TABLE %I ADD CONSTRAINT %I '
|
|
1130
|
+
|| 'FOREIGN KEY (scope_workspace_id, account_id) '
|
|
1131
|
+
|| 'REFERENCES workspaces(id, account_id) ON DELETE RESTRICT',
|
|
1132
|
+
table_name,
|
|
1133
|
+
table_name || '_scope_workspace_account_fk'
|
|
1134
|
+
);
|
|
1135
|
+
END LOOP;
|
|
1136
|
+
END
|
|
1137
|
+
$scope_workspace_fks$;
|
|
1138
|
+
|
|
1139
|
+
CREATE OR REPLACE FUNCTION scoped_knowledge_guard_head_insert()
|
|
1140
|
+
RETURNS trigger LANGUAGE plpgsql AS $$
|
|
1141
|
+
DECLARE row_value jsonb := to_jsonb(NEW);
|
|
1142
|
+
BEGIN
|
|
1143
|
+
IF TG_TABLE_NAME = 'knowledge_providers' THEN
|
|
1144
|
+
IF row_value->>'lifecycle_state' <> 'active'
|
|
1145
|
+
OR (row_value->>'lifecycle_generation')::bigint <> 1
|
|
1146
|
+
THEN
|
|
1147
|
+
RAISE EXCEPTION 'knowledge provider must start active at generation one'
|
|
1148
|
+
USING ERRCODE = '23514';
|
|
1149
|
+
END IF;
|
|
1150
|
+
ELSIF TG_TABLE_NAME = 'knowledge_sources' THEN
|
|
1151
|
+
IF row_value->>'lifecycle_state' <> 'active'
|
|
1152
|
+
OR (row_value->>'lifecycle_generation')::bigint <> 1
|
|
1153
|
+
OR (row_value->>'sync_generation')::bigint <> 0
|
|
1154
|
+
OR row_value->'current_acl_generation' <> 'null'::jsonb
|
|
1155
|
+
OR row_value->'sync_cursor' <> 'null'::jsonb
|
|
1156
|
+
THEN
|
|
1157
|
+
RAISE EXCEPTION 'knowledge source initial lifecycle, ACL, and sync state is invalid'
|
|
1158
|
+
USING ERRCODE = '23514';
|
|
1159
|
+
END IF;
|
|
1160
|
+
ELSIF TG_TABLE_NAME = 'knowledge_source_objects' THEN
|
|
1161
|
+
IF row_value->>'lifecycle_state' <> 'active'
|
|
1162
|
+
OR (row_value->>'lifecycle_generation')::bigint <> 1
|
|
1163
|
+
OR (row_value->>'version_generation')::bigint <> 0
|
|
1164
|
+
OR row_value->'current_version_id' <> 'null'::jsonb
|
|
1165
|
+
THEN
|
|
1166
|
+
RAISE EXCEPTION 'knowledge source object must start active without a current version'
|
|
1167
|
+
USING ERRCODE = '23514';
|
|
1168
|
+
END IF;
|
|
1169
|
+
END IF;
|
|
1170
|
+
RETURN NEW;
|
|
1171
|
+
END;
|
|
1172
|
+
$$;
|
|
1173
|
+
|
|
1174
|
+
DO $insert_guard_functions$
|
|
1175
|
+
DECLARE data_schema text := current_schema();
|
|
1176
|
+
BEGIN
|
|
1177
|
+
EXECUTE format($ddl$
|
|
1178
|
+
CREATE OR REPLACE FUNCTION %1$I.scoped_knowledge_guard_acl_insert()
|
|
1179
|
+
RETURNS trigger
|
|
1180
|
+
LANGUAGE plpgsql SECURITY DEFINER SET search_path = %1$I, pg_catalog
|
|
1181
|
+
AS $body$
|
|
1182
|
+
DECLARE source_row knowledge_sources%%ROWTYPE;
|
|
1183
|
+
BEGIN
|
|
1184
|
+
IF NEW.account_id IS DISTINCT FROM opengeni_private.current_account_id()
|
|
1185
|
+
OR NOT opengeni_private.scoped_knowledge_scope_visible(
|
|
1186
|
+
NEW.account_id, NEW.scope_kind, NEW.scope_workspace_id, NEW.scope_subject_id
|
|
1187
|
+
)
|
|
1188
|
+
OR NOT opengeni_private.scoped_knowledge_actor_authorized(
|
|
1189
|
+
NEW.actor_kind, NEW.actor_subject_id, NEW.initiating_human_subject_id
|
|
1190
|
+
)
|
|
1191
|
+
THEN
|
|
1192
|
+
RAISE EXCEPTION 'knowledge ACL insert authority is invalid'
|
|
1193
|
+
USING ERRCODE = '42501';
|
|
1194
|
+
END IF;
|
|
1195
|
+
SELECT * INTO source_row
|
|
1196
|
+
FROM knowledge_sources source
|
|
1197
|
+
WHERE source.id = NEW.source_id
|
|
1198
|
+
AND source.account_id = NEW.account_id
|
|
1199
|
+
AND source.scope_key = NEW.source_scope_key
|
|
1200
|
+
AND opengeni_private.scoped_knowledge_scope_visible(
|
|
1201
|
+
source.account_id, source.scope_kind,
|
|
1202
|
+
source.scope_workspace_id, source.scope_subject_id
|
|
1203
|
+
)
|
|
1204
|
+
FOR UPDATE;
|
|
1205
|
+
IF NOT FOUND
|
|
1206
|
+
OR NEW.generation > coalesce(source_row.current_acl_generation, 0) + 1
|
|
1207
|
+
OR (
|
|
1208
|
+
NEW.generation = coalesce(source_row.current_acl_generation, 0) + 1
|
|
1209
|
+
AND source_row.lifecycle_state <> 'active'
|
|
1210
|
+
)
|
|
1211
|
+
THEN
|
|
1212
|
+
RAISE EXCEPTION 'knowledge ACL insert is outside the current source generation fence'
|
|
1213
|
+
USING ERRCODE = '40001';
|
|
1214
|
+
END IF;
|
|
1215
|
+
RETURN NEW;
|
|
1216
|
+
END;
|
|
1217
|
+
$body$;
|
|
1218
|
+
|
|
1219
|
+
CREATE OR REPLACE FUNCTION %1$I.scoped_knowledge_guard_sync_insert()
|
|
1220
|
+
RETURNS trigger
|
|
1221
|
+
LANGUAGE plpgsql SECURITY DEFINER SET search_path = %1$I, pg_catalog
|
|
1222
|
+
AS $body$
|
|
1223
|
+
DECLARE source_row knowledge_sources%%ROWTYPE;
|
|
1224
|
+
BEGIN
|
|
1225
|
+
IF NEW.account_id IS DISTINCT FROM opengeni_private.current_account_id()
|
|
1226
|
+
OR NOT opengeni_private.scoped_knowledge_scope_visible(
|
|
1227
|
+
NEW.account_id, NEW.scope_kind, NEW.scope_workspace_id, NEW.scope_subject_id
|
|
1228
|
+
)
|
|
1229
|
+
OR NOT opengeni_private.scoped_knowledge_actor_authorized(
|
|
1230
|
+
NEW.actor_kind, NEW.actor_subject_id, NEW.initiating_human_subject_id
|
|
1231
|
+
)
|
|
1232
|
+
THEN
|
|
1233
|
+
RAISE EXCEPTION 'knowledge sync insert authority is invalid'
|
|
1234
|
+
USING ERRCODE = '42501';
|
|
1235
|
+
END IF;
|
|
1236
|
+
SELECT * INTO source_row
|
|
1237
|
+
FROM knowledge_sources source
|
|
1238
|
+
WHERE source.id = NEW.source_id
|
|
1239
|
+
AND source.account_id = NEW.account_id
|
|
1240
|
+
AND source.scope_key = NEW.scope_key
|
|
1241
|
+
AND source.scope_kind = NEW.scope_kind
|
|
1242
|
+
AND source.scope_workspace_id IS NOT DISTINCT FROM NEW.scope_workspace_id
|
|
1243
|
+
AND source.scope_subject_id IS NOT DISTINCT FROM NEW.scope_subject_id
|
|
1244
|
+
AND opengeni_private.scoped_knowledge_scope_visible(
|
|
1245
|
+
source.account_id, source.scope_kind,
|
|
1246
|
+
source.scope_workspace_id, source.scope_subject_id
|
|
1247
|
+
)
|
|
1248
|
+
FOR UPDATE;
|
|
1249
|
+
IF NOT FOUND
|
|
1250
|
+
OR source_row.lifecycle_state <> 'active'
|
|
1251
|
+
OR NEW.state <> 'started'
|
|
1252
|
+
OR NEW.input_lifecycle_generation <> source_row.lifecycle_generation
|
|
1253
|
+
OR NEW.input_sync_generation <> source_row.sync_generation
|
|
1254
|
+
OR NEW.input_cursor IS DISTINCT FROM source_row.sync_cursor
|
|
1255
|
+
THEN
|
|
1256
|
+
RAISE EXCEPTION 'knowledge sync insert is outside the current source generation fence'
|
|
1257
|
+
USING ERRCODE = '40001';
|
|
1258
|
+
END IF;
|
|
1259
|
+
RETURN NEW;
|
|
1260
|
+
END;
|
|
1261
|
+
$body$;
|
|
1262
|
+
|
|
1263
|
+
CREATE OR REPLACE FUNCTION %1$I.scoped_knowledge_guard_version_insert()
|
|
1264
|
+
RETURNS trigger
|
|
1265
|
+
LANGUAGE plpgsql SECURITY DEFINER SET search_path = %1$I, pg_catalog
|
|
1266
|
+
AS $body$
|
|
1267
|
+
DECLARE object_row knowledge_source_objects%%ROWTYPE;
|
|
1268
|
+
DECLARE source_row knowledge_sources%%ROWTYPE;
|
|
1269
|
+
BEGIN
|
|
1270
|
+
IF NEW.account_id IS DISTINCT FROM opengeni_private.current_account_id()
|
|
1271
|
+
OR NOT opengeni_private.scoped_knowledge_scope_visible(
|
|
1272
|
+
NEW.account_id, NEW.scope_kind, NEW.scope_workspace_id, NEW.scope_subject_id
|
|
1273
|
+
)
|
|
1274
|
+
OR NOT opengeni_private.scoped_knowledge_actor_authorized(
|
|
1275
|
+
NEW.actor_kind, NEW.actor_subject_id, NEW.initiating_human_subject_id
|
|
1276
|
+
)
|
|
1277
|
+
THEN
|
|
1278
|
+
RAISE EXCEPTION 'knowledge document version insert authority is invalid'
|
|
1279
|
+
USING ERRCODE = '42501';
|
|
1280
|
+
END IF;
|
|
1281
|
+
SELECT * INTO object_row
|
|
1282
|
+
FROM knowledge_source_objects object
|
|
1283
|
+
WHERE object.id = NEW.object_id
|
|
1284
|
+
AND object.account_id = NEW.account_id
|
|
1285
|
+
AND object.source_id = NEW.source_id
|
|
1286
|
+
AND object.scope_key = NEW.scope_key
|
|
1287
|
+
AND object.scope_kind = NEW.scope_kind
|
|
1288
|
+
AND object.scope_workspace_id IS NOT DISTINCT FROM NEW.scope_workspace_id
|
|
1289
|
+
AND object.scope_subject_id IS NOT DISTINCT FROM NEW.scope_subject_id
|
|
1290
|
+
AND opengeni_private.scoped_knowledge_scope_visible(
|
|
1291
|
+
object.account_id, object.scope_kind,
|
|
1292
|
+
object.scope_workspace_id, object.scope_subject_id
|
|
1293
|
+
)
|
|
1294
|
+
FOR UPDATE;
|
|
1295
|
+
IF NOT FOUND THEN
|
|
1296
|
+
RAISE EXCEPTION 'knowledge document version object is outside the exact scope'
|
|
1297
|
+
USING ERRCODE = '23514';
|
|
1298
|
+
END IF;
|
|
1299
|
+
SELECT * INTO source_row
|
|
1300
|
+
FROM knowledge_sources source
|
|
1301
|
+
WHERE source.id = NEW.source_id
|
|
1302
|
+
AND source.account_id = NEW.account_id
|
|
1303
|
+
AND source.scope_key = NEW.scope_key
|
|
1304
|
+
AND source.scope_kind = NEW.scope_kind
|
|
1305
|
+
AND source.scope_workspace_id IS NOT DISTINCT FROM NEW.scope_workspace_id
|
|
1306
|
+
AND source.scope_subject_id IS NOT DISTINCT FROM NEW.scope_subject_id
|
|
1307
|
+
AND opengeni_private.scoped_knowledge_scope_visible(
|
|
1308
|
+
source.account_id, source.scope_kind,
|
|
1309
|
+
source.scope_workspace_id, source.scope_subject_id
|
|
1310
|
+
)
|
|
1311
|
+
FOR UPDATE;
|
|
1312
|
+
IF NOT FOUND
|
|
1313
|
+
OR NEW.version_generation > object_row.version_generation + 1
|
|
1314
|
+
OR (
|
|
1315
|
+
NEW.version_generation = object_row.version_generation + 1
|
|
1316
|
+
AND (
|
|
1317
|
+
object_row.lifecycle_state <> 'active'
|
|
1318
|
+
OR source_row.lifecycle_state <> 'active'
|
|
1319
|
+
OR NEW.acl_generation IS DISTINCT FROM source_row.current_acl_generation
|
|
1320
|
+
)
|
|
1321
|
+
)
|
|
1322
|
+
THEN
|
|
1323
|
+
RAISE EXCEPTION 'knowledge document version is outside the current object/source fence'
|
|
1324
|
+
USING ERRCODE = '40001';
|
|
1325
|
+
END IF;
|
|
1326
|
+
RETURN NEW;
|
|
1327
|
+
END;
|
|
1328
|
+
$body$;
|
|
1329
|
+
$ddl$, data_schema);
|
|
1330
|
+
|
|
1331
|
+
EXECUTE format(
|
|
1332
|
+
'REVOKE ALL ON FUNCTION %I.scoped_knowledge_guard_acl_insert() FROM PUBLIC',
|
|
1333
|
+
data_schema
|
|
1334
|
+
);
|
|
1335
|
+
EXECUTE format(
|
|
1336
|
+
'REVOKE ALL ON FUNCTION %I.scoped_knowledge_guard_sync_insert() FROM PUBLIC',
|
|
1337
|
+
data_schema
|
|
1338
|
+
);
|
|
1339
|
+
EXECUTE format(
|
|
1340
|
+
'REVOKE ALL ON FUNCTION %I.scoped_knowledge_guard_version_insert() FROM PUBLIC',
|
|
1341
|
+
data_schema
|
|
1342
|
+
);
|
|
1343
|
+
END
|
|
1344
|
+
$insert_guard_functions$;
|
|
1345
|
+
|
|
1346
|
+
CREATE TRIGGER knowledge_providers_10_initial_guard
|
|
1347
|
+
BEFORE INSERT ON "knowledge_providers"
|
|
1348
|
+
FOR EACH ROW EXECUTE FUNCTION scoped_knowledge_guard_head_insert();
|
|
1349
|
+
CREATE TRIGGER knowledge_sources_10_initial_guard
|
|
1350
|
+
BEFORE INSERT ON "knowledge_sources"
|
|
1351
|
+
FOR EACH ROW EXECUTE FUNCTION scoped_knowledge_guard_head_insert();
|
|
1352
|
+
CREATE TRIGGER knowledge_source_objects_10_initial_guard
|
|
1353
|
+
BEFORE INSERT ON "knowledge_source_objects"
|
|
1354
|
+
FOR EACH ROW EXECUTE FUNCTION scoped_knowledge_guard_head_insert();
|
|
1355
|
+
CREATE TRIGGER knowledge_source_acl_versions_10_generation_guard
|
|
1356
|
+
BEFORE INSERT ON "knowledge_source_acl_versions"
|
|
1357
|
+
FOR EACH ROW EXECUTE FUNCTION scoped_knowledge_guard_acl_insert();
|
|
1358
|
+
CREATE TRIGGER knowledge_sync_runs_10_generation_guard
|
|
1359
|
+
BEFORE INSERT ON "knowledge_sync_runs"
|
|
1360
|
+
FOR EACH ROW EXECUTE FUNCTION scoped_knowledge_guard_sync_insert();
|
|
1361
|
+
CREATE TRIGGER knowledge_document_versions_10_generation_guard
|
|
1362
|
+
BEFORE INSERT ON "knowledge_document_versions"
|
|
1363
|
+
FOR EACH ROW EXECUTE FUNCTION scoped_knowledge_guard_version_insert();
|
|
1364
|
+
|
|
1365
|
+
CREATE OR REPLACE FUNCTION scoped_knowledge_validate_bridge()
|
|
1366
|
+
RETURNS trigger LANGUAGE plpgsql AS $$
|
|
1367
|
+
DECLARE version_document_id uuid;
|
|
1368
|
+
BEGIN
|
|
1369
|
+
IF TG_TABLE_NAME = 'knowledge_source_objects' THEN
|
|
1370
|
+
IF NEW.document_id IS NOT NULL THEN
|
|
1371
|
+
IF NEW.scope_workspace_id IS NULL OR NOT EXISTS (
|
|
1372
|
+
SELECT 1 FROM documents document
|
|
1373
|
+
WHERE document.id = NEW.document_id
|
|
1374
|
+
AND document.account_id = NEW.account_id
|
|
1375
|
+
AND document.workspace_id = NEW.scope_workspace_id
|
|
1376
|
+
) THEN
|
|
1377
|
+
RAISE EXCEPTION 'knowledge source object document bridge is outside its exact account/workspace'
|
|
1378
|
+
USING ERRCODE = '23514';
|
|
1379
|
+
END IF;
|
|
1380
|
+
END IF;
|
|
1381
|
+
ELSIF TG_TABLE_NAME = 'knowledge_document_versions' THEN
|
|
1382
|
+
IF NOT EXISTS (
|
|
1383
|
+
SELECT 1 FROM knowledge_source_acl_versions acl
|
|
1384
|
+
WHERE acl.id = NEW.acl_version_id
|
|
1385
|
+
AND acl.account_id = NEW.account_id
|
|
1386
|
+
AND acl.source_id = NEW.source_id
|
|
1387
|
+
AND acl.generation = NEW.acl_generation
|
|
1388
|
+
) THEN
|
|
1389
|
+
RAISE EXCEPTION 'knowledge document version ACL reference is not exact'
|
|
1390
|
+
USING ERRCODE = '23514';
|
|
1391
|
+
END IF;
|
|
1392
|
+
IF NEW.document_id IS NULL AND NEW.file_id IS NOT NULL THEN
|
|
1393
|
+
RAISE EXCEPTION 'knowledge document version file bridge requires a document bridge'
|
|
1394
|
+
USING ERRCODE = '23514';
|
|
1395
|
+
END IF;
|
|
1396
|
+
IF NEW.document_id IS NOT NULL AND (
|
|
1397
|
+
NEW.scope_workspace_id IS NULL OR NOT EXISTS (
|
|
1398
|
+
SELECT 1 FROM documents document
|
|
1399
|
+
WHERE document.id = NEW.document_id
|
|
1400
|
+
AND document.account_id = NEW.account_id
|
|
1401
|
+
AND document.workspace_id = NEW.scope_workspace_id
|
|
1402
|
+
AND (NEW.file_id IS NULL OR document.file_id = NEW.file_id)
|
|
1403
|
+
)
|
|
1404
|
+
) THEN
|
|
1405
|
+
RAISE EXCEPTION 'knowledge document version bridge is outside its exact account/workspace/file'
|
|
1406
|
+
USING ERRCODE = '23514';
|
|
1407
|
+
END IF;
|
|
1408
|
+
ELSIF TG_TABLE_NAME = 'knowledge_claim_evidence' THEN
|
|
1409
|
+
IF NEW.document_chunk_id IS NOT NULL THEN
|
|
1410
|
+
SELECT document_id INTO version_document_id
|
|
1411
|
+
FROM knowledge_document_versions
|
|
1412
|
+
WHERE id = NEW.document_version_id;
|
|
1413
|
+
IF version_document_id IS NULL OR NEW.scope_workspace_id IS NULL OR NOT EXISTS (
|
|
1414
|
+
SELECT 1 FROM document_chunks chunk
|
|
1415
|
+
WHERE chunk.id = NEW.document_chunk_id
|
|
1416
|
+
AND chunk.account_id = NEW.account_id
|
|
1417
|
+
AND chunk.workspace_id = NEW.scope_workspace_id
|
|
1418
|
+
AND chunk.document_id = version_document_id
|
|
1419
|
+
AND (NEW.chunk_index IS NULL OR chunk.chunk_index = NEW.chunk_index)
|
|
1420
|
+
) THEN
|
|
1421
|
+
RAISE EXCEPTION 'knowledge claim evidence chunk bridge is not exact'
|
|
1422
|
+
USING ERRCODE = '23514';
|
|
1423
|
+
END IF;
|
|
1424
|
+
END IF;
|
|
1425
|
+
END IF;
|
|
1426
|
+
RETURN NEW;
|
|
1427
|
+
END;
|
|
1428
|
+
$$;
|
|
1429
|
+
|
|
1430
|
+
CREATE TRIGGER knowledge_source_objects_20_validate_bridge
|
|
1431
|
+
BEFORE INSERT ON "knowledge_source_objects"
|
|
1432
|
+
FOR EACH ROW EXECUTE FUNCTION scoped_knowledge_validate_bridge();
|
|
1433
|
+
CREATE TRIGGER knowledge_document_versions_20_validate_bridge
|
|
1434
|
+
BEFORE INSERT ON "knowledge_document_versions"
|
|
1435
|
+
FOR EACH ROW EXECUTE FUNCTION scoped_knowledge_validate_bridge();
|
|
1436
|
+
CREATE TRIGGER knowledge_claim_evidence_20_validate_bridge
|
|
1437
|
+
BEFORE INSERT ON "knowledge_claim_evidence"
|
|
1438
|
+
FOR EACH ROW EXECUTE FUNCTION scoped_knowledge_validate_bridge();
|
|
1439
|
+
|
|
1440
|
+
CREATE OR REPLACE FUNCTION scoped_knowledge_validate_claim_relation()
|
|
1441
|
+
RETURNS trigger LANGUAGE plpgsql AS $$
|
|
1442
|
+
BEGIN
|
|
1443
|
+
PERFORM pg_catalog.pg_advisory_xact_lock(
|
|
1444
|
+
pg_catalog.hashtextextended(NEW.account_id::text || ':' || NEW.scope_key, 0)
|
|
1445
|
+
);
|
|
1446
|
+
IF NEW.relation_type = 'supersedes' AND EXISTS (
|
|
1447
|
+
WITH RECURSIVE path(claim_id) AS (
|
|
1448
|
+
SELECT relation.to_claim_id
|
|
1449
|
+
FROM knowledge_claim_relations relation
|
|
1450
|
+
WHERE relation.account_id = NEW.account_id
|
|
1451
|
+
AND relation.scope_key = NEW.scope_key
|
|
1452
|
+
AND relation.relation_type = 'supersedes'
|
|
1453
|
+
AND relation.from_claim_id = NEW.to_claim_id
|
|
1454
|
+
UNION
|
|
1455
|
+
SELECT relation.to_claim_id
|
|
1456
|
+
FROM knowledge_claim_relations relation
|
|
1457
|
+
JOIN path ON relation.from_claim_id = path.claim_id
|
|
1458
|
+
WHERE relation.account_id = NEW.account_id
|
|
1459
|
+
AND relation.scope_key = NEW.scope_key
|
|
1460
|
+
AND relation.relation_type = 'supersedes'
|
|
1461
|
+
)
|
|
1462
|
+
SELECT 1 FROM path WHERE claim_id = NEW.from_claim_id
|
|
1463
|
+
) THEN
|
|
1464
|
+
RAISE EXCEPTION 'knowledge claim supersession cannot create a cycle'
|
|
1465
|
+
USING ERRCODE = '23514';
|
|
1466
|
+
END IF;
|
|
1467
|
+
RETURN NEW;
|
|
1468
|
+
END;
|
|
1469
|
+
$$;
|
|
1470
|
+
|
|
1471
|
+
CREATE TRIGGER knowledge_claim_relations_validate
|
|
1472
|
+
BEFORE INSERT ON "knowledge_claim_relations"
|
|
1473
|
+
FOR EACH ROW EXECUTE FUNCTION scoped_knowledge_validate_claim_relation();
|
|
1474
|
+
|
|
1475
|
+
CREATE OR REPLACE FUNCTION scoped_knowledge_reject_immutable_mutation()
|
|
1476
|
+
RETURNS trigger LANGUAGE plpgsql AS $$
|
|
1477
|
+
BEGIN
|
|
1478
|
+
RAISE EXCEPTION 'scoped knowledge provenance is immutable' USING ERRCODE = '55000';
|
|
1479
|
+
END;
|
|
1480
|
+
$$;
|
|
1481
|
+
|
|
1482
|
+
DO $immutable_triggers$
|
|
1483
|
+
DECLARE table_name text;
|
|
1484
|
+
BEGIN
|
|
1485
|
+
FOREACH table_name IN ARRAY ARRAY[
|
|
1486
|
+
'knowledge_operation_receipts',
|
|
1487
|
+
'knowledge_source_acl_versions',
|
|
1488
|
+
'knowledge_document_versions',
|
|
1489
|
+
'knowledge_lifecycle_events',
|
|
1490
|
+
'knowledge_entities',
|
|
1491
|
+
'knowledge_entity_aliases',
|
|
1492
|
+
'knowledge_facts',
|
|
1493
|
+
'knowledge_claims',
|
|
1494
|
+
'knowledge_claim_relations',
|
|
1495
|
+
'knowledge_claim_evidence',
|
|
1496
|
+
'knowledge_claim_reviews',
|
|
1497
|
+
'knowledge_change_proposals'
|
|
1498
|
+
] LOOP
|
|
1499
|
+
EXECUTE format(
|
|
1500
|
+
'CREATE TRIGGER %I BEFORE UPDATE OR DELETE ON %I '
|
|
1501
|
+
|| 'FOR EACH ROW EXECUTE FUNCTION scoped_knowledge_reject_immutable_mutation()',
|
|
1502
|
+
table_name || '_immutable', table_name
|
|
1503
|
+
);
|
|
1504
|
+
END LOOP;
|
|
1505
|
+
END
|
|
1506
|
+
$immutable_triggers$;
|
|
1507
|
+
|
|
1508
|
+
CREATE OR REPLACE FUNCTION scoped_knowledge_guard_provider_mutation()
|
|
1509
|
+
RETURNS trigger LANGUAGE plpgsql AS $$
|
|
1510
|
+
DECLARE mutation_writer boolean;
|
|
1511
|
+
BEGIN
|
|
1512
|
+
IF TG_OP = 'DELETE' THEN
|
|
1513
|
+
RAISE EXCEPTION 'knowledge provider tombstones are not deleted' USING ERRCODE = '55000';
|
|
1514
|
+
END IF;
|
|
1515
|
+
SELECT current_user = pg_get_userbyid(procedure_row.proowner)
|
|
1516
|
+
INTO mutation_writer
|
|
1517
|
+
FROM pg_proc procedure_row
|
|
1518
|
+
WHERE procedure_row.oid = to_regprocedure(format(
|
|
1519
|
+
'%I.scoped_knowledge_apply_lifecycle(uuid,text,uuid,text,bigint,text,text,text,text,text,text)',
|
|
1520
|
+
TG_TABLE_SCHEMA
|
|
1521
|
+
));
|
|
1522
|
+
IF NOT coalesce(mutation_writer, false)
|
|
1523
|
+
OR current_setting('opengeni.knowledge_mutation_kind', true) <> 'lifecycle'
|
|
1524
|
+
OR current_setting('opengeni.knowledge_mutation_target', true) <> OLD.id::text
|
|
1525
|
+
OR (to_jsonb(NEW) - ARRAY['lifecycle_state', 'lifecycle_generation', 'updated_at'])
|
|
1526
|
+
IS DISTINCT FROM
|
|
1527
|
+
(to_jsonb(OLD) - ARRAY['lifecycle_state', 'lifecycle_generation', 'updated_at'])
|
|
1528
|
+
OR NEW.lifecycle_generation <> OLD.lifecycle_generation + 1
|
|
1529
|
+
OR NOT (
|
|
1530
|
+
(OLD.lifecycle_state = 'active' AND NEW.lifecycle_state IN ('deleted', 'revoked'))
|
|
1531
|
+
OR (OLD.lifecycle_state IN ('deleted', 'revoked') AND NEW.lifecycle_state = 'active')
|
|
1532
|
+
)
|
|
1533
|
+
THEN
|
|
1534
|
+
RAISE EXCEPTION 'invalid knowledge provider lifecycle mutation' USING ERRCODE = '23514';
|
|
1535
|
+
END IF;
|
|
1536
|
+
RETURN NEW;
|
|
1537
|
+
END;
|
|
1538
|
+
$$;
|
|
1539
|
+
|
|
1540
|
+
CREATE OR REPLACE FUNCTION scoped_knowledge_guard_source_mutation()
|
|
1541
|
+
RETURNS trigger LANGUAGE plpgsql AS $$
|
|
1542
|
+
DECLARE mutation_kind text := current_setting('opengeni.knowledge_mutation_kind', true);
|
|
1543
|
+
DECLARE mutation_writer boolean;
|
|
1544
|
+
BEGIN
|
|
1545
|
+
IF TG_OP = 'DELETE' THEN
|
|
1546
|
+
RAISE EXCEPTION 'knowledge source tombstones are not deleted' USING ERRCODE = '55000';
|
|
1547
|
+
END IF;
|
|
1548
|
+
SELECT current_user = pg_get_userbyid(procedure_row.proowner)
|
|
1549
|
+
INTO mutation_writer
|
|
1550
|
+
FROM pg_proc procedure_row
|
|
1551
|
+
WHERE procedure_row.oid = CASE mutation_kind
|
|
1552
|
+
WHEN 'lifecycle' THEN to_regprocedure(format(
|
|
1553
|
+
'%I.scoped_knowledge_apply_lifecycle(uuid,text,uuid,text,bigint,text,text,text,text,text,text)',
|
|
1554
|
+
TG_TABLE_SCHEMA
|
|
1555
|
+
))
|
|
1556
|
+
WHEN 'acl' THEN to_regprocedure(format(
|
|
1557
|
+
'%I.scoped_knowledge_advance_source_acl(uuid,uuid,bigint,bigint,uuid,text,text,text,text,text,text)',
|
|
1558
|
+
TG_TABLE_SCHEMA
|
|
1559
|
+
))
|
|
1560
|
+
WHEN 'sync' THEN to_regprocedure(format(
|
|
1561
|
+
'%I.scoped_knowledge_complete_sync(uuid,uuid,text,text,timestamptz,jsonb,text,text,text)',
|
|
1562
|
+
TG_TABLE_SCHEMA
|
|
1563
|
+
))
|
|
1564
|
+
ELSE NULL
|
|
1565
|
+
END;
|
|
1566
|
+
IF NOT coalesce(mutation_writer, false)
|
|
1567
|
+
OR current_setting('opengeni.knowledge_mutation_target', true) <> OLD.id::text
|
|
1568
|
+
THEN
|
|
1569
|
+
RAISE EXCEPTION 'knowledge source mutation target is not fenced' USING ERRCODE = '55000';
|
|
1570
|
+
END IF;
|
|
1571
|
+
IF mutation_kind = 'lifecycle' THEN
|
|
1572
|
+
IF (to_jsonb(NEW) - ARRAY['lifecycle_state', 'lifecycle_generation', 'updated_at'])
|
|
1573
|
+
IS DISTINCT FROM
|
|
1574
|
+
(to_jsonb(OLD) - ARRAY['lifecycle_state', 'lifecycle_generation', 'updated_at'])
|
|
1575
|
+
OR NEW.lifecycle_generation <> OLD.lifecycle_generation + 1
|
|
1576
|
+
OR NOT (
|
|
1577
|
+
(OLD.lifecycle_state = 'active' AND NEW.lifecycle_state IN ('deleted', 'revoked'))
|
|
1578
|
+
OR (OLD.lifecycle_state IN ('deleted', 'revoked') AND NEW.lifecycle_state = 'active')
|
|
1579
|
+
)
|
|
1580
|
+
THEN
|
|
1581
|
+
RAISE EXCEPTION 'invalid knowledge source lifecycle mutation' USING ERRCODE = '23514';
|
|
1582
|
+
END IF;
|
|
1583
|
+
ELSIF mutation_kind = 'acl' THEN
|
|
1584
|
+
IF (to_jsonb(NEW) - ARRAY['current_acl_generation', 'updated_at'])
|
|
1585
|
+
IS DISTINCT FROM (to_jsonb(OLD) - ARRAY['current_acl_generation', 'updated_at'])
|
|
1586
|
+
OR NEW.lifecycle_state <> 'active'
|
|
1587
|
+
OR NEW.current_acl_generation IS NULL
|
|
1588
|
+
OR NEW.current_acl_generation <> coalesce(OLD.current_acl_generation, 0) + 1
|
|
1589
|
+
THEN
|
|
1590
|
+
RAISE EXCEPTION 'invalid knowledge source ACL mutation' USING ERRCODE = '23514';
|
|
1591
|
+
END IF;
|
|
1592
|
+
ELSIF mutation_kind = 'sync' THEN
|
|
1593
|
+
IF (to_jsonb(NEW) - ARRAY['sync_generation', 'sync_cursor', 'updated_at'])
|
|
1594
|
+
IS DISTINCT FROM (to_jsonb(OLD) - ARRAY['sync_generation', 'sync_cursor', 'updated_at'])
|
|
1595
|
+
OR NEW.lifecycle_state <> 'active'
|
|
1596
|
+
OR NEW.sync_generation <> OLD.sync_generation + 1
|
|
1597
|
+
THEN
|
|
1598
|
+
RAISE EXCEPTION 'invalid knowledge source sync mutation' USING ERRCODE = '23514';
|
|
1599
|
+
END IF;
|
|
1600
|
+
ELSE
|
|
1601
|
+
RAISE EXCEPTION 'knowledge source mutation kind is invalid' USING ERRCODE = '55000';
|
|
1602
|
+
END IF;
|
|
1603
|
+
RETURN NEW;
|
|
1604
|
+
END;
|
|
1605
|
+
$$;
|
|
1606
|
+
|
|
1607
|
+
CREATE OR REPLACE FUNCTION scoped_knowledge_guard_object_mutation()
|
|
1608
|
+
RETURNS trigger LANGUAGE plpgsql AS $$
|
|
1609
|
+
DECLARE mutation_kind text := current_setting('opengeni.knowledge_mutation_kind', true);
|
|
1610
|
+
DECLARE mutation_writer boolean;
|
|
1611
|
+
BEGIN
|
|
1612
|
+
IF TG_OP = 'DELETE' THEN
|
|
1613
|
+
RAISE EXCEPTION 'knowledge source object tombstones are not deleted' USING ERRCODE = '55000';
|
|
1614
|
+
END IF;
|
|
1615
|
+
SELECT current_user = pg_get_userbyid(procedure_row.proowner)
|
|
1616
|
+
INTO mutation_writer
|
|
1617
|
+
FROM pg_proc procedure_row
|
|
1618
|
+
WHERE procedure_row.oid = CASE mutation_kind
|
|
1619
|
+
WHEN 'lifecycle' THEN to_regprocedure(format(
|
|
1620
|
+
'%I.scoped_knowledge_apply_lifecycle(uuid,text,uuid,text,bigint,text,text,text,text,text,text)',
|
|
1621
|
+
TG_TABLE_SCHEMA
|
|
1622
|
+
))
|
|
1623
|
+
WHEN 'version' THEN to_regprocedure(format(
|
|
1624
|
+
'%I.scoped_knowledge_advance_object_version(uuid,uuid,bigint,bigint,uuid,text,text,text,text,text,text)',
|
|
1625
|
+
TG_TABLE_SCHEMA
|
|
1626
|
+
))
|
|
1627
|
+
ELSE NULL
|
|
1628
|
+
END;
|
|
1629
|
+
IF NOT coalesce(mutation_writer, false)
|
|
1630
|
+
OR current_setting('opengeni.knowledge_mutation_target', true) <> OLD.id::text
|
|
1631
|
+
THEN
|
|
1632
|
+
RAISE EXCEPTION 'knowledge source object mutation target is not fenced' USING ERRCODE = '55000';
|
|
1633
|
+
END IF;
|
|
1634
|
+
IF mutation_kind = 'lifecycle' THEN
|
|
1635
|
+
IF (to_jsonb(NEW) - ARRAY['lifecycle_state', 'lifecycle_generation', 'updated_at'])
|
|
1636
|
+
IS DISTINCT FROM
|
|
1637
|
+
(to_jsonb(OLD) - ARRAY['lifecycle_state', 'lifecycle_generation', 'updated_at'])
|
|
1638
|
+
OR NEW.lifecycle_generation <> OLD.lifecycle_generation + 1
|
|
1639
|
+
OR NOT (
|
|
1640
|
+
(OLD.lifecycle_state = 'active' AND NEW.lifecycle_state IN ('deleted', 'revoked'))
|
|
1641
|
+
OR (OLD.lifecycle_state IN ('deleted', 'revoked') AND NEW.lifecycle_state = 'active')
|
|
1642
|
+
)
|
|
1643
|
+
THEN
|
|
1644
|
+
RAISE EXCEPTION 'invalid knowledge source object lifecycle mutation'
|
|
1645
|
+
USING ERRCODE = '23514';
|
|
1646
|
+
END IF;
|
|
1647
|
+
ELSIF mutation_kind = 'version' THEN
|
|
1648
|
+
IF (to_jsonb(NEW) - ARRAY['version_generation', 'current_version_id', 'updated_at'])
|
|
1649
|
+
IS DISTINCT FROM (to_jsonb(OLD) - ARRAY['version_generation', 'current_version_id', 'updated_at'])
|
|
1650
|
+
OR NEW.lifecycle_state <> 'active'
|
|
1651
|
+
OR NEW.current_version_id IS NULL
|
|
1652
|
+
OR NEW.version_generation <> OLD.version_generation + 1
|
|
1653
|
+
THEN
|
|
1654
|
+
RAISE EXCEPTION 'invalid knowledge source object version mutation'
|
|
1655
|
+
USING ERRCODE = '23514';
|
|
1656
|
+
END IF;
|
|
1657
|
+
ELSE
|
|
1658
|
+
RAISE EXCEPTION 'knowledge source object mutation kind is invalid' USING ERRCODE = '55000';
|
|
1659
|
+
END IF;
|
|
1660
|
+
RETURN NEW;
|
|
1661
|
+
END;
|
|
1662
|
+
$$;
|
|
1663
|
+
|
|
1664
|
+
CREATE OR REPLACE FUNCTION scoped_knowledge_guard_sync_mutation()
|
|
1665
|
+
RETURNS trigger LANGUAGE plpgsql AS $$
|
|
1666
|
+
DECLARE mutation_writer boolean;
|
|
1667
|
+
BEGIN
|
|
1668
|
+
IF TG_OP = 'DELETE' THEN
|
|
1669
|
+
RAISE EXCEPTION 'knowledge sync runs are not deleted' USING ERRCODE = '55000';
|
|
1670
|
+
END IF;
|
|
1671
|
+
SELECT current_user = pg_get_userbyid(procedure_row.proowner)
|
|
1672
|
+
INTO mutation_writer
|
|
1673
|
+
FROM pg_proc procedure_row
|
|
1674
|
+
WHERE procedure_row.oid = to_regprocedure(format(
|
|
1675
|
+
'%I.scoped_knowledge_complete_sync(uuid,uuid,text,text,timestamptz,jsonb,text,text,text)',
|
|
1676
|
+
TG_TABLE_SCHEMA
|
|
1677
|
+
));
|
|
1678
|
+
IF NOT coalesce(mutation_writer, false)
|
|
1679
|
+
OR current_setting('opengeni.knowledge_mutation_kind', true) <> 'sync_complete'
|
|
1680
|
+
OR current_setting('opengeni.knowledge_mutation_target', true) <> OLD.id::text
|
|
1681
|
+
OR (to_jsonb(NEW) - ARRAY[
|
|
1682
|
+
'state', 'output_cursor', 'watermark', 'metadata', 'error_code',
|
|
1683
|
+
'completion_hash', 'completed_at'
|
|
1684
|
+
]) IS DISTINCT FROM (to_jsonb(OLD) - ARRAY[
|
|
1685
|
+
'state', 'output_cursor', 'watermark', 'metadata', 'error_code',
|
|
1686
|
+
'completion_hash', 'completed_at'
|
|
1687
|
+
])
|
|
1688
|
+
OR OLD.state <> 'started'
|
|
1689
|
+
OR NEW.state NOT IN ('succeeded', 'failed')
|
|
1690
|
+
THEN
|
|
1691
|
+
RAISE EXCEPTION 'invalid knowledge sync completion mutation' USING ERRCODE = '23514';
|
|
1692
|
+
END IF;
|
|
1693
|
+
RETURN NEW;
|
|
1694
|
+
END;
|
|
1695
|
+
$$;
|
|
1696
|
+
|
|
1697
|
+
CREATE TRIGGER knowledge_providers_lifecycle_only
|
|
1698
|
+
BEFORE UPDATE OR DELETE ON "knowledge_providers"
|
|
1699
|
+
FOR EACH ROW EXECUTE FUNCTION scoped_knowledge_guard_provider_mutation();
|
|
1700
|
+
CREATE TRIGGER knowledge_sources_mutation_guard
|
|
1701
|
+
BEFORE UPDATE OR DELETE ON "knowledge_sources"
|
|
1702
|
+
FOR EACH ROW EXECUTE FUNCTION scoped_knowledge_guard_source_mutation();
|
|
1703
|
+
CREATE TRIGGER knowledge_source_objects_mutation_guard
|
|
1704
|
+
BEFORE UPDATE OR DELETE ON "knowledge_source_objects"
|
|
1705
|
+
FOR EACH ROW EXECUTE FUNCTION scoped_knowledge_guard_object_mutation();
|
|
1706
|
+
CREATE TRIGGER knowledge_sync_runs_completion_only
|
|
1707
|
+
BEFORE UPDATE OR DELETE ON "knowledge_sync_runs"
|
|
1708
|
+
FOR EACH ROW EXECUTE FUNCTION scoped_knowledge_guard_sync_mutation();
|
|
1709
|
+
|
|
1710
|
+
DO $lifecycle_function$
|
|
1711
|
+
DECLARE data_schema text := current_schema();
|
|
1712
|
+
BEGIN
|
|
1713
|
+
EXECUTE format($ddl$
|
|
1714
|
+
CREATE OR REPLACE FUNCTION %1$I.scoped_knowledge_apply_lifecycle(
|
|
1715
|
+
p_account_id uuid,
|
|
1716
|
+
p_target_kind text,
|
|
1717
|
+
p_target_id uuid,
|
|
1718
|
+
p_event_type text,
|
|
1719
|
+
p_expected_generation bigint,
|
|
1720
|
+
p_operation_id text,
|
|
1721
|
+
p_input_hash text,
|
|
1722
|
+
p_reason_code text,
|
|
1723
|
+
p_actor_kind text,
|
|
1724
|
+
p_actor_subject_id text,
|
|
1725
|
+
p_initiating_human_subject_id text
|
|
1726
|
+
) RETURNS TABLE (
|
|
1727
|
+
target_id uuid,
|
|
1728
|
+
lifecycle_state text,
|
|
1729
|
+
lifecycle_generation bigint,
|
|
1730
|
+
replayed boolean
|
|
1731
|
+
)
|
|
1732
|
+
LANGUAGE plpgsql SECURITY DEFINER SET search_path = %1$I, pg_catalog
|
|
1733
|
+
AS $body$
|
|
1734
|
+
DECLARE target_row record;
|
|
1735
|
+
DECLARE prior_event knowledge_lifecycle_events%%ROWTYPE;
|
|
1736
|
+
DECLARE next_state text;
|
|
1737
|
+
DECLARE provider_ref uuid;
|
|
1738
|
+
DECLARE source_ref uuid;
|
|
1739
|
+
DECLARE object_ref uuid;
|
|
1740
|
+
BEGIN
|
|
1741
|
+
IF p_account_id IS DISTINCT FROM opengeni_private.current_account_id()
|
|
1742
|
+
OR p_target_kind NOT IN ('provider', 'source', 'object')
|
|
1743
|
+
OR p_event_type NOT IN ('deleted', 'revoked', 'restored')
|
|
1744
|
+
OR NOT opengeni_private.scoped_knowledge_actor_authorized(
|
|
1745
|
+
p_actor_kind, p_actor_subject_id, p_initiating_human_subject_id
|
|
1746
|
+
)
|
|
1747
|
+
THEN
|
|
1748
|
+
RAISE EXCEPTION 'scoped knowledge lifecycle authority is invalid'
|
|
1749
|
+
USING ERRCODE = '42501';
|
|
1750
|
+
END IF;
|
|
1751
|
+
|
|
1752
|
+
SELECT * INTO prior_event
|
|
1753
|
+
FROM knowledge_lifecycle_events event
|
|
1754
|
+
WHERE event.account_id = p_account_id
|
|
1755
|
+
AND event.target_kind = p_target_kind
|
|
1756
|
+
AND opengeni_private.scoped_knowledge_scope_visible(
|
|
1757
|
+
event.account_id, event.scope_kind, event.scope_workspace_id, event.scope_subject_id
|
|
1758
|
+
)
|
|
1759
|
+
AND event.provider_id IS NOT DISTINCT FROM
|
|
1760
|
+
CASE WHEN p_target_kind = 'provider' THEN p_target_id ELSE NULL END
|
|
1761
|
+
AND event.source_id IS NOT DISTINCT FROM
|
|
1762
|
+
CASE WHEN p_target_kind = 'source' THEN p_target_id ELSE NULL END
|
|
1763
|
+
AND event.object_id IS NOT DISTINCT FROM
|
|
1764
|
+
CASE WHEN p_target_kind = 'object' THEN p_target_id ELSE NULL END
|
|
1765
|
+
AND event.operation_id = p_operation_id;
|
|
1766
|
+
IF FOUND THEN
|
|
1767
|
+
IF prior_event.input_hash <> p_input_hash OR prior_event.event_type <> p_event_type THEN
|
|
1768
|
+
RAISE EXCEPTION 'knowledge lifecycle operation id was replayed with different input'
|
|
1769
|
+
USING ERRCODE = '23505';
|
|
1770
|
+
END IF;
|
|
1771
|
+
RETURN QUERY SELECT p_target_id, prior_event.new_state,
|
|
1772
|
+
prior_event.new_generation, true;
|
|
1773
|
+
RETURN;
|
|
1774
|
+
END IF;
|
|
1775
|
+
|
|
1776
|
+
IF p_target_kind = 'provider' THEN
|
|
1777
|
+
SELECT * INTO target_row FROM knowledge_providers row
|
|
1778
|
+
WHERE row.id = p_target_id AND row.account_id = p_account_id
|
|
1779
|
+
AND opengeni_private.scoped_knowledge_scope_visible(
|
|
1780
|
+
row.account_id, row.scope_kind, row.scope_workspace_id, row.scope_subject_id
|
|
1781
|
+
)
|
|
1782
|
+
FOR UPDATE;
|
|
1783
|
+
provider_ref := p_target_id;
|
|
1784
|
+
ELSIF p_target_kind = 'source' THEN
|
|
1785
|
+
SELECT * INTO target_row FROM knowledge_sources row
|
|
1786
|
+
WHERE row.id = p_target_id AND row.account_id = p_account_id
|
|
1787
|
+
AND opengeni_private.scoped_knowledge_scope_visible(
|
|
1788
|
+
row.account_id, row.scope_kind, row.scope_workspace_id, row.scope_subject_id
|
|
1789
|
+
)
|
|
1790
|
+
FOR UPDATE;
|
|
1791
|
+
source_ref := p_target_id;
|
|
1792
|
+
ELSE
|
|
1793
|
+
SELECT * INTO target_row FROM knowledge_source_objects row
|
|
1794
|
+
WHERE row.id = p_target_id AND row.account_id = p_account_id
|
|
1795
|
+
AND opengeni_private.scoped_knowledge_scope_visible(
|
|
1796
|
+
row.account_id, row.scope_kind, row.scope_workspace_id, row.scope_subject_id
|
|
1797
|
+
)
|
|
1798
|
+
FOR UPDATE;
|
|
1799
|
+
object_ref := p_target_id;
|
|
1800
|
+
END IF;
|
|
1801
|
+
IF NOT FOUND THEN
|
|
1802
|
+
RAISE EXCEPTION 'knowledge lifecycle target was not found' USING ERRCODE = 'P0002';
|
|
1803
|
+
END IF;
|
|
1804
|
+
IF target_row.lifecycle_generation <> p_expected_generation THEN
|
|
1805
|
+
RAISE EXCEPTION 'knowledge lifecycle generation changed' USING ERRCODE = '40001';
|
|
1806
|
+
END IF;
|
|
1807
|
+
IF p_event_type = 'restored' THEN
|
|
1808
|
+
IF target_row.lifecycle_state NOT IN ('deleted', 'revoked') THEN
|
|
1809
|
+
RAISE EXCEPTION 'only a tombstoned knowledge target can be restored'
|
|
1810
|
+
USING ERRCODE = '23514';
|
|
1811
|
+
END IF;
|
|
1812
|
+
next_state := 'active';
|
|
1813
|
+
ELSE
|
|
1814
|
+
IF target_row.lifecycle_state <> 'active' THEN
|
|
1815
|
+
RAISE EXCEPTION 'ordinary lifecycle mutation cannot resurrect a knowledge tombstone'
|
|
1816
|
+
USING ERRCODE = '23514';
|
|
1817
|
+
END IF;
|
|
1818
|
+
next_state := CASE p_event_type WHEN 'deleted' THEN 'deleted' ELSE 'revoked' END;
|
|
1819
|
+
END IF;
|
|
1820
|
+
|
|
1821
|
+
PERFORM set_config('opengeni.knowledge_mutation_kind', 'lifecycle', true);
|
|
1822
|
+
PERFORM set_config('opengeni.knowledge_mutation_target', p_target_id::text, true);
|
|
1823
|
+
IF p_target_kind = 'provider' THEN
|
|
1824
|
+
UPDATE knowledge_providers target_provider SET lifecycle_state = next_state,
|
|
1825
|
+
lifecycle_generation = target_provider.lifecycle_generation + 1,
|
|
1826
|
+
updated_at = clock_timestamp()
|
|
1827
|
+
WHERE target_provider.id = p_target_id;
|
|
1828
|
+
ELSIF p_target_kind = 'source' THEN
|
|
1829
|
+
UPDATE knowledge_sources target_source SET lifecycle_state = next_state,
|
|
1830
|
+
lifecycle_generation = target_source.lifecycle_generation + 1,
|
|
1831
|
+
updated_at = clock_timestamp()
|
|
1832
|
+
WHERE target_source.id = p_target_id;
|
|
1833
|
+
ELSE
|
|
1834
|
+
UPDATE knowledge_source_objects target_object SET lifecycle_state = next_state,
|
|
1835
|
+
lifecycle_generation = target_object.lifecycle_generation + 1,
|
|
1836
|
+
updated_at = clock_timestamp()
|
|
1837
|
+
WHERE target_object.id = p_target_id;
|
|
1838
|
+
END IF;
|
|
1839
|
+
PERFORM set_config('opengeni.knowledge_mutation_kind', '', true);
|
|
1840
|
+
PERFORM set_config('opengeni.knowledge_mutation_target', '', true);
|
|
1841
|
+
|
|
1842
|
+
INSERT INTO knowledge_lifecycle_events (
|
|
1843
|
+
account_id, scope_kind, scope_workspace_id, scope_subject_id, scope_key,
|
|
1844
|
+
target_kind, provider_id, source_id, object_id, event_type,
|
|
1845
|
+
old_state, new_state, old_generation, new_generation,
|
|
1846
|
+
operation_id, input_hash, reason_code, actor_kind, actor_subject_id,
|
|
1847
|
+
initiating_human_subject_id
|
|
1848
|
+
) VALUES (
|
|
1849
|
+
p_account_id, target_row.scope_kind, target_row.scope_workspace_id,
|
|
1850
|
+
target_row.scope_subject_id, target_row.scope_key,
|
|
1851
|
+
p_target_kind, provider_ref, source_ref, object_ref, p_event_type,
|
|
1852
|
+
target_row.lifecycle_state, next_state, target_row.lifecycle_generation,
|
|
1853
|
+
target_row.lifecycle_generation + 1, p_operation_id, p_input_hash,
|
|
1854
|
+
p_reason_code, p_actor_kind, p_actor_subject_id, p_initiating_human_subject_id
|
|
1855
|
+
);
|
|
1856
|
+
RETURN QUERY SELECT p_target_id, next_state,
|
|
1857
|
+
target_row.lifecycle_generation + 1, false;
|
|
1858
|
+
END;
|
|
1859
|
+
$body$;
|
|
1860
|
+
$ddl$, data_schema);
|
|
1861
|
+
END
|
|
1862
|
+
$lifecycle_function$;
|
|
1863
|
+
|
|
1864
|
+
DO $source_acl_function$
|
|
1865
|
+
DECLARE data_schema text := current_schema();
|
|
1866
|
+
BEGIN
|
|
1867
|
+
EXECUTE format($ddl$
|
|
1868
|
+
CREATE OR REPLACE FUNCTION %1$I.scoped_knowledge_advance_source_acl(
|
|
1869
|
+
p_account_id uuid,
|
|
1870
|
+
p_source_id uuid,
|
|
1871
|
+
p_expected_lifecycle_generation bigint,
|
|
1872
|
+
p_expected_acl_generation bigint,
|
|
1873
|
+
p_acl_version_id uuid,
|
|
1874
|
+
p_operation_id text,
|
|
1875
|
+
p_input_hash text,
|
|
1876
|
+
p_reason_code text,
|
|
1877
|
+
p_actor_kind text,
|
|
1878
|
+
p_actor_subject_id text,
|
|
1879
|
+
p_initiating_human_subject_id text
|
|
1880
|
+
) RETURNS bigint
|
|
1881
|
+
LANGUAGE plpgsql SECURITY DEFINER SET search_path = %1$I, pg_catalog
|
|
1882
|
+
AS $body$
|
|
1883
|
+
DECLARE source_row knowledge_sources%%ROWTYPE;
|
|
1884
|
+
DECLARE acl_row knowledge_source_acl_versions%%ROWTYPE;
|
|
1885
|
+
BEGIN
|
|
1886
|
+
IF p_account_id IS DISTINCT FROM opengeni_private.current_account_id()
|
|
1887
|
+
OR NOT opengeni_private.scoped_knowledge_actor_authorized(
|
|
1888
|
+
p_actor_kind, p_actor_subject_id, p_initiating_human_subject_id
|
|
1889
|
+
)
|
|
1890
|
+
THEN
|
|
1891
|
+
RAISE EXCEPTION 'knowledge ACL account authority is invalid' USING ERRCODE = '42501';
|
|
1892
|
+
END IF;
|
|
1893
|
+
SELECT * INTO source_row FROM knowledge_sources row
|
|
1894
|
+
WHERE row.id = p_source_id AND row.account_id = p_account_id
|
|
1895
|
+
AND opengeni_private.scoped_knowledge_scope_visible(
|
|
1896
|
+
row.account_id, row.scope_kind, row.scope_workspace_id, row.scope_subject_id
|
|
1897
|
+
)
|
|
1898
|
+
FOR UPDATE;
|
|
1899
|
+
IF NOT FOUND THEN RAISE EXCEPTION 'knowledge source was not found' USING ERRCODE = 'P0002'; END IF;
|
|
1900
|
+
SELECT * INTO acl_row FROM knowledge_source_acl_versions row
|
|
1901
|
+
WHERE row.id = p_acl_version_id AND row.account_id = p_account_id
|
|
1902
|
+
AND row.source_id = p_source_id AND row.operation_id = p_operation_id
|
|
1903
|
+
AND opengeni_private.scoped_knowledge_scope_visible(
|
|
1904
|
+
row.account_id, row.scope_kind, row.scope_workspace_id, row.scope_subject_id
|
|
1905
|
+
);
|
|
1906
|
+
IF NOT FOUND THEN RAISE EXCEPTION 'knowledge ACL version was not found' USING ERRCODE = 'P0002'; END IF;
|
|
1907
|
+
IF source_row.current_acl_generation = acl_row.generation THEN
|
|
1908
|
+
IF acl_row.input_hash <> p_input_hash THEN
|
|
1909
|
+
RAISE EXCEPTION 'knowledge ACL operation id was replayed with different input'
|
|
1910
|
+
USING ERRCODE = '23505';
|
|
1911
|
+
END IF;
|
|
1912
|
+
RETURN acl_row.generation;
|
|
1913
|
+
END IF;
|
|
1914
|
+
IF source_row.lifecycle_state <> 'active'
|
|
1915
|
+
OR source_row.lifecycle_generation <> p_expected_lifecycle_generation
|
|
1916
|
+
OR coalesce(source_row.current_acl_generation, 0) <> p_expected_acl_generation
|
|
1917
|
+
OR acl_row.generation <> p_expected_acl_generation + 1
|
|
1918
|
+
OR acl_row.input_hash <> p_input_hash
|
|
1919
|
+
OR acl_row.actor_kind IS DISTINCT FROM p_actor_kind
|
|
1920
|
+
OR acl_row.actor_subject_id IS DISTINCT FROM p_actor_subject_id
|
|
1921
|
+
OR acl_row.initiating_human_subject_id IS DISTINCT FROM p_initiating_human_subject_id
|
|
1922
|
+
THEN
|
|
1923
|
+
RAISE EXCEPTION 'knowledge ACL generation or lifecycle fence changed'
|
|
1924
|
+
USING ERRCODE = '40001';
|
|
1925
|
+
END IF;
|
|
1926
|
+
PERFORM set_config('opengeni.knowledge_mutation_kind', 'acl', true);
|
|
1927
|
+
PERFORM set_config('opengeni.knowledge_mutation_target', p_source_id::text, true);
|
|
1928
|
+
UPDATE knowledge_sources SET current_acl_generation = acl_row.generation,
|
|
1929
|
+
updated_at = clock_timestamp() WHERE id = p_source_id;
|
|
1930
|
+
PERFORM set_config('opengeni.knowledge_mutation_kind', '', true);
|
|
1931
|
+
PERFORM set_config('opengeni.knowledge_mutation_target', '', true);
|
|
1932
|
+
INSERT INTO knowledge_lifecycle_events (
|
|
1933
|
+
account_id, scope_kind, scope_workspace_id, scope_subject_id, scope_key,
|
|
1934
|
+
target_kind, source_id, event_type, old_state, new_state,
|
|
1935
|
+
old_generation, new_generation, operation_id, input_hash, reason_code,
|
|
1936
|
+
actor_kind, actor_subject_id, initiating_human_subject_id
|
|
1937
|
+
) VALUES (
|
|
1938
|
+
p_account_id, source_row.scope_kind, source_row.scope_workspace_id,
|
|
1939
|
+
source_row.scope_subject_id, source_row.scope_key, 'source', p_source_id,
|
|
1940
|
+
'acl_changed', source_row.lifecycle_state, source_row.lifecycle_state,
|
|
1941
|
+
source_row.lifecycle_generation, source_row.lifecycle_generation,
|
|
1942
|
+
p_operation_id, p_input_hash, p_reason_code, p_actor_kind,
|
|
1943
|
+
p_actor_subject_id, p_initiating_human_subject_id
|
|
1944
|
+
);
|
|
1945
|
+
RETURN acl_row.generation;
|
|
1946
|
+
END;
|
|
1947
|
+
$body$;
|
|
1948
|
+
$ddl$, data_schema);
|
|
1949
|
+
END
|
|
1950
|
+
$source_acl_function$;
|
|
1951
|
+
|
|
1952
|
+
DO $sync_function$
|
|
1953
|
+
DECLARE data_schema text := current_schema();
|
|
1954
|
+
BEGIN
|
|
1955
|
+
EXECUTE format($ddl$
|
|
1956
|
+
CREATE OR REPLACE FUNCTION %1$I.scoped_knowledge_complete_sync(
|
|
1957
|
+
p_account_id uuid,
|
|
1958
|
+
p_run_id uuid,
|
|
1959
|
+
p_state text,
|
|
1960
|
+
p_output_cursor text,
|
|
1961
|
+
p_watermark timestamptz,
|
|
1962
|
+
p_metadata jsonb,
|
|
1963
|
+
p_error_code text,
|
|
1964
|
+
p_completion_hash text,
|
|
1965
|
+
p_reason_code text
|
|
1966
|
+
) RETURNS SETOF %1$I.knowledge_sync_runs
|
|
1967
|
+
LANGUAGE plpgsql SECURITY DEFINER SET search_path = %1$I, pg_catalog
|
|
1968
|
+
AS $body$
|
|
1969
|
+
DECLARE run_row knowledge_sync_runs%%ROWTYPE;
|
|
1970
|
+
DECLARE source_row knowledge_sources%%ROWTYPE;
|
|
1971
|
+
BEGIN
|
|
1972
|
+
IF p_account_id IS DISTINCT FROM opengeni_private.current_account_id()
|
|
1973
|
+
OR p_state NOT IN ('succeeded', 'failed')
|
|
1974
|
+
THEN
|
|
1975
|
+
RAISE EXCEPTION 'knowledge sync completion authority is invalid' USING ERRCODE = '42501';
|
|
1976
|
+
END IF;
|
|
1977
|
+
SELECT * INTO run_row FROM knowledge_sync_runs row
|
|
1978
|
+
WHERE row.id = p_run_id AND row.account_id = p_account_id
|
|
1979
|
+
AND opengeni_private.scoped_knowledge_scope_visible(
|
|
1980
|
+
row.account_id, row.scope_kind, row.scope_workspace_id, row.scope_subject_id
|
|
1981
|
+
)
|
|
1982
|
+
FOR UPDATE;
|
|
1983
|
+
IF NOT FOUND THEN RAISE EXCEPTION 'knowledge sync run was not found' USING ERRCODE = 'P0002'; END IF;
|
|
1984
|
+
IF NOT opengeni_private.scoped_knowledge_actor_authorized(
|
|
1985
|
+
run_row.actor_kind, run_row.actor_subject_id, run_row.initiating_human_subject_id
|
|
1986
|
+
) THEN
|
|
1987
|
+
RAISE EXCEPTION 'knowledge sync completion actor authority changed'
|
|
1988
|
+
USING ERRCODE = '42501';
|
|
1989
|
+
END IF;
|
|
1990
|
+
IF run_row.state <> 'started' THEN
|
|
1991
|
+
IF run_row.state <> p_state OR run_row.completion_hash <> p_completion_hash THEN
|
|
1992
|
+
RAISE EXCEPTION 'knowledge sync completion was replayed with different input'
|
|
1993
|
+
USING ERRCODE = '23505';
|
|
1994
|
+
END IF;
|
|
1995
|
+
RETURN NEXT run_row;
|
|
1996
|
+
RETURN;
|
|
1997
|
+
END IF;
|
|
1998
|
+
SELECT * INTO source_row FROM knowledge_sources row
|
|
1999
|
+
WHERE row.id = run_row.source_id AND row.account_id = p_account_id
|
|
2000
|
+
AND opengeni_private.scoped_knowledge_scope_visible(
|
|
2001
|
+
row.account_id, row.scope_kind, row.scope_workspace_id, row.scope_subject_id
|
|
2002
|
+
)
|
|
2003
|
+
FOR UPDATE;
|
|
2004
|
+
IF NOT FOUND OR source_row.lifecycle_state <> 'active'
|
|
2005
|
+
OR source_row.lifecycle_generation <> run_row.input_lifecycle_generation
|
|
2006
|
+
OR source_row.sync_generation <> run_row.input_sync_generation
|
|
2007
|
+
THEN
|
|
2008
|
+
RAISE EXCEPTION 'knowledge sync generation or lifecycle fence changed'
|
|
2009
|
+
USING ERRCODE = '40001';
|
|
2010
|
+
END IF;
|
|
2011
|
+
IF p_state = 'succeeded' THEN
|
|
2012
|
+
PERFORM set_config('opengeni.knowledge_mutation_kind', 'sync', true);
|
|
2013
|
+
PERFORM set_config('opengeni.knowledge_mutation_target', source_row.id::text, true);
|
|
2014
|
+
UPDATE knowledge_sources SET sync_generation = sync_generation + 1,
|
|
2015
|
+
sync_cursor = p_output_cursor, updated_at = clock_timestamp()
|
|
2016
|
+
WHERE id = source_row.id;
|
|
2017
|
+
END IF;
|
|
2018
|
+
PERFORM set_config('opengeni.knowledge_mutation_kind', 'sync_complete', true);
|
|
2019
|
+
PERFORM set_config('opengeni.knowledge_mutation_target', run_row.id::text, true);
|
|
2020
|
+
UPDATE knowledge_sync_runs SET state = p_state,
|
|
2021
|
+
output_cursor = CASE WHEN p_state = 'succeeded' THEN p_output_cursor ELSE NULL END,
|
|
2022
|
+
watermark = p_watermark, metadata = p_metadata,
|
|
2023
|
+
error_code = CASE WHEN p_state = 'failed' THEN p_error_code ELSE NULL END,
|
|
2024
|
+
completion_hash = p_completion_hash, completed_at = clock_timestamp()
|
|
2025
|
+
WHERE id = run_row.id RETURNING * INTO run_row;
|
|
2026
|
+
PERFORM set_config('opengeni.knowledge_mutation_kind', '', true);
|
|
2027
|
+
PERFORM set_config('opengeni.knowledge_mutation_target', '', true);
|
|
2028
|
+
INSERT INTO knowledge_lifecycle_events (
|
|
2029
|
+
account_id, scope_kind, scope_workspace_id, scope_subject_id, scope_key,
|
|
2030
|
+
target_kind, source_id, event_type, old_state, new_state,
|
|
2031
|
+
old_generation, new_generation, operation_id, input_hash, reason_code,
|
|
2032
|
+
actor_kind, actor_subject_id, initiating_human_subject_id
|
|
2033
|
+
) VALUES (
|
|
2034
|
+
p_account_id, source_row.scope_kind, source_row.scope_workspace_id,
|
|
2035
|
+
source_row.scope_subject_id, source_row.scope_key, 'source', source_row.id,
|
|
2036
|
+
CASE WHEN p_state = 'succeeded' THEN 'sync_succeeded' ELSE 'sync_failed' END,
|
|
2037
|
+
source_row.lifecycle_state, source_row.lifecycle_state,
|
|
2038
|
+
source_row.lifecycle_generation, source_row.lifecycle_generation,
|
|
2039
|
+
run_row.operation_id, p_completion_hash, p_reason_code,
|
|
2040
|
+
run_row.actor_kind, run_row.actor_subject_id, run_row.initiating_human_subject_id
|
|
2041
|
+
);
|
|
2042
|
+
RETURN NEXT run_row;
|
|
2043
|
+
END;
|
|
2044
|
+
$body$;
|
|
2045
|
+
$ddl$, data_schema);
|
|
2046
|
+
END
|
|
2047
|
+
$sync_function$;
|
|
2048
|
+
|
|
2049
|
+
DO $object_version_function$
|
|
2050
|
+
DECLARE data_schema text := current_schema();
|
|
2051
|
+
BEGIN
|
|
2052
|
+
EXECUTE format($ddl$
|
|
2053
|
+
CREATE OR REPLACE FUNCTION %1$I.scoped_knowledge_advance_object_version(
|
|
2054
|
+
p_account_id uuid,
|
|
2055
|
+
p_object_id uuid,
|
|
2056
|
+
p_expected_lifecycle_generation bigint,
|
|
2057
|
+
p_expected_version_generation bigint,
|
|
2058
|
+
p_version_id uuid,
|
|
2059
|
+
p_operation_id text,
|
|
2060
|
+
p_input_hash text,
|
|
2061
|
+
p_reason_code text,
|
|
2062
|
+
p_actor_kind text,
|
|
2063
|
+
p_actor_subject_id text,
|
|
2064
|
+
p_initiating_human_subject_id text
|
|
2065
|
+
) RETURNS bigint
|
|
2066
|
+
LANGUAGE plpgsql SECURITY DEFINER SET search_path = %1$I, pg_catalog
|
|
2067
|
+
AS $body$
|
|
2068
|
+
DECLARE object_row knowledge_source_objects%%ROWTYPE;
|
|
2069
|
+
DECLARE version_row knowledge_document_versions%%ROWTYPE;
|
|
2070
|
+
DECLARE source_row knowledge_sources%%ROWTYPE;
|
|
2071
|
+
BEGIN
|
|
2072
|
+
IF p_account_id IS DISTINCT FROM opengeni_private.current_account_id()
|
|
2073
|
+
OR NOT opengeni_private.scoped_knowledge_actor_authorized(
|
|
2074
|
+
p_actor_kind, p_actor_subject_id, p_initiating_human_subject_id
|
|
2075
|
+
)
|
|
2076
|
+
THEN
|
|
2077
|
+
RAISE EXCEPTION 'knowledge object version account authority is invalid'
|
|
2078
|
+
USING ERRCODE = '42501';
|
|
2079
|
+
END IF;
|
|
2080
|
+
SELECT * INTO object_row FROM knowledge_source_objects row
|
|
2081
|
+
WHERE row.id = p_object_id AND row.account_id = p_account_id
|
|
2082
|
+
AND opengeni_private.scoped_knowledge_scope_visible(
|
|
2083
|
+
row.account_id, row.scope_kind, row.scope_workspace_id, row.scope_subject_id
|
|
2084
|
+
)
|
|
2085
|
+
FOR UPDATE;
|
|
2086
|
+
IF NOT FOUND THEN RAISE EXCEPTION 'knowledge source object was not found' USING ERRCODE = 'P0002'; END IF;
|
|
2087
|
+
SELECT * INTO version_row FROM knowledge_document_versions row
|
|
2088
|
+
WHERE row.id = p_version_id AND row.account_id = p_account_id
|
|
2089
|
+
AND row.object_id = p_object_id AND row.operation_id = p_operation_id
|
|
2090
|
+
AND opengeni_private.scoped_knowledge_scope_visible(
|
|
2091
|
+
row.account_id, row.scope_kind, row.scope_workspace_id, row.scope_subject_id
|
|
2092
|
+
);
|
|
2093
|
+
IF NOT FOUND THEN RAISE EXCEPTION 'knowledge document version was not found' USING ERRCODE = 'P0002'; END IF;
|
|
2094
|
+
IF object_row.current_version_id = version_row.id THEN
|
|
2095
|
+
IF version_row.input_hash <> p_input_hash THEN
|
|
2096
|
+
RAISE EXCEPTION 'knowledge document version operation was replayed with different input'
|
|
2097
|
+
USING ERRCODE = '23505';
|
|
2098
|
+
END IF;
|
|
2099
|
+
RETURN version_row.version_generation;
|
|
2100
|
+
END IF;
|
|
2101
|
+
SELECT * INTO source_row FROM knowledge_sources row
|
|
2102
|
+
WHERE row.id = version_row.source_id AND row.account_id = p_account_id
|
|
2103
|
+
AND opengeni_private.scoped_knowledge_scope_visible(
|
|
2104
|
+
row.account_id, row.scope_kind, row.scope_workspace_id, row.scope_subject_id
|
|
2105
|
+
)
|
|
2106
|
+
FOR UPDATE;
|
|
2107
|
+
IF NOT FOUND THEN
|
|
2108
|
+
RAISE EXCEPTION 'knowledge document version source was not found'
|
|
2109
|
+
USING ERRCODE = 'P0002';
|
|
2110
|
+
END IF;
|
|
2111
|
+
IF object_row.lifecycle_state <> 'active'
|
|
2112
|
+
OR object_row.lifecycle_generation <> p_expected_lifecycle_generation
|
|
2113
|
+
OR object_row.version_generation <> p_expected_version_generation
|
|
2114
|
+
OR source_row.lifecycle_state <> 'active'
|
|
2115
|
+
OR source_row.current_acl_generation IS DISTINCT FROM version_row.acl_generation
|
|
2116
|
+
OR version_row.version_generation <> p_expected_version_generation + 1
|
|
2117
|
+
OR version_row.input_hash <> p_input_hash
|
|
2118
|
+
OR version_row.actor_kind IS DISTINCT FROM p_actor_kind
|
|
2119
|
+
OR version_row.actor_subject_id IS DISTINCT FROM p_actor_subject_id
|
|
2120
|
+
OR version_row.initiating_human_subject_id IS DISTINCT FROM p_initiating_human_subject_id
|
|
2121
|
+
THEN
|
|
2122
|
+
RAISE EXCEPTION 'knowledge object version or lifecycle fence changed'
|
|
2123
|
+
USING ERRCODE = '40001';
|
|
2124
|
+
END IF;
|
|
2125
|
+
PERFORM set_config('opengeni.knowledge_mutation_kind', 'version', true);
|
|
2126
|
+
PERFORM set_config('opengeni.knowledge_mutation_target', p_object_id::text, true);
|
|
2127
|
+
UPDATE knowledge_source_objects SET current_version_id = version_row.id,
|
|
2128
|
+
version_generation = version_row.version_generation, updated_at = clock_timestamp()
|
|
2129
|
+
WHERE id = p_object_id;
|
|
2130
|
+
PERFORM set_config('opengeni.knowledge_mutation_kind', '', true);
|
|
2131
|
+
PERFORM set_config('opengeni.knowledge_mutation_target', '', true);
|
|
2132
|
+
INSERT INTO knowledge_lifecycle_events (
|
|
2133
|
+
account_id, scope_kind, scope_workspace_id, scope_subject_id, scope_key,
|
|
2134
|
+
target_kind, object_id, event_type, old_state, new_state,
|
|
2135
|
+
old_generation, new_generation, operation_id, input_hash, reason_code,
|
|
2136
|
+
actor_kind, actor_subject_id, initiating_human_subject_id
|
|
2137
|
+
) VALUES (
|
|
2138
|
+
p_account_id, object_row.scope_kind, object_row.scope_workspace_id,
|
|
2139
|
+
object_row.scope_subject_id, object_row.scope_key, 'object', p_object_id,
|
|
2140
|
+
'object_version_added', object_row.lifecycle_state, object_row.lifecycle_state,
|
|
2141
|
+
object_row.lifecycle_generation, object_row.lifecycle_generation,
|
|
2142
|
+
p_operation_id, p_input_hash, p_reason_code, p_actor_kind,
|
|
2143
|
+
p_actor_subject_id, p_initiating_human_subject_id
|
|
2144
|
+
);
|
|
2145
|
+
RETURN version_row.version_generation;
|
|
2146
|
+
END;
|
|
2147
|
+
$body$;
|
|
2148
|
+
$ddl$, data_schema);
|
|
2149
|
+
END
|
|
2150
|
+
$object_version_function$;
|
|
2151
|
+
|
|
2152
|
+
DO $revoke_public_function_access$
|
|
2153
|
+
DECLARE data_schema text := current_schema();
|
|
2154
|
+
BEGIN
|
|
2155
|
+
EXECUTE format(
|
|
2156
|
+
'REVOKE ALL ON FUNCTION %I.scoped_knowledge_apply_lifecycle('
|
|
2157
|
+
|| 'uuid,text,uuid,text,bigint,text,text,text,text,text,text) FROM PUBLIC',
|
|
2158
|
+
data_schema
|
|
2159
|
+
);
|
|
2160
|
+
EXECUTE format(
|
|
2161
|
+
'REVOKE ALL ON FUNCTION %I.scoped_knowledge_advance_source_acl('
|
|
2162
|
+
|| 'uuid,uuid,bigint,bigint,uuid,text,text,text,text,text,text) FROM PUBLIC',
|
|
2163
|
+
data_schema
|
|
2164
|
+
);
|
|
2165
|
+
EXECUTE format(
|
|
2166
|
+
'REVOKE ALL ON FUNCTION %I.scoped_knowledge_complete_sync('
|
|
2167
|
+
|| 'uuid,uuid,text,text,timestamptz,jsonb,text,text,text) FROM PUBLIC',
|
|
2168
|
+
data_schema
|
|
2169
|
+
);
|
|
2170
|
+
EXECUTE format(
|
|
2171
|
+
'REVOKE ALL ON FUNCTION %I.scoped_knowledge_advance_object_version('
|
|
2172
|
+
|| 'uuid,uuid,bigint,bigint,uuid,text,text,text,text,text,text) FROM PUBLIC',
|
|
2173
|
+
data_schema
|
|
2174
|
+
);
|
|
2175
|
+
END
|
|
2176
|
+
$revoke_public_function_access$;
|
|
2177
|
+
|
|
2178
|
+
DO $rls$
|
|
2179
|
+
DECLARE table_name text;
|
|
2180
|
+
BEGIN
|
|
2181
|
+
FOREACH table_name IN ARRAY ARRAY[
|
|
2182
|
+
'knowledge_operation_receipts',
|
|
2183
|
+
'knowledge_providers',
|
|
2184
|
+
'knowledge_sources',
|
|
2185
|
+
'knowledge_source_acl_versions',
|
|
2186
|
+
'knowledge_sync_runs',
|
|
2187
|
+
'knowledge_source_objects',
|
|
2188
|
+
'knowledge_document_versions',
|
|
2189
|
+
'knowledge_lifecycle_events',
|
|
2190
|
+
'knowledge_entities',
|
|
2191
|
+
'knowledge_entity_aliases',
|
|
2192
|
+
'knowledge_facts',
|
|
2193
|
+
'knowledge_claims',
|
|
2194
|
+
'knowledge_claim_relations',
|
|
2195
|
+
'knowledge_claim_evidence',
|
|
2196
|
+
'knowledge_claim_reviews',
|
|
2197
|
+
'knowledge_change_proposals'
|
|
2198
|
+
] LOOP
|
|
2199
|
+
EXECUTE format('ALTER TABLE %I ENABLE ROW LEVEL SECURITY', table_name);
|
|
2200
|
+
EXECUTE format('ALTER TABLE %I FORCE ROW LEVEL SECURITY', table_name);
|
|
2201
|
+
EXECUTE format(
|
|
2202
|
+
'CREATE POLICY scoped_knowledge_select ON %I FOR SELECT '
|
|
2203
|
+
|| 'USING (opengeni_private.scoped_knowledge_scope_visible('
|
|
2204
|
+
|| 'account_id, scope_kind, scope_workspace_id, scope_subject_id))',
|
|
2205
|
+
table_name
|
|
2206
|
+
);
|
|
2207
|
+
EXECUTE format(
|
|
2208
|
+
'CREATE POLICY scoped_knowledge_insert ON %I FOR INSERT '
|
|
2209
|
+
|| 'WITH CHECK ('
|
|
2210
|
+
|| 'opengeni_private.scoped_knowledge_scope_visible('
|
|
2211
|
+
|| 'account_id, scope_kind, scope_workspace_id, scope_subject_id) '
|
|
2212
|
+
|| 'AND opengeni_private.scoped_knowledge_actor_authorized('
|
|
2213
|
+
|| 'actor_kind, actor_subject_id, initiating_human_subject_id))',
|
|
2214
|
+
table_name
|
|
2215
|
+
);
|
|
2216
|
+
END LOOP;
|
|
2217
|
+
|
|
2218
|
+
FOREACH table_name IN ARRAY ARRAY[
|
|
2219
|
+
'knowledge_providers',
|
|
2220
|
+
'knowledge_sources',
|
|
2221
|
+
'knowledge_sync_runs',
|
|
2222
|
+
'knowledge_source_objects'
|
|
2223
|
+
] LOOP
|
|
2224
|
+
EXECUTE format(
|
|
2225
|
+
'CREATE POLICY scoped_knowledge_update ON %I FOR UPDATE '
|
|
2226
|
+
|| 'USING (opengeni_private.scoped_knowledge_scope_visible('
|
|
2227
|
+
|| 'account_id, scope_kind, scope_workspace_id, scope_subject_id)) '
|
|
2228
|
+
|| 'WITH CHECK (opengeni_private.scoped_knowledge_scope_visible('
|
|
2229
|
+
|| 'account_id, scope_kind, scope_workspace_id, scope_subject_id))',
|
|
2230
|
+
table_name
|
|
2231
|
+
);
|
|
2232
|
+
END LOOP;
|
|
2233
|
+
END
|
|
2234
|
+
$rls$;
|
|
2235
|
+
|
|
2236
|
+
DO $runtime_grants$
|
|
2237
|
+
DECLARE table_name text;
|
|
2238
|
+
DECLARE data_schema text := current_schema();
|
|
2239
|
+
BEGIN
|
|
2240
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
|
|
2241
|
+
FOREACH table_name IN ARRAY ARRAY[
|
|
2242
|
+
'knowledge_operation_receipts',
|
|
2243
|
+
'knowledge_providers',
|
|
2244
|
+
'knowledge_sources',
|
|
2245
|
+
'knowledge_source_acl_versions',
|
|
2246
|
+
'knowledge_sync_runs',
|
|
2247
|
+
'knowledge_source_objects',
|
|
2248
|
+
'knowledge_document_versions',
|
|
2249
|
+
'knowledge_entities',
|
|
2250
|
+
'knowledge_entity_aliases',
|
|
2251
|
+
'knowledge_facts',
|
|
2252
|
+
'knowledge_claims',
|
|
2253
|
+
'knowledge_claim_relations',
|
|
2254
|
+
'knowledge_claim_evidence',
|
|
2255
|
+
'knowledge_claim_reviews',
|
|
2256
|
+
'knowledge_change_proposals'
|
|
2257
|
+
] LOOP
|
|
2258
|
+
EXECUTE format('GRANT SELECT, INSERT ON TABLE %I.%I TO opengeni_app', data_schema, table_name);
|
|
2259
|
+
END LOOP;
|
|
2260
|
+
EXECUTE format(
|
|
2261
|
+
'GRANT SELECT ON TABLE %I.knowledge_lifecycle_events TO opengeni_app', data_schema
|
|
2262
|
+
);
|
|
2263
|
+
EXECUTE format(
|
|
2264
|
+
'GRANT USAGE, SELECT ON SEQUENCE %I.knowledge_claim_review_revision_seq TO opengeni_app',
|
|
2265
|
+
data_schema
|
|
2266
|
+
);
|
|
2267
|
+
EXECUTE format(
|
|
2268
|
+
'GRANT EXECUTE ON FUNCTION %I.scoped_knowledge_apply_lifecycle('
|
|
2269
|
+
|| 'uuid,text,uuid,text,bigint,text,text,text,text,text,text) TO opengeni_app',
|
|
2270
|
+
data_schema
|
|
2271
|
+
);
|
|
2272
|
+
EXECUTE format(
|
|
2273
|
+
'GRANT EXECUTE ON FUNCTION %I.scoped_knowledge_advance_source_acl('
|
|
2274
|
+
|| 'uuid,uuid,bigint,bigint,uuid,text,text,text,text,text,text) TO opengeni_app',
|
|
2275
|
+
data_schema
|
|
2276
|
+
);
|
|
2277
|
+
EXECUTE format(
|
|
2278
|
+
'GRANT EXECUTE ON FUNCTION %I.scoped_knowledge_complete_sync('
|
|
2279
|
+
|| 'uuid,uuid,text,text,timestamptz,jsonb,text,text,text) TO opengeni_app',
|
|
2280
|
+
data_schema
|
|
2281
|
+
);
|
|
2282
|
+
EXECUTE format(
|
|
2283
|
+
'GRANT EXECUTE ON FUNCTION %I.scoped_knowledge_advance_object_version('
|
|
2284
|
+
|| 'uuid,uuid,bigint,bigint,uuid,text,text,text,text,text,text) TO opengeni_app',
|
|
2285
|
+
data_schema
|
|
2286
|
+
);
|
|
2287
|
+
END IF;
|
|
2288
|
+
END
|
|
2289
|
+
$runtime_grants$;
|
|
2290
|
+
|
|
2291
|
+
RESET statement_timeout;
|
|
2292
|
+
RESET lock_timeout;
|