@opengeni/db 0.28.9 → 0.36.0
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 +299 -0
- package/dist/browser-downloads.d.ts +51 -0
- package/dist/browser-identities.d.ts +137 -0
- package/dist/browser-sessions.d.ts +354 -0
- package/dist/browser-state-artifacts.d.ts +91 -0
- package/dist/canonical-human-identities.d.ts +48 -0
- package/dist/canonical-human-identities.js +28 -0
- package/dist/canonical-human-identities.js.map +1 -0
- package/dist/capability-components.d.ts +37 -0
- package/dist/capability-integrations.d.ts +194 -0
- package/dist/chunk-5KTIEDX7.js +13200 -0
- package/dist/chunk-5KTIEDX7.js.map +1 -0
- package/dist/chunk-CQZ4QSP5.js +995 -0
- package/dist/chunk-CQZ4QSP5.js.map +1 -0
- package/dist/chunk-F32YT7MP.js +117 -0
- package/dist/chunk-F32YT7MP.js.map +1 -0
- package/dist/chunk-JC5QPLUM.js +666 -0
- package/dist/chunk-JC5QPLUM.js.map +1 -0
- package/dist/chunk-P3RE4D2B.js +3535 -0
- package/dist/chunk-P3RE4D2B.js.map +1 -0
- package/dist/chunk-TAUTWOS5.js +259 -0
- package/dist/chunk-TAUTWOS5.js.map +1 -0
- package/dist/chunk-VKLB2ZGI.js +2389 -0
- package/dist/chunk-VKLB2ZGI.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 +57 -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 +1122 -126
- package/dist/index.js +42329 -18407
- package/dist/index.js.map +1 -1
- package/dist/insights.d.ts +73 -10
- package/dist/integration-bindings.d.ts +70 -0
- package/dist/integration-facets.d.ts +99 -0
- package/dist/interaction-revisions.d.ts +14 -0
- package/dist/interaction-schema.d.ts +6036 -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 +19 -6
- package/dist/schema.d.ts +22654 -9359
- package/dist/schema.js +209 -3
- package/dist/scoped-knowledge.d.ts +97 -1
- package/dist/session-control.d.ts +33 -7
- 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-tenancy.d.ts +44 -0
- package/dist/session-tenancy.js +16 -0
- package/dist/session-tenancy.js.map +1 -0
- package/dist/session-tool-call-settlement.d.ts +1 -1
- package/dist/slack-task-policy-schema.d.ts +847 -0
- package/dist/slack-task-policy.d.ts +65 -0
- package/dist/slack-user-link-access.d.ts +54 -0
- package/dist/task-notes-schema.d.ts +924 -0
- package/dist/task-notes.d.ts +85 -0
- package/dist/transcription-recordings.d.ts +2 -2
- package/dist/turn-initiator.d.ts +2 -2
- package/dist/video-generation.d.ts +225 -0
- package/dist/video-generation.js +68 -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/dist/xai-subscription.d.ts +326 -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_browser_state_transfer_hardening.sql +560 -0
- package/drizzle/0212_slack_installation_bindings.sql +348 -0
- package/drizzle/0213_browser_interaction_authority.sql +67 -0
- package/drizzle/0213_slack_user_link_access_requests.sql +177 -0
- package/drizzle/0214_browser_download_saves.sql +34 -0
- package/drizzle/0214_session_activity_commit_gate.sql +262 -0
- package/drizzle/0215_browser_controller_host.sql +385 -0
- package/drizzle/0215_capabilities_platform.sql +980 -0
- package/drizzle/0216_browser_auth_health_evidence.sql +137 -0
- package/drizzle/0216_pack_component_ownership.sql +235 -0
- package/drizzle/0217_capability_definition_delete_authority.sql +32 -0
- package/drizzle/0217_external_browser_auth_operations.sql +22 -0
- package/drizzle/0218_organization_tenancy_foundation.sql +299 -0
- package/drizzle/0219_organization_tenancy_managed_human_provisioning.sql +317 -0
- package/drizzle/0219_site_auth_maintenance_sessions.sql +234 -0
- package/drizzle/0220_memory_slack_append_only_cascade.sql +38 -0
- package/drizzle/0220_session_channels.sql +54 -0
- package/drizzle/0221_sessions_channel_index.sql +5 -0
- package/drizzle/0222_session_visibility_authority_epochs.sql +158 -0
- package/drizzle/0222_sessions_channel_fk.sql +89 -0
- package/drizzle/0223_pending_tool_event_output.sql +55 -0
- package/drizzle/0223_sessions_channel_fk_validate.sql +118 -0
- package/drizzle/0224_slack_post_outcome_reconciliation.sql +123 -0
- package/drizzle/0225_session_visibility_fork_activation.sql +1363 -0
- package/drizzle/0226_personal_codex_authority_foundation.sql +365 -0
- package/drizzle/0227_slack_native_actions.sql +145 -0
- package/drizzle/0228_interaction_controller_data_plane.sql +14 -0
- package/drizzle/0228_slack_task_policy.sql +400 -0
- package/drizzle/0229_slack_inbox_file_fact.sql +10 -0
- package/drizzle/0230_user_scoped_variable_sets_rigs.sql +84 -0
- package/drizzle/0231_integration_definition_identity_cutover.sql +271 -0
- package/drizzle/0232_integration_facet_authority_cutover.sql +610 -0
- package/drizzle/0233_skill_and_integration_authority_cutover.sql +1162 -0
- package/drizzle/0234_xai_subscription_authority.sql +1139 -0
- package/drizzle/0235_canonical_human_login_bindings.sql +920 -0
- package/drizzle/0236_browser_identity_lifecycle.sql +39 -0
- package/drizzle/0236_session_visibility_slack_policy.sql +72 -0
- package/drizzle/0237_interaction_transition_reaper.sql +263 -0
- package/drizzle/0238_goal_persistence_policy.sql +471 -0
- package/drizzle/0238_supergrok_realtime_model.sql +29 -0
- package/drizzle/0239_supergrok_video_funding.sql +50 -0
- package/drizzle/0239_task_tree_notes.sql +990 -0
- package/package.json +26 -5
- package/src/attached-browser-devices.ts +371 -0
- package/src/attempt-tool-catalogs.ts +131 -0
- package/src/browser-auth.ts +4373 -0
- package/src/browser-downloads.ts +517 -0
- package/src/browser-identities.ts +1385 -0
- package/src/browser-sessions.ts +2294 -0
- package/src/browser-state-artifacts.ts +449 -0
- package/src/canonical-human-identities.ts +317 -0
- package/src/capability-components.ts +166 -0
- package/src/capability-integrations.ts +1571 -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 +468 -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/generated-images.ts +740 -0
- package/src/index.ts +9963 -1465
- package/src/insights.ts +251 -23
- package/src/integration-bindings.ts +455 -0
- package/src/integration-facets.ts +862 -0
- package/src/interaction-revisions.ts +70 -0
- package/src/interaction-schema.ts +1606 -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 +886 -0
- package/src/persistence-errors.ts +4 -7
- package/src/plugin-packages.ts +1186 -0
- package/src/provision-roles.ts +534 -1
- package/src/runtime-posture-cli.ts +9 -0
- package/src/runtime-posture.ts +721 -5
- package/src/schema.ts +6270 -3159
- package/src/scoped-knowledge-schema.ts +1 -1
- package/src/scoped-knowledge.ts +655 -3
- package/src/session-control.ts +130 -42
- package/src/session-queue-commands.ts +265 -15
- 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-tenancy.ts +188 -0
- package/src/session-tool-call-settlement.ts +18 -3
- package/src/slack-task-policy-schema.ts +130 -0
- package/src/slack-task-policy.ts +341 -0
- package/src/slack-user-link-access.ts +702 -0
- package/src/task-notes-schema.ts +135 -0
- package/src/task-notes.ts +178 -0
- package/src/video-generation.ts +1530 -0
- package/src/workspace-artifacts.ts +1 -1
- package/src/workspace-learning-policy-schema.ts +139 -0
- package/src/workspace-learning-policy.ts +595 -0
- package/src/xai-subscription.ts +1887 -0
- package/dist/chunk-P6CUHXQZ.js +0 -7248
- package/dist/chunk-P6CUHXQZ.js.map +0 -1
- package/dist/chunk-VHBFHMPC.js +0 -1219
- package/dist/chunk-VHBFHMPC.js.map +0 -1
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { sql } from "drizzle-orm";
|
|
3
|
+
import {
|
|
4
|
+
rawRows,
|
|
5
|
+
type Database,
|
|
6
|
+
rlsContextForWorkspace,
|
|
7
|
+
withWorkspaceSubjectRls,
|
|
8
|
+
withWorkspaceSubjectSessionActivityRls,
|
|
9
|
+
} from "./database";
|
|
10
|
+
|
|
11
|
+
export type SessionTenancyVisibility = "user_private" | "workspace_shared";
|
|
12
|
+
|
|
13
|
+
export type TransitionSessionVisibilityInput = {
|
|
14
|
+
workspaceId: string;
|
|
15
|
+
sessionId: string;
|
|
16
|
+
actorSubjectId: string;
|
|
17
|
+
targetVisibility: SessionTenancyVisibility;
|
|
18
|
+
expectedAuthorityEpoch: number;
|
|
19
|
+
operationKey: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type TransitionSessionVisibilityResult = {
|
|
23
|
+
operationId: string;
|
|
24
|
+
visibility: SessionTenancyVisibility;
|
|
25
|
+
authorityEpoch: number;
|
|
26
|
+
ownerOrganizationMembershipId: string | null;
|
|
27
|
+
changed: boolean;
|
|
28
|
+
replay: boolean;
|
|
29
|
+
interruptedAttemptCount: number;
|
|
30
|
+
cancelledTurnCount: number;
|
|
31
|
+
cancelledUpdateCount: number;
|
|
32
|
+
pausedGoalCount: number;
|
|
33
|
+
revokedGrantCount: number;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export type ForkSessionContentInput = {
|
|
37
|
+
sourceWorkspaceId: string;
|
|
38
|
+
sourceSessionId: string;
|
|
39
|
+
actorSubjectId: string;
|
|
40
|
+
destinationWorkspaceId: string;
|
|
41
|
+
destinationVisibility: SessionTenancyVisibility;
|
|
42
|
+
operationKey: string;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export type ForkSessionContentResult = {
|
|
46
|
+
operationId: string;
|
|
47
|
+
sessionId: string;
|
|
48
|
+
workspaceId: string;
|
|
49
|
+
visibility: SessionTenancyVisibility;
|
|
50
|
+
authorityEpoch: number;
|
|
51
|
+
copiedHistoryItemCount: number;
|
|
52
|
+
replay: boolean;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export function canonicalSessionVisibilityTransitionHash(
|
|
56
|
+
input: Pick<
|
|
57
|
+
TransitionSessionVisibilityInput,
|
|
58
|
+
"sessionId" | "targetVisibility" | "expectedAuthorityEpoch"
|
|
59
|
+
>,
|
|
60
|
+
): string {
|
|
61
|
+
return createHash("sha256")
|
|
62
|
+
.update(
|
|
63
|
+
JSON.stringify({
|
|
64
|
+
version: 1,
|
|
65
|
+
sessionId: input.sessionId,
|
|
66
|
+
targetVisibility: input.targetVisibility,
|
|
67
|
+
expectedAuthorityEpoch: input.expectedAuthorityEpoch,
|
|
68
|
+
}),
|
|
69
|
+
)
|
|
70
|
+
.digest("hex");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function canonicalSessionForkHash(
|
|
74
|
+
input: Pick<
|
|
75
|
+
ForkSessionContentInput,
|
|
76
|
+
"sourceSessionId" | "destinationWorkspaceId" | "destinationVisibility"
|
|
77
|
+
>,
|
|
78
|
+
): string {
|
|
79
|
+
return createHash("sha256")
|
|
80
|
+
.update(
|
|
81
|
+
JSON.stringify({
|
|
82
|
+
version: 1,
|
|
83
|
+
sourceSessionId: input.sourceSessionId,
|
|
84
|
+
destinationWorkspaceId: input.destinationWorkspaceId,
|
|
85
|
+
destinationVisibility: input.destinationVisibility,
|
|
86
|
+
}),
|
|
87
|
+
)
|
|
88
|
+
.digest("hex");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function transitionSessionVisibility(
|
|
92
|
+
db: Database,
|
|
93
|
+
input: TransitionSessionVisibilityInput,
|
|
94
|
+
): Promise<TransitionSessionVisibilityResult> {
|
|
95
|
+
if (!Number.isSafeInteger(input.expectedAuthorityEpoch) || input.expectedAuthorityEpoch < 1) {
|
|
96
|
+
throw new Error("expectedAuthorityEpoch must be a positive safe integer");
|
|
97
|
+
}
|
|
98
|
+
if (!input.operationKey.trim()) throw new Error("operationKey must not be empty");
|
|
99
|
+
const requestHash = canonicalSessionVisibilityTransitionHash(input);
|
|
100
|
+
const { accountId } = await rlsContextForWorkspace(db, input.workspaceId);
|
|
101
|
+
return await withWorkspaceSubjectSessionActivityRls(
|
|
102
|
+
db,
|
|
103
|
+
input.workspaceId,
|
|
104
|
+
input.actorSubjectId,
|
|
105
|
+
async (scopedDb) => {
|
|
106
|
+
const rows = await rawRows<{
|
|
107
|
+
operationId: string;
|
|
108
|
+
visibility: SessionTenancyVisibility;
|
|
109
|
+
authorityEpoch: number;
|
|
110
|
+
ownerOrganizationMembershipId: string | null;
|
|
111
|
+
changed: boolean;
|
|
112
|
+
replay: boolean;
|
|
113
|
+
interruptedAttemptCount: number;
|
|
114
|
+
cancelledTurnCount: number;
|
|
115
|
+
cancelledUpdateCount: number;
|
|
116
|
+
pausedGoalCount: number;
|
|
117
|
+
revokedGrantCount: number;
|
|
118
|
+
}>(
|
|
119
|
+
scopedDb,
|
|
120
|
+
sql`select
|
|
121
|
+
operation_id as "operationId",
|
|
122
|
+
visibility,
|
|
123
|
+
authority_epoch as "authorityEpoch",
|
|
124
|
+
owner_organization_membership_id as "ownerOrganizationMembershipId",
|
|
125
|
+
changed,
|
|
126
|
+
replay,
|
|
127
|
+
interrupted_attempt_count as "interruptedAttemptCount",
|
|
128
|
+
cancelled_turn_count as "cancelledTurnCount",
|
|
129
|
+
cancelled_update_count as "cancelledUpdateCount",
|
|
130
|
+
paused_goal_count as "pausedGoalCount",
|
|
131
|
+
revoked_grant_count as "revokedGrantCount"
|
|
132
|
+
from transition_session_visibility(
|
|
133
|
+
${accountId}::uuid,
|
|
134
|
+
${input.workspaceId}::uuid,
|
|
135
|
+
${input.sessionId}::uuid,
|
|
136
|
+
${input.actorSubjectId},
|
|
137
|
+
${input.targetVisibility},
|
|
138
|
+
${input.expectedAuthorityEpoch},
|
|
139
|
+
${input.operationKey},
|
|
140
|
+
${requestHash}
|
|
141
|
+
)`,
|
|
142
|
+
);
|
|
143
|
+
const result = rows[0];
|
|
144
|
+
if (!result) throw new Error("Session visibility transition returned no result");
|
|
145
|
+
return result;
|
|
146
|
+
},
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export async function forkSessionContent(
|
|
151
|
+
db: Database,
|
|
152
|
+
input: ForkSessionContentInput,
|
|
153
|
+
): Promise<ForkSessionContentResult> {
|
|
154
|
+
if (!input.operationKey.trim()) throw new Error("operationKey must not be empty");
|
|
155
|
+
const { accountId } = await rlsContextForWorkspace(db, input.sourceWorkspaceId);
|
|
156
|
+
const requestHash = canonicalSessionForkHash(input);
|
|
157
|
+
return await withWorkspaceSubjectRls(
|
|
158
|
+
db,
|
|
159
|
+
input.sourceWorkspaceId,
|
|
160
|
+
input.actorSubjectId,
|
|
161
|
+
async (scopedDb) => {
|
|
162
|
+
const rows = await rawRows<ForkSessionContentResult>(
|
|
163
|
+
scopedDb,
|
|
164
|
+
sql`select
|
|
165
|
+
operation_id as "operationId",
|
|
166
|
+
session_id as "sessionId",
|
|
167
|
+
workspace_id as "workspaceId",
|
|
168
|
+
visibility,
|
|
169
|
+
authority_epoch as "authorityEpoch",
|
|
170
|
+
copied_history_item_count as "copiedHistoryItemCount",
|
|
171
|
+
replay
|
|
172
|
+
from fork_session_content(
|
|
173
|
+
${accountId}::uuid,
|
|
174
|
+
${input.sourceWorkspaceId}::uuid,
|
|
175
|
+
${input.sourceSessionId}::uuid,
|
|
176
|
+
${input.actorSubjectId},
|
|
177
|
+
${input.destinationWorkspaceId}::uuid,
|
|
178
|
+
${input.destinationVisibility},
|
|
179
|
+
${input.operationKey},
|
|
180
|
+
${requestHash}
|
|
181
|
+
)`,
|
|
182
|
+
);
|
|
183
|
+
const result = rows[0];
|
|
184
|
+
if (!result) throw new Error("Session fork returned no result");
|
|
185
|
+
return result;
|
|
186
|
+
},
|
|
187
|
+
);
|
|
188
|
+
}
|
|
@@ -4,6 +4,7 @@ import { and, asc, eq, sql } from "drizzle-orm";
|
|
|
4
4
|
import type { Database } from "./database";
|
|
5
5
|
import { fromPostgresLosslessJson, LOSSLESS_CONTENT_CODEC_VERSION } from "./lossless-json";
|
|
6
6
|
import * as schema from "./schema";
|
|
7
|
+
import { cancelUnacceptedVideoGenerationsForToolCallsInTransaction } from "./video-generation";
|
|
7
8
|
|
|
8
9
|
export const TOOL_RESULT_TYPE_BY_CALL_TYPE: Readonly<Record<string, string>> = {
|
|
9
10
|
function_call: "function_call_result",
|
|
@@ -134,6 +135,10 @@ export async function closePendingSessionToolCallsInTransaction(
|
|
|
134
135
|
row.resultItem === null
|
|
135
136
|
? null
|
|
136
137
|
: fromPostgresLosslessJson(row.resultItem, row.resultItemCodecVersion),
|
|
138
|
+
eventOutput:
|
|
139
|
+
row.eventOutput === null
|
|
140
|
+
? null
|
|
141
|
+
: fromPostgresLosslessJson(row.eventOutput, row.eventOutputCodecVersion),
|
|
137
142
|
}));
|
|
138
143
|
if (pending.length === 0) return { sequence: input.sequence, events: [], closed: 0 };
|
|
139
144
|
|
|
@@ -305,9 +310,11 @@ export async function closePendingSessionToolCallsInTransaction(
|
|
|
305
310
|
},
|
|
306
311
|
],
|
|
307
312
|
}
|
|
308
|
-
:
|
|
309
|
-
resolution.
|
|
310
|
-
resolution.call.resultItem)
|
|
313
|
+
: resolution.call.eventOutput !== null
|
|
314
|
+
? resolution.call.eventOutput.value
|
|
315
|
+
: ((resolution.existingResult?.item ?? resolution.call.resultItem)?.output ??
|
|
316
|
+
resolution.existingResult?.item ??
|
|
317
|
+
resolution.call.resultItem),
|
|
311
318
|
recovery: {
|
|
312
319
|
interrupted: resolution.interrupted,
|
|
313
320
|
outcome: resolution.interrupted ? "unknown" : "durable_result_found",
|
|
@@ -326,6 +333,14 @@ export async function closePendingSessionToolCallsInTransaction(
|
|
|
326
333
|
eventValues.length > 0
|
|
327
334
|
? await tx.insert(schema.sessionEvents).values(eventValues).returning()
|
|
328
335
|
: [];
|
|
336
|
+
await cancelUnacceptedVideoGenerationsForToolCallsInTransaction(tx, {
|
|
337
|
+
workspaceId: input.workspaceId,
|
|
338
|
+
sessionId: input.sessionId,
|
|
339
|
+
turnId: input.turnId,
|
|
340
|
+
toolCallIds: pending.map((call) => call.callId),
|
|
341
|
+
reason: input.reason,
|
|
342
|
+
now: input.now,
|
|
343
|
+
});
|
|
329
344
|
await tx
|
|
330
345
|
.delete(schema.sessionPendingToolCalls)
|
|
331
346
|
.where(
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import type { SlackTaskPolicyContent } from "@opengeni/contracts";
|
|
2
|
+
import { sql } from "drizzle-orm";
|
|
3
|
+
import {
|
|
4
|
+
bigint,
|
|
5
|
+
check,
|
|
6
|
+
index,
|
|
7
|
+
jsonb,
|
|
8
|
+
pgTable,
|
|
9
|
+
text,
|
|
10
|
+
timestamp,
|
|
11
|
+
uniqueIndex,
|
|
12
|
+
uuid,
|
|
13
|
+
} from "drizzle-orm/pg-core";
|
|
14
|
+
|
|
15
|
+
export const slackTaskPolicyRevisions = pgTable(
|
|
16
|
+
"slack_task_policy_revisions",
|
|
17
|
+
{
|
|
18
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
19
|
+
operationId: uuid("operation_id").notNull(),
|
|
20
|
+
requestFingerprint: text("request_fingerprint").notNull(),
|
|
21
|
+
accountId: uuid("account_id").notNull(),
|
|
22
|
+
workspaceId: uuid("workspace_id").notNull(),
|
|
23
|
+
revision: bigint("revision", { mode: "number" })
|
|
24
|
+
.notNull()
|
|
25
|
+
.default(sql`nextval('slack_task_policy_revision_seq')`),
|
|
26
|
+
policy: jsonb("policy").$type<SlackTaskPolicyContent>().notNull(),
|
|
27
|
+
policyHash: text("policy_hash").notNull(),
|
|
28
|
+
supersedesRevisionId: uuid("supersedes_revision_id"),
|
|
29
|
+
createdBySubjectId: text("created_by_subject_id").notNull(),
|
|
30
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
31
|
+
},
|
|
32
|
+
(table) => ({
|
|
33
|
+
workspaceOperation: uniqueIndex("slack_task_policy_revisions_workspace_operation_uq").on(
|
|
34
|
+
table.workspaceId,
|
|
35
|
+
table.operationId,
|
|
36
|
+
),
|
|
37
|
+
workspaceRevision: uniqueIndex("slack_task_policy_revisions_workspace_revision_uq").on(
|
|
38
|
+
table.workspaceId,
|
|
39
|
+
table.revision,
|
|
40
|
+
),
|
|
41
|
+
workspaceHistory: index("slack_task_policy_revisions_workspace_history_idx").on(
|
|
42
|
+
table.workspaceId,
|
|
43
|
+
table.revision,
|
|
44
|
+
),
|
|
45
|
+
receipt: check(
|
|
46
|
+
"slack_task_policy_revisions_receipt_chk",
|
|
47
|
+
sql`${table.requestFingerprint} ~ '^[0-9a-f]{64}$' and ${table.policyHash} ~ '^[0-9a-f]{64}$'`,
|
|
48
|
+
),
|
|
49
|
+
}),
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
export const slackTaskPolicyHeads = pgTable(
|
|
53
|
+
"slack_task_policy_heads",
|
|
54
|
+
{
|
|
55
|
+
accountId: uuid("account_id").notNull(),
|
|
56
|
+
workspaceId: uuid("workspace_id").primaryKey(),
|
|
57
|
+
revisionId: uuid("revision_id").notNull(),
|
|
58
|
+
revision: bigint("revision", { mode: "number" }).notNull(),
|
|
59
|
+
policyHash: text("policy_hash").notNull(),
|
|
60
|
+
activationVersion: bigint("activation_version", { mode: "number" }).notNull(),
|
|
61
|
+
activatedAt: timestamp("activated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
62
|
+
},
|
|
63
|
+
(table) => ({
|
|
64
|
+
account: index("slack_task_policy_heads_account_idx").on(table.accountId),
|
|
65
|
+
}),
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
export const slackTaskPolicyActivationEvents = pgTable(
|
|
69
|
+
"slack_task_policy_activation_events",
|
|
70
|
+
{
|
|
71
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
72
|
+
operationId: uuid("operation_id").notNull(),
|
|
73
|
+
requestFingerprint: text("request_fingerprint").notNull(),
|
|
74
|
+
accountId: uuid("account_id").notNull(),
|
|
75
|
+
workspaceId: uuid("workspace_id").notNull(),
|
|
76
|
+
activationVersion: bigint("activation_version", { mode: "number" }).notNull(),
|
|
77
|
+
oldRevisionId: uuid("old_revision_id"),
|
|
78
|
+
oldRevision: bigint("old_revision", { mode: "number" }),
|
|
79
|
+
oldPolicyHash: text("old_policy_hash"),
|
|
80
|
+
newRevisionId: uuid("new_revision_id").notNull(),
|
|
81
|
+
newRevision: bigint("new_revision", { mode: "number" }).notNull(),
|
|
82
|
+
newPolicyHash: text("new_policy_hash").notNull(),
|
|
83
|
+
actorSubjectId: text("actor_subject_id").notNull(),
|
|
84
|
+
reason: text("reason").notNull(),
|
|
85
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
86
|
+
},
|
|
87
|
+
(table) => ({
|
|
88
|
+
workspaceOperation: uniqueIndex("slack_task_policy_events_workspace_operation_uq").on(
|
|
89
|
+
table.workspaceId,
|
|
90
|
+
table.operationId,
|
|
91
|
+
),
|
|
92
|
+
workspaceVersion: uniqueIndex("slack_task_policy_events_workspace_version_uq").on(
|
|
93
|
+
table.workspaceId,
|
|
94
|
+
table.activationVersion,
|
|
95
|
+
),
|
|
96
|
+
workspaceTimeline: index("slack_task_policy_events_workspace_time_idx").on(
|
|
97
|
+
table.workspaceId,
|
|
98
|
+
table.createdAt,
|
|
99
|
+
table.id,
|
|
100
|
+
),
|
|
101
|
+
}),
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
export const slackSharedTaskOrigins = pgTable(
|
|
105
|
+
"slack_shared_task_origins",
|
|
106
|
+
{
|
|
107
|
+
interactionId: uuid("interaction_id").primaryKey(),
|
|
108
|
+
accountId: uuid("account_id").notNull(),
|
|
109
|
+
workspaceId: uuid("workspace_id").notNull(),
|
|
110
|
+
connectionId: uuid("connection_id").notNull(),
|
|
111
|
+
sessionId: uuid("session_id").notNull(),
|
|
112
|
+
slackTeamId: text("slack_team_id").notNull(),
|
|
113
|
+
sourceChannelId: text("source_channel_id").notNull(),
|
|
114
|
+
sourceThreadTs: text("source_thread_ts").notNull(),
|
|
115
|
+
initiatingSlackUserId: text("initiating_slack_user_id").notNull(),
|
|
116
|
+
policyRevisionId: uuid("policy_revision_id").notNull(),
|
|
117
|
+
policyHash: text("policy_hash").notNull(),
|
|
118
|
+
policyActivationVersion: bigint("policy_activation_version", { mode: "number" }).notNull(),
|
|
119
|
+
publicationMode: text("publication_mode")
|
|
120
|
+
.$type<"never" | "approval_required" | "allow">()
|
|
121
|
+
.notNull(),
|
|
122
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
123
|
+
},
|
|
124
|
+
(table) => ({
|
|
125
|
+
workspaceSession: uniqueIndex("slack_shared_task_origins_workspace_session_uq").on(
|
|
126
|
+
table.workspaceId,
|
|
127
|
+
table.sessionId,
|
|
128
|
+
),
|
|
129
|
+
}),
|
|
130
|
+
);
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
SlackTaskPolicyContent,
|
|
4
|
+
canonicalizeSlackTaskPolicy,
|
|
5
|
+
type SlackTaskPolicyActivationEvent,
|
|
6
|
+
type SlackTaskPolicyHead,
|
|
7
|
+
type SlackTaskPolicyListResponse,
|
|
8
|
+
type SlackTaskPolicyMutationResponse,
|
|
9
|
+
type SlackTaskPolicyRevision,
|
|
10
|
+
} from "@opengeni/contracts";
|
|
11
|
+
import { and, desc, eq, sql } from "drizzle-orm";
|
|
12
|
+
import type { Database } from "./database";
|
|
13
|
+
import { rawRows, withRlsContext } from "./database";
|
|
14
|
+
import { nestedPostgresSqlState } from "./persistence-errors";
|
|
15
|
+
import * as schema from "./schema";
|
|
16
|
+
|
|
17
|
+
type RevisionRow = typeof schema.slackTaskPolicyRevisions.$inferSelect;
|
|
18
|
+
type HeadRow = typeof schema.slackTaskPolicyHeads.$inferSelect;
|
|
19
|
+
type EventRow = typeof schema.slackTaskPolicyActivationEvents.$inferSelect;
|
|
20
|
+
type OriginRow = typeof schema.slackSharedTaskOrigins.$inferSelect;
|
|
21
|
+
|
|
22
|
+
export type SlackSharedTaskOrigin = {
|
|
23
|
+
interactionId: string;
|
|
24
|
+
accountId: string;
|
|
25
|
+
workspaceId: string;
|
|
26
|
+
connectionId: string;
|
|
27
|
+
sessionId: string;
|
|
28
|
+
slackTeamId: string;
|
|
29
|
+
sourceChannelId: string;
|
|
30
|
+
sourceThreadTs: string;
|
|
31
|
+
initiatingSlackUserId: string;
|
|
32
|
+
policyRevisionId: string;
|
|
33
|
+
policyHash: string;
|
|
34
|
+
policyActivationVersion: number;
|
|
35
|
+
publicationMode: "never" | "approval_required" | "allow";
|
|
36
|
+
createdAt: Date;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export class SlackTaskPolicyConflictError extends Error {
|
|
40
|
+
readonly name = "SlackTaskPolicyConflictError";
|
|
41
|
+
readonly code = "SLACK_TASK_POLICY_CONFLICT";
|
|
42
|
+
constructor(readonly currentHead: SlackTaskPolicyHead | null) {
|
|
43
|
+
super("The active Slack task policy changed in another request");
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class SlackTaskPolicyOperationReuseError extends Error {
|
|
48
|
+
readonly name = "SlackTaskPolicyOperationReuseError";
|
|
49
|
+
readonly code = "SLACK_TASK_POLICY_OPERATION_REUSED";
|
|
50
|
+
constructor() {
|
|
51
|
+
super("The Slack task-policy operation id was already used for another request");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export class SlackTaskPolicyInvalidOperationError extends Error {
|
|
56
|
+
readonly name = "SlackTaskPolicyInvalidOperationError";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export class SlackTaskPolicyAuthorityError extends Error {
|
|
60
|
+
readonly name = "SlackTaskPolicyAuthorityError";
|
|
61
|
+
|
|
62
|
+
constructor(message = "Slack task-policy administration requires exact workspace authority") {
|
|
63
|
+
super(message);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function iso(value: Date | string): string {
|
|
68
|
+
return (value instanceof Date ? value : new Date(value)).toISOString();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function revisionFromRow(row: RevisionRow): SlackTaskPolicyRevision {
|
|
72
|
+
return {
|
|
73
|
+
id: row.id,
|
|
74
|
+
operationId: row.operationId,
|
|
75
|
+
accountId: row.accountId,
|
|
76
|
+
workspaceId: row.workspaceId,
|
|
77
|
+
revision: row.revision,
|
|
78
|
+
policy: canonicalizeSlackTaskPolicy(SlackTaskPolicyContent.parse(row.policy)),
|
|
79
|
+
policyHash: row.policyHash,
|
|
80
|
+
supersedesRevisionId: row.supersedesRevisionId,
|
|
81
|
+
createdBySubjectId: row.createdBySubjectId,
|
|
82
|
+
createdAt: iso(row.createdAt),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function headFromRow(row: HeadRow): SlackTaskPolicyHead {
|
|
87
|
+
return {
|
|
88
|
+
accountId: row.accountId,
|
|
89
|
+
workspaceId: row.workspaceId,
|
|
90
|
+
revisionId: row.revisionId,
|
|
91
|
+
revision: row.revision,
|
|
92
|
+
policyHash: row.policyHash,
|
|
93
|
+
activationVersion: row.activationVersion,
|
|
94
|
+
activatedAt: iso(row.activatedAt),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function eventFromRow(row: EventRow): SlackTaskPolicyActivationEvent {
|
|
99
|
+
return {
|
|
100
|
+
id: row.id,
|
|
101
|
+
operationId: row.operationId,
|
|
102
|
+
accountId: row.accountId,
|
|
103
|
+
workspaceId: row.workspaceId,
|
|
104
|
+
activationVersion: row.activationVersion,
|
|
105
|
+
oldRevision:
|
|
106
|
+
row.oldRevisionId === null
|
|
107
|
+
? null
|
|
108
|
+
: { id: row.oldRevisionId, revision: row.oldRevision!, policyHash: row.oldPolicyHash! },
|
|
109
|
+
newRevision: {
|
|
110
|
+
id: row.newRevisionId,
|
|
111
|
+
revision: row.newRevision,
|
|
112
|
+
policyHash: row.newPolicyHash,
|
|
113
|
+
},
|
|
114
|
+
actorSubjectId: row.actorSubjectId,
|
|
115
|
+
reason: row.reason,
|
|
116
|
+
createdAt: iso(row.createdAt),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function currentSlackTaskPolicyHead(
|
|
121
|
+
db: Database,
|
|
122
|
+
accountId: string,
|
|
123
|
+
workspaceId: string,
|
|
124
|
+
): Promise<SlackTaskPolicyHead | null> {
|
|
125
|
+
return (await getActiveSlackTaskPolicy(db, { accountId, workspaceId }))?.head ?? null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export async function getActiveSlackTaskPolicy(
|
|
129
|
+
db: Database,
|
|
130
|
+
input: { accountId: string; workspaceId: string },
|
|
131
|
+
): Promise<{ head: SlackTaskPolicyHead; revision: SlackTaskPolicyRevision } | null> {
|
|
132
|
+
return await withRlsContext(db, input, async (scopedDb) => {
|
|
133
|
+
const [row] = await scopedDb
|
|
134
|
+
.select({
|
|
135
|
+
head: schema.slackTaskPolicyHeads,
|
|
136
|
+
revision: schema.slackTaskPolicyRevisions,
|
|
137
|
+
})
|
|
138
|
+
.from(schema.slackTaskPolicyHeads)
|
|
139
|
+
.innerJoin(
|
|
140
|
+
schema.slackTaskPolicyRevisions,
|
|
141
|
+
and(
|
|
142
|
+
eq(schema.slackTaskPolicyRevisions.id, schema.slackTaskPolicyHeads.revisionId),
|
|
143
|
+
eq(schema.slackTaskPolicyRevisions.workspaceId, schema.slackTaskPolicyHeads.workspaceId),
|
|
144
|
+
),
|
|
145
|
+
)
|
|
146
|
+
.where(
|
|
147
|
+
and(
|
|
148
|
+
eq(schema.slackTaskPolicyHeads.accountId, input.accountId),
|
|
149
|
+
eq(schema.slackTaskPolicyHeads.workspaceId, input.workspaceId),
|
|
150
|
+
),
|
|
151
|
+
)
|
|
152
|
+
.limit(1);
|
|
153
|
+
return row ? { head: headFromRow(row.head), revision: revisionFromRow(row.revision) } : null;
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export async function listSlackTaskPolicy(
|
|
158
|
+
db: Database,
|
|
159
|
+
input: { accountId: string; workspaceId: string; limit?: number },
|
|
160
|
+
): Promise<SlackTaskPolicyListResponse> {
|
|
161
|
+
return await withRlsContext(db, input, async (scopedDb) => {
|
|
162
|
+
const limit = Math.max(1, Math.min(100, input.limit ?? 50));
|
|
163
|
+
const [active, revisions, events] = await Promise.all([
|
|
164
|
+
getActiveSlackTaskPolicy(scopedDb, input),
|
|
165
|
+
scopedDb
|
|
166
|
+
.select()
|
|
167
|
+
.from(schema.slackTaskPolicyRevisions)
|
|
168
|
+
.where(eq(schema.slackTaskPolicyRevisions.workspaceId, input.workspaceId))
|
|
169
|
+
.orderBy(desc(schema.slackTaskPolicyRevisions.revision))
|
|
170
|
+
.limit(limit),
|
|
171
|
+
scopedDb
|
|
172
|
+
.select()
|
|
173
|
+
.from(schema.slackTaskPolicyActivationEvents)
|
|
174
|
+
.where(eq(schema.slackTaskPolicyActivationEvents.workspaceId, input.workspaceId))
|
|
175
|
+
.orderBy(desc(schema.slackTaskPolicyActivationEvents.activationVersion))
|
|
176
|
+
.limit(limit),
|
|
177
|
+
]);
|
|
178
|
+
return {
|
|
179
|
+
current: active?.head ?? null,
|
|
180
|
+
activeRevision: active?.revision ?? null,
|
|
181
|
+
revisions: revisions.map(revisionFromRow),
|
|
182
|
+
activationEvents: events.map(eventFromRow),
|
|
183
|
+
};
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export async function updateSlackTaskPolicy(
|
|
188
|
+
db: Database,
|
|
189
|
+
input: {
|
|
190
|
+
operationId?: string;
|
|
191
|
+
accountId: string;
|
|
192
|
+
workspaceId: string;
|
|
193
|
+
policy: SlackTaskPolicyContent;
|
|
194
|
+
expectedCurrentRevisionId: string | null;
|
|
195
|
+
expectedActivationVersion: number;
|
|
196
|
+
actorSubjectId: string;
|
|
197
|
+
principalKind: "human_session" | undefined;
|
|
198
|
+
reason: string;
|
|
199
|
+
},
|
|
200
|
+
): Promise<SlackTaskPolicyMutationResponse> {
|
|
201
|
+
if (input.principalKind !== "human_session" || input.actorSubjectId.trim().length === 0) {
|
|
202
|
+
throw new SlackTaskPolicyAuthorityError(
|
|
203
|
+
"Slack task-policy changes require an exact authenticated human actor",
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
const operationId = input.operationId ?? randomUUID();
|
|
207
|
+
const policy = canonicalizeSlackTaskPolicy(input.policy);
|
|
208
|
+
const requestFingerprint = createHash("sha256")
|
|
209
|
+
.update(
|
|
210
|
+
JSON.stringify([
|
|
211
|
+
"slack_task_policy_operation",
|
|
212
|
+
1,
|
|
213
|
+
input.accountId,
|
|
214
|
+
input.workspaceId,
|
|
215
|
+
policy,
|
|
216
|
+
input.expectedCurrentRevisionId,
|
|
217
|
+
input.expectedActivationVersion,
|
|
218
|
+
input.actorSubjectId,
|
|
219
|
+
input.reason,
|
|
220
|
+
]),
|
|
221
|
+
"utf8",
|
|
222
|
+
)
|
|
223
|
+
.digest("hex");
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
return await withRlsContext(db, input, async (scopedDb) => {
|
|
227
|
+
await scopedDb.execute(
|
|
228
|
+
sql`select set_config('opengeni.subject_id', ${input.actorSubjectId}, true)`,
|
|
229
|
+
);
|
|
230
|
+
await scopedDb.execute(
|
|
231
|
+
sql`select set_config('opengeni.principal_kind', 'human_session', true)`,
|
|
232
|
+
);
|
|
233
|
+
const [receipt] = await rawRows<{ revisionId: string; eventId: string }>(
|
|
234
|
+
scopedDb,
|
|
235
|
+
sql`select revision_id as "revisionId", event_id as "eventId"
|
|
236
|
+
from slack_task_policy_update(
|
|
237
|
+
${operationId}::uuid,
|
|
238
|
+
${requestFingerprint},
|
|
239
|
+
${input.accountId}::uuid,
|
|
240
|
+
${input.workspaceId}::uuid,
|
|
241
|
+
${JSON.stringify(policy)}::jsonb,
|
|
242
|
+
${input.expectedCurrentRevisionId}::uuid,
|
|
243
|
+
${input.expectedActivationVersion}::bigint,
|
|
244
|
+
${input.actorSubjectId},
|
|
245
|
+
${input.reason}
|
|
246
|
+
)`,
|
|
247
|
+
);
|
|
248
|
+
if (!receipt) throw new Error("Slack task-policy update returned no lifecycle receipt");
|
|
249
|
+
const [[revision], [head], [event]] = await Promise.all([
|
|
250
|
+
scopedDb
|
|
251
|
+
.select()
|
|
252
|
+
.from(schema.slackTaskPolicyRevisions)
|
|
253
|
+
.where(eq(schema.slackTaskPolicyRevisions.id, receipt.revisionId))
|
|
254
|
+
.limit(1),
|
|
255
|
+
scopedDb
|
|
256
|
+
.select()
|
|
257
|
+
.from(schema.slackTaskPolicyHeads)
|
|
258
|
+
.where(eq(schema.slackTaskPolicyHeads.workspaceId, input.workspaceId))
|
|
259
|
+
.limit(1),
|
|
260
|
+
scopedDb
|
|
261
|
+
.select()
|
|
262
|
+
.from(schema.slackTaskPolicyActivationEvents)
|
|
263
|
+
.where(eq(schema.slackTaskPolicyActivationEvents.id, receipt.eventId))
|
|
264
|
+
.limit(1),
|
|
265
|
+
]);
|
|
266
|
+
if (!revision || !head || !event) {
|
|
267
|
+
throw new Error("Slack task-policy lifecycle receipt was incomplete");
|
|
268
|
+
}
|
|
269
|
+
return {
|
|
270
|
+
revision: revisionFromRow(revision),
|
|
271
|
+
head: headFromRow(head),
|
|
272
|
+
event: eventFromRow(event),
|
|
273
|
+
};
|
|
274
|
+
});
|
|
275
|
+
} catch (error) {
|
|
276
|
+
const state = nestedPostgresSqlState(error);
|
|
277
|
+
if (state === "P1471") throw new SlackTaskPolicyOperationReuseError();
|
|
278
|
+
if (state === "40001") {
|
|
279
|
+
throw new SlackTaskPolicyConflictError(
|
|
280
|
+
await currentSlackTaskPolicyHead(db, input.accountId, input.workspaceId),
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
if (state === "42501") {
|
|
284
|
+
throw new SlackTaskPolicyAuthorityError(error instanceof Error ? error.message : undefined);
|
|
285
|
+
}
|
|
286
|
+
if (state === "22023") throw new SlackTaskPolicyInvalidOperationError();
|
|
287
|
+
throw error;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function originFromRow(row: OriginRow): SlackSharedTaskOrigin {
|
|
292
|
+
return { ...row };
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export async function saveSlackSharedTaskOrigin(
|
|
296
|
+
db: Database,
|
|
297
|
+
input: Omit<SlackSharedTaskOrigin, "createdAt">,
|
|
298
|
+
): Promise<SlackSharedTaskOrigin> {
|
|
299
|
+
return await withRlsContext(db, input, async (scopedDb) => {
|
|
300
|
+
const [created] = await scopedDb
|
|
301
|
+
.insert(schema.slackSharedTaskOrigins)
|
|
302
|
+
.values(input)
|
|
303
|
+
.onConflictDoNothing({ target: schema.slackSharedTaskOrigins.interactionId })
|
|
304
|
+
.returning();
|
|
305
|
+
if (created) return originFromRow(created);
|
|
306
|
+
const [existing] = await scopedDb
|
|
307
|
+
.select()
|
|
308
|
+
.from(schema.slackSharedTaskOrigins)
|
|
309
|
+
.where(eq(schema.slackSharedTaskOrigins.interactionId, input.interactionId))
|
|
310
|
+
.limit(1);
|
|
311
|
+
if (!existing) throw new Error("Slack shared-task origin conflict could not be resolved");
|
|
312
|
+
for (const [key, value] of Object.entries(input)) {
|
|
313
|
+
if (existing[key as keyof OriginRow] !== value) {
|
|
314
|
+
throw new SlackTaskPolicyInvalidOperationError(
|
|
315
|
+
"Slack shared-task origin identity conflicted with immutable evidence",
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return originFromRow(existing);
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export async function getSlackSharedTaskOrigin(
|
|
324
|
+
db: Database,
|
|
325
|
+
input: { accountId: string; workspaceId: string; interactionId: string },
|
|
326
|
+
): Promise<SlackSharedTaskOrigin | null> {
|
|
327
|
+
return await withRlsContext(db, input, async (scopedDb) => {
|
|
328
|
+
const [row] = await scopedDb
|
|
329
|
+
.select()
|
|
330
|
+
.from(schema.slackSharedTaskOrigins)
|
|
331
|
+
.where(
|
|
332
|
+
and(
|
|
333
|
+
eq(schema.slackSharedTaskOrigins.accountId, input.accountId),
|
|
334
|
+
eq(schema.slackSharedTaskOrigins.workspaceId, input.workspaceId),
|
|
335
|
+
eq(schema.slackSharedTaskOrigins.interactionId, input.interactionId),
|
|
336
|
+
),
|
|
337
|
+
)
|
|
338
|
+
.limit(1);
|
|
339
|
+
return row ? originFromRow(row) : null;
|
|
340
|
+
});
|
|
341
|
+
}
|