@opengeni/db 0.28.9 → 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-VHBFHMPC.js → chunk-YHZXQ7HP.js} +754 -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/generated-images.d.ts +139 -0
- package/dist/index.d.ts +698 -111
- package/dist/index.js +27573 -10322
- 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/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 +3 -3
- 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 +18342 -9099
- package/dist/schema.js +165 -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 +1 -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.d.ts +2 -2
- 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/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 +427 -23
- 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/generated-images.ts +740 -0
- package/src/index.ts +5917 -1182
- 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/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 +4 -7
- 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 +508 -5
- package/src/schema.ts +5117 -3119
- package/src/scoped-knowledge-schema.ts +1 -1
- package/src/scoped-knowledge.ts +655 -3
- package/src/session-control.ts +8 -5
- package/src/session-queue-commands.ts +187 -14
- package/src/session-realtime-context.ts +2 -2
- package/src/session-realtime-ledger.ts +3 -3
- package/src/session-realtime.ts +7 -7
- package/src/session-tool-call-settlement.ts +9 -0
- package/src/slack-user-link-access.ts +702 -0
- 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-P6CUHXQZ.js +0 -7248
- package/dist/chunk-P6CUHXQZ.js.map +0 -1
- package/dist/chunk-VHBFHMPC.js.map +0 -1
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- Add a durable Slack-team routing authority. Existing verified rows are
|
|
3
|
+
-- classified without deleting credentials: one exact tenant/principal becomes
|
|
4
|
+
-- active, while conflicting legacy rows are quarantined and fail closed.
|
|
5
|
+
|
|
6
|
+
SET LOCAL lock_timeout = '5s';
|
|
7
|
+
SET LOCAL statement_timeout = '10min';
|
|
8
|
+
|
|
9
|
+
CREATE TABLE "slack_installation_bindings" (
|
|
10
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
11
|
+
"account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
|
|
12
|
+
"workspace_id" uuid NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
|
|
13
|
+
"connection_id" uuid NOT NULL REFERENCES "connections"("id") ON DELETE CASCADE,
|
|
14
|
+
"slack_team_id" text NOT NULL,
|
|
15
|
+
"slack_team_name" text NOT NULL,
|
|
16
|
+
"bot_id" text NOT NULL,
|
|
17
|
+
"bot_user_id" text NOT NULL,
|
|
18
|
+
"bot_display_name" text NOT NULL,
|
|
19
|
+
"state" text NOT NULL,
|
|
20
|
+
"quarantine_reason" text,
|
|
21
|
+
"version" integer NOT NULL DEFAULT 1,
|
|
22
|
+
"created_by_subject_id" text,
|
|
23
|
+
"updated_by_subject_id" text,
|
|
24
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
25
|
+
"updated_at" timestamptz NOT NULL DEFAULT now(),
|
|
26
|
+
CONSTRAINT "slack_installation_bindings_workspace_account_fk"
|
|
27
|
+
FOREIGN KEY ("workspace_id", "account_id")
|
|
28
|
+
REFERENCES "workspaces"("id", "account_id") ON DELETE CASCADE,
|
|
29
|
+
CONSTRAINT "slack_installation_bindings_connection_uq" UNIQUE ("connection_id"),
|
|
30
|
+
CONSTRAINT "slack_installation_bindings_state_check"
|
|
31
|
+
CHECK ("state" IN ('active', 'quarantined')),
|
|
32
|
+
CONSTRAINT "slack_installation_bindings_quarantine_check"
|
|
33
|
+
CHECK (
|
|
34
|
+
("state" = 'active' AND "quarantine_reason" IS NULL)
|
|
35
|
+
OR ("state" = 'quarantined' AND length(btrim("quarantine_reason")) > 0)
|
|
36
|
+
),
|
|
37
|
+
CONSTRAINT "slack_installation_bindings_identity_check"
|
|
38
|
+
CHECK (
|
|
39
|
+
octet_length("slack_team_id") BETWEEN 1 AND 64
|
|
40
|
+
AND octet_length("slack_team_name") BETWEEN 1 AND 256
|
|
41
|
+
AND octet_length("bot_id") BETWEEN 1 AND 64
|
|
42
|
+
AND octet_length("bot_user_id") BETWEEN 1 AND 64
|
|
43
|
+
AND "bot_display_name" = 'OpenGeni'
|
|
44
|
+
),
|
|
45
|
+
CONSTRAINT "slack_installation_bindings_version_check" CHECK ("version" > 0)
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
CREATE UNIQUE INDEX "slack_installation_bindings_active_team_uq"
|
|
49
|
+
ON "slack_installation_bindings" ("slack_team_id")
|
|
50
|
+
WHERE "state" = 'active';
|
|
51
|
+
CREATE INDEX "slack_installation_bindings_workspace_state_idx"
|
|
52
|
+
ON "slack_installation_bindings" ("workspace_id", "state", "updated_at");
|
|
53
|
+
|
|
54
|
+
ALTER TABLE "slack_installation_bindings" ENABLE ROW LEVEL SECURITY;
|
|
55
|
+
ALTER TABLE "slack_installation_bindings" FORCE ROW LEVEL SECURITY;
|
|
56
|
+
CREATE POLICY workspace_isolation ON "slack_installation_bindings"
|
|
57
|
+
USING (opengeni_private.workspace_rls_visible("account_id", "workspace_id"))
|
|
58
|
+
WITH CHECK (opengeni_private.workspace_rls_visible("account_id", "workspace_id"));
|
|
59
|
+
|
|
60
|
+
WITH base AS (
|
|
61
|
+
SELECT
|
|
62
|
+
C.id AS connection_id,
|
|
63
|
+
C.account_id,
|
|
64
|
+
C.workspace_id,
|
|
65
|
+
C.metadata->>'slackTeamId' AS slack_team_id,
|
|
66
|
+
C.metadata->>'slackTeamName' AS slack_team_name,
|
|
67
|
+
C.metadata->>'botId' AS bot_id,
|
|
68
|
+
C.metadata->>'botUserId' AS bot_user_id,
|
|
69
|
+
C.metadata->>'botDisplayName' AS bot_display_name,
|
|
70
|
+
C.created_by_subject_id,
|
|
71
|
+
C.updated_by_subject_id,
|
|
72
|
+
C.created_at,
|
|
73
|
+
C.updated_at
|
|
74
|
+
FROM "connections" C
|
|
75
|
+
WHERE C.subject_id IS NULL
|
|
76
|
+
AND C.provider_domain = 'slack.com'
|
|
77
|
+
AND C.kind = 'app_install'
|
|
78
|
+
AND C.verified_install_at IS NOT NULL
|
|
79
|
+
AND C.verified_install_version = C.version
|
|
80
|
+
AND C.metadata->>'credentialRole' = 'opengeni_slack_bot'
|
|
81
|
+
AND octet_length(C.metadata->>'slackTeamId') BETWEEN 1 AND 64
|
|
82
|
+
AND octet_length(C.metadata->>'slackTeamName') BETWEEN 1 AND 256
|
|
83
|
+
AND octet_length(C.metadata->>'botId') BETWEEN 1 AND 64
|
|
84
|
+
AND octet_length(C.metadata->>'botUserId') BETWEEN 1 AND 64
|
|
85
|
+
AND C.metadata->>'botDisplayName' = 'OpenGeni'
|
|
86
|
+
), classified AS (
|
|
87
|
+
SELECT
|
|
88
|
+
slack_team_id,
|
|
89
|
+
count(DISTINCT (account_id, workspace_id, bot_id, bot_user_id)) AS principal_count
|
|
90
|
+
FROM base
|
|
91
|
+
GROUP BY slack_team_id
|
|
92
|
+
), eligible AS (
|
|
93
|
+
SELECT
|
|
94
|
+
B.*,
|
|
95
|
+
C.principal_count,
|
|
96
|
+
row_number() OVER (
|
|
97
|
+
PARTITION BY B.slack_team_id
|
|
98
|
+
ORDER BY B.created_at DESC, B.connection_id DESC
|
|
99
|
+
) AS canonical_rank
|
|
100
|
+
FROM base B
|
|
101
|
+
JOIN classified C USING (slack_team_id)
|
|
102
|
+
)
|
|
103
|
+
INSERT INTO "slack_installation_bindings" (
|
|
104
|
+
account_id, workspace_id, connection_id, slack_team_id, slack_team_name,
|
|
105
|
+
bot_id, bot_user_id, bot_display_name, state, quarantine_reason,
|
|
106
|
+
created_by_subject_id, updated_by_subject_id, created_at, updated_at
|
|
107
|
+
)
|
|
108
|
+
SELECT
|
|
109
|
+
account_id,
|
|
110
|
+
workspace_id,
|
|
111
|
+
connection_id,
|
|
112
|
+
slack_team_id,
|
|
113
|
+
slack_team_name,
|
|
114
|
+
bot_id,
|
|
115
|
+
bot_user_id,
|
|
116
|
+
bot_display_name,
|
|
117
|
+
CASE WHEN principal_count = 1 THEN 'active' ELSE 'quarantined' END,
|
|
118
|
+
CASE WHEN principal_count = 1 THEN NULL ELSE 'legacy_conflicting_installations' END,
|
|
119
|
+
created_by_subject_id,
|
|
120
|
+
updated_by_subject_id,
|
|
121
|
+
created_at,
|
|
122
|
+
updated_at
|
|
123
|
+
FROM eligible
|
|
124
|
+
WHERE principal_count > 1 OR canonical_rank = 1;
|
|
125
|
+
|
|
126
|
+
DO $binding_functions$
|
|
127
|
+
DECLARE data_schema text := current_schema();
|
|
128
|
+
BEGIN
|
|
129
|
+
EXECUTE format($ddl$
|
|
130
|
+
CREATE OR REPLACE FUNCTION opengeni_private.inspect_slack_installation_binding(
|
|
131
|
+
p_team_id text,
|
|
132
|
+
p_bot_id text,
|
|
133
|
+
p_bot_user_id text
|
|
134
|
+
)
|
|
135
|
+
RETURNS TABLE (outcome text, connection_id uuid, connection_version integer)
|
|
136
|
+
LANGUAGE plpgsql
|
|
137
|
+
SECURITY DEFINER
|
|
138
|
+
SET search_path = pg_catalog
|
|
139
|
+
AS $function$
|
|
140
|
+
DECLARE
|
|
141
|
+
context_account_id uuid := NULLIF(current_setting('opengeni.account_id', true), '')::uuid;
|
|
142
|
+
context_workspace_id uuid := NULLIF(current_setting('opengeni.workspace_id', true), '')::uuid;
|
|
143
|
+
active_binding record;
|
|
144
|
+
BEGIN
|
|
145
|
+
IF context_account_id IS NULL OR context_workspace_id IS NULL THEN
|
|
146
|
+
RAISE EXCEPTION 'Slack installation binding inspection requires tenant context';
|
|
147
|
+
END IF;
|
|
148
|
+
IF octet_length(p_team_id) NOT BETWEEN 1 AND 64
|
|
149
|
+
OR octet_length(p_bot_id) NOT BETWEEN 1 AND 64
|
|
150
|
+
OR octet_length(p_bot_user_id) NOT BETWEEN 1 AND 64
|
|
151
|
+
THEN
|
|
152
|
+
RAISE EXCEPTION 'invalid Slack installation principal';
|
|
153
|
+
END IF;
|
|
154
|
+
|
|
155
|
+
PERFORM pg_advisory_xact_lock(hashtextextended('opengeni:slack-install:' || p_team_id, 0));
|
|
156
|
+
|
|
157
|
+
IF EXISTS (
|
|
158
|
+
SELECT 1 FROM %1$I.slack_installation_bindings B
|
|
159
|
+
WHERE B.slack_team_id = p_team_id AND B.state = 'quarantined'
|
|
160
|
+
) THEN
|
|
161
|
+
RETURN QUERY SELECT 'legacy_quarantine'::text, NULL::uuid, NULL::integer;
|
|
162
|
+
RETURN;
|
|
163
|
+
END IF;
|
|
164
|
+
|
|
165
|
+
SELECT B.*, C.version AS current_connection_version
|
|
166
|
+
INTO active_binding
|
|
167
|
+
FROM %1$I.slack_installation_bindings B
|
|
168
|
+
JOIN %1$I.connections C ON C.id = B.connection_id
|
|
169
|
+
WHERE B.slack_team_id = p_team_id AND B.state = 'active';
|
|
170
|
+
|
|
171
|
+
IF active_binding IS NULL THEN
|
|
172
|
+
RETURN QUERY SELECT 'available'::text, NULL::uuid, NULL::integer;
|
|
173
|
+
ELSIF active_binding.account_id IS DISTINCT FROM context_account_id
|
|
174
|
+
OR active_binding.workspace_id IS DISTINCT FROM context_workspace_id
|
|
175
|
+
THEN
|
|
176
|
+
RETURN QUERY SELECT 'conflicting_workspace'::text, NULL::uuid, NULL::integer;
|
|
177
|
+
ELSIF active_binding.bot_id IS DISTINCT FROM p_bot_id
|
|
178
|
+
OR active_binding.bot_user_id IS DISTINCT FROM p_bot_user_id
|
|
179
|
+
THEN
|
|
180
|
+
RETURN QUERY SELECT 'conflicting_principal'::text, NULL::uuid, NULL::integer;
|
|
181
|
+
ELSE
|
|
182
|
+
RETURN QUERY SELECT
|
|
183
|
+
'same_binding'::text,
|
|
184
|
+
active_binding.connection_id,
|
|
185
|
+
active_binding.current_connection_version;
|
|
186
|
+
END IF;
|
|
187
|
+
END
|
|
188
|
+
$function$
|
|
189
|
+
$ddl$, data_schema);
|
|
190
|
+
|
|
191
|
+
EXECUTE format($ddl$
|
|
192
|
+
CREATE OR REPLACE FUNCTION opengeni_private.sync_slack_installation_binding()
|
|
193
|
+
RETURNS trigger
|
|
194
|
+
LANGUAGE plpgsql
|
|
195
|
+
SECURITY DEFINER
|
|
196
|
+
SET search_path = pg_catalog
|
|
197
|
+
AS $function$
|
|
198
|
+
DECLARE
|
|
199
|
+
existing_binding record;
|
|
200
|
+
team_id text := NEW.metadata->>'slackTeamId';
|
|
201
|
+
team_name text := NEW.metadata->>'slackTeamName';
|
|
202
|
+
bot_id_value text := NEW.metadata->>'botId';
|
|
203
|
+
bot_user_id_value text := NEW.metadata->>'botUserId';
|
|
204
|
+
is_verified_bot boolean :=
|
|
205
|
+
NEW.subject_id IS NULL
|
|
206
|
+
AND NEW.provider_domain = 'slack.com'
|
|
207
|
+
AND NEW.kind = 'app_install'
|
|
208
|
+
AND NEW.verified_install_at IS NOT NULL
|
|
209
|
+
AND NEW.verified_install_version = NEW.version
|
|
210
|
+
AND NEW.metadata->>'credentialRole' = 'opengeni_slack_bot';
|
|
211
|
+
BEGIN
|
|
212
|
+
SELECT * INTO existing_binding
|
|
213
|
+
FROM %1$I.slack_installation_bindings B
|
|
214
|
+
WHERE B.connection_id = NEW.id;
|
|
215
|
+
|
|
216
|
+
-- Composite `IS NOT NULL` is false when any nullable field is null. Use
|
|
217
|
+
-- the SELECT result flag so active bindings with a null quarantine reason
|
|
218
|
+
-- still take the existing-binding path on reinstall and safe updates.
|
|
219
|
+
IF FOUND THEN
|
|
220
|
+
IF team_id IS DISTINCT FROM existing_binding.slack_team_id
|
|
221
|
+
OR bot_id_value IS DISTINCT FROM existing_binding.bot_id
|
|
222
|
+
OR bot_user_id_value IS DISTINCT FROM existing_binding.bot_user_id
|
|
223
|
+
OR NEW.account_id IS DISTINCT FROM existing_binding.account_id
|
|
224
|
+
OR NEW.workspace_id IS DISTINCT FROM existing_binding.workspace_id
|
|
225
|
+
THEN
|
|
226
|
+
RAISE EXCEPTION USING
|
|
227
|
+
ERRCODE = 'P0001',
|
|
228
|
+
MESSAGE = 'OPENGENI_SLACK_BINDING_CONFLICT: bound Slack identity and tenant are immutable';
|
|
229
|
+
END IF;
|
|
230
|
+
IF is_verified_bot AND NEW.status = 'active' THEN
|
|
231
|
+
UPDATE %1$I.slack_installation_bindings B
|
|
232
|
+
SET slack_team_name = team_name,
|
|
233
|
+
bot_display_name = NEW.metadata->>'botDisplayName',
|
|
234
|
+
version = B.version + 1,
|
|
235
|
+
updated_by_subject_id = NEW.updated_by_subject_id,
|
|
236
|
+
updated_at = now()
|
|
237
|
+
WHERE B.id = existing_binding.id;
|
|
238
|
+
END IF;
|
|
239
|
+
RETURN NEW;
|
|
240
|
+
END IF;
|
|
241
|
+
|
|
242
|
+
IF NOT is_verified_bot OR NEW.status <> 'active' THEN
|
|
243
|
+
RETURN NEW;
|
|
244
|
+
END IF;
|
|
245
|
+
IF octet_length(team_id) NOT BETWEEN 1 AND 64
|
|
246
|
+
OR octet_length(team_name) NOT BETWEEN 1 AND 256
|
|
247
|
+
OR octet_length(bot_id_value) NOT BETWEEN 1 AND 64
|
|
248
|
+
OR octet_length(bot_user_id_value) NOT BETWEEN 1 AND 64
|
|
249
|
+
OR NEW.metadata->>'botDisplayName' IS DISTINCT FROM 'OpenGeni'
|
|
250
|
+
THEN
|
|
251
|
+
RAISE EXCEPTION USING
|
|
252
|
+
ERRCODE = 'P0001',
|
|
253
|
+
MESSAGE = 'OPENGENI_SLACK_BINDING_CONFLICT: invalid verified Slack identity';
|
|
254
|
+
END IF;
|
|
255
|
+
|
|
256
|
+
PERFORM pg_advisory_xact_lock(hashtextextended('opengeni:slack-install:' || team_id, 0));
|
|
257
|
+
IF EXISTS (
|
|
258
|
+
SELECT 1 FROM %1$I.slack_installation_bindings B
|
|
259
|
+
WHERE B.slack_team_id = team_id
|
|
260
|
+
) THEN
|
|
261
|
+
RAISE EXCEPTION USING
|
|
262
|
+
ERRCODE = 'P0001',
|
|
263
|
+
MESSAGE = 'OPENGENI_SLACK_BINDING_CONFLICT: Slack team already bound or quarantined';
|
|
264
|
+
END IF;
|
|
265
|
+
|
|
266
|
+
INSERT INTO %1$I.slack_installation_bindings (
|
|
267
|
+
account_id, workspace_id, connection_id, slack_team_id, slack_team_name,
|
|
268
|
+
bot_id, bot_user_id, bot_display_name, state,
|
|
269
|
+
created_by_subject_id, updated_by_subject_id
|
|
270
|
+
) VALUES (
|
|
271
|
+
NEW.account_id, NEW.workspace_id, NEW.id, team_id, team_name,
|
|
272
|
+
bot_id_value, bot_user_id_value, 'OpenGeni', 'active',
|
|
273
|
+
NEW.created_by_subject_id, NEW.updated_by_subject_id
|
|
274
|
+
);
|
|
275
|
+
RETURN NEW;
|
|
276
|
+
END
|
|
277
|
+
$function$
|
|
278
|
+
$ddl$, data_schema);
|
|
279
|
+
|
|
280
|
+
EXECUTE format($ddl$
|
|
281
|
+
CREATE OR REPLACE FUNCTION opengeni_private.resolve_slack_installation(p_team_id text)
|
|
282
|
+
RETURNS TABLE (
|
|
283
|
+
account_id uuid,
|
|
284
|
+
workspace_id uuid,
|
|
285
|
+
connection_id uuid,
|
|
286
|
+
bot_id text,
|
|
287
|
+
bot_user_id text
|
|
288
|
+
)
|
|
289
|
+
LANGUAGE sql
|
|
290
|
+
SECURITY DEFINER
|
|
291
|
+
SET search_path = pg_catalog
|
|
292
|
+
AS $function$
|
|
293
|
+
SELECT B.account_id, B.workspace_id, B.connection_id, B.bot_id, B.bot_user_id
|
|
294
|
+
FROM %1$I.slack_installation_bindings B
|
|
295
|
+
JOIN %1$I.connections C ON C.id = B.connection_id
|
|
296
|
+
WHERE B.slack_team_id = p_team_id
|
|
297
|
+
AND B.state = 'active'
|
|
298
|
+
AND C.account_id = B.account_id
|
|
299
|
+
AND C.workspace_id = B.workspace_id
|
|
300
|
+
AND C.status = 'active'
|
|
301
|
+
AND C.subject_id IS NULL
|
|
302
|
+
AND C.provider_domain = 'slack.com'
|
|
303
|
+
AND C.kind = 'app_install'
|
|
304
|
+
AND C.verified_install_at IS NOT NULL
|
|
305
|
+
AND C.verified_install_version = C.version
|
|
306
|
+
AND C.metadata->>'credentialRole' = 'opengeni_slack_bot'
|
|
307
|
+
AND C.metadata->>'slackTeamId' = B.slack_team_id
|
|
308
|
+
AND C.metadata->>'botId' = B.bot_id
|
|
309
|
+
AND C.metadata->>'botUserId' = B.bot_user_id
|
|
310
|
+
LIMIT 1
|
|
311
|
+
$function$
|
|
312
|
+
$ddl$, data_schema);
|
|
313
|
+
END
|
|
314
|
+
$binding_functions$;
|
|
315
|
+
|
|
316
|
+
REVOKE ALL ON FUNCTION opengeni_private.inspect_slack_installation_binding(text, text, text)
|
|
317
|
+
FROM PUBLIC;
|
|
318
|
+
REVOKE ALL ON FUNCTION opengeni_private.sync_slack_installation_binding()
|
|
319
|
+
FROM PUBLIC;
|
|
320
|
+
REVOKE ALL ON FUNCTION opengeni_private.resolve_slack_installation(text)
|
|
321
|
+
FROM PUBLIC;
|
|
322
|
+
|
|
323
|
+
CREATE TRIGGER connections_sync_slack_installation_binding
|
|
324
|
+
AFTER INSERT OR UPDATE OF
|
|
325
|
+
account_id, workspace_id, subject_id, provider_domain, kind, status,
|
|
326
|
+
granted_scopes, metadata, version, verified_install_at, verified_install_version
|
|
327
|
+
ON "connections"
|
|
328
|
+
FOR EACH ROW
|
|
329
|
+
EXECUTE FUNCTION opengeni_private.sync_slack_installation_binding();
|
|
330
|
+
|
|
331
|
+
DO $grants$
|
|
332
|
+
DECLARE target_schema text := current_schema();
|
|
333
|
+
BEGIN
|
|
334
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
|
|
335
|
+
EXECUTE format(
|
|
336
|
+
'GRANT SELECT ON TABLE %I.slack_installation_bindings TO opengeni_app',
|
|
337
|
+
target_schema
|
|
338
|
+
);
|
|
339
|
+
GRANT EXECUTE ON FUNCTION opengeni_private.inspect_slack_installation_binding(text, text, text)
|
|
340
|
+
TO opengeni_app;
|
|
341
|
+
GRANT EXECUTE ON FUNCTION opengeni_private.resolve_slack_installation(text)
|
|
342
|
+
TO opengeni_app;
|
|
343
|
+
END IF;
|
|
344
|
+
END
|
|
345
|
+
$grants$;
|
|
346
|
+
|
|
347
|
+
COMMENT ON TABLE "slack_installation_bindings" IS
|
|
348
|
+
'Secret-free Slack team to OpenGeni account/workspace routing authority; conflicts are quarantined, never guessed.';
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- Exchange short-lived Slack identity-link bearers for durable, token-free,
|
|
3
|
+
-- subject-bound access-request state. Runtime may update request lifecycle rows
|
|
4
|
+
-- through CAS, but operation receipts are append-only and neither table grants
|
|
5
|
+
-- runtime DELETE.
|
|
6
|
+
|
|
7
|
+
SET LOCAL lock_timeout = '5s';
|
|
8
|
+
SET LOCAL statement_timeout = '10min';
|
|
9
|
+
|
|
10
|
+
CREATE TABLE "slack_user_link_access_requests" (
|
|
11
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
12
|
+
"account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
|
|
13
|
+
"workspace_id" uuid NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
|
|
14
|
+
"token_digest" text NOT NULL,
|
|
15
|
+
"connection_id" uuid NOT NULL REFERENCES "connections"("id") ON DELETE CASCADE,
|
|
16
|
+
"slack_team_id" text NOT NULL,
|
|
17
|
+
"slack_user_id" text NOT NULL,
|
|
18
|
+
"subject_id" text NOT NULL,
|
|
19
|
+
"subject_label" text,
|
|
20
|
+
"status" text NOT NULL DEFAULT 'prepared',
|
|
21
|
+
"version" integer NOT NULL DEFAULT 1,
|
|
22
|
+
"expires_at" timestamptz NOT NULL,
|
|
23
|
+
"requested_at" timestamptz,
|
|
24
|
+
"decided_at" timestamptz,
|
|
25
|
+
"decision_by_subject_id" text,
|
|
26
|
+
"approved_role" text,
|
|
27
|
+
"approved_permissions" jsonb,
|
|
28
|
+
"completed_at" timestamptz,
|
|
29
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
30
|
+
"updated_at" timestamptz NOT NULL DEFAULT now(),
|
|
31
|
+
CONSTRAINT "slack_user_link_access_requests_workspace_account_fk"
|
|
32
|
+
FOREIGN KEY ("workspace_id", "account_id")
|
|
33
|
+
REFERENCES "workspaces"("id", "account_id") ON DELETE CASCADE,
|
|
34
|
+
CONSTRAINT "slack_user_link_access_requests_tenant_uq"
|
|
35
|
+
UNIQUE ("id", "workspace_id", "account_id"),
|
|
36
|
+
CONSTRAINT "slack_user_link_access_requests_identity_check"
|
|
37
|
+
CHECK (
|
|
38
|
+
length("token_digest") = 64
|
|
39
|
+
AND "token_digest" ~ '^[0-9a-f]{64}$'
|
|
40
|
+
AND length("slack_team_id") BETWEEN 1 AND 64
|
|
41
|
+
AND length("slack_user_id") BETWEEN 1 AND 64
|
|
42
|
+
AND length("subject_id") BETWEEN 1 AND 512
|
|
43
|
+
AND ("subject_label" IS NULL OR length("subject_label") BETWEEN 1 AND 512)
|
|
44
|
+
AND "version" > 0
|
|
45
|
+
),
|
|
46
|
+
CONSTRAINT "slack_user_link_access_requests_status_check"
|
|
47
|
+
CHECK (
|
|
48
|
+
"status" IN ('prepared', 'pending', 'completed', 'denied', 'cancelled', 'expired')
|
|
49
|
+
),
|
|
50
|
+
CONSTRAINT "slack_user_link_access_requests_lifecycle_check"
|
|
51
|
+
CHECK (
|
|
52
|
+
(
|
|
53
|
+
"status" = 'prepared'
|
|
54
|
+
AND "requested_at" IS NULL
|
|
55
|
+
AND "decided_at" IS NULL
|
|
56
|
+
AND "completed_at" IS NULL
|
|
57
|
+
)
|
|
58
|
+
OR (
|
|
59
|
+
"status" = 'pending'
|
|
60
|
+
AND "requested_at" IS NOT NULL
|
|
61
|
+
AND "decided_at" IS NULL
|
|
62
|
+
AND "completed_at" IS NULL
|
|
63
|
+
)
|
|
64
|
+
OR ("status" = 'completed' AND "completed_at" IS NOT NULL)
|
|
65
|
+
OR (
|
|
66
|
+
"status" IN ('denied', 'cancelled', 'expired')
|
|
67
|
+
AND "decided_at" IS NOT NULL
|
|
68
|
+
AND "completed_at" IS NULL
|
|
69
|
+
)
|
|
70
|
+
)
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
CREATE UNIQUE INDEX "slack_user_link_access_requests_token_digest_uq"
|
|
74
|
+
ON "slack_user_link_access_requests" ("token_digest");
|
|
75
|
+
CREATE UNIQUE INDEX "slack_user_link_access_requests_active_principal_uq"
|
|
76
|
+
ON "slack_user_link_access_requests"
|
|
77
|
+
("workspace_id", "connection_id", "slack_user_id", "subject_id")
|
|
78
|
+
WHERE "status" IN ('prepared', 'pending');
|
|
79
|
+
CREATE INDEX "slack_user_link_access_requests_workspace_pending_idx"
|
|
80
|
+
ON "slack_user_link_access_requests"
|
|
81
|
+
("workspace_id", "status", "expires_at", "created_at");
|
|
82
|
+
CREATE INDEX "slack_user_link_access_requests_subject_idx"
|
|
83
|
+
ON "slack_user_link_access_requests" ("workspace_id", "subject_id", "id");
|
|
84
|
+
|
|
85
|
+
CREATE TABLE "slack_user_link_access_request_operations" (
|
|
86
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
87
|
+
"account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
|
|
88
|
+
"workspace_id" uuid NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
|
|
89
|
+
"request_id" uuid NOT NULL,
|
|
90
|
+
"actor_subject_id" text NOT NULL,
|
|
91
|
+
"operation" text NOT NULL,
|
|
92
|
+
"idempotency_key" text NOT NULL,
|
|
93
|
+
"request_digest" text NOT NULL,
|
|
94
|
+
"expected_version" integer NOT NULL,
|
|
95
|
+
"result_version" integer NOT NULL,
|
|
96
|
+
"result_status" text NOT NULL,
|
|
97
|
+
"result" jsonb NOT NULL,
|
|
98
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
99
|
+
CONSTRAINT "slack_user_link_access_request_operations_workspace_account_fk"
|
|
100
|
+
FOREIGN KEY ("workspace_id", "account_id")
|
|
101
|
+
REFERENCES "workspaces"("id", "account_id") ON DELETE CASCADE,
|
|
102
|
+
CONSTRAINT "slack_user_link_access_request_operations_request_tenant_fk"
|
|
103
|
+
FOREIGN KEY ("request_id", "workspace_id", "account_id")
|
|
104
|
+
REFERENCES "slack_user_link_access_requests"("id", "workspace_id", "account_id")
|
|
105
|
+
ON DELETE CASCADE,
|
|
106
|
+
CONSTRAINT "slack_user_link_access_request_operations_identity_check"
|
|
107
|
+
CHECK (
|
|
108
|
+
length("actor_subject_id") BETWEEN 1 AND 512
|
|
109
|
+
AND length("idempotency_key") BETWEEN 1 AND 200
|
|
110
|
+
AND "idempotency_key" = btrim("idempotency_key")
|
|
111
|
+
AND length("request_digest") = 64
|
|
112
|
+
AND "request_digest" ~ '^[0-9a-f]{64}$'
|
|
113
|
+
AND "expected_version" > 0
|
|
114
|
+
AND "result_version" = "expected_version" + 1
|
|
115
|
+
AND "operation" IN ('request', 'cancel', 'approve', 'deny')
|
|
116
|
+
AND "result_status" IN ('pending', 'completed', 'denied', 'cancelled')
|
|
117
|
+
AND jsonb_typeof("result") = 'object'
|
|
118
|
+
AND "result" ?& ARRAY[
|
|
119
|
+
'id', 'workspaceId', 'workspaceDisplayName', 'subjectLabel', 'status', 'version',
|
|
120
|
+
'expiresAt', 'requestedAt', 'decidedAt', 'completedAt', 'createdAt', 'updatedAt'
|
|
121
|
+
]
|
|
122
|
+
AND "result" - ARRAY[
|
|
123
|
+
'id', 'workspaceId', 'workspaceDisplayName', 'subjectLabel', 'status', 'version',
|
|
124
|
+
'expiresAt', 'requestedAt', 'decidedAt', 'completedAt', 'createdAt', 'updatedAt'
|
|
125
|
+
] = '{}'::jsonb
|
|
126
|
+
AND jsonb_typeof("result"->'id') = 'string'
|
|
127
|
+
AND jsonb_typeof("result"->'workspaceId') = 'string'
|
|
128
|
+
AND jsonb_typeof("result"->'workspaceDisplayName') IN ('string', 'null')
|
|
129
|
+
AND jsonb_typeof("result"->'subjectLabel') IN ('string', 'null')
|
|
130
|
+
AND jsonb_typeof("result"->'status') = 'string'
|
|
131
|
+
AND jsonb_typeof("result"->'version') = 'number'
|
|
132
|
+
AND jsonb_typeof("result"->'expiresAt') = 'string'
|
|
133
|
+
AND jsonb_typeof("result"->'requestedAt') IN ('string', 'null')
|
|
134
|
+
AND jsonb_typeof("result"->'decidedAt') IN ('string', 'null')
|
|
135
|
+
AND jsonb_typeof("result"->'completedAt') IN ('string', 'null')
|
|
136
|
+
AND jsonb_typeof("result"->'createdAt') = 'string'
|
|
137
|
+
AND jsonb_typeof("result"->'updatedAt') = 'string'
|
|
138
|
+
AND "result"->>'id' = "request_id"::text
|
|
139
|
+
AND "result"->>'workspaceId' = "workspace_id"::text
|
|
140
|
+
AND "result"->>'status' = "result_status"
|
|
141
|
+
AND "result"->'version' = to_jsonb("result_version")
|
|
142
|
+
)
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
CREATE UNIQUE INDEX "slack_user_link_access_request_operations_idempotency_uq"
|
|
146
|
+
ON "slack_user_link_access_request_operations"
|
|
147
|
+
("request_id", "actor_subject_id", "operation", "idempotency_key");
|
|
148
|
+
CREATE UNIQUE INDEX "slack_user_link_access_request_operations_result_version_uq"
|
|
149
|
+
ON "slack_user_link_access_request_operations" ("request_id", "result_version");
|
|
150
|
+
|
|
151
|
+
ALTER TABLE "slack_user_link_access_requests" ENABLE ROW LEVEL SECURITY;
|
|
152
|
+
ALTER TABLE "slack_user_link_access_requests" FORCE ROW LEVEL SECURITY;
|
|
153
|
+
ALTER TABLE "slack_user_link_access_request_operations" ENABLE ROW LEVEL SECURITY;
|
|
154
|
+
ALTER TABLE "slack_user_link_access_request_operations" FORCE ROW LEVEL SECURITY;
|
|
155
|
+
|
|
156
|
+
CREATE POLICY workspace_isolation ON "slack_user_link_access_requests"
|
|
157
|
+
USING (opengeni_private.workspace_rls_visible("account_id", "workspace_id"));
|
|
158
|
+
|
|
159
|
+
CREATE POLICY workspace_isolation ON "slack_user_link_access_request_operations"
|
|
160
|
+
USING (opengeni_private.workspace_rls_visible("account_id", "workspace_id"));
|
|
161
|
+
|
|
162
|
+
REVOKE ALL ON TABLE "slack_user_link_access_requests" FROM PUBLIC;
|
|
163
|
+
REVOKE ALL ON TABLE "slack_user_link_access_request_operations" FROM PUBLIC;
|
|
164
|
+
|
|
165
|
+
DO $runtime_grants$
|
|
166
|
+
BEGIN
|
|
167
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
|
|
168
|
+
GRANT SELECT, INSERT, UPDATE ON TABLE "slack_user_link_access_requests" TO "opengeni_app";
|
|
169
|
+
GRANT SELECT, INSERT ON TABLE "slack_user_link_access_request_operations" TO "opengeni_app";
|
|
170
|
+
END IF;
|
|
171
|
+
END
|
|
172
|
+
$runtime_grants$;
|
|
173
|
+
|
|
174
|
+
COMMENT ON TABLE "slack_user_link_access_requests" IS
|
|
175
|
+
'Token-free, subject-bound lifecycle for signed Slack identity-link workspace access.';
|
|
176
|
+
COMMENT ON TABLE "slack_user_link_access_request_operations" IS
|
|
177
|
+
'Append-only CAS and idempotency receipts for Slack identity-link access decisions.';
|