@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,801 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
stableJson,
|
|
4
|
+
type MemorySlackPublication,
|
|
5
|
+
type MemorySlackPublicationConfiguration,
|
|
6
|
+
type MemorySlackPublicationReceipt,
|
|
7
|
+
type MemorySlackPublicationReceiptKind,
|
|
8
|
+
type MemorySlackPublicationState,
|
|
9
|
+
} from "@opengeni/contracts";
|
|
10
|
+
import { and, asc, desc, eq, inArray, sql } from "drizzle-orm";
|
|
11
|
+
import { withWorkspaceRls, type Database } from "./database";
|
|
12
|
+
import * as schema from "./schema";
|
|
13
|
+
|
|
14
|
+
export type MemorySlackPublicationActor = {
|
|
15
|
+
kind: "human" | "agent" | "service";
|
|
16
|
+
subjectId: string;
|
|
17
|
+
initiatingHumanSubjectId?: string | null;
|
|
18
|
+
sessionId?: string | null;
|
|
19
|
+
turnId?: string | null;
|
|
20
|
+
attemptId?: string | null;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type WorkspaceMemorySlackPublicationSnapshot = {
|
|
24
|
+
accountId: string;
|
|
25
|
+
workspaceId: string;
|
|
26
|
+
id: string;
|
|
27
|
+
version: number;
|
|
28
|
+
status: string;
|
|
29
|
+
kind: string;
|
|
30
|
+
scopeType: string;
|
|
31
|
+
namespace: string;
|
|
32
|
+
labels: string[];
|
|
33
|
+
validFrom: string;
|
|
34
|
+
validUntil: string | null;
|
|
35
|
+
supersedesId: string | null;
|
|
36
|
+
supersededById: string | null;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export async function getWorkspaceMemorySlackPublicationSnapshot(
|
|
40
|
+
db: Database,
|
|
41
|
+
workspaceId: string,
|
|
42
|
+
memoryId: string,
|
|
43
|
+
): Promise<WorkspaceMemorySlackPublicationSnapshot | null> {
|
|
44
|
+
return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
|
|
45
|
+
const [row] = await scopedDb
|
|
46
|
+
.select({
|
|
47
|
+
accountId: schema.knowledgeMemories.accountId,
|
|
48
|
+
workspaceId: schema.knowledgeMemories.workspaceId,
|
|
49
|
+
id: schema.knowledgeMemories.id,
|
|
50
|
+
version: schema.knowledgeMemories.memoryVersion,
|
|
51
|
+
status: schema.knowledgeMemories.status,
|
|
52
|
+
kind: schema.knowledgeMemories.kind,
|
|
53
|
+
scopeType: schema.knowledgeMemories.scopeType,
|
|
54
|
+
namespace: schema.knowledgeMemories.namespace,
|
|
55
|
+
labels: schema.knowledgeMemories.labels,
|
|
56
|
+
validFrom: schema.knowledgeMemories.validFrom,
|
|
57
|
+
validUntil: schema.knowledgeMemories.validUntil,
|
|
58
|
+
supersedesId: schema.knowledgeMemories.supersedesId,
|
|
59
|
+
supersededById: schema.knowledgeMemories.supersededById,
|
|
60
|
+
})
|
|
61
|
+
.from(schema.knowledgeMemories)
|
|
62
|
+
.where(
|
|
63
|
+
and(
|
|
64
|
+
eq(schema.knowledgeMemories.workspaceId, workspaceId),
|
|
65
|
+
eq(schema.knowledgeMemories.id, memoryId),
|
|
66
|
+
),
|
|
67
|
+
)
|
|
68
|
+
.limit(1);
|
|
69
|
+
return row
|
|
70
|
+
? {
|
|
71
|
+
...row,
|
|
72
|
+
labels: [...row.labels],
|
|
73
|
+
validFrom: row.validFrom.toISOString(),
|
|
74
|
+
validUntil: row.validUntil?.toISOString() ?? null,
|
|
75
|
+
}
|
|
76
|
+
: null;
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export type MemorySlackPublicationConfigurationInput = {
|
|
81
|
+
accountId: string;
|
|
82
|
+
workspaceId: string;
|
|
83
|
+
expectedRevision: number;
|
|
84
|
+
enabled: boolean;
|
|
85
|
+
connectionId: string | null;
|
|
86
|
+
slackTeamId: string | null;
|
|
87
|
+
slackChannelId: string | null;
|
|
88
|
+
slackChannelName: string | null;
|
|
89
|
+
autoImportances: Array<"major" | "normal" | "minor">;
|
|
90
|
+
reviewImportances: Array<"major" | "normal" | "minor">;
|
|
91
|
+
subjectId: string;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export class MemorySlackPublicationRevisionConflictError extends Error {
|
|
95
|
+
constructor(readonly currentRevision: number) {
|
|
96
|
+
super(`Memory Slack publication configuration moved to revision ${currentRevision}`);
|
|
97
|
+
this.name = "MemorySlackPublicationRevisionConflictError";
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export async function listMemorySlackPublicationConfigurations(
|
|
102
|
+
db: Database,
|
|
103
|
+
workspaceId: string,
|
|
104
|
+
limit = 25,
|
|
105
|
+
): Promise<MemorySlackPublicationConfiguration[]> {
|
|
106
|
+
return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
|
|
107
|
+
const rows = await scopedDb
|
|
108
|
+
.select()
|
|
109
|
+
.from(schema.memorySlackPublicationConfigurations)
|
|
110
|
+
.where(eq(schema.memorySlackPublicationConfigurations.workspaceId, workspaceId))
|
|
111
|
+
.orderBy(desc(schema.memorySlackPublicationConfigurations.revision))
|
|
112
|
+
.limit(Math.min(Math.max(limit, 1), 100));
|
|
113
|
+
return rows.map(mapConfiguration);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export async function getCurrentMemorySlackPublicationConfiguration(
|
|
118
|
+
db: Database,
|
|
119
|
+
workspaceId: string,
|
|
120
|
+
): Promise<MemorySlackPublicationConfiguration | null> {
|
|
121
|
+
const configurations = await listMemorySlackPublicationConfigurations(db, workspaceId, 1);
|
|
122
|
+
return configurations[0] ?? null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export async function createMemorySlackPublicationConfiguration(
|
|
126
|
+
db: Database,
|
|
127
|
+
input: MemorySlackPublicationConfigurationInput,
|
|
128
|
+
): Promise<MemorySlackPublicationConfiguration> {
|
|
129
|
+
return await withWorkspaceRls(db, input.workspaceId, async (scopedDb) => {
|
|
130
|
+
await scopedDb
|
|
131
|
+
.select({ id: schema.workspaces.id })
|
|
132
|
+
.from(schema.workspaces)
|
|
133
|
+
.where(eq(schema.workspaces.id, input.workspaceId))
|
|
134
|
+
.for("update");
|
|
135
|
+
const [current] = await scopedDb
|
|
136
|
+
.select({ revision: schema.memorySlackPublicationConfigurations.revision })
|
|
137
|
+
.from(schema.memorySlackPublicationConfigurations)
|
|
138
|
+
.where(eq(schema.memorySlackPublicationConfigurations.workspaceId, input.workspaceId))
|
|
139
|
+
.orderBy(desc(schema.memorySlackPublicationConfigurations.revision))
|
|
140
|
+
.limit(1);
|
|
141
|
+
const currentRevision = current?.revision ?? 0;
|
|
142
|
+
if (currentRevision !== input.expectedRevision) {
|
|
143
|
+
throw new MemorySlackPublicationRevisionConflictError(currentRevision);
|
|
144
|
+
}
|
|
145
|
+
const [created] = await scopedDb
|
|
146
|
+
.insert(schema.memorySlackPublicationConfigurations)
|
|
147
|
+
.values({
|
|
148
|
+
accountId: input.accountId,
|
|
149
|
+
workspaceId: input.workspaceId,
|
|
150
|
+
revision: currentRevision + 1,
|
|
151
|
+
enabled: input.enabled,
|
|
152
|
+
connectionId: input.connectionId,
|
|
153
|
+
slackTeamId: input.slackTeamId,
|
|
154
|
+
slackChannelId: input.slackChannelId,
|
|
155
|
+
slackChannelName: input.slackChannelName,
|
|
156
|
+
autoImportances: normalizeImportances(input.autoImportances),
|
|
157
|
+
reviewImportances: normalizeImportances(input.reviewImportances),
|
|
158
|
+
createdBySubjectId: boundedSubject(input.subjectId),
|
|
159
|
+
})
|
|
160
|
+
.returning();
|
|
161
|
+
if (!created) throw new Error("Failed to create Memory Slack publication configuration");
|
|
162
|
+
return mapConfiguration(created);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export type EnqueueMemorySlackPublicationInput = {
|
|
167
|
+
accountId: string;
|
|
168
|
+
workspaceId: string;
|
|
169
|
+
sourceType: "workspace_memory" | "durable_learning";
|
|
170
|
+
sourceId: string;
|
|
171
|
+
sourceVersion?: string | null;
|
|
172
|
+
sourceIdempotencyKey: string;
|
|
173
|
+
projection: Record<string, unknown>;
|
|
174
|
+
importance: "major" | "normal" | "minor";
|
|
175
|
+
deliveryMode: "auto" | "review";
|
|
176
|
+
actor: MemorySlackPublicationActor;
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
export type EnqueueMemorySlackPublicationResult =
|
|
180
|
+
| { kind: "not_configured" }
|
|
181
|
+
| { kind: "disabled"; configuration: MemorySlackPublicationConfiguration }
|
|
182
|
+
| { kind: "enqueued" | "replayed"; publication: MemorySlackPublication };
|
|
183
|
+
|
|
184
|
+
export async function enqueueMemorySlackPublication(
|
|
185
|
+
db: Database,
|
|
186
|
+
input: EnqueueMemorySlackPublicationInput,
|
|
187
|
+
): Promise<EnqueueMemorySlackPublicationResult> {
|
|
188
|
+
return await withWorkspaceRls(db, input.workspaceId, async (scopedDb) => {
|
|
189
|
+
const [configurationRow] = await scopedDb
|
|
190
|
+
.select()
|
|
191
|
+
.from(schema.memorySlackPublicationConfigurations)
|
|
192
|
+
.where(eq(schema.memorySlackPublicationConfigurations.workspaceId, input.workspaceId))
|
|
193
|
+
.orderBy(desc(schema.memorySlackPublicationConfigurations.revision))
|
|
194
|
+
.limit(1);
|
|
195
|
+
if (!configurationRow) return { kind: "not_configured" };
|
|
196
|
+
const configuration = mapConfiguration(configurationRow);
|
|
197
|
+
if (
|
|
198
|
+
!configuration.enabled ||
|
|
199
|
+
!configuration.connectionId ||
|
|
200
|
+
!configuration.slackTeamId ||
|
|
201
|
+
!configuration.slackChannelId
|
|
202
|
+
) {
|
|
203
|
+
return { kind: "disabled", configuration };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const projectionJson = stableJson(input.projection);
|
|
207
|
+
const projectionSha256 = createHash("sha256").update(projectionJson, "utf8").digest("hex");
|
|
208
|
+
const operationId = deterministicUuid(
|
|
209
|
+
`memory-slack:${input.workspaceId}:${input.sourceIdempotencyKey}`,
|
|
210
|
+
);
|
|
211
|
+
const state = input.deliveryMode === "auto" ? "queued" : "review_pending";
|
|
212
|
+
const actor = normalizeActor(input.actor);
|
|
213
|
+
const [created] = await scopedDb
|
|
214
|
+
.insert(schema.memorySlackPublications)
|
|
215
|
+
.values({
|
|
216
|
+
accountId: input.accountId,
|
|
217
|
+
workspaceId: input.workspaceId,
|
|
218
|
+
configurationId: configuration.id,
|
|
219
|
+
configurationRevision: configuration.revision,
|
|
220
|
+
connectionId: configuration.connectionId,
|
|
221
|
+
slackTeamId: configuration.slackTeamId,
|
|
222
|
+
slackChannelId: configuration.slackChannelId,
|
|
223
|
+
sourceType: input.sourceType,
|
|
224
|
+
sourceId: bounded(input.sourceId, 1_024, "publication source id"),
|
|
225
|
+
sourceVersion: input.sourceVersion
|
|
226
|
+
? bounded(input.sourceVersion, 512, "publication source version")
|
|
227
|
+
: null,
|
|
228
|
+
sourceIdempotencyKey: bounded(
|
|
229
|
+
input.sourceIdempotencyKey,
|
|
230
|
+
256,
|
|
231
|
+
"publication source idempotency key",
|
|
232
|
+
),
|
|
233
|
+
projection: input.projection,
|
|
234
|
+
projectionSha256,
|
|
235
|
+
importance: input.importance,
|
|
236
|
+
deliveryMode: input.deliveryMode,
|
|
237
|
+
state,
|
|
238
|
+
operationId,
|
|
239
|
+
initiatorKind: actor.kind,
|
|
240
|
+
initiatorSubjectId: actor.subjectId,
|
|
241
|
+
initiatingHumanSubjectId: actor.initiatingHumanSubjectId,
|
|
242
|
+
sessionId: actor.sessionId,
|
|
243
|
+
turnId: actor.turnId,
|
|
244
|
+
attemptId: actor.attemptId,
|
|
245
|
+
})
|
|
246
|
+
.onConflictDoNothing({
|
|
247
|
+
target: [
|
|
248
|
+
schema.memorySlackPublications.workspaceId,
|
|
249
|
+
schema.memorySlackPublications.sourceIdempotencyKey,
|
|
250
|
+
],
|
|
251
|
+
})
|
|
252
|
+
.returning();
|
|
253
|
+
|
|
254
|
+
let publicationRow = created;
|
|
255
|
+
if (!publicationRow) {
|
|
256
|
+
[publicationRow] = await scopedDb
|
|
257
|
+
.select()
|
|
258
|
+
.from(schema.memorySlackPublications)
|
|
259
|
+
.where(
|
|
260
|
+
and(
|
|
261
|
+
eq(schema.memorySlackPublications.workspaceId, input.workspaceId),
|
|
262
|
+
eq(schema.memorySlackPublications.sourceIdempotencyKey, input.sourceIdempotencyKey),
|
|
263
|
+
),
|
|
264
|
+
)
|
|
265
|
+
.limit(1);
|
|
266
|
+
if (!publicationRow) throw new Error("Memory Slack publication conflict disappeared");
|
|
267
|
+
if (
|
|
268
|
+
publicationRow.projectionSha256 !== projectionSha256 ||
|
|
269
|
+
publicationRow.sourceType !== input.sourceType ||
|
|
270
|
+
publicationRow.sourceId !== input.sourceId ||
|
|
271
|
+
publicationRow.sourceVersion !== (input.sourceVersion ?? null) ||
|
|
272
|
+
publicationRow.importance !== input.importance ||
|
|
273
|
+
publicationRow.initiatorKind !== actor.kind ||
|
|
274
|
+
publicationRow.initiatorSubjectId !== actor.subjectId ||
|
|
275
|
+
publicationRow.initiatingHumanSubjectId !== actor.initiatingHumanSubjectId ||
|
|
276
|
+
publicationRow.sessionId !== actor.sessionId ||
|
|
277
|
+
publicationRow.turnId !== actor.turnId ||
|
|
278
|
+
publicationRow.attemptId !== actor.attemptId
|
|
279
|
+
) {
|
|
280
|
+
throw new Error("Memory Slack publication idempotency key was reused with different input");
|
|
281
|
+
}
|
|
282
|
+
return {
|
|
283
|
+
kind: "replayed",
|
|
284
|
+
publication: await mapPublicationWithReceipts(scopedDb, publicationRow),
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
await insertReceipt(scopedDb, publicationRow, {
|
|
289
|
+
kind: "enqueued",
|
|
290
|
+
state,
|
|
291
|
+
actorKind: actor.kind,
|
|
292
|
+
actorSubjectId: actor.subjectId,
|
|
293
|
+
});
|
|
294
|
+
return {
|
|
295
|
+
kind: "enqueued",
|
|
296
|
+
publication: await mapPublicationWithReceipts(scopedDb, publicationRow),
|
|
297
|
+
};
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export type ClaimedMemorySlackPublication = typeof schema.memorySlackPublications.$inferSelect;
|
|
302
|
+
|
|
303
|
+
export async function claimMemorySlackPublication(
|
|
304
|
+
db: Database,
|
|
305
|
+
claimHolderId: string,
|
|
306
|
+
claimLeaseMs: number,
|
|
307
|
+
): Promise<ClaimedMemorySlackPublication | null> {
|
|
308
|
+
const result = await db.execute(
|
|
309
|
+
sql`select * from opengeni_private.claim_memory_slack_publication(
|
|
310
|
+
${claimHolderId}::uuid,
|
|
311
|
+
${claimLeaseMs}
|
|
312
|
+
)`,
|
|
313
|
+
);
|
|
314
|
+
const row = rawRows(result)[0];
|
|
315
|
+
return row ? mapClaimedPublication(row) : null;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export async function currentMemorySlackConfigurationMatches(
|
|
319
|
+
db: Database,
|
|
320
|
+
publication: ClaimedMemorySlackPublication,
|
|
321
|
+
): Promise<boolean> {
|
|
322
|
+
return await withWorkspaceRls(db, publication.workspaceId, async (scopedDb) => {
|
|
323
|
+
const [current] = await scopedDb
|
|
324
|
+
.select()
|
|
325
|
+
.from(schema.memorySlackPublicationConfigurations)
|
|
326
|
+
.where(eq(schema.memorySlackPublicationConfigurations.workspaceId, publication.workspaceId))
|
|
327
|
+
.orderBy(desc(schema.memorySlackPublicationConfigurations.revision))
|
|
328
|
+
.limit(1);
|
|
329
|
+
return Boolean(
|
|
330
|
+
current?.enabled &&
|
|
331
|
+
current.revision === publication.configurationRevision &&
|
|
332
|
+
current.id === publication.configurationId &&
|
|
333
|
+
current.connectionId === publication.connectionId &&
|
|
334
|
+
current.slackTeamId === publication.slackTeamId &&
|
|
335
|
+
current.slackChannelId === publication.slackChannelId,
|
|
336
|
+
);
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export async function completeMemorySlackPublication(
|
|
341
|
+
db: Database,
|
|
342
|
+
input: {
|
|
343
|
+
publication: ClaimedMemorySlackPublication;
|
|
344
|
+
claimHolderId: string;
|
|
345
|
+
slackChannelId: string;
|
|
346
|
+
slackMessageTimestamp: string;
|
|
347
|
+
},
|
|
348
|
+
): Promise<boolean> {
|
|
349
|
+
return await transitionClaimedPublication(db, input.publication, input.claimHolderId, {
|
|
350
|
+
state: "delivered",
|
|
351
|
+
kind: "delivered",
|
|
352
|
+
actorKind: "service",
|
|
353
|
+
actorSubjectId: "memory-slack-delivery",
|
|
354
|
+
slackChannelId: input.slackChannelId,
|
|
355
|
+
slackMessageTimestamp: input.slackMessageTimestamp,
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
export async function deferMemorySlackPublication(
|
|
360
|
+
db: Database,
|
|
361
|
+
input: {
|
|
362
|
+
publication: ClaimedMemorySlackPublication;
|
|
363
|
+
claimHolderId: string;
|
|
364
|
+
retryAt: Date;
|
|
365
|
+
errorCode: string;
|
|
366
|
+
},
|
|
367
|
+
): Promise<boolean> {
|
|
368
|
+
return await transitionClaimedPublication(db, input.publication, input.claimHolderId, {
|
|
369
|
+
state: "retry_wait",
|
|
370
|
+
kind: "retry_scheduled",
|
|
371
|
+
actorKind: "service",
|
|
372
|
+
actorSubjectId: "memory-slack-delivery",
|
|
373
|
+
retryAt: input.retryAt,
|
|
374
|
+
errorCode: input.errorCode,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
export async function failMemorySlackPublication(
|
|
379
|
+
db: Database,
|
|
380
|
+
input: {
|
|
381
|
+
publication: ClaimedMemorySlackPublication;
|
|
382
|
+
claimHolderId: string;
|
|
383
|
+
errorCode: string;
|
|
384
|
+
cancelled?: boolean;
|
|
385
|
+
},
|
|
386
|
+
): Promise<boolean> {
|
|
387
|
+
return await transitionClaimedPublication(db, input.publication, input.claimHolderId, {
|
|
388
|
+
state: input.cancelled ? "cancelled" : "failed",
|
|
389
|
+
kind: input.cancelled ? "cancelled" : "failed",
|
|
390
|
+
actorKind: "service",
|
|
391
|
+
actorSubjectId: "memory-slack-delivery",
|
|
392
|
+
errorCode: input.errorCode,
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
export async function actOnMemorySlackPublication(
|
|
397
|
+
db: Database,
|
|
398
|
+
input: {
|
|
399
|
+
workspaceId: string;
|
|
400
|
+
publicationId: string;
|
|
401
|
+
expectedState: MemorySlackPublicationState;
|
|
402
|
+
action: "approve" | "reject" | "retry";
|
|
403
|
+
subjectId: string;
|
|
404
|
+
},
|
|
405
|
+
): Promise<MemorySlackPublication | null> {
|
|
406
|
+
return await withWorkspaceRls(db, input.workspaceId, async (scopedDb) => {
|
|
407
|
+
const [current] = await scopedDb
|
|
408
|
+
.select()
|
|
409
|
+
.from(schema.memorySlackPublications)
|
|
410
|
+
.where(
|
|
411
|
+
and(
|
|
412
|
+
eq(schema.memorySlackPublications.workspaceId, input.workspaceId),
|
|
413
|
+
eq(schema.memorySlackPublications.id, input.publicationId),
|
|
414
|
+
),
|
|
415
|
+
)
|
|
416
|
+
.for("update")
|
|
417
|
+
.limit(1);
|
|
418
|
+
if (!current || current.state !== input.expectedState) return null;
|
|
419
|
+
const transition = manualTransition(input.action, current.state);
|
|
420
|
+
if (!transition) return null;
|
|
421
|
+
const now = new Date();
|
|
422
|
+
const [updated] = await scopedDb
|
|
423
|
+
.update(schema.memorySlackPublications)
|
|
424
|
+
.set({
|
|
425
|
+
state: transition.state,
|
|
426
|
+
claimHolderId: null,
|
|
427
|
+
claimExpiresAt: null,
|
|
428
|
+
retryAt: null,
|
|
429
|
+
lastErrorCode: null,
|
|
430
|
+
...(transition.terminal ? { terminalAt: now } : { terminalAt: null }),
|
|
431
|
+
updatedAt: now,
|
|
432
|
+
})
|
|
433
|
+
.where(eq(schema.memorySlackPublications.id, current.id))
|
|
434
|
+
.returning();
|
|
435
|
+
if (!updated) return null;
|
|
436
|
+
await insertReceipt(scopedDb, updated, {
|
|
437
|
+
kind: transition.kind,
|
|
438
|
+
state: transition.state,
|
|
439
|
+
actorKind: "human",
|
|
440
|
+
actorSubjectId: boundedSubject(input.subjectId),
|
|
441
|
+
});
|
|
442
|
+
return await mapPublicationWithReceipts(scopedDb, updated);
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
export async function listMemorySlackPublications(
|
|
447
|
+
db: Database,
|
|
448
|
+
workspaceId: string,
|
|
449
|
+
options: { limit?: number; states?: MemorySlackPublicationState[] } = {},
|
|
450
|
+
): Promise<MemorySlackPublication[]> {
|
|
451
|
+
return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
|
|
452
|
+
const conditions = [eq(schema.memorySlackPublications.workspaceId, workspaceId)];
|
|
453
|
+
if (options.states?.length) {
|
|
454
|
+
conditions.push(inArray(schema.memorySlackPublications.state, options.states));
|
|
455
|
+
}
|
|
456
|
+
const rows = await scopedDb
|
|
457
|
+
.select()
|
|
458
|
+
.from(schema.memorySlackPublications)
|
|
459
|
+
.where(and(...conditions))
|
|
460
|
+
.orderBy(
|
|
461
|
+
desc(schema.memorySlackPublications.createdAt),
|
|
462
|
+
desc(schema.memorySlackPublications.id),
|
|
463
|
+
)
|
|
464
|
+
.limit(Math.min(Math.max(options.limit ?? 50, 1), 100));
|
|
465
|
+
return await Promise.all(rows.map((row) => mapPublicationWithReceipts(scopedDb, row)));
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
async function transitionClaimedPublication(
|
|
470
|
+
db: Database,
|
|
471
|
+
publication: ClaimedMemorySlackPublication,
|
|
472
|
+
claimHolderId: string,
|
|
473
|
+
transition: {
|
|
474
|
+
state: "retry_wait" | "delivered" | "failed" | "cancelled";
|
|
475
|
+
kind: "retry_scheduled" | "delivered" | "failed" | "cancelled";
|
|
476
|
+
actorKind: "service";
|
|
477
|
+
actorSubjectId: string;
|
|
478
|
+
retryAt?: Date;
|
|
479
|
+
errorCode?: string;
|
|
480
|
+
slackChannelId?: string;
|
|
481
|
+
slackMessageTimestamp?: string;
|
|
482
|
+
},
|
|
483
|
+
): Promise<boolean> {
|
|
484
|
+
return await withWorkspaceRls(db, publication.workspaceId, async (scopedDb) => {
|
|
485
|
+
const terminal = ["delivered", "failed", "cancelled"].includes(transition.state);
|
|
486
|
+
const now = new Date();
|
|
487
|
+
const [updated] = await scopedDb
|
|
488
|
+
.update(schema.memorySlackPublications)
|
|
489
|
+
.set({
|
|
490
|
+
state: transition.state,
|
|
491
|
+
claimHolderId: null,
|
|
492
|
+
claimExpiresAt: null,
|
|
493
|
+
retryAt: transition.retryAt ?? null,
|
|
494
|
+
lastErrorCode: transition.errorCode
|
|
495
|
+
? bounded(transition.errorCode, 128, "publication error code")
|
|
496
|
+
: null,
|
|
497
|
+
slackMessageTimestamp: transition.slackMessageTimestamp ?? null,
|
|
498
|
+
deliveredAt: transition.state === "delivered" ? now : null,
|
|
499
|
+
terminalAt: terminal ? now : null,
|
|
500
|
+
updatedAt: now,
|
|
501
|
+
})
|
|
502
|
+
.where(
|
|
503
|
+
and(
|
|
504
|
+
eq(schema.memorySlackPublications.id, publication.id),
|
|
505
|
+
eq(schema.memorySlackPublications.workspaceId, publication.workspaceId),
|
|
506
|
+
eq(schema.memorySlackPublications.state, "delivering"),
|
|
507
|
+
eq(schema.memorySlackPublications.claimHolderId, claimHolderId),
|
|
508
|
+
),
|
|
509
|
+
)
|
|
510
|
+
.returning();
|
|
511
|
+
if (!updated) return false;
|
|
512
|
+
await insertReceipt(scopedDb, updated, {
|
|
513
|
+
kind: transition.kind,
|
|
514
|
+
state: transition.state,
|
|
515
|
+
actorKind: transition.actorKind,
|
|
516
|
+
actorSubjectId: transition.actorSubjectId,
|
|
517
|
+
retryAt: transition.retryAt ?? null,
|
|
518
|
+
errorCode: transition.errorCode ?? null,
|
|
519
|
+
slackChannelId: transition.slackChannelId ?? null,
|
|
520
|
+
slackMessageTimestamp: transition.slackMessageTimestamp ?? null,
|
|
521
|
+
});
|
|
522
|
+
return true;
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
async function insertReceipt(
|
|
527
|
+
db: Database,
|
|
528
|
+
publication: typeof schema.memorySlackPublications.$inferSelect,
|
|
529
|
+
input: {
|
|
530
|
+
kind: MemorySlackPublicationReceiptKind;
|
|
531
|
+
state: MemorySlackPublicationState;
|
|
532
|
+
actorKind: "human" | "agent" | "service";
|
|
533
|
+
actorSubjectId: string;
|
|
534
|
+
retryAt?: Date | null;
|
|
535
|
+
errorCode?: string | null;
|
|
536
|
+
slackChannelId?: string | null;
|
|
537
|
+
slackMessageTimestamp?: string | null;
|
|
538
|
+
},
|
|
539
|
+
) {
|
|
540
|
+
const [{ nextSequence } = { nextSequence: 1 }] = await db
|
|
541
|
+
.select({
|
|
542
|
+
nextSequence: sql<number>`coalesce(max(${schema.memorySlackPublicationReceipts.sequence}), 0)::int + 1`,
|
|
543
|
+
})
|
|
544
|
+
.from(schema.memorySlackPublicationReceipts)
|
|
545
|
+
.where(eq(schema.memorySlackPublicationReceipts.publicationId, publication.id));
|
|
546
|
+
await db.insert(schema.memorySlackPublicationReceipts).values({
|
|
547
|
+
accountId: publication.accountId,
|
|
548
|
+
workspaceId: publication.workspaceId,
|
|
549
|
+
publicationId: publication.id,
|
|
550
|
+
sequence: Number(nextSequence),
|
|
551
|
+
kind: input.kind,
|
|
552
|
+
state: input.state,
|
|
553
|
+
attemptNumber: publication.attemptCount,
|
|
554
|
+
actorKind: input.actorKind,
|
|
555
|
+
actorSubjectId: boundedSubject(input.actorSubjectId),
|
|
556
|
+
operationId: publication.operationId,
|
|
557
|
+
errorCode: input.errorCode
|
|
558
|
+
? bounded(input.errorCode, 128, "publication receipt error code")
|
|
559
|
+
: null,
|
|
560
|
+
retryAt: input.retryAt ?? null,
|
|
561
|
+
slackChannelId: input.slackChannelId ?? null,
|
|
562
|
+
slackMessageTimestamp: input.slackMessageTimestamp ?? null,
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function manualTransition(
|
|
567
|
+
action: "approve" | "reject" | "retry",
|
|
568
|
+
state: MemorySlackPublicationState,
|
|
569
|
+
): {
|
|
570
|
+
state: "queued" | "rejected";
|
|
571
|
+
kind: "review_approved" | "review_rejected" | "manual_retry";
|
|
572
|
+
terminal: boolean;
|
|
573
|
+
} | null {
|
|
574
|
+
if (action === "approve" && state === "review_pending") {
|
|
575
|
+
return { state: "queued", kind: "review_approved", terminal: false };
|
|
576
|
+
}
|
|
577
|
+
if (action === "reject" && state === "review_pending") {
|
|
578
|
+
return { state: "rejected", kind: "review_rejected", terminal: true };
|
|
579
|
+
}
|
|
580
|
+
if (action === "retry" && state === "failed") {
|
|
581
|
+
return { state: "queued", kind: "manual_retry", terminal: false };
|
|
582
|
+
}
|
|
583
|
+
return null;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
async function mapPublicationWithReceipts(
|
|
587
|
+
db: Database,
|
|
588
|
+
row: typeof schema.memorySlackPublications.$inferSelect,
|
|
589
|
+
): Promise<MemorySlackPublication> {
|
|
590
|
+
const receiptRows = await db
|
|
591
|
+
.select()
|
|
592
|
+
.from(schema.memorySlackPublicationReceipts)
|
|
593
|
+
.where(eq(schema.memorySlackPublicationReceipts.publicationId, row.id))
|
|
594
|
+
.orderBy(asc(schema.memorySlackPublicationReceipts.sequence))
|
|
595
|
+
.limit(100);
|
|
596
|
+
const projection = row.projection;
|
|
597
|
+
const summary = typeof projection["summary"] === "string" ? projection["summary"] : "";
|
|
598
|
+
const authoritativePath = publicationAuthoritativePath(row, projection);
|
|
599
|
+
return {
|
|
600
|
+
id: row.id,
|
|
601
|
+
workspaceId: row.workspaceId,
|
|
602
|
+
configurationRevision: row.configurationRevision,
|
|
603
|
+
connectionId: row.connectionId,
|
|
604
|
+
slackTeamId: row.slackTeamId,
|
|
605
|
+
slackChannelId: row.slackChannelId,
|
|
606
|
+
sourceType: row.sourceType,
|
|
607
|
+
sourceId: row.sourceId,
|
|
608
|
+
sourceVersion: row.sourceVersion,
|
|
609
|
+
importance: row.importance,
|
|
610
|
+
deliveryMode: row.deliveryMode,
|
|
611
|
+
state: row.state,
|
|
612
|
+
summary: summary.slice(0, 512),
|
|
613
|
+
sourceLabel: row.sourceType === "workspace_memory" ? "Workspace Memory" : "Governed learning",
|
|
614
|
+
authoritativePath,
|
|
615
|
+
initiatorKind: row.initiatorKind,
|
|
616
|
+
initiatorSubjectId: row.initiatorSubjectId,
|
|
617
|
+
initiatingHumanSubjectId: row.initiatingHumanSubjectId,
|
|
618
|
+
attemptCount: row.attemptCount,
|
|
619
|
+
retryAt: row.retryAt?.toISOString() ?? null,
|
|
620
|
+
lastErrorCode: row.lastErrorCode,
|
|
621
|
+
slackMessageTimestamp: row.slackMessageTimestamp,
|
|
622
|
+
deliveredAt: row.deliveredAt?.toISOString() ?? null,
|
|
623
|
+
terminalAt: row.terminalAt?.toISOString() ?? null,
|
|
624
|
+
createdAt: row.createdAt.toISOString(),
|
|
625
|
+
updatedAt: row.updatedAt.toISOString(),
|
|
626
|
+
receipts: receiptRows.map(mapReceipt),
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
function publicationAuthoritativePath(
|
|
631
|
+
row: typeof schema.memorySlackPublications.$inferSelect,
|
|
632
|
+
projection: Record<string, unknown>,
|
|
633
|
+
): string | null {
|
|
634
|
+
if (row.sourceType === "workspace_memory") {
|
|
635
|
+
const authoritative = record(projection["authoritativeRecord"]);
|
|
636
|
+
const memoryId = authoritative?.["memoryId"];
|
|
637
|
+
return typeof memoryId === "string"
|
|
638
|
+
? `/workspaces/${row.workspaceId}/memory?memoryId=${encodeURIComponent(memoryId)}`
|
|
639
|
+
: null;
|
|
640
|
+
}
|
|
641
|
+
return `/workspaces/${row.workspaceId}/workspace-state`;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
function mapConfiguration(
|
|
645
|
+
row: typeof schema.memorySlackPublicationConfigurations.$inferSelect,
|
|
646
|
+
): MemorySlackPublicationConfiguration {
|
|
647
|
+
return {
|
|
648
|
+
id: row.id,
|
|
649
|
+
workspaceId: row.workspaceId,
|
|
650
|
+
revision: row.revision,
|
|
651
|
+
enabled: row.enabled,
|
|
652
|
+
connectionId: row.connectionId,
|
|
653
|
+
slackTeamId: row.slackTeamId,
|
|
654
|
+
slackChannelId: row.slackChannelId,
|
|
655
|
+
slackChannelName: row.slackChannelName,
|
|
656
|
+
autoImportances: normalizeImportances(row.autoImportances),
|
|
657
|
+
reviewImportances: normalizeImportances(row.reviewImportances),
|
|
658
|
+
createdBySubjectId: row.createdBySubjectId,
|
|
659
|
+
createdAt: row.createdAt.toISOString(),
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
function mapReceipt(
|
|
664
|
+
row: typeof schema.memorySlackPublicationReceipts.$inferSelect,
|
|
665
|
+
): MemorySlackPublicationReceipt {
|
|
666
|
+
return {
|
|
667
|
+
id: row.id,
|
|
668
|
+
publicationId: row.publicationId,
|
|
669
|
+
sequence: row.sequence,
|
|
670
|
+
kind: row.kind,
|
|
671
|
+
state: row.state,
|
|
672
|
+
attemptNumber: row.attemptNumber,
|
|
673
|
+
actorKind: row.actorKind,
|
|
674
|
+
actorSubjectId: row.actorSubjectId,
|
|
675
|
+
operationId: row.operationId,
|
|
676
|
+
errorCode: row.errorCode,
|
|
677
|
+
retryAt: row.retryAt?.toISOString() ?? null,
|
|
678
|
+
slackChannelId: row.slackChannelId,
|
|
679
|
+
slackMessageTimestamp: row.slackMessageTimestamp,
|
|
680
|
+
createdAt: row.createdAt.toISOString(),
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
function normalizeImportances(values: readonly string[]) {
|
|
685
|
+
const allowed = new Set(["major", "normal", "minor"] as const);
|
|
686
|
+
return [...new Set(values)]
|
|
687
|
+
.filter((value): value is "major" | "normal" | "minor" =>
|
|
688
|
+
allowed.has(value as "major" | "normal" | "minor"),
|
|
689
|
+
)
|
|
690
|
+
.sort();
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
function normalizeActor(actor: MemorySlackPublicationActor) {
|
|
694
|
+
return {
|
|
695
|
+
kind: actor.kind,
|
|
696
|
+
subjectId: boundedSubject(actor.subjectId),
|
|
697
|
+
initiatingHumanSubjectId: actor.initiatingHumanSubjectId
|
|
698
|
+
? boundedSubject(actor.initiatingHumanSubjectId)
|
|
699
|
+
: null,
|
|
700
|
+
sessionId: actor.sessionId ?? null,
|
|
701
|
+
turnId: actor.turnId ?? null,
|
|
702
|
+
attemptId: actor.attemptId ?? null,
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
function boundedSubject(value: string) {
|
|
707
|
+
return bounded(value, 1_024, "publication actor subject id");
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
function bounded(value: string, maxBytes: number, label: string) {
|
|
711
|
+
const trimmed = value.trim();
|
|
712
|
+
if (!trimmed || Buffer.byteLength(trimmed, "utf8") > maxBytes) {
|
|
713
|
+
throw new Error(`${label} is invalid`);
|
|
714
|
+
}
|
|
715
|
+
return trimmed;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
function deterministicUuid(value: string) {
|
|
719
|
+
const bytes = createHash("sha256").update(value).digest().subarray(0, 16);
|
|
720
|
+
bytes[6] = (bytes[6]! & 0x0f) | 0x50;
|
|
721
|
+
bytes[8] = (bytes[8]! & 0x3f) | 0x80;
|
|
722
|
+
const hex = bytes.toString("hex");
|
|
723
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
function record(value: unknown): Record<string, unknown> | null {
|
|
727
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
728
|
+
? (value as Record<string, unknown>)
|
|
729
|
+
: null;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
function rawRows(value: unknown): Array<Record<string, unknown>> {
|
|
733
|
+
if (Array.isArray(value)) return value as Array<Record<string, unknown>>;
|
|
734
|
+
const rows = record(value)?.["rows"];
|
|
735
|
+
return Array.isArray(rows) ? (rows as Array<Record<string, unknown>>) : [];
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
function raw(row: Record<string, unknown>, camel: string, snake: string) {
|
|
739
|
+
return row[camel] ?? row[snake];
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
function mapClaimedPublication(row: Record<string, unknown>): ClaimedMemorySlackPublication {
|
|
743
|
+
const date = (camel: string, snake: string) => {
|
|
744
|
+
const value = raw(row, camel, snake);
|
|
745
|
+
return value instanceof Date ? value : new Date(String(value));
|
|
746
|
+
};
|
|
747
|
+
const nullableDate = (camel: string, snake: string) => {
|
|
748
|
+
const value = raw(row, camel, snake);
|
|
749
|
+
return value == null ? null : value instanceof Date ? value : new Date(String(value));
|
|
750
|
+
};
|
|
751
|
+
const string = (camel: string, snake: string) => String(raw(row, camel, snake));
|
|
752
|
+
const nullableString = (camel: string, snake: string) => {
|
|
753
|
+
const value = raw(row, camel, snake);
|
|
754
|
+
return value == null ? null : String(value);
|
|
755
|
+
};
|
|
756
|
+
return {
|
|
757
|
+
id: string("id", "id"),
|
|
758
|
+
accountId: string("accountId", "account_id"),
|
|
759
|
+
workspaceId: string("workspaceId", "workspace_id"),
|
|
760
|
+
configurationId: string("configurationId", "configuration_id"),
|
|
761
|
+
configurationRevision: Number(raw(row, "configurationRevision", "configuration_revision")),
|
|
762
|
+
connectionId: string("connectionId", "connection_id"),
|
|
763
|
+
slackTeamId: string("slackTeamId", "slack_team_id"),
|
|
764
|
+
slackChannelId: string("slackChannelId", "slack_channel_id"),
|
|
765
|
+
sourceType: string("sourceType", "source_type") as ClaimedMemorySlackPublication["sourceType"],
|
|
766
|
+
sourceId: string("sourceId", "source_id"),
|
|
767
|
+
sourceVersion: nullableString("sourceVersion", "source_version"),
|
|
768
|
+
sourceIdempotencyKey: string("sourceIdempotencyKey", "source_idempotency_key"),
|
|
769
|
+
projection: record(raw(row, "projection", "projection")) ?? {},
|
|
770
|
+
projectionSha256: string("projectionSha256", "projection_sha256"),
|
|
771
|
+
importance: string("importance", "importance") as ClaimedMemorySlackPublication["importance"],
|
|
772
|
+
deliveryMode: string(
|
|
773
|
+
"deliveryMode",
|
|
774
|
+
"delivery_mode",
|
|
775
|
+
) as ClaimedMemorySlackPublication["deliveryMode"],
|
|
776
|
+
state: string("state", "state") as ClaimedMemorySlackPublication["state"],
|
|
777
|
+
operationId: string("operationId", "operation_id"),
|
|
778
|
+
initiatorKind: string(
|
|
779
|
+
"initiatorKind",
|
|
780
|
+
"initiator_kind",
|
|
781
|
+
) as ClaimedMemorySlackPublication["initiatorKind"],
|
|
782
|
+
initiatorSubjectId: string("initiatorSubjectId", "initiator_subject_id"),
|
|
783
|
+
initiatingHumanSubjectId: nullableString(
|
|
784
|
+
"initiatingHumanSubjectId",
|
|
785
|
+
"initiating_human_subject_id",
|
|
786
|
+
),
|
|
787
|
+
sessionId: nullableString("sessionId", "session_id"),
|
|
788
|
+
turnId: nullableString("turnId", "turn_id"),
|
|
789
|
+
attemptId: nullableString("attemptId", "attempt_id"),
|
|
790
|
+
claimHolderId: nullableString("claimHolderId", "claim_holder_id"),
|
|
791
|
+
claimExpiresAt: nullableDate("claimExpiresAt", "claim_expires_at"),
|
|
792
|
+
attemptCount: Number(raw(row, "attemptCount", "attempt_count")),
|
|
793
|
+
retryAt: nullableDate("retryAt", "retry_at"),
|
|
794
|
+
lastErrorCode: nullableString("lastErrorCode", "last_error_code"),
|
|
795
|
+
slackMessageTimestamp: nullableString("slackMessageTimestamp", "slack_message_timestamp"),
|
|
796
|
+
deliveredAt: nullableDate("deliveredAt", "delivered_at"),
|
|
797
|
+
terminalAt: nullableDate("terminalAt", "terminal_at"),
|
|
798
|
+
createdAt: date("createdAt", "created_at"),
|
|
799
|
+
updatedAt: date("updatedAt", "updated_at"),
|
|
800
|
+
};
|
|
801
|
+
}
|