@opengeni/db 0.28.1 → 0.31.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/attached-browser-devices.d.ts +42 -0
- package/dist/attempt-tool-catalogs.d.ts +16 -0
- package/dist/browser-auth.d.ts +91 -0
- package/dist/browser-identities.d.ts +128 -0
- package/dist/browser-sessions.d.ts +272 -0
- package/dist/browser-state-artifacts.d.ts +17 -0
- package/dist/capability-components.d.ts +37 -0
- package/dist/capability-integrations.d.ts +192 -0
- package/dist/chunk-F5FT7BEZ.js +3535 -0
- package/dist/chunk-F5FT7BEZ.js.map +1 -0
- package/dist/chunk-ISI2TIUG.js +639 -0
- package/dist/chunk-ISI2TIUG.js.map +1 -0
- package/dist/chunk-MWDVXQOL.js +11639 -0
- package/dist/chunk-MWDVXQOL.js.map +1 -0
- package/dist/chunk-NBE6CHRI.js +955 -0
- package/dist/chunk-NBE6CHRI.js.map +1 -0
- package/dist/{chunk-DBS5HPEW.js → chunk-YHZXQ7HP.js} +760 -11
- package/dist/chunk-YHZXQ7HP.js.map +1 -0
- package/dist/codemode-operations.d.ts +117 -0
- package/dist/codex-token-resolver.d.ts +2 -2
- package/dist/company-profile-schema.d.ts +798 -0
- package/dist/company-profile.d.ts +268 -0
- package/dist/computer-sessions.d.ts +109 -0
- package/dist/connection-token-resolver.d.ts +9 -4
- package/dist/database.d.ts +52 -3
- package/dist/editable-artifact-durable-export.d.ts +153 -0
- package/dist/editable-artifact-durable-export.js +788 -0
- package/dist/editable-artifact-durable-export.js.map +1 -0
- package/dist/editable-artifact-materialization.d.ts +123 -0
- package/dist/editable-artifacts-schema.d.ts +4152 -0
- package/dist/editable-artifacts.d.ts +610 -0
- package/dist/editable-artifacts.js +26 -0
- package/dist/editable-artifacts.js.map +1 -0
- package/dist/environment-crypto.d.ts +8 -4
- package/dist/generated-images.d.ts +139 -0
- package/dist/index.d.ts +958 -126
- package/dist/index.js +31204 -12172
- package/dist/index.js.map +1 -1
- package/dist/insights.d.ts +51 -4
- package/dist/integration-bindings.d.ts +70 -0
- package/dist/integration-features.d.ts +99 -0
- package/dist/interaction-revisions.d.ts +3 -0
- package/dist/interaction-schema.d.ts +5372 -0
- package/dist/knowledge-source-sync-schema.d.ts +1679 -0
- package/dist/knowledge-source-sync.d.ts +274 -0
- package/dist/lossless-columns.d.ts +58 -0
- package/dist/lossless-json.d.ts +39 -0
- package/dist/memory-domain.d.ts +3 -6
- package/dist/memory-governance.d.ts +2 -2
- package/dist/memory-slack-delivery.d.ts +101 -0
- package/dist/new-session-drafts.d.ts +2 -2
- package/dist/pack-components.d.ts +76 -0
- package/dist/pack-installations.d.ts +86 -0
- package/dist/persistence-errors.d.ts +7 -14
- package/dist/plugin-packages.d.ts +126 -0
- package/dist/preference-registry.d.ts +1 -1
- package/dist/provision-roles.d.ts +9 -1
- package/dist/provision-roles.js +1 -1
- package/dist/runtime-posture.d.ts +18 -6
- package/dist/schema.d.ts +19122 -8731
- package/dist/schema.js +173 -1
- package/dist/scoped-knowledge.d.ts +97 -1
- package/dist/session-control.d.ts +4 -4
- package/dist/session-queue-commands.d.ts +19 -11
- package/dist/session-realtime-context.d.ts +2 -2
- package/dist/session-realtime-ledger.d.ts +4 -4
- package/dist/session-realtime-mirror.d.ts +2 -2
- package/dist/session-realtime-state.d.ts +1 -1
- package/dist/session-realtime-terminal.d.ts +8 -1
- package/dist/session-realtime.d.ts +7 -7
- package/dist/session-tool-call-settlement.d.ts +1 -1
- package/dist/slack-user-link-access.d.ts +54 -0
- package/dist/transcription-recordings-schema.d.ts +44 -6
- package/dist/transcription-recordings.d.ts +3 -3
- package/dist/turn-initiator.d.ts +2 -2
- package/dist/video-generation.d.ts +216 -0
- package/dist/video-generation.js +66 -0
- package/dist/video-generation.js.map +1 -0
- package/dist/workspace-artifacts.d.ts +1 -1
- package/dist/workspace-instruction-policies.d.ts +1 -1
- package/dist/workspace-learning-policy-schema.d.ts +932 -0
- package/dist/workspace-learning-policy.d.ts +161 -0
- package/drizzle/0065_enrollment_credential_generation.sql +10 -0
- package/drizzle/0140_retained_screenshot_artifacts.sql +192 -0
- package/drizzle/0176_lossless_canonical_json.sql +975 -0
- package/drizzle/0177_session_events_workspace_turn_type_index.sql +5 -0
- package/drizzle/0178_permissioned_secret_reads.sql +14 -0
- package/drizzle/0179_slack_private_shortcut_delivery_gate.sql +85 -0
- package/drizzle/0180_retained_screenshot_lifecycle_fences.sql +273 -0
- package/drizzle/0181_connected_machine_removal.sql +52 -0
- package/drizzle/0182_connected_machine_remove_session_default.sql +77 -0
- package/drizzle/0183_model_call_provider_cost_estimates.sql +39 -0
- package/drizzle/0184_sandbox_drain_teardown_fence.sql +682 -0
- package/drizzle/0185_temporal_schedule_cleanup_outbox.sql +168 -0
- package/drizzle/0186_sandbox_capture_provider_contract.sql +400 -0
- package/drizzle/0187_generated_image_artifacts.sql +173 -0
- package/drizzle/0188_image_generation_retention_failure.sql +25 -0
- package/drizzle/0189_retained_session_image_formats.sql +13 -0
- package/drizzle/0190_timeline_annotations.sql +31 -0
- package/drizzle/0191_editable_artifact_engine.sql +3853 -0
- package/drizzle/0192_editable_artifact_live_tickets.sql +338 -0
- package/drizzle/0193_editable_artifact_authorization.sql +190 -0
- package/drizzle/0194_editable_artifact_durable_exports.sql +160 -0
- package/drizzle/0195_editable_artifact_import_authorization.sql +203 -0
- package/drizzle/0196_rig_provider_images.sql +152 -0
- package/drizzle/0197_knowledge_source_sync_schedules.sql +523 -0
- package/drizzle/0198_memory_slack_publication_delivery.sql +393 -0
- package/drizzle/0199_workspace_learning_policy.sql +980 -0
- package/drizzle/0201_company_profile_authority.sql +849 -0
- package/drizzle/0202_document_index_checkpoints.sql +85 -0
- package/drizzle/0203_durable_video_generation.sql +353 -0
- package/drizzle/0204_video_generation_funding.sql +44 -0
- package/drizzle/0205_attempt_tool_catalogs.sql +230 -0
- package/drizzle/0206_browser_sessions.sql +646 -0
- package/drizzle/0207_browser_identities.sql +379 -0
- package/drizzle/0208_attached_browser_devices.sql +165 -0
- package/drizzle/0209_computer_sessions.sql +575 -0
- package/drizzle/0210_browser_auth_network_interventions.sql +441 -0
- package/drizzle/0211_editable_artifact_session_links.sql +58 -0
- package/drizzle/0212_slack_installation_bindings.sql +348 -0
- package/drizzle/0213_slack_user_link_access_requests.sql +177 -0
- package/drizzle/0214_session_activity_commit_gate.sql +262 -0
- package/drizzle/0215_capabilities_platform.sql +980 -0
- package/drizzle/0216_pack_component_ownership.sql +235 -0
- package/drizzle/0217_capability_definition_delete_authority.sql +32 -0
- package/package.json +18 -5
- package/src/attached-browser-devices.ts +371 -0
- package/src/attempt-tool-catalogs.ts +131 -0
- package/src/browser-auth.ts +1896 -0
- package/src/browser-identities.ts +1168 -0
- package/src/browser-sessions.ts +2005 -0
- package/src/browser-state-artifacts.ts +59 -0
- package/src/capability-components.ts +166 -0
- package/src/capability-integrations.ts +1663 -0
- package/src/codemode-operations.ts +674 -0
- package/src/company-profile-schema.ts +143 -0
- package/src/company-profile.ts +968 -0
- package/src/computer-sessions.ts +1130 -0
- package/src/connection-token-resolver.ts +270 -25
- package/src/database.ts +434 -24
- package/src/editable-artifact-durable-export.ts +1201 -0
- package/src/editable-artifact-materialization.ts +893 -0
- package/src/editable-artifacts-schema.ts +879 -0
- package/src/editable-artifacts.ts +5404 -0
- package/src/environment-crypto.ts +22 -9
- package/src/generated-images.ts +740 -0
- package/src/index.ts +10117 -2730
- package/src/insights.ts +160 -13
- package/src/integration-bindings.ts +455 -0
- package/src/integration-features.ts +859 -0
- package/src/interaction-revisions.ts +40 -0
- package/src/interaction-schema.ts +1303 -0
- package/src/knowledge-source-sync-schema.ts +192 -0
- package/src/knowledge-source-sync.ts +1922 -0
- package/src/lossless-columns.ts +35 -0
- package/src/lossless-json.ts +322 -0
- package/src/memory-domain.ts +5 -44
- package/src/memory-slack-delivery.ts +801 -0
- package/src/pack-components.ts +954 -0
- package/src/pack-installations.ts +929 -0
- package/src/persistence-errors.ts +26 -34
- package/src/plugin-packages.ts +1186 -0
- package/src/provision-roles.ts +302 -1
- package/src/runtime-posture-cli.ts +9 -0
- package/src/runtime-posture.ts +514 -5
- package/src/schema.ts +5177 -2957
- package/src/scoped-knowledge-schema.ts +1 -1
- package/src/scoped-knowledge.ts +655 -3
- package/src/session-control.ts +133 -81
- package/src/session-queue-commands.ts +511 -247
- package/src/session-realtime-context.ts +20 -7
- package/src/session-realtime-ledger.ts +32 -10
- package/src/session-realtime-mirror.ts +4 -2
- package/src/session-realtime-terminal.ts +89 -21
- package/src/session-realtime.ts +10 -9
- package/src/session-tool-call-settlement.ts +38 -15
- package/src/slack-user-link-access.ts +702 -0
- package/src/transcription-recordings-schema.ts +5 -2
- package/src/transcription-recordings.ts +70 -40
- package/src/video-generation.ts +1452 -0
- package/src/workspace-learning-policy-schema.ts +139 -0
- package/src/workspace-learning-policy.ts +595 -0
- package/dist/chunk-DBS5HPEW.js.map +0 -1
- package/dist/chunk-HT5T62CC.js +0 -6803
- package/dist/chunk-HT5T62CC.js.map +0 -1
- package/dist/event-payload-sanitizer.d.ts +0 -32
- package/src/event-payload-sanitizer.ts +0 -377
|
@@ -0,0 +1,849 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- Canonical organization-wide company profile authority and exact-attempt
|
|
3
|
+
-- delivery. It does not use Documents/RAG, Memory, Preference Registry,
|
|
4
|
+
-- and workspace instruction-policy storage.
|
|
5
|
+
|
|
6
|
+
SET LOCAL lock_timeout = '5s';
|
|
7
|
+
SET LOCAL statement_timeout = '10min';
|
|
8
|
+
|
|
9
|
+
CREATE SEQUENCE "company_profile_revision_seq" AS bigint;
|
|
10
|
+
|
|
11
|
+
CREATE TABLE "company_profile_revisions" (
|
|
12
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
13
|
+
"operation_id" uuid NOT NULL,
|
|
14
|
+
"request_fingerprint" text NOT NULL,
|
|
15
|
+
"account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
|
|
16
|
+
"revision" bigint NOT NULL DEFAULT nextval('company_profile_revision_seq'),
|
|
17
|
+
"intent" text NOT NULL,
|
|
18
|
+
"content_json" text NOT NULL,
|
|
19
|
+
"content_hash" text NOT NULL,
|
|
20
|
+
"provenance_source" text NOT NULL,
|
|
21
|
+
"provenance_source_id" text,
|
|
22
|
+
"supersedes_revision_id" uuid REFERENCES "company_profile_revisions"("id") ON DELETE RESTRICT,
|
|
23
|
+
"created_by_subject_id" text NOT NULL,
|
|
24
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
25
|
+
CONSTRAINT "company_profile_revisions_revision_chk" CHECK ("revision" > 0),
|
|
26
|
+
CONSTRAINT "company_profile_revisions_intent_chk" CHECK ("intent" IN ('active', 'proposal')),
|
|
27
|
+
CONSTRAINT "company_profile_revisions_receipt_chk" CHECK (
|
|
28
|
+
"request_fingerprint" ~ '^[0-9a-f]{64}$'
|
|
29
|
+
),
|
|
30
|
+
CONSTRAINT "company_profile_revisions_content_chk" CHECK (
|
|
31
|
+
octet_length(convert_to("content_json", 'UTF8')) BETWEEN 1 AND 28672
|
|
32
|
+
AND "content_hash" ~ '^[0-9a-f]{64}$'
|
|
33
|
+
AND "content_hash" = encode(sha256(convert_to("content_json", 'UTF8')), 'hex')
|
|
34
|
+
),
|
|
35
|
+
CONSTRAINT "company_profile_revisions_provenance_chk" CHECK (
|
|
36
|
+
"provenance_source" IN ('human', 'durable_learning', 'migration')
|
|
37
|
+
AND (
|
|
38
|
+
"provenance_source_id" IS NULL
|
|
39
|
+
OR length("provenance_source_id") BETWEEN 1 AND 512
|
|
40
|
+
)
|
|
41
|
+
),
|
|
42
|
+
CONSTRAINT "company_profile_revisions_actor_chk" CHECK (
|
|
43
|
+
length(btrim("created_by_subject_id")) BETWEEN 1 AND 1024
|
|
44
|
+
),
|
|
45
|
+
CONSTRAINT "company_profile_revisions_account_operation_uq"
|
|
46
|
+
UNIQUE ("account_id", "operation_id"),
|
|
47
|
+
CONSTRAINT "company_profile_revisions_account_revision_uq"
|
|
48
|
+
UNIQUE ("account_id", "revision")
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
CREATE INDEX "company_profile_revisions_account_history_idx"
|
|
52
|
+
ON "company_profile_revisions" ("account_id", "revision" DESC);
|
|
53
|
+
|
|
54
|
+
CREATE TABLE "company_profile_heads" (
|
|
55
|
+
"account_id" uuid PRIMARY KEY REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
|
|
56
|
+
"revision_id" uuid NOT NULL REFERENCES "company_profile_revisions"("id") ON DELETE RESTRICT,
|
|
57
|
+
"revision" bigint NOT NULL,
|
|
58
|
+
"content_hash" text NOT NULL,
|
|
59
|
+
"activation_version" bigint NOT NULL,
|
|
60
|
+
"activated_at" timestamptz NOT NULL DEFAULT now(),
|
|
61
|
+
CONSTRAINT "company_profile_heads_identity_chk" CHECK (
|
|
62
|
+
"revision" > 0
|
|
63
|
+
AND "activation_version" > 0
|
|
64
|
+
AND "content_hash" ~ '^[0-9a-f]{64}$'
|
|
65
|
+
)
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
CREATE TABLE "company_profile_activation_events" (
|
|
69
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
70
|
+
"operation_id" uuid NOT NULL,
|
|
71
|
+
"request_fingerprint" text NOT NULL,
|
|
72
|
+
"account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
|
|
73
|
+
"type" text NOT NULL,
|
|
74
|
+
"activation_version" bigint NOT NULL,
|
|
75
|
+
"old_revision_id" uuid REFERENCES "company_profile_revisions"("id") ON DELETE RESTRICT,
|
|
76
|
+
"old_revision" bigint,
|
|
77
|
+
"old_content_hash" text,
|
|
78
|
+
"new_revision_id" uuid REFERENCES "company_profile_revisions"("id") ON DELETE RESTRICT,
|
|
79
|
+
"new_revision" bigint,
|
|
80
|
+
"new_content_hash" text,
|
|
81
|
+
"actor_subject_id" text NOT NULL,
|
|
82
|
+
"reason" text NOT NULL,
|
|
83
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
84
|
+
CONSTRAINT "company_profile_events_type_chk" CHECK ("type" IN ('activate', 'rollback')),
|
|
85
|
+
CONSTRAINT "company_profile_events_receipt_chk" CHECK (
|
|
86
|
+
"request_fingerprint" ~ '^[0-9a-f]{64}$'
|
|
87
|
+
),
|
|
88
|
+
CONSTRAINT "company_profile_events_old_revision_chk" CHECK (
|
|
89
|
+
("old_revision_id" IS NULL AND "old_revision" IS NULL AND "old_content_hash" IS NULL)
|
|
90
|
+
OR (
|
|
91
|
+
"old_revision_id" IS NOT NULL
|
|
92
|
+
AND "old_revision" > 0
|
|
93
|
+
AND "old_content_hash" ~ '^[0-9a-f]{64}$'
|
|
94
|
+
)
|
|
95
|
+
),
|
|
96
|
+
CONSTRAINT "company_profile_events_new_revision_chk" CHECK (
|
|
97
|
+
("new_revision_id" IS NULL AND "new_revision" IS NULL AND "new_content_hash" IS NULL)
|
|
98
|
+
OR (
|
|
99
|
+
"new_revision_id" IS NOT NULL
|
|
100
|
+
AND "new_revision" > 0
|
|
101
|
+
AND "new_content_hash" ~ '^[0-9a-f]{64}$'
|
|
102
|
+
)
|
|
103
|
+
),
|
|
104
|
+
CONSTRAINT "company_profile_events_audit_chk" CHECK (
|
|
105
|
+
"activation_version" > 0
|
|
106
|
+
AND length(btrim("actor_subject_id")) BETWEEN 1 AND 1024
|
|
107
|
+
AND length(btrim("reason")) BETWEEN 1 AND 4096
|
|
108
|
+
),
|
|
109
|
+
CONSTRAINT "company_profile_events_account_operation_uq"
|
|
110
|
+
UNIQUE ("account_id", "operation_id"),
|
|
111
|
+
CONSTRAINT "company_profile_events_account_version_uq"
|
|
112
|
+
UNIQUE ("account_id", "activation_version")
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
CREATE INDEX "company_profile_events_account_time_idx"
|
|
116
|
+
ON "company_profile_activation_events" ("account_id", "created_at" DESC, "id");
|
|
117
|
+
|
|
118
|
+
CREATE TABLE "company_profile_snapshots" (
|
|
119
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
120
|
+
"account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
|
|
121
|
+
"workspace_id" uuid NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
|
|
122
|
+
"session_id" uuid NOT NULL REFERENCES "sessions"("id") ON DELETE CASCADE,
|
|
123
|
+
"turn_id" uuid NOT NULL REFERENCES "session_turns"("id") ON DELETE CASCADE,
|
|
124
|
+
"attempt_id" uuid NOT NULL REFERENCES "session_turn_attempts"("id") ON DELETE CASCADE,
|
|
125
|
+
"execution_generation" integer NOT NULL,
|
|
126
|
+
"profile" jsonb,
|
|
127
|
+
"snapshot_hash" text NOT NULL,
|
|
128
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
129
|
+
CONSTRAINT "company_profile_snapshots_generation_chk" CHECK ("execution_generation" > 0),
|
|
130
|
+
CONSTRAINT "company_profile_snapshots_profile_chk" CHECK (
|
|
131
|
+
"profile" IS NULL OR jsonb_typeof("profile") = 'object'
|
|
132
|
+
),
|
|
133
|
+
CONSTRAINT "company_profile_snapshots_hash_chk" CHECK (
|
|
134
|
+
"snapshot_hash" ~ '^[0-9a-f]{64}$'
|
|
135
|
+
AND "snapshot_hash" = encode(
|
|
136
|
+
sha256(convert_to(coalesce("profile", 'null'::jsonb)::text, 'UTF8')),
|
|
137
|
+
'hex'
|
|
138
|
+
)
|
|
139
|
+
),
|
|
140
|
+
CONSTRAINT "company_profile_snapshots_attempt_uq"
|
|
141
|
+
UNIQUE ("account_id", "workspace_id", "attempt_id")
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
CREATE INDEX "company_profile_snapshots_workspace_time_idx"
|
|
145
|
+
ON "company_profile_snapshots" ("workspace_id", "created_at" DESC, "id");
|
|
146
|
+
|
|
147
|
+
CREATE OR REPLACE FUNCTION company_profile_validate_revision()
|
|
148
|
+
RETURNS trigger
|
|
149
|
+
LANGUAGE plpgsql
|
|
150
|
+
AS $$
|
|
151
|
+
DECLARE
|
|
152
|
+
profile jsonb;
|
|
153
|
+
field_name text;
|
|
154
|
+
entries jsonb;
|
|
155
|
+
BEGIN
|
|
156
|
+
BEGIN
|
|
157
|
+
profile := NEW.content_json::jsonb;
|
|
158
|
+
EXCEPTION WHEN others THEN
|
|
159
|
+
RAISE EXCEPTION 'company profile content must be canonical JSON'
|
|
160
|
+
USING ERRCODE = '23514';
|
|
161
|
+
END;
|
|
162
|
+
IF jsonb_typeof(profile) <> 'object'
|
|
163
|
+
OR NOT profile ?& ARRAY['identity', 'mission', 'products', 'customers', 'goals', 'constraints']
|
|
164
|
+
OR (profile - ARRAY['identity', 'mission', 'products', 'customers', 'goals', 'constraints']) <> '{}'::jsonb
|
|
165
|
+
THEN
|
|
166
|
+
RAISE EXCEPTION 'company profile must contain exactly the canonical fields'
|
|
167
|
+
USING ERRCODE = '23514';
|
|
168
|
+
END IF;
|
|
169
|
+
IF (jsonb_typeof(profile->'identity') NOT IN ('string', 'null'))
|
|
170
|
+
OR (jsonb_typeof(profile->'mission') NOT IN ('string', 'null'))
|
|
171
|
+
OR (
|
|
172
|
+
jsonb_typeof(profile->'identity') = 'string'
|
|
173
|
+
AND length(btrim(profile->>'identity')) NOT BETWEEN 1 AND 2048
|
|
174
|
+
)
|
|
175
|
+
OR (
|
|
176
|
+
jsonb_typeof(profile->'mission') = 'string'
|
|
177
|
+
AND length(btrim(profile->>'mission')) NOT BETWEEN 1 AND 2048
|
|
178
|
+
)
|
|
179
|
+
THEN
|
|
180
|
+
RAISE EXCEPTION 'company profile identity and mission are invalid'
|
|
181
|
+
USING ERRCODE = '23514';
|
|
182
|
+
END IF;
|
|
183
|
+
FOREACH field_name IN ARRAY ARRAY['products', 'customers', 'goals', 'constraints'] LOOP
|
|
184
|
+
entries := profile->field_name;
|
|
185
|
+
IF jsonb_typeof(entries) <> 'array' OR jsonb_array_length(entries) > 16 THEN
|
|
186
|
+
RAISE EXCEPTION 'company profile list field % is invalid', field_name
|
|
187
|
+
USING ERRCODE = '23514';
|
|
188
|
+
END IF;
|
|
189
|
+
IF EXISTS (
|
|
190
|
+
SELECT 1
|
|
191
|
+
FROM jsonb_array_elements(entries) item
|
|
192
|
+
WHERE jsonb_typeof(item) <> 'object'
|
|
193
|
+
OR NOT item ?& ARRAY['key', 'content']
|
|
194
|
+
OR (item - ARRAY['key', 'content']) <> '{}'::jsonb
|
|
195
|
+
OR jsonb_typeof(item->'key') <> 'string'
|
|
196
|
+
OR jsonb_typeof(item->'content') <> 'string'
|
|
197
|
+
OR length(item->>'key') NOT BETWEEN 1 AND 96
|
|
198
|
+
OR item->>'key' <> lower(btrim(item->>'key'))
|
|
199
|
+
OR item->>'key' !~ '^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$'
|
|
200
|
+
OR item->>'key' ~ '--'
|
|
201
|
+
OR length(btrim(item->>'content')) NOT BETWEEN 1 AND 1024
|
|
202
|
+
) OR (
|
|
203
|
+
SELECT count(*) FROM jsonb_array_elements(entries)
|
|
204
|
+
) <> (
|
|
205
|
+
SELECT count(DISTINCT item->>'key') FROM jsonb_array_elements(entries) item
|
|
206
|
+
) THEN
|
|
207
|
+
RAISE EXCEPTION 'company profile list field % contains invalid entries', field_name
|
|
208
|
+
USING ERRCODE = '23514';
|
|
209
|
+
END IF;
|
|
210
|
+
END LOOP;
|
|
211
|
+
IF profile->'identity' = 'null'::jsonb
|
|
212
|
+
AND profile->'mission' = 'null'::jsonb
|
|
213
|
+
AND jsonb_array_length(profile->'products') = 0
|
|
214
|
+
AND jsonb_array_length(profile->'customers') = 0
|
|
215
|
+
AND jsonb_array_length(profile->'goals') = 0
|
|
216
|
+
AND jsonb_array_length(profile->'constraints') = 0
|
|
217
|
+
THEN
|
|
218
|
+
RAISE EXCEPTION 'company profile must contain at least one field'
|
|
219
|
+
USING ERRCODE = '23514';
|
|
220
|
+
END IF;
|
|
221
|
+
IF NEW.supersedes_revision_id IS NOT NULL AND NOT EXISTS (
|
|
222
|
+
SELECT 1 FROM company_profile_revisions prior
|
|
223
|
+
WHERE prior.id = NEW.supersedes_revision_id
|
|
224
|
+
AND prior.account_id = NEW.account_id
|
|
225
|
+
) THEN
|
|
226
|
+
RAISE EXCEPTION 'superseded company-profile revision must belong to the same account'
|
|
227
|
+
USING ERRCODE = '23514';
|
|
228
|
+
END IF;
|
|
229
|
+
RETURN NEW;
|
|
230
|
+
END;
|
|
231
|
+
$$;
|
|
232
|
+
|
|
233
|
+
CREATE TRIGGER company_profile_revisions_validate
|
|
234
|
+
BEFORE INSERT OR UPDATE ON "company_profile_revisions"
|
|
235
|
+
FOR EACH ROW EXECUTE FUNCTION company_profile_validate_revision();
|
|
236
|
+
|
|
237
|
+
CREATE OR REPLACE FUNCTION company_profile_validate_head()
|
|
238
|
+
RETURNS trigger
|
|
239
|
+
LANGUAGE plpgsql
|
|
240
|
+
AS $$
|
|
241
|
+
BEGIN
|
|
242
|
+
IF NOT EXISTS (
|
|
243
|
+
SELECT 1 FROM company_profile_revisions revision
|
|
244
|
+
WHERE revision.id = NEW.revision_id
|
|
245
|
+
AND revision.account_id = NEW.account_id
|
|
246
|
+
AND revision.revision = NEW.revision
|
|
247
|
+
AND revision.content_hash = NEW.content_hash
|
|
248
|
+
) THEN
|
|
249
|
+
RAISE EXCEPTION 'company-profile head must identify an exact account revision'
|
|
250
|
+
USING ERRCODE = '23514';
|
|
251
|
+
END IF;
|
|
252
|
+
RETURN NEW;
|
|
253
|
+
END;
|
|
254
|
+
$$;
|
|
255
|
+
|
|
256
|
+
CREATE TRIGGER company_profile_heads_validate
|
|
257
|
+
BEFORE INSERT OR UPDATE ON "company_profile_heads"
|
|
258
|
+
FOR EACH ROW EXECUTE FUNCTION company_profile_validate_head();
|
|
259
|
+
|
|
260
|
+
CREATE OR REPLACE FUNCTION company_profile_guard_head_mutation()
|
|
261
|
+
RETURNS trigger
|
|
262
|
+
LANGUAGE plpgsql
|
|
263
|
+
AS $$
|
|
264
|
+
DECLARE
|
|
265
|
+
lifecycle_account_id text;
|
|
266
|
+
BEGIN
|
|
267
|
+
IF TG_OP = 'DELETE'
|
|
268
|
+
AND pg_trigger_depth() > 1
|
|
269
|
+
AND NOT EXISTS (
|
|
270
|
+
SELECT 1 FROM managed_accounts account WHERE account.id = OLD.account_id
|
|
271
|
+
)
|
|
272
|
+
THEN
|
|
273
|
+
RETURN OLD;
|
|
274
|
+
END IF;
|
|
275
|
+
lifecycle_account_id := current_setting(
|
|
276
|
+
'opengeni.company_profile_lifecycle_account_id',
|
|
277
|
+
true
|
|
278
|
+
);
|
|
279
|
+
IF lifecycle_account_id IS DISTINCT FROM coalesce(NEW.account_id, OLD.account_id)::text THEN
|
|
280
|
+
RAISE EXCEPTION 'company-profile heads change only through the lifecycle function'
|
|
281
|
+
USING ERRCODE = '55000';
|
|
282
|
+
END IF;
|
|
283
|
+
IF TG_OP = 'UPDATE' THEN
|
|
284
|
+
IF NEW.account_id IS DISTINCT FROM OLD.account_id
|
|
285
|
+
OR NEW.activation_version IS DISTINCT FROM OLD.activation_version + 1
|
|
286
|
+
THEN
|
|
287
|
+
RAISE EXCEPTION 'invalid company-profile head transition'
|
|
288
|
+
USING ERRCODE = '23514';
|
|
289
|
+
END IF;
|
|
290
|
+
END IF;
|
|
291
|
+
IF TG_OP = 'DELETE' THEN
|
|
292
|
+
RETURN OLD;
|
|
293
|
+
END IF;
|
|
294
|
+
RETURN NEW;
|
|
295
|
+
END;
|
|
296
|
+
$$;
|
|
297
|
+
|
|
298
|
+
CREATE TRIGGER company_profile_heads_lifecycle_only
|
|
299
|
+
BEFORE INSERT OR UPDATE OR DELETE ON "company_profile_heads"
|
|
300
|
+
FOR EACH ROW EXECUTE FUNCTION company_profile_guard_head_mutation();
|
|
301
|
+
|
|
302
|
+
CREATE OR REPLACE FUNCTION company_profile_validate_event()
|
|
303
|
+
RETURNS trigger
|
|
304
|
+
LANGUAGE plpgsql
|
|
305
|
+
AS $$
|
|
306
|
+
BEGIN
|
|
307
|
+
IF NEW.old_revision_id IS NOT NULL AND NOT EXISTS (
|
|
308
|
+
SELECT 1 FROM company_profile_revisions revision
|
|
309
|
+
WHERE revision.id = NEW.old_revision_id
|
|
310
|
+
AND revision.account_id = NEW.account_id
|
|
311
|
+
AND revision.revision = NEW.old_revision
|
|
312
|
+
AND revision.content_hash = NEW.old_content_hash
|
|
313
|
+
) THEN
|
|
314
|
+
RAISE EXCEPTION 'company-profile event has an invalid old revision'
|
|
315
|
+
USING ERRCODE = '23514';
|
|
316
|
+
END IF;
|
|
317
|
+
IF NEW.new_revision_id IS NOT NULL AND NOT EXISTS (
|
|
318
|
+
SELECT 1 FROM company_profile_revisions revision
|
|
319
|
+
WHERE revision.id = NEW.new_revision_id
|
|
320
|
+
AND revision.account_id = NEW.account_id
|
|
321
|
+
AND revision.revision = NEW.new_revision
|
|
322
|
+
AND revision.content_hash = NEW.new_content_hash
|
|
323
|
+
) THEN
|
|
324
|
+
RAISE EXCEPTION 'company-profile event has an invalid new revision'
|
|
325
|
+
USING ERRCODE = '23514';
|
|
326
|
+
END IF;
|
|
327
|
+
IF NEW.old_revision_id IS NULL AND NEW.new_revision_id IS NULL THEN
|
|
328
|
+
RAISE EXCEPTION 'company-profile event must change an active revision'
|
|
329
|
+
USING ERRCODE = '23514';
|
|
330
|
+
END IF;
|
|
331
|
+
RETURN NEW;
|
|
332
|
+
END;
|
|
333
|
+
$$;
|
|
334
|
+
|
|
335
|
+
CREATE TRIGGER company_profile_events_validate
|
|
336
|
+
BEFORE INSERT OR UPDATE ON "company_profile_activation_events"
|
|
337
|
+
FOR EACH ROW EXECUTE FUNCTION company_profile_validate_event();
|
|
338
|
+
|
|
339
|
+
CREATE OR REPLACE FUNCTION company_profile_reject_history_mutation()
|
|
340
|
+
RETURNS trigger
|
|
341
|
+
LANGUAGE plpgsql
|
|
342
|
+
AS $$
|
|
343
|
+
BEGIN
|
|
344
|
+
IF TG_OP = 'DELETE'
|
|
345
|
+
AND pg_trigger_depth() > 1
|
|
346
|
+
AND NOT EXISTS (SELECT 1 FROM managed_accounts account WHERE account.id = OLD.account_id)
|
|
347
|
+
THEN
|
|
348
|
+
RETURN OLD;
|
|
349
|
+
END IF;
|
|
350
|
+
RAISE EXCEPTION 'company-profile history is immutable' USING ERRCODE = '55000';
|
|
351
|
+
END;
|
|
352
|
+
$$;
|
|
353
|
+
|
|
354
|
+
CREATE TRIGGER company_profile_revisions_immutable
|
|
355
|
+
BEFORE UPDATE OR DELETE ON "company_profile_revisions"
|
|
356
|
+
FOR EACH ROW EXECUTE FUNCTION company_profile_reject_history_mutation();
|
|
357
|
+
CREATE TRIGGER company_profile_events_immutable
|
|
358
|
+
BEFORE UPDATE OR DELETE ON "company_profile_activation_events"
|
|
359
|
+
FOR EACH ROW EXECUTE FUNCTION company_profile_reject_history_mutation();
|
|
360
|
+
|
|
361
|
+
CREATE OR REPLACE FUNCTION company_profile_canonical_snapshot_at(
|
|
362
|
+
p_account_id uuid,
|
|
363
|
+
p_accepted_at timestamptz
|
|
364
|
+
) RETURNS jsonb
|
|
365
|
+
LANGUAGE sql
|
|
366
|
+
STABLE
|
|
367
|
+
AS $$
|
|
368
|
+
SELECT CASE
|
|
369
|
+
WHEN event.new_revision_id IS NULL THEN NULL
|
|
370
|
+
ELSE jsonb_build_object(
|
|
371
|
+
'id', revision.id::text,
|
|
372
|
+
'revision', revision.revision,
|
|
373
|
+
'contentHash', revision.content_hash,
|
|
374
|
+
'activationVersion', event.activation_version,
|
|
375
|
+
'activatedAt', to_char(event.created_at AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'),
|
|
376
|
+
'provenance', jsonb_build_object(
|
|
377
|
+
'source', revision.provenance_source,
|
|
378
|
+
'sourceIdHash', CASE
|
|
379
|
+
WHEN revision.provenance_source_id IS NULL THEN NULL
|
|
380
|
+
ELSE encode(sha256(convert_to(revision.provenance_source_id, 'UTF8')), 'hex')
|
|
381
|
+
END
|
|
382
|
+
)
|
|
383
|
+
)
|
|
384
|
+
END
|
|
385
|
+
FROM (
|
|
386
|
+
SELECT activation.*
|
|
387
|
+
FROM company_profile_activation_events activation
|
|
388
|
+
WHERE activation.account_id = p_account_id
|
|
389
|
+
AND activation.created_at <= p_accepted_at
|
|
390
|
+
ORDER BY activation.created_at DESC, activation.activation_version DESC, activation.id DESC
|
|
391
|
+
LIMIT 1
|
|
392
|
+
) event
|
|
393
|
+
LEFT JOIN company_profile_revisions revision
|
|
394
|
+
ON revision.account_id = event.account_id
|
|
395
|
+
AND revision.id = event.new_revision_id
|
|
396
|
+
AND revision.revision = event.new_revision
|
|
397
|
+
AND revision.content_hash = event.new_content_hash;
|
|
398
|
+
$$;
|
|
399
|
+
|
|
400
|
+
REVOKE ALL ON FUNCTION company_profile_canonical_snapshot_at(uuid, timestamptz) FROM PUBLIC;
|
|
401
|
+
|
|
402
|
+
CREATE OR REPLACE FUNCTION company_profile_validate_snapshot()
|
|
403
|
+
RETURNS trigger
|
|
404
|
+
LANGUAGE plpgsql
|
|
405
|
+
AS $$
|
|
406
|
+
DECLARE
|
|
407
|
+
accepted_at timestamptz;
|
|
408
|
+
canonical_profile jsonb;
|
|
409
|
+
BEGIN
|
|
410
|
+
SELECT turn.created_at INTO accepted_at
|
|
411
|
+
FROM session_turn_attempts attempt
|
|
412
|
+
JOIN session_turns turn
|
|
413
|
+
ON turn.account_id = attempt.account_id
|
|
414
|
+
AND turn.workspace_id = attempt.workspace_id
|
|
415
|
+
AND turn.session_id = attempt.session_id
|
|
416
|
+
AND turn.id = attempt.turn_id
|
|
417
|
+
JOIN sessions session
|
|
418
|
+
ON session.account_id = attempt.account_id
|
|
419
|
+
AND session.workspace_id = attempt.workspace_id
|
|
420
|
+
AND session.id = attempt.session_id
|
|
421
|
+
WHERE attempt.id = NEW.attempt_id
|
|
422
|
+
AND attempt.account_id = NEW.account_id
|
|
423
|
+
AND attempt.workspace_id = NEW.workspace_id
|
|
424
|
+
AND attempt.session_id = NEW.session_id
|
|
425
|
+
AND attempt.turn_id = NEW.turn_id
|
|
426
|
+
AND attempt.execution_generation = NEW.execution_generation
|
|
427
|
+
AND turn.execution_generation = NEW.execution_generation
|
|
428
|
+
AND attempt.state IN ('claimed', 'running')
|
|
429
|
+
AND turn.active_attempt_id = attempt.id
|
|
430
|
+
AND session.active_turn_id = turn.id
|
|
431
|
+
AND turn.status IN ('running', 'requires_action', 'recovering', 'waiting_capacity')
|
|
432
|
+
AND NOT EXISTS (
|
|
433
|
+
SELECT 1 FROM session_attempt_interruptions interruption
|
|
434
|
+
WHERE interruption.workspace_id = NEW.workspace_id
|
|
435
|
+
AND interruption.attempt_id = NEW.attempt_id
|
|
436
|
+
AND interruption.state IN ('pending', 'delivered', 'acknowledged')
|
|
437
|
+
);
|
|
438
|
+
IF NOT FOUND THEN
|
|
439
|
+
RAISE EXCEPTION 'company-profile snapshot requires an exact active attempt'
|
|
440
|
+
USING ERRCODE = '23514';
|
|
441
|
+
END IF;
|
|
442
|
+
canonical_profile := company_profile_canonical_snapshot_at(NEW.account_id, accepted_at);
|
|
443
|
+
IF NEW.profile IS DISTINCT FROM canonical_profile
|
|
444
|
+
OR NEW.snapshot_hash IS DISTINCT FROM encode(
|
|
445
|
+
sha256(convert_to(coalesce(canonical_profile, 'null'::jsonb)::text, 'UTF8')),
|
|
446
|
+
'hex'
|
|
447
|
+
)
|
|
448
|
+
THEN
|
|
449
|
+
RAISE EXCEPTION 'company-profile snapshot is not canonical for the accepted turn'
|
|
450
|
+
USING ERRCODE = '23514';
|
|
451
|
+
END IF;
|
|
452
|
+
RETURN NEW;
|
|
453
|
+
END;
|
|
454
|
+
$$;
|
|
455
|
+
|
|
456
|
+
CREATE TRIGGER company_profile_snapshots_validate
|
|
457
|
+
BEFORE INSERT ON "company_profile_snapshots"
|
|
458
|
+
FOR EACH ROW EXECUTE FUNCTION company_profile_validate_snapshot();
|
|
459
|
+
|
|
460
|
+
CREATE OR REPLACE FUNCTION company_profile_reject_snapshot_mutation()
|
|
461
|
+
RETURNS trigger
|
|
462
|
+
LANGUAGE plpgsql
|
|
463
|
+
AS $$
|
|
464
|
+
BEGIN
|
|
465
|
+
IF TG_OP = 'DELETE'
|
|
466
|
+
AND pg_trigger_depth() > 1
|
|
467
|
+
AND (
|
|
468
|
+
NOT EXISTS (SELECT 1 FROM managed_accounts WHERE id = OLD.account_id)
|
|
469
|
+
OR NOT EXISTS (SELECT 1 FROM workspaces WHERE id = OLD.workspace_id)
|
|
470
|
+
OR NOT EXISTS (SELECT 1 FROM sessions WHERE id = OLD.session_id)
|
|
471
|
+
OR NOT EXISTS (SELECT 1 FROM session_turns WHERE id = OLD.turn_id)
|
|
472
|
+
OR NOT EXISTS (SELECT 1 FROM session_turn_attempts WHERE id = OLD.attempt_id)
|
|
473
|
+
)
|
|
474
|
+
THEN
|
|
475
|
+
RETURN OLD;
|
|
476
|
+
END IF;
|
|
477
|
+
RAISE EXCEPTION 'company-profile accepted-turn snapshots are immutable'
|
|
478
|
+
USING ERRCODE = '55000';
|
|
479
|
+
END;
|
|
480
|
+
$$;
|
|
481
|
+
|
|
482
|
+
CREATE TRIGGER company_profile_snapshots_immutable
|
|
483
|
+
BEFORE UPDATE OR DELETE ON "company_profile_snapshots"
|
|
484
|
+
FOR EACH ROW EXECUTE FUNCTION company_profile_reject_snapshot_mutation();
|
|
485
|
+
|
|
486
|
+
ALTER TABLE "company_profile_revisions" ENABLE ROW LEVEL SECURITY;
|
|
487
|
+
ALTER TABLE "company_profile_revisions" FORCE ROW LEVEL SECURITY;
|
|
488
|
+
ALTER TABLE "company_profile_heads" ENABLE ROW LEVEL SECURITY;
|
|
489
|
+
ALTER TABLE "company_profile_heads" FORCE ROW LEVEL SECURITY;
|
|
490
|
+
ALTER TABLE "company_profile_activation_events" ENABLE ROW LEVEL SECURITY;
|
|
491
|
+
ALTER TABLE "company_profile_activation_events" FORCE ROW LEVEL SECURITY;
|
|
492
|
+
ALTER TABLE "company_profile_snapshots" ENABLE ROW LEVEL SECURITY;
|
|
493
|
+
ALTER TABLE "company_profile_snapshots" FORCE ROW LEVEL SECURITY;
|
|
494
|
+
|
|
495
|
+
CREATE POLICY account_isolation ON "company_profile_revisions"
|
|
496
|
+
USING (opengeni_private.account_rls_visible("account_id"))
|
|
497
|
+
WITH CHECK (opengeni_private.account_rls_visible("account_id"));
|
|
498
|
+
CREATE POLICY account_isolation ON "company_profile_heads"
|
|
499
|
+
USING (opengeni_private.account_rls_visible("account_id"))
|
|
500
|
+
WITH CHECK (opengeni_private.account_rls_visible("account_id"));
|
|
501
|
+
CREATE POLICY account_isolation ON "company_profile_activation_events"
|
|
502
|
+
USING (opengeni_private.account_rls_visible("account_id"))
|
|
503
|
+
WITH CHECK (opengeni_private.account_rls_visible("account_id"));
|
|
504
|
+
CREATE POLICY workspace_isolation ON "company_profile_snapshots"
|
|
505
|
+
USING (opengeni_private.workspace_rls_visible("account_id", "workspace_id"))
|
|
506
|
+
WITH CHECK (opengeni_private.workspace_rls_visible("account_id", "workspace_id"));
|
|
507
|
+
|
|
508
|
+
DO $activation_function$
|
|
509
|
+
DECLARE target_schema text := current_schema();
|
|
510
|
+
BEGIN
|
|
511
|
+
EXECUTE format($ddl$
|
|
512
|
+
CREATE OR REPLACE FUNCTION %I.company_profile_apply_activation(
|
|
513
|
+
p_operation_id uuid,
|
|
514
|
+
p_request_fingerprint text,
|
|
515
|
+
p_account_id uuid,
|
|
516
|
+
p_workspace_id uuid,
|
|
517
|
+
p_target_revision_id uuid,
|
|
518
|
+
p_expected_current_revision_id uuid,
|
|
519
|
+
p_expected_activation_version bigint,
|
|
520
|
+
p_type text,
|
|
521
|
+
p_actor_subject_id text,
|
|
522
|
+
p_principal_kind text,
|
|
523
|
+
p_reason text
|
|
524
|
+
) RETURNS TABLE (event_id uuid)
|
|
525
|
+
LANGUAGE plpgsql SECURITY DEFINER SET search_path = %I, pg_catalog
|
|
526
|
+
AS $body$
|
|
527
|
+
DECLARE
|
|
528
|
+
target_revision company_profile_revisions%%ROWTYPE;
|
|
529
|
+
current_head company_profile_heads%%ROWTYPE;
|
|
530
|
+
existing_event company_profile_activation_events%%ROWTYPE;
|
|
531
|
+
revision_operation company_profile_revisions%%ROWTYPE;
|
|
532
|
+
next_activation_version bigint;
|
|
533
|
+
event_created_at timestamptz;
|
|
534
|
+
context_account_id uuid;
|
|
535
|
+
context_workspace_id uuid;
|
|
536
|
+
context_subject_id text;
|
|
537
|
+
context_principal_kind text;
|
|
538
|
+
BEGIN
|
|
539
|
+
context_account_id := NULLIF(current_setting('opengeni.account_id', true), '')::uuid;
|
|
540
|
+
context_workspace_id := NULLIF(current_setting('opengeni.workspace_id', true), '')::uuid;
|
|
541
|
+
context_subject_id := NULLIF(current_setting('opengeni.subject_id', true), '');
|
|
542
|
+
context_principal_kind := NULLIF(current_setting('opengeni.principal_kind', true), '');
|
|
543
|
+
IF context_account_id IS DISTINCT FROM p_account_id
|
|
544
|
+
OR context_workspace_id IS DISTINCT FROM p_workspace_id
|
|
545
|
+
OR context_subject_id IS DISTINCT FROM p_actor_subject_id
|
|
546
|
+
OR context_principal_kind IS DISTINCT FROM p_principal_kind
|
|
547
|
+
OR p_principal_kind NOT IN ('human_session', 'agent_attempt', 'service')
|
|
548
|
+
THEN
|
|
549
|
+
RAISE EXCEPTION 'company-profile activation requires exact tenant and actor authority'
|
|
550
|
+
USING ERRCODE = '42501';
|
|
551
|
+
END IF;
|
|
552
|
+
IF p_request_fingerprint !~ '^[0-9a-f]{64}$'
|
|
553
|
+
OR p_expected_activation_version < 0
|
|
554
|
+
OR p_type NOT IN ('activate', 'rollback')
|
|
555
|
+
OR (p_target_revision_id IS NULL AND p_type <> 'rollback')
|
|
556
|
+
OR length(btrim(p_actor_subject_id)) NOT BETWEEN 1 AND 1024
|
|
557
|
+
OR length(btrim(p_reason)) NOT BETWEEN 1 AND 4096
|
|
558
|
+
THEN
|
|
559
|
+
RAISE EXCEPTION 'company-profile activation input is invalid'
|
|
560
|
+
USING ERRCODE = '22023';
|
|
561
|
+
END IF;
|
|
562
|
+
|
|
563
|
+
PERFORM 1
|
|
564
|
+
FROM managed_accounts account
|
|
565
|
+
JOIN workspaces workspace ON workspace.account_id = account.id
|
|
566
|
+
WHERE account.id = p_account_id AND workspace.id = p_workspace_id
|
|
567
|
+
FOR UPDATE OF account;
|
|
568
|
+
IF NOT FOUND THEN
|
|
569
|
+
RAISE EXCEPTION 'organization workspace was not found' USING ERRCODE = '42501';
|
|
570
|
+
END IF;
|
|
571
|
+
|
|
572
|
+
SELECT * INTO existing_event
|
|
573
|
+
FROM company_profile_activation_events event
|
|
574
|
+
WHERE event.account_id = p_account_id
|
|
575
|
+
AND event.operation_id = p_operation_id;
|
|
576
|
+
IF FOUND THEN
|
|
577
|
+
IF existing_event.request_fingerprint IS DISTINCT FROM p_request_fingerprint THEN
|
|
578
|
+
RAISE EXCEPTION 'company-profile operation id was reused'
|
|
579
|
+
USING ERRCODE = 'P1851';
|
|
580
|
+
END IF;
|
|
581
|
+
event_id := existing_event.id;
|
|
582
|
+
RETURN NEXT;
|
|
583
|
+
RETURN;
|
|
584
|
+
END IF;
|
|
585
|
+
|
|
586
|
+
SELECT * INTO revision_operation
|
|
587
|
+
FROM company_profile_revisions revision
|
|
588
|
+
WHERE revision.account_id = p_account_id
|
|
589
|
+
AND revision.operation_id = p_operation_id;
|
|
590
|
+
IF FOUND AND (
|
|
591
|
+
revision_operation.request_fingerprint IS DISTINCT FROM p_request_fingerprint
|
|
592
|
+
OR revision_operation.id IS DISTINCT FROM p_target_revision_id
|
|
593
|
+
) THEN
|
|
594
|
+
RAISE EXCEPTION 'company-profile operation id was reused'
|
|
595
|
+
USING ERRCODE = 'P1851';
|
|
596
|
+
END IF;
|
|
597
|
+
|
|
598
|
+
IF p_target_revision_id IS NOT NULL THEN
|
|
599
|
+
SELECT * INTO target_revision
|
|
600
|
+
FROM company_profile_revisions revision
|
|
601
|
+
WHERE revision.id = p_target_revision_id
|
|
602
|
+
AND revision.account_id = p_account_id;
|
|
603
|
+
IF NOT FOUND THEN
|
|
604
|
+
RAISE EXCEPTION 'company-profile revision was not found' USING ERRCODE = '23503';
|
|
605
|
+
END IF;
|
|
606
|
+
END IF;
|
|
607
|
+
|
|
608
|
+
SELECT * INTO current_head
|
|
609
|
+
FROM company_profile_heads head
|
|
610
|
+
WHERE head.account_id = p_account_id
|
|
611
|
+
FOR UPDATE;
|
|
612
|
+
IF coalesce(current_head.activation_version, 0) IS DISTINCT FROM p_expected_activation_version
|
|
613
|
+
OR current_head.revision_id IS DISTINCT FROM p_expected_current_revision_id
|
|
614
|
+
THEN
|
|
615
|
+
RAISE EXCEPTION 'company-profile active revision changed'
|
|
616
|
+
USING ERRCODE = '40001';
|
|
617
|
+
END IF;
|
|
618
|
+
IF current_head.revision_id IS NOT NULL
|
|
619
|
+
AND current_head.revision_id IS NOT DISTINCT FROM p_target_revision_id
|
|
620
|
+
THEN
|
|
621
|
+
RAISE EXCEPTION 'company-profile revision is already active'
|
|
622
|
+
USING ERRCODE = '23514';
|
|
623
|
+
END IF;
|
|
624
|
+
IF current_head.revision_id IS NULL AND p_target_revision_id IS NULL THEN
|
|
625
|
+
RAISE EXCEPTION 'company-profile is already inactive'
|
|
626
|
+
USING ERRCODE = '23514';
|
|
627
|
+
END IF;
|
|
628
|
+
IF p_type = 'rollback' AND p_target_revision_id IS NOT NULL AND NOT EXISTS (
|
|
629
|
+
SELECT 1
|
|
630
|
+
FROM company_profile_activation_events event
|
|
631
|
+
WHERE event.account_id = p_account_id
|
|
632
|
+
AND event.new_revision_id = p_target_revision_id
|
|
633
|
+
) THEN
|
|
634
|
+
RAISE EXCEPTION 'company-profile rollback target was not previously active'
|
|
635
|
+
USING ERRCODE = '23514';
|
|
636
|
+
END IF;
|
|
637
|
+
|
|
638
|
+
event_created_at := clock_timestamp();
|
|
639
|
+
next_activation_version := coalesce(current_head.activation_version, 0) + 1;
|
|
640
|
+
PERFORM set_config(
|
|
641
|
+
'opengeni.company_profile_lifecycle_account_id',
|
|
642
|
+
p_account_id::text,
|
|
643
|
+
true
|
|
644
|
+
);
|
|
645
|
+
IF p_target_revision_id IS NULL THEN
|
|
646
|
+
DELETE FROM company_profile_heads WHERE account_id = p_account_id;
|
|
647
|
+
ELSIF current_head.account_id IS NULL THEN
|
|
648
|
+
INSERT INTO company_profile_heads (
|
|
649
|
+
account_id, revision_id, revision, content_hash, activation_version, activated_at
|
|
650
|
+
) VALUES (
|
|
651
|
+
p_account_id, target_revision.id, target_revision.revision,
|
|
652
|
+
target_revision.content_hash, next_activation_version, event_created_at
|
|
653
|
+
);
|
|
654
|
+
ELSE
|
|
655
|
+
UPDATE company_profile_heads
|
|
656
|
+
SET revision_id = target_revision.id,
|
|
657
|
+
revision = target_revision.revision,
|
|
658
|
+
content_hash = target_revision.content_hash,
|
|
659
|
+
activation_version = next_activation_version,
|
|
660
|
+
activated_at = event_created_at
|
|
661
|
+
WHERE account_id = p_account_id;
|
|
662
|
+
END IF;
|
|
663
|
+
PERFORM set_config('opengeni.company_profile_lifecycle_account_id', '', true);
|
|
664
|
+
|
|
665
|
+
INSERT INTO company_profile_activation_events (
|
|
666
|
+
operation_id, request_fingerprint, account_id, type, activation_version,
|
|
667
|
+
old_revision_id, old_revision, old_content_hash,
|
|
668
|
+
new_revision_id, new_revision, new_content_hash,
|
|
669
|
+
actor_subject_id, reason, created_at
|
|
670
|
+
) VALUES (
|
|
671
|
+
p_operation_id, p_request_fingerprint, p_account_id, p_type, next_activation_version,
|
|
672
|
+
current_head.revision_id, current_head.revision, current_head.content_hash,
|
|
673
|
+
target_revision.id, target_revision.revision, target_revision.content_hash,
|
|
674
|
+
p_actor_subject_id, p_reason, event_created_at
|
|
675
|
+
) RETURNING id INTO event_id;
|
|
676
|
+
RETURN NEXT;
|
|
677
|
+
END
|
|
678
|
+
$body$
|
|
679
|
+
$ddl$, target_schema, target_schema);
|
|
680
|
+
EXECUTE format(
|
|
681
|
+
'REVOKE ALL ON FUNCTION %I.company_profile_apply_activation(uuid,text,uuid,uuid,uuid,uuid,bigint,text,text,text,text) FROM PUBLIC',
|
|
682
|
+
target_schema
|
|
683
|
+
);
|
|
684
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
|
|
685
|
+
EXECUTE format(
|
|
686
|
+
'GRANT EXECUTE ON FUNCTION %I.company_profile_apply_activation(uuid, text, uuid, uuid, uuid, uuid, bigint, text, text, text, text) TO opengeni_app',
|
|
687
|
+
target_schema
|
|
688
|
+
);
|
|
689
|
+
END IF;
|
|
690
|
+
END $activation_function$;
|
|
691
|
+
|
|
692
|
+
DO $snapshot_function$
|
|
693
|
+
DECLARE target_schema text := current_schema();
|
|
694
|
+
BEGIN
|
|
695
|
+
EXECUTE format($ddl$
|
|
696
|
+
CREATE OR REPLACE FUNCTION %I.company_profile_get_or_create_snapshot(
|
|
697
|
+
p_account_id uuid,
|
|
698
|
+
p_workspace_id uuid,
|
|
699
|
+
p_session_id uuid,
|
|
700
|
+
p_turn_id uuid,
|
|
701
|
+
p_attempt_id uuid,
|
|
702
|
+
p_execution_generation integer
|
|
703
|
+
) RETURNS SETOF %I.company_profile_snapshots
|
|
704
|
+
LANGUAGE plpgsql SECURITY DEFINER SET search_path = %I, pg_catalog
|
|
705
|
+
AS $body$
|
|
706
|
+
DECLARE
|
|
707
|
+
context_account_id uuid;
|
|
708
|
+
context_workspace_id uuid;
|
|
709
|
+
accepted_at timestamptz;
|
|
710
|
+
canonical_profile jsonb;
|
|
711
|
+
canonical_hash text;
|
|
712
|
+
snapshot_time timestamptz := transaction_timestamp();
|
|
713
|
+
winner_id uuid;
|
|
714
|
+
BEGIN
|
|
715
|
+
context_account_id := NULLIF(current_setting('opengeni.account_id', true), '')::uuid;
|
|
716
|
+
context_workspace_id := NULLIF(current_setting('opengeni.workspace_id', true), '')::uuid;
|
|
717
|
+
IF context_account_id IS DISTINCT FROM p_account_id
|
|
718
|
+
OR context_workspace_id IS DISTINCT FROM p_workspace_id
|
|
719
|
+
OR p_execution_generation < 1
|
|
720
|
+
THEN
|
|
721
|
+
RAISE EXCEPTION 'company-profile snapshot requires exact tenant authority'
|
|
722
|
+
USING ERRCODE = '42501';
|
|
723
|
+
END IF;
|
|
724
|
+
|
|
725
|
+
SELECT turn.created_at INTO accepted_at
|
|
726
|
+
FROM workspaces workspace
|
|
727
|
+
JOIN sessions session
|
|
728
|
+
ON session.account_id = workspace.account_id AND session.workspace_id = workspace.id
|
|
729
|
+
JOIN session_turns turn
|
|
730
|
+
ON turn.account_id = session.account_id
|
|
731
|
+
AND turn.workspace_id = session.workspace_id
|
|
732
|
+
AND turn.session_id = session.id
|
|
733
|
+
JOIN session_turn_attempts attempt
|
|
734
|
+
ON attempt.account_id = turn.account_id
|
|
735
|
+
AND attempt.workspace_id = turn.workspace_id
|
|
736
|
+
AND attempt.session_id = turn.session_id
|
|
737
|
+
AND attempt.turn_id = turn.id
|
|
738
|
+
WHERE workspace.id = p_workspace_id
|
|
739
|
+
AND workspace.account_id = p_account_id
|
|
740
|
+
AND session.id = p_session_id
|
|
741
|
+
AND session.active_turn_id = p_turn_id
|
|
742
|
+
AND turn.id = p_turn_id
|
|
743
|
+
AND turn.active_attempt_id = p_attempt_id
|
|
744
|
+
AND turn.execution_generation = p_execution_generation
|
|
745
|
+
AND turn.status IN ('running', 'requires_action', 'recovering', 'waiting_capacity')
|
|
746
|
+
AND attempt.id = p_attempt_id
|
|
747
|
+
AND attempt.execution_generation = p_execution_generation
|
|
748
|
+
AND attempt.state IN ('claimed', 'running')
|
|
749
|
+
AND NOT EXISTS (
|
|
750
|
+
SELECT 1 FROM session_attempt_interruptions interruption
|
|
751
|
+
WHERE interruption.workspace_id = attempt.workspace_id
|
|
752
|
+
AND interruption.attempt_id = attempt.id
|
|
753
|
+
AND interruption.state IN ('pending', 'delivered', 'acknowledged')
|
|
754
|
+
)
|
|
755
|
+
FOR KEY SHARE OF workspace
|
|
756
|
+
FOR SHARE OF session, turn
|
|
757
|
+
FOR UPDATE OF attempt;
|
|
758
|
+
IF NOT FOUND THEN
|
|
759
|
+
RAISE EXCEPTION 'company-profile snapshot requires the exact current attempt'
|
|
760
|
+
USING ERRCODE = '42501';
|
|
761
|
+
END IF;
|
|
762
|
+
|
|
763
|
+
SELECT snapshot.id INTO winner_id
|
|
764
|
+
FROM company_profile_snapshots snapshot
|
|
765
|
+
WHERE snapshot.account_id = p_account_id
|
|
766
|
+
AND snapshot.workspace_id = p_workspace_id
|
|
767
|
+
AND snapshot.session_id = p_session_id
|
|
768
|
+
AND snapshot.turn_id = p_turn_id
|
|
769
|
+
AND snapshot.attempt_id = p_attempt_id
|
|
770
|
+
AND snapshot.execution_generation = p_execution_generation
|
|
771
|
+
FOR SHARE;
|
|
772
|
+
IF winner_id IS NOT NULL THEN
|
|
773
|
+
RETURN QUERY SELECT snapshot.* FROM company_profile_snapshots snapshot WHERE snapshot.id = winner_id;
|
|
774
|
+
RETURN;
|
|
775
|
+
END IF;
|
|
776
|
+
|
|
777
|
+
canonical_profile := company_profile_canonical_snapshot_at(p_account_id, accepted_at);
|
|
778
|
+
canonical_hash := encode(
|
|
779
|
+
sha256(convert_to(coalesce(canonical_profile, 'null'::jsonb)::text, 'UTF8')),
|
|
780
|
+
'hex'
|
|
781
|
+
);
|
|
782
|
+
INSERT INTO company_profile_snapshots (
|
|
783
|
+
account_id, workspace_id, session_id, turn_id, attempt_id,
|
|
784
|
+
execution_generation, profile, snapshot_hash, created_at
|
|
785
|
+
) VALUES (
|
|
786
|
+
p_account_id, p_workspace_id, p_session_id, p_turn_id, p_attempt_id,
|
|
787
|
+
p_execution_generation, canonical_profile, canonical_hash, snapshot_time
|
|
788
|
+
)
|
|
789
|
+
ON CONFLICT (account_id, workspace_id, attempt_id) DO NOTHING
|
|
790
|
+
RETURNING id INTO winner_id;
|
|
791
|
+
IF winner_id IS NULL THEN
|
|
792
|
+
SELECT snapshot.id INTO winner_id
|
|
793
|
+
FROM company_profile_snapshots snapshot
|
|
794
|
+
WHERE snapshot.account_id = p_account_id
|
|
795
|
+
AND snapshot.workspace_id = p_workspace_id
|
|
796
|
+
AND snapshot.session_id = p_session_id
|
|
797
|
+
AND snapshot.turn_id = p_turn_id
|
|
798
|
+
AND snapshot.attempt_id = p_attempt_id
|
|
799
|
+
AND snapshot.execution_generation = p_execution_generation
|
|
800
|
+
FOR SHARE;
|
|
801
|
+
END IF;
|
|
802
|
+
IF winner_id IS NULL THEN
|
|
803
|
+
RAISE EXCEPTION 'company-profile snapshot winner conflicts with exact attempt authority'
|
|
804
|
+
USING ERRCODE = '40001';
|
|
805
|
+
END IF;
|
|
806
|
+
IF EXISTS (
|
|
807
|
+
SELECT 1 FROM company_profile_snapshots snapshot
|
|
808
|
+
WHERE snapshot.id = winner_id
|
|
809
|
+
AND (
|
|
810
|
+
snapshot.profile IS DISTINCT FROM canonical_profile
|
|
811
|
+
OR snapshot.snapshot_hash IS DISTINCT FROM canonical_hash
|
|
812
|
+
OR snapshot.created_at IS DISTINCT FROM snapshot_time
|
|
813
|
+
)
|
|
814
|
+
) THEN
|
|
815
|
+
RAISE EXCEPTION 'company-profile snapshot winner is not canonical'
|
|
816
|
+
USING ERRCODE = '40001';
|
|
817
|
+
END IF;
|
|
818
|
+
RETURN QUERY SELECT snapshot.* FROM company_profile_snapshots snapshot WHERE snapshot.id = winner_id;
|
|
819
|
+
END
|
|
820
|
+
$body$
|
|
821
|
+
$ddl$, target_schema, target_schema, target_schema);
|
|
822
|
+
EXECUTE format(
|
|
823
|
+
'REVOKE ALL ON FUNCTION %I.company_profile_get_or_create_snapshot(uuid, uuid, uuid, uuid, uuid, integer) FROM PUBLIC',
|
|
824
|
+
target_schema
|
|
825
|
+
);
|
|
826
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
|
|
827
|
+
EXECUTE format(
|
|
828
|
+
'GRANT EXECUTE ON FUNCTION %I.company_profile_get_or_create_snapshot(uuid, uuid, uuid, uuid, uuid, integer) TO opengeni_app',
|
|
829
|
+
target_schema
|
|
830
|
+
);
|
|
831
|
+
END IF;
|
|
832
|
+
END $snapshot_function$;
|
|
833
|
+
|
|
834
|
+
REVOKE ALL ON TABLE company_profile_revisions FROM PUBLIC;
|
|
835
|
+
REVOKE ALL ON TABLE company_profile_heads FROM PUBLIC;
|
|
836
|
+
REVOKE ALL ON TABLE company_profile_activation_events FROM PUBLIC;
|
|
837
|
+
REVOKE ALL ON TABLE company_profile_snapshots FROM PUBLIC;
|
|
838
|
+
REVOKE ALL ON SEQUENCE company_profile_revision_seq FROM PUBLIC;
|
|
839
|
+
|
|
840
|
+
DO $runtime_grants$
|
|
841
|
+
BEGIN
|
|
842
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
|
|
843
|
+
GRANT SELECT, INSERT ON TABLE company_profile_revisions TO opengeni_app;
|
|
844
|
+
GRANT SELECT ON TABLE company_profile_heads TO opengeni_app;
|
|
845
|
+
GRANT SELECT ON TABLE company_profile_activation_events TO opengeni_app;
|
|
846
|
+
GRANT SELECT ON TABLE company_profile_snapshots TO opengeni_app;
|
|
847
|
+
GRANT USAGE, SELECT ON SEQUENCE company_profile_revision_seq TO opengeni_app;
|
|
848
|
+
END IF;
|
|
849
|
+
END $runtime_grants$;
|