@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.
Files changed (163) hide show
  1. package/dist/attached-browser-devices.d.ts +42 -0
  2. package/dist/attempt-tool-catalogs.d.ts +16 -0
  3. package/dist/browser-auth.d.ts +91 -0
  4. package/dist/browser-identities.d.ts +128 -0
  5. package/dist/browser-sessions.d.ts +272 -0
  6. package/dist/browser-state-artifacts.d.ts +17 -0
  7. package/dist/capability-components.d.ts +37 -0
  8. package/dist/capability-integrations.d.ts +192 -0
  9. package/dist/chunk-F5FT7BEZ.js +3535 -0
  10. package/dist/chunk-F5FT7BEZ.js.map +1 -0
  11. package/dist/chunk-ISI2TIUG.js +639 -0
  12. package/dist/chunk-ISI2TIUG.js.map +1 -0
  13. package/dist/chunk-MWDVXQOL.js +11639 -0
  14. package/dist/chunk-MWDVXQOL.js.map +1 -0
  15. package/dist/chunk-NBE6CHRI.js +955 -0
  16. package/dist/chunk-NBE6CHRI.js.map +1 -0
  17. package/dist/{chunk-VHBFHMPC.js → chunk-YHZXQ7HP.js} +754 -11
  18. package/dist/chunk-YHZXQ7HP.js.map +1 -0
  19. package/dist/codemode-operations.d.ts +117 -0
  20. package/dist/codex-token-resolver.d.ts +2 -2
  21. package/dist/company-profile-schema.d.ts +798 -0
  22. package/dist/company-profile.d.ts +268 -0
  23. package/dist/computer-sessions.d.ts +109 -0
  24. package/dist/connection-token-resolver.d.ts +9 -4
  25. package/dist/database.d.ts +52 -3
  26. package/dist/editable-artifact-durable-export.d.ts +153 -0
  27. package/dist/editable-artifact-durable-export.js +788 -0
  28. package/dist/editable-artifact-durable-export.js.map +1 -0
  29. package/dist/editable-artifact-materialization.d.ts +123 -0
  30. package/dist/editable-artifacts-schema.d.ts +4152 -0
  31. package/dist/editable-artifacts.d.ts +610 -0
  32. package/dist/editable-artifacts.js +26 -0
  33. package/dist/editable-artifacts.js.map +1 -0
  34. package/dist/generated-images.d.ts +139 -0
  35. package/dist/index.d.ts +698 -111
  36. package/dist/index.js +27573 -10322
  37. package/dist/index.js.map +1 -1
  38. package/dist/insights.d.ts +51 -4
  39. package/dist/integration-bindings.d.ts +70 -0
  40. package/dist/integration-features.d.ts +99 -0
  41. package/dist/interaction-revisions.d.ts +3 -0
  42. package/dist/interaction-schema.d.ts +5372 -0
  43. package/dist/knowledge-source-sync-schema.d.ts +1679 -0
  44. package/dist/knowledge-source-sync.d.ts +274 -0
  45. package/dist/memory-governance.d.ts +2 -2
  46. package/dist/memory-slack-delivery.d.ts +101 -0
  47. package/dist/new-session-drafts.d.ts +2 -2
  48. package/dist/pack-components.d.ts +76 -0
  49. package/dist/pack-installations.d.ts +86 -0
  50. package/dist/persistence-errors.d.ts +3 -3
  51. package/dist/plugin-packages.d.ts +126 -0
  52. package/dist/preference-registry.d.ts +1 -1
  53. package/dist/provision-roles.d.ts +9 -1
  54. package/dist/provision-roles.js +1 -1
  55. package/dist/runtime-posture.d.ts +18 -6
  56. package/dist/schema.d.ts +18342 -9099
  57. package/dist/schema.js +165 -1
  58. package/dist/scoped-knowledge.d.ts +97 -1
  59. package/dist/session-control.d.ts +4 -4
  60. package/dist/session-queue-commands.d.ts +19 -11
  61. package/dist/session-realtime-context.d.ts +2 -2
  62. package/dist/session-realtime-ledger.d.ts +4 -4
  63. package/dist/session-realtime-mirror.d.ts +2 -2
  64. package/dist/session-realtime-state.d.ts +1 -1
  65. package/dist/session-realtime-terminal.d.ts +1 -1
  66. package/dist/session-realtime.d.ts +7 -7
  67. package/dist/session-tool-call-settlement.d.ts +1 -1
  68. package/dist/slack-user-link-access.d.ts +54 -0
  69. package/dist/transcription-recordings.d.ts +2 -2
  70. package/dist/turn-initiator.d.ts +2 -2
  71. package/dist/video-generation.d.ts +216 -0
  72. package/dist/video-generation.js +66 -0
  73. package/dist/video-generation.js.map +1 -0
  74. package/dist/workspace-artifacts.d.ts +1 -1
  75. package/dist/workspace-instruction-policies.d.ts +1 -1
  76. package/dist/workspace-learning-policy-schema.d.ts +932 -0
  77. package/dist/workspace-learning-policy.d.ts +161 -0
  78. package/drizzle/0183_model_call_provider_cost_estimates.sql +39 -0
  79. package/drizzle/0184_sandbox_drain_teardown_fence.sql +682 -0
  80. package/drizzle/0185_temporal_schedule_cleanup_outbox.sql +168 -0
  81. package/drizzle/0186_sandbox_capture_provider_contract.sql +400 -0
  82. package/drizzle/0187_generated_image_artifacts.sql +173 -0
  83. package/drizzle/0188_image_generation_retention_failure.sql +25 -0
  84. package/drizzle/0189_retained_session_image_formats.sql +13 -0
  85. package/drizzle/0190_timeline_annotations.sql +31 -0
  86. package/drizzle/0191_editable_artifact_engine.sql +3853 -0
  87. package/drizzle/0192_editable_artifact_live_tickets.sql +338 -0
  88. package/drizzle/0193_editable_artifact_authorization.sql +190 -0
  89. package/drizzle/0194_editable_artifact_durable_exports.sql +160 -0
  90. package/drizzle/0195_editable_artifact_import_authorization.sql +203 -0
  91. package/drizzle/0196_rig_provider_images.sql +152 -0
  92. package/drizzle/0197_knowledge_source_sync_schedules.sql +523 -0
  93. package/drizzle/0198_memory_slack_publication_delivery.sql +393 -0
  94. package/drizzle/0199_workspace_learning_policy.sql +980 -0
  95. package/drizzle/0201_company_profile_authority.sql +849 -0
  96. package/drizzle/0202_document_index_checkpoints.sql +85 -0
  97. package/drizzle/0203_durable_video_generation.sql +353 -0
  98. package/drizzle/0204_video_generation_funding.sql +44 -0
  99. package/drizzle/0205_attempt_tool_catalogs.sql +230 -0
  100. package/drizzle/0206_browser_sessions.sql +646 -0
  101. package/drizzle/0207_browser_identities.sql +379 -0
  102. package/drizzle/0208_attached_browser_devices.sql +165 -0
  103. package/drizzle/0209_computer_sessions.sql +575 -0
  104. package/drizzle/0210_browser_auth_network_interventions.sql +441 -0
  105. package/drizzle/0211_editable_artifact_session_links.sql +58 -0
  106. package/drizzle/0212_slack_installation_bindings.sql +348 -0
  107. package/drizzle/0213_slack_user_link_access_requests.sql +177 -0
  108. package/drizzle/0214_session_activity_commit_gate.sql +262 -0
  109. package/drizzle/0215_capabilities_platform.sql +980 -0
  110. package/drizzle/0216_pack_component_ownership.sql +235 -0
  111. package/drizzle/0217_capability_definition_delete_authority.sql +32 -0
  112. package/package.json +18 -5
  113. package/src/attached-browser-devices.ts +371 -0
  114. package/src/attempt-tool-catalogs.ts +131 -0
  115. package/src/browser-auth.ts +1896 -0
  116. package/src/browser-identities.ts +1168 -0
  117. package/src/browser-sessions.ts +2005 -0
  118. package/src/browser-state-artifacts.ts +59 -0
  119. package/src/capability-components.ts +166 -0
  120. package/src/capability-integrations.ts +1663 -0
  121. package/src/codemode-operations.ts +674 -0
  122. package/src/company-profile-schema.ts +143 -0
  123. package/src/company-profile.ts +968 -0
  124. package/src/computer-sessions.ts +1130 -0
  125. package/src/connection-token-resolver.ts +270 -25
  126. package/src/database.ts +427 -23
  127. package/src/editable-artifact-durable-export.ts +1201 -0
  128. package/src/editable-artifact-materialization.ts +893 -0
  129. package/src/editable-artifacts-schema.ts +879 -0
  130. package/src/editable-artifacts.ts +5404 -0
  131. package/src/generated-images.ts +740 -0
  132. package/src/index.ts +5917 -1182
  133. package/src/insights.ts +160 -13
  134. package/src/integration-bindings.ts +455 -0
  135. package/src/integration-features.ts +859 -0
  136. package/src/interaction-revisions.ts +40 -0
  137. package/src/interaction-schema.ts +1303 -0
  138. package/src/knowledge-source-sync-schema.ts +192 -0
  139. package/src/knowledge-source-sync.ts +1922 -0
  140. package/src/memory-slack-delivery.ts +801 -0
  141. package/src/pack-components.ts +954 -0
  142. package/src/pack-installations.ts +929 -0
  143. package/src/persistence-errors.ts +4 -7
  144. package/src/plugin-packages.ts +1186 -0
  145. package/src/provision-roles.ts +302 -1
  146. package/src/runtime-posture-cli.ts +9 -0
  147. package/src/runtime-posture.ts +508 -5
  148. package/src/schema.ts +5117 -3119
  149. package/src/scoped-knowledge-schema.ts +1 -1
  150. package/src/scoped-knowledge.ts +655 -3
  151. package/src/session-control.ts +8 -5
  152. package/src/session-queue-commands.ts +187 -14
  153. package/src/session-realtime-context.ts +2 -2
  154. package/src/session-realtime-ledger.ts +3 -3
  155. package/src/session-realtime.ts +7 -7
  156. package/src/session-tool-call-settlement.ts +9 -0
  157. package/src/slack-user-link-access.ts +702 -0
  158. package/src/video-generation.ts +1452 -0
  159. package/src/workspace-learning-policy-schema.ts +139 -0
  160. package/src/workspace-learning-policy.ts +595 -0
  161. package/dist/chunk-P6CUHXQZ.js +0 -7248
  162. package/dist/chunk-P6CUHXQZ.js.map +0 -1
  163. 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.';