@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,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
+ }