@opengeni/db 0.28.1 → 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 (187) 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-DBS5HPEW.js → chunk-YHZXQ7HP.js} +760 -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/environment-crypto.d.ts +8 -4
  35. package/dist/generated-images.d.ts +139 -0
  36. package/dist/index.d.ts +958 -126
  37. package/dist/index.js +31204 -12172
  38. package/dist/index.js.map +1 -1
  39. package/dist/insights.d.ts +51 -4
  40. package/dist/integration-bindings.d.ts +70 -0
  41. package/dist/integration-features.d.ts +99 -0
  42. package/dist/interaction-revisions.d.ts +3 -0
  43. package/dist/interaction-schema.d.ts +5372 -0
  44. package/dist/knowledge-source-sync-schema.d.ts +1679 -0
  45. package/dist/knowledge-source-sync.d.ts +274 -0
  46. package/dist/lossless-columns.d.ts +58 -0
  47. package/dist/lossless-json.d.ts +39 -0
  48. package/dist/memory-domain.d.ts +3 -6
  49. package/dist/memory-governance.d.ts +2 -2
  50. package/dist/memory-slack-delivery.d.ts +101 -0
  51. package/dist/new-session-drafts.d.ts +2 -2
  52. package/dist/pack-components.d.ts +76 -0
  53. package/dist/pack-installations.d.ts +86 -0
  54. package/dist/persistence-errors.d.ts +7 -14
  55. package/dist/plugin-packages.d.ts +126 -0
  56. package/dist/preference-registry.d.ts +1 -1
  57. package/dist/provision-roles.d.ts +9 -1
  58. package/dist/provision-roles.js +1 -1
  59. package/dist/runtime-posture.d.ts +18 -6
  60. package/dist/schema.d.ts +19122 -8731
  61. package/dist/schema.js +173 -1
  62. package/dist/scoped-knowledge.d.ts +97 -1
  63. package/dist/session-control.d.ts +4 -4
  64. package/dist/session-queue-commands.d.ts +19 -11
  65. package/dist/session-realtime-context.d.ts +2 -2
  66. package/dist/session-realtime-ledger.d.ts +4 -4
  67. package/dist/session-realtime-mirror.d.ts +2 -2
  68. package/dist/session-realtime-state.d.ts +1 -1
  69. package/dist/session-realtime-terminal.d.ts +8 -1
  70. package/dist/session-realtime.d.ts +7 -7
  71. package/dist/session-tool-call-settlement.d.ts +1 -1
  72. package/dist/slack-user-link-access.d.ts +54 -0
  73. package/dist/transcription-recordings-schema.d.ts +44 -6
  74. package/dist/transcription-recordings.d.ts +3 -3
  75. package/dist/turn-initiator.d.ts +2 -2
  76. package/dist/video-generation.d.ts +216 -0
  77. package/dist/video-generation.js +66 -0
  78. package/dist/video-generation.js.map +1 -0
  79. package/dist/workspace-artifacts.d.ts +1 -1
  80. package/dist/workspace-instruction-policies.d.ts +1 -1
  81. package/dist/workspace-learning-policy-schema.d.ts +932 -0
  82. package/dist/workspace-learning-policy.d.ts +161 -0
  83. package/drizzle/0065_enrollment_credential_generation.sql +10 -0
  84. package/drizzle/0140_retained_screenshot_artifacts.sql +192 -0
  85. package/drizzle/0176_lossless_canonical_json.sql +975 -0
  86. package/drizzle/0177_session_events_workspace_turn_type_index.sql +5 -0
  87. package/drizzle/0178_permissioned_secret_reads.sql +14 -0
  88. package/drizzle/0179_slack_private_shortcut_delivery_gate.sql +85 -0
  89. package/drizzle/0180_retained_screenshot_lifecycle_fences.sql +273 -0
  90. package/drizzle/0181_connected_machine_removal.sql +52 -0
  91. package/drizzle/0182_connected_machine_remove_session_default.sql +77 -0
  92. package/drizzle/0183_model_call_provider_cost_estimates.sql +39 -0
  93. package/drizzle/0184_sandbox_drain_teardown_fence.sql +682 -0
  94. package/drizzle/0185_temporal_schedule_cleanup_outbox.sql +168 -0
  95. package/drizzle/0186_sandbox_capture_provider_contract.sql +400 -0
  96. package/drizzle/0187_generated_image_artifacts.sql +173 -0
  97. package/drizzle/0188_image_generation_retention_failure.sql +25 -0
  98. package/drizzle/0189_retained_session_image_formats.sql +13 -0
  99. package/drizzle/0190_timeline_annotations.sql +31 -0
  100. package/drizzle/0191_editable_artifact_engine.sql +3853 -0
  101. package/drizzle/0192_editable_artifact_live_tickets.sql +338 -0
  102. package/drizzle/0193_editable_artifact_authorization.sql +190 -0
  103. package/drizzle/0194_editable_artifact_durable_exports.sql +160 -0
  104. package/drizzle/0195_editable_artifact_import_authorization.sql +203 -0
  105. package/drizzle/0196_rig_provider_images.sql +152 -0
  106. package/drizzle/0197_knowledge_source_sync_schedules.sql +523 -0
  107. package/drizzle/0198_memory_slack_publication_delivery.sql +393 -0
  108. package/drizzle/0199_workspace_learning_policy.sql +980 -0
  109. package/drizzle/0201_company_profile_authority.sql +849 -0
  110. package/drizzle/0202_document_index_checkpoints.sql +85 -0
  111. package/drizzle/0203_durable_video_generation.sql +353 -0
  112. package/drizzle/0204_video_generation_funding.sql +44 -0
  113. package/drizzle/0205_attempt_tool_catalogs.sql +230 -0
  114. package/drizzle/0206_browser_sessions.sql +646 -0
  115. package/drizzle/0207_browser_identities.sql +379 -0
  116. package/drizzle/0208_attached_browser_devices.sql +165 -0
  117. package/drizzle/0209_computer_sessions.sql +575 -0
  118. package/drizzle/0210_browser_auth_network_interventions.sql +441 -0
  119. package/drizzle/0211_editable_artifact_session_links.sql +58 -0
  120. package/drizzle/0212_slack_installation_bindings.sql +348 -0
  121. package/drizzle/0213_slack_user_link_access_requests.sql +177 -0
  122. package/drizzle/0214_session_activity_commit_gate.sql +262 -0
  123. package/drizzle/0215_capabilities_platform.sql +980 -0
  124. package/drizzle/0216_pack_component_ownership.sql +235 -0
  125. package/drizzle/0217_capability_definition_delete_authority.sql +32 -0
  126. package/package.json +18 -5
  127. package/src/attached-browser-devices.ts +371 -0
  128. package/src/attempt-tool-catalogs.ts +131 -0
  129. package/src/browser-auth.ts +1896 -0
  130. package/src/browser-identities.ts +1168 -0
  131. package/src/browser-sessions.ts +2005 -0
  132. package/src/browser-state-artifacts.ts +59 -0
  133. package/src/capability-components.ts +166 -0
  134. package/src/capability-integrations.ts +1663 -0
  135. package/src/codemode-operations.ts +674 -0
  136. package/src/company-profile-schema.ts +143 -0
  137. package/src/company-profile.ts +968 -0
  138. package/src/computer-sessions.ts +1130 -0
  139. package/src/connection-token-resolver.ts +270 -25
  140. package/src/database.ts +434 -24
  141. package/src/editable-artifact-durable-export.ts +1201 -0
  142. package/src/editable-artifact-materialization.ts +893 -0
  143. package/src/editable-artifacts-schema.ts +879 -0
  144. package/src/editable-artifacts.ts +5404 -0
  145. package/src/environment-crypto.ts +22 -9
  146. package/src/generated-images.ts +740 -0
  147. package/src/index.ts +10117 -2730
  148. package/src/insights.ts +160 -13
  149. package/src/integration-bindings.ts +455 -0
  150. package/src/integration-features.ts +859 -0
  151. package/src/interaction-revisions.ts +40 -0
  152. package/src/interaction-schema.ts +1303 -0
  153. package/src/knowledge-source-sync-schema.ts +192 -0
  154. package/src/knowledge-source-sync.ts +1922 -0
  155. package/src/lossless-columns.ts +35 -0
  156. package/src/lossless-json.ts +322 -0
  157. package/src/memory-domain.ts +5 -44
  158. package/src/memory-slack-delivery.ts +801 -0
  159. package/src/pack-components.ts +954 -0
  160. package/src/pack-installations.ts +929 -0
  161. package/src/persistence-errors.ts +26 -34
  162. package/src/plugin-packages.ts +1186 -0
  163. package/src/provision-roles.ts +302 -1
  164. package/src/runtime-posture-cli.ts +9 -0
  165. package/src/runtime-posture.ts +514 -5
  166. package/src/schema.ts +5177 -2957
  167. package/src/scoped-knowledge-schema.ts +1 -1
  168. package/src/scoped-knowledge.ts +655 -3
  169. package/src/session-control.ts +133 -81
  170. package/src/session-queue-commands.ts +511 -247
  171. package/src/session-realtime-context.ts +20 -7
  172. package/src/session-realtime-ledger.ts +32 -10
  173. package/src/session-realtime-mirror.ts +4 -2
  174. package/src/session-realtime-terminal.ts +89 -21
  175. package/src/session-realtime.ts +10 -9
  176. package/src/session-tool-call-settlement.ts +38 -15
  177. package/src/slack-user-link-access.ts +702 -0
  178. package/src/transcription-recordings-schema.ts +5 -2
  179. package/src/transcription-recordings.ts +70 -40
  180. package/src/video-generation.ts +1452 -0
  181. package/src/workspace-learning-policy-schema.ts +139 -0
  182. package/src/workspace-learning-policy.ts +595 -0
  183. package/dist/chunk-DBS5HPEW.js.map +0 -1
  184. package/dist/chunk-HT5T62CC.js +0 -6803
  185. package/dist/chunk-HT5T62CC.js.map +0 -1
  186. package/dist/event-payload-sanitizer.d.ts +0 -32
  187. package/src/event-payload-sanitizer.ts +0 -377
@@ -0,0 +1,595 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import {
3
+ WorkspaceLearningMode,
4
+ WorkspaceLearningPolicySnapshot,
5
+ WORKSPACE_LEARNING_POLICY_REASON_MAX_CHARS,
6
+ canonicalizeWorkspaceLearningSourceOverrides,
7
+ type WorkspaceLearningPolicyActivationEvent,
8
+ type WorkspaceLearningPolicyActivationType,
9
+ type WorkspaceLearningPolicyHead,
10
+ type WorkspaceLearningPolicyRevision,
11
+ type WorkspaceLearningSourceOverrideInput,
12
+ } from "@opengeni/contracts";
13
+ import { and, asc, desc, eq, sql } from "drizzle-orm";
14
+ import type { Database } from "./database";
15
+ import { rawRows, withRlsContext } from "./database";
16
+ import { nestedPostgresSqlState } from "./persistence-errors";
17
+ import * as schema from "./schema";
18
+
19
+ export class WorkspaceLearningPolicyConflictError extends Error {
20
+ readonly name = "WorkspaceLearningPolicyConflictError";
21
+ readonly code = "WORKSPACE_LEARNING_POLICY_CONFLICT";
22
+
23
+ constructor(readonly currentHead: WorkspaceLearningPolicyHead | null) {
24
+ super("The active workspace learning policy changed in another request");
25
+ }
26
+ }
27
+
28
+ export class WorkspaceLearningPolicyOperationReuseError extends Error {
29
+ readonly name = "WorkspaceLearningPolicyOperationReuseError";
30
+ readonly code = "WORKSPACE_LEARNING_POLICY_OPERATION_REUSED";
31
+
32
+ constructor() {
33
+ super("The workspace learning-policy operation id was already used for another request");
34
+ }
35
+ }
36
+
37
+ export class WorkspaceLearningPolicyNotFoundError extends Error {
38
+ readonly name = "WorkspaceLearningPolicyNotFoundError";
39
+
40
+ constructor(message = "Workspace learning-policy revision was not found") {
41
+ super(message);
42
+ }
43
+ }
44
+
45
+ export class WorkspaceLearningPolicyInvalidOperationError extends Error {
46
+ readonly name = "WorkspaceLearningPolicyInvalidOperationError";
47
+ }
48
+
49
+ export class WorkspaceLearningPolicyAuthorityError extends Error {
50
+ readonly name = "WorkspaceLearningPolicyAuthorityError";
51
+ }
52
+
53
+ export type WorkspaceLearningPolicyAttemptClaims = {
54
+ accountId: string;
55
+ workspaceId: string;
56
+ sessionId: string;
57
+ turnId: string;
58
+ attemptId: string;
59
+ executionGeneration: number;
60
+ };
61
+
62
+ type RevisionRow = typeof schema.workspaceLearningPolicyRevisions.$inferSelect;
63
+ type HeadRow = typeof schema.workspaceLearningPolicyHeads.$inferSelect;
64
+ type EventRow = typeof schema.workspaceLearningPolicyActivationEvents.$inferSelect;
65
+ type SnapshotRow = typeof schema.workspaceLearningPolicySnapshots.$inferSelect;
66
+
67
+ function iso(value: Date | string): string {
68
+ return (value instanceof Date ? value : new Date(value)).toISOString();
69
+ }
70
+
71
+ function revisionIdentity(row: RevisionRow) {
72
+ return { id: row.id, revision: row.revision, policyHash: row.policyHash };
73
+ }
74
+
75
+ function revisionFromRow(row: RevisionRow): WorkspaceLearningPolicyRevision {
76
+ return {
77
+ ...revisionIdentity(row),
78
+ operationId: row.operationId,
79
+ accountId: row.accountId,
80
+ workspaceId: row.workspaceId,
81
+ workspaceMode: WorkspaceLearningMode.parse(row.workspaceMode),
82
+ sourceOverrides: canonicalizeWorkspaceLearningSourceOverrides(row.sourceOverrides),
83
+ supersedesRevisionId: row.supersedesRevisionId,
84
+ createdBySubjectId: row.createdBySubjectId,
85
+ createdAt: iso(row.createdAt),
86
+ };
87
+ }
88
+
89
+ function headFromRow(row: HeadRow): WorkspaceLearningPolicyHead {
90
+ return {
91
+ accountId: row.accountId,
92
+ workspaceId: row.workspaceId,
93
+ revisionId: row.revisionId,
94
+ revision: row.revision,
95
+ policyHash: row.policyHash,
96
+ activationVersion: row.activationVersion,
97
+ activatedAt: iso(row.activatedAt),
98
+ };
99
+ }
100
+
101
+ function headFromEventRow(row: EventRow): WorkspaceLearningPolicyHead {
102
+ return {
103
+ accountId: row.accountId,
104
+ workspaceId: row.workspaceId,
105
+ revisionId: row.newRevisionId,
106
+ revision: row.newRevision,
107
+ policyHash: row.newPolicyHash,
108
+ activationVersion: row.activationVersion,
109
+ activatedAt: iso(row.createdAt),
110
+ };
111
+ }
112
+
113
+ function eventFromRow(row: EventRow): WorkspaceLearningPolicyActivationEvent {
114
+ return {
115
+ id: row.id,
116
+ operationId: row.operationId,
117
+ accountId: row.accountId,
118
+ workspaceId: row.workspaceId,
119
+ type: row.type as WorkspaceLearningPolicyActivationType,
120
+ activationVersion: row.activationVersion,
121
+ oldRevision:
122
+ row.oldRevisionId === null
123
+ ? null
124
+ : {
125
+ id: row.oldRevisionId,
126
+ revision: row.oldRevision!,
127
+ policyHash: row.oldPolicyHash!,
128
+ },
129
+ newRevision: {
130
+ id: row.newRevisionId,
131
+ revision: row.newRevision,
132
+ policyHash: row.newPolicyHash,
133
+ },
134
+ actorSubjectId: row.actorSubjectId,
135
+ reason: row.reason,
136
+ createdAt: iso(row.createdAt),
137
+ };
138
+ }
139
+
140
+ function snapshotFromRow(row: SnapshotRow): WorkspaceLearningPolicySnapshot {
141
+ return WorkspaceLearningPolicySnapshot.parse({
142
+ id: row.id,
143
+ accountId: row.accountId,
144
+ workspaceId: row.workspaceId,
145
+ sessionId: row.sessionId,
146
+ turnId: row.turnId,
147
+ attemptId: row.attemptId,
148
+ executionGeneration: row.executionGeneration,
149
+ revision:
150
+ row.revisionId === null
151
+ ? null
152
+ : { id: row.revisionId, revision: row.revision, policyHash: row.policyHash },
153
+ activationVersion: row.activationVersion,
154
+ activatedAt: row.activatedAt === null ? null : iso(row.activatedAt),
155
+ workspaceMode: row.workspaceMode,
156
+ sourceOverrides: row.sourceOverrides,
157
+ snapshotHash: row.snapshotHash,
158
+ createdAt: iso(row.createdAt),
159
+ });
160
+ }
161
+
162
+ function operationFingerprint(operation: string, fields: readonly unknown[]): string {
163
+ return createHash("sha256")
164
+ .update(JSON.stringify(["workspace_learning_policy_operation", 1, operation, fields]), "utf8")
165
+ .digest("hex");
166
+ }
167
+
168
+ function requireHumanAuthority(input: {
169
+ actorSubjectId: string;
170
+ principalKind: string | undefined;
171
+ }): void {
172
+ if (input.principalKind !== "human_session" || input.actorSubjectId.trim().length === 0) {
173
+ throw new WorkspaceLearningPolicyAuthorityError(
174
+ "Workspace learning-policy changes require an exact authenticated human actor",
175
+ );
176
+ }
177
+ }
178
+
179
+ function requireActivationInput(input: {
180
+ expectedActivationVersion: number;
181
+ actorSubjectId: string;
182
+ reason: string;
183
+ }): void {
184
+ if (
185
+ !Number.isSafeInteger(input.expectedActivationVersion) ||
186
+ input.expectedActivationVersion < 0
187
+ ) {
188
+ throw new WorkspaceLearningPolicyInvalidOperationError(
189
+ "Workspace learning-policy activation version must be a nonnegative safe integer",
190
+ );
191
+ }
192
+ if (input.actorSubjectId.trim() !== input.actorSubjectId || input.actorSubjectId.length > 1_024) {
193
+ throw new WorkspaceLearningPolicyInvalidOperationError(
194
+ "Workspace learning-policy actor identity is invalid",
195
+ );
196
+ }
197
+ if (
198
+ input.reason.trim().length === 0 ||
199
+ input.reason.trim() !== input.reason ||
200
+ input.reason.length > WORKSPACE_LEARNING_POLICY_REASON_MAX_CHARS
201
+ ) {
202
+ throw new WorkspaceLearningPolicyInvalidOperationError(
203
+ `Workspace learning-policy reason must contain 1-${WORKSPACE_LEARNING_POLICY_REASON_MAX_CHARS} non-edge-whitespace characters`,
204
+ );
205
+ }
206
+ }
207
+
208
+ async function setHumanAuthority(
209
+ db: Database,
210
+ input: { actorSubjectId: string; principalKind: string },
211
+ ): Promise<void> {
212
+ await db.execute(sql`select set_config('opengeni.subject_id', ${input.actorSubjectId}, true)`);
213
+ await db.execute(sql`select set_config('opengeni.principal_kind', ${input.principalKind}, true)`);
214
+ }
215
+
216
+ async function currentHeadInTransaction(
217
+ db: Database,
218
+ accountId: string,
219
+ workspaceId: string,
220
+ ): Promise<WorkspaceLearningPolicyHead | null> {
221
+ const [row] = await db
222
+ .select()
223
+ .from(schema.workspaceLearningPolicyHeads)
224
+ .where(
225
+ and(
226
+ eq(schema.workspaceLearningPolicyHeads.accountId, accountId),
227
+ eq(schema.workspaceLearningPolicyHeads.workspaceId, workspaceId),
228
+ ),
229
+ )
230
+ .limit(1);
231
+ return row ? headFromRow(row) : null;
232
+ }
233
+
234
+ async function lockWorkspace(
235
+ db: Database,
236
+ input: { accountId: string; workspaceId: string },
237
+ ): Promise<void> {
238
+ const [workspace] = await db
239
+ .select({ id: schema.workspaces.id })
240
+ .from(schema.workspaces)
241
+ .where(
242
+ and(
243
+ eq(schema.workspaces.id, input.workspaceId),
244
+ eq(schema.workspaces.accountId, input.accountId),
245
+ ),
246
+ )
247
+ .for("update")
248
+ .limit(1);
249
+ if (!workspace) throw new WorkspaceLearningPolicyNotFoundError("Workspace was not found");
250
+ }
251
+
252
+ async function revisionByOperationInTransaction(
253
+ db: Database,
254
+ workspaceId: string,
255
+ operationId: string,
256
+ ): Promise<RevisionRow | null> {
257
+ const [row] = await db
258
+ .select()
259
+ .from(schema.workspaceLearningPolicyRevisions)
260
+ .where(
261
+ and(
262
+ eq(schema.workspaceLearningPolicyRevisions.workspaceId, workspaceId),
263
+ eq(schema.workspaceLearningPolicyRevisions.operationId, operationId),
264
+ ),
265
+ )
266
+ .limit(1);
267
+ return row ?? null;
268
+ }
269
+
270
+ async function eventByOperationInTransaction(
271
+ db: Database,
272
+ workspaceId: string,
273
+ operationId: string,
274
+ ): Promise<EventRow | null> {
275
+ const [row] = await db
276
+ .select()
277
+ .from(schema.workspaceLearningPolicyActivationEvents)
278
+ .where(
279
+ and(
280
+ eq(schema.workspaceLearningPolicyActivationEvents.workspaceId, workspaceId),
281
+ eq(schema.workspaceLearningPolicyActivationEvents.operationId, operationId),
282
+ ),
283
+ )
284
+ .limit(1);
285
+ return row ?? null;
286
+ }
287
+
288
+ export async function createWorkspaceLearningPolicyRevision(
289
+ db: Database,
290
+ input: {
291
+ operationId?: string;
292
+ accountId: string;
293
+ workspaceId: string;
294
+ workspaceMode: "off" | "suggest" | "automatic";
295
+ sourceOverrides?: WorkspaceLearningSourceOverrideInput[];
296
+ supersedesRevisionId?: string | null;
297
+ actorSubjectId: string;
298
+ principalKind: string | undefined;
299
+ },
300
+ ): Promise<WorkspaceLearningPolicyRevision> {
301
+ requireHumanAuthority(input);
302
+ const operationId = input.operationId ?? randomUUID();
303
+ const workspaceMode = WorkspaceLearningMode.parse(input.workspaceMode);
304
+ const sourceOverrides = canonicalizeWorkspaceLearningSourceOverrides(input.sourceOverrides ?? []);
305
+ const supersedesRevisionId = input.supersedesRevisionId ?? null;
306
+ const requestFingerprint = operationFingerprint("create_revision", [
307
+ input.accountId,
308
+ input.workspaceId,
309
+ workspaceMode,
310
+ sourceOverrides,
311
+ supersedesRevisionId,
312
+ input.actorSubjectId,
313
+ ]);
314
+
315
+ return await withRlsContext(
316
+ db,
317
+ { accountId: input.accountId, workspaceId: input.workspaceId },
318
+ async (scopedDb) => {
319
+ // Revision creation and activation share this lock, making their
320
+ // workspace-local operation-id namespace exact under concurrency.
321
+ await lockWorkspace(scopedDb, input);
322
+ const existing = await revisionByOperationInTransaction(
323
+ scopedDb,
324
+ input.workspaceId,
325
+ operationId,
326
+ );
327
+ if (existing) {
328
+ if (existing.requestFingerprint !== requestFingerprint) {
329
+ throw new WorkspaceLearningPolicyOperationReuseError();
330
+ }
331
+ return revisionFromRow(existing);
332
+ }
333
+ const eventReuse = await eventByOperationInTransaction(
334
+ scopedDb,
335
+ input.workspaceId,
336
+ operationId,
337
+ );
338
+ if (eventReuse) throw new WorkspaceLearningPolicyOperationReuseError();
339
+
340
+ try {
341
+ const [row] = await scopedDb
342
+ .insert(schema.workspaceLearningPolicyRevisions)
343
+ .values({
344
+ operationId,
345
+ requestFingerprint,
346
+ accountId: input.accountId,
347
+ workspaceId: input.workspaceId,
348
+ workspaceMode,
349
+ sourceOverrides,
350
+ policyHash: sql<string>`workspace_learning_policy_hash(${workspaceMode}, ${JSON.stringify(
351
+ sourceOverrides,
352
+ )}::jsonb)`,
353
+ supersedesRevisionId,
354
+ createdBySubjectId: input.actorSubjectId,
355
+ })
356
+ .onConflictDoNothing()
357
+ .returning();
358
+ if (row) return revisionFromRow(row);
359
+ const winner = await revisionByOperationInTransaction(
360
+ scopedDb,
361
+ input.workspaceId,
362
+ operationId,
363
+ );
364
+ if (!winner || winner.requestFingerprint !== requestFingerprint) {
365
+ throw new WorkspaceLearningPolicyOperationReuseError();
366
+ }
367
+ return revisionFromRow(winner);
368
+ } catch (error) {
369
+ if (nestedPostgresSqlState(error) === "23514") {
370
+ throw new WorkspaceLearningPolicyInvalidOperationError(
371
+ "Workspace learning-policy revision failed immutable scope validation",
372
+ );
373
+ }
374
+ throw error;
375
+ }
376
+ },
377
+ );
378
+ }
379
+
380
+ async function changeActiveWorkspaceLearningPolicy(
381
+ db: Database,
382
+ input: {
383
+ operationId?: string;
384
+ accountId: string;
385
+ workspaceId: string;
386
+ targetRevisionId: string;
387
+ expectedCurrentRevisionId: string | null;
388
+ expectedActivationVersion: number;
389
+ actorSubjectId: string;
390
+ principalKind: string | undefined;
391
+ reason: string;
392
+ type: WorkspaceLearningPolicyActivationType;
393
+ },
394
+ ) {
395
+ requireHumanAuthority(input);
396
+ requireActivationInput(input);
397
+ const operationId = input.operationId ?? randomUUID();
398
+ const requestFingerprint = operationFingerprint(`change_active:${input.type}`, [
399
+ input.accountId,
400
+ input.workspaceId,
401
+ input.targetRevisionId,
402
+ input.expectedCurrentRevisionId,
403
+ input.expectedActivationVersion,
404
+ input.actorSubjectId,
405
+ input.reason,
406
+ ]);
407
+ try {
408
+ return await withRlsContext(
409
+ db,
410
+ { accountId: input.accountId, workspaceId: input.workspaceId },
411
+ async (scopedDb) => {
412
+ await setHumanAuthority(scopedDb, {
413
+ actorSubjectId: input.actorSubjectId,
414
+ principalKind: input.principalKind!,
415
+ });
416
+ const [receipt] = await rawRows<{ eventId: string }>(
417
+ scopedDb,
418
+ sql`select event_id as "eventId"
419
+ from workspace_learning_policy_apply_activation(
420
+ ${operationId}::uuid,
421
+ ${requestFingerprint},
422
+ ${input.accountId}::uuid,
423
+ ${input.workspaceId}::uuid,
424
+ ${input.targetRevisionId}::uuid,
425
+ ${input.expectedCurrentRevisionId}::uuid,
426
+ ${input.expectedActivationVersion}::bigint,
427
+ ${input.type},
428
+ ${input.actorSubjectId},
429
+ ${input.reason}
430
+ )`,
431
+ );
432
+ if (!receipt) throw new Error("Workspace learning-policy activation returned no receipt");
433
+ const [event] = await scopedDb
434
+ .select()
435
+ .from(schema.workspaceLearningPolicyActivationEvents)
436
+ .where(eq(schema.workspaceLearningPolicyActivationEvents.id, receipt.eventId))
437
+ .limit(1);
438
+ if (!event) throw new Error("Workspace learning-policy activation was incomplete");
439
+ return { head: headFromEventRow(event), event: eventFromRow(event) };
440
+ },
441
+ );
442
+ } catch (error) {
443
+ const state = nestedPostgresSqlState(error);
444
+ if (state === "40001") {
445
+ const currentHead = await withRlsContext(
446
+ db,
447
+ { accountId: input.accountId, workspaceId: input.workspaceId },
448
+ async (scopedDb) =>
449
+ await currentHeadInTransaction(scopedDb, input.accountId, input.workspaceId),
450
+ );
451
+ throw new WorkspaceLearningPolicyConflictError(currentHead);
452
+ }
453
+ if (state === "42501") {
454
+ throw new WorkspaceLearningPolicyAuthorityError(
455
+ "Workspace learning-policy activation was not authorized",
456
+ );
457
+ }
458
+ if (state === "23503") throw new WorkspaceLearningPolicyNotFoundError();
459
+ if (state === "P1471") throw new WorkspaceLearningPolicyOperationReuseError();
460
+ if (state === "22023") {
461
+ throw new WorkspaceLearningPolicyInvalidOperationError(
462
+ "Workspace learning-policy activation input is invalid",
463
+ );
464
+ }
465
+ if (state === "23514") {
466
+ throw new WorkspaceLearningPolicyInvalidOperationError(
467
+ input.type === "rollback"
468
+ ? "Workspace learning-policy rollback requires a distinct previously active revision"
469
+ : "Workspace learning-policy revision cannot be activated",
470
+ );
471
+ }
472
+ throw error;
473
+ }
474
+ }
475
+
476
+ export async function activateWorkspaceLearningPolicyRevision(
477
+ db: Database,
478
+ input: Omit<
479
+ Parameters<typeof changeActiveWorkspaceLearningPolicy>[1],
480
+ "type" | "targetRevisionId"
481
+ > & {
482
+ revisionId: string;
483
+ },
484
+ ) {
485
+ return await changeActiveWorkspaceLearningPolicy(db, {
486
+ ...input,
487
+ targetRevisionId: input.revisionId,
488
+ type: "activate",
489
+ });
490
+ }
491
+
492
+ export async function rollbackWorkspaceLearningPolicyRevision(
493
+ db: Database,
494
+ input: Omit<Parameters<typeof changeActiveWorkspaceLearningPolicy>[1], "type">,
495
+ ) {
496
+ return await changeActiveWorkspaceLearningPolicy(db, { ...input, type: "rollback" });
497
+ }
498
+
499
+ export async function listWorkspaceLearningPolicyHistory(
500
+ db: Database,
501
+ input: { accountId: string; workspaceId: string; limit?: number },
502
+ ): Promise<{
503
+ head: WorkspaceLearningPolicyHead | null;
504
+ revisions: WorkspaceLearningPolicyRevision[];
505
+ events: WorkspaceLearningPolicyActivationEvent[];
506
+ }> {
507
+ const limit = Math.max(1, Math.min(input.limit ?? 50, 100));
508
+ return await withRlsContext(
509
+ db,
510
+ { accountId: input.accountId, workspaceId: input.workspaceId },
511
+ async (scopedDb) => {
512
+ const [heads, revisions, events] = await Promise.all([
513
+ scopedDb
514
+ .select()
515
+ .from(schema.workspaceLearningPolicyHeads)
516
+ .where(eq(schema.workspaceLearningPolicyHeads.workspaceId, input.workspaceId))
517
+ .limit(1),
518
+ scopedDb
519
+ .select()
520
+ .from(schema.workspaceLearningPolicyRevisions)
521
+ .where(eq(schema.workspaceLearningPolicyRevisions.workspaceId, input.workspaceId))
522
+ .orderBy(desc(schema.workspaceLearningPolicyRevisions.revision))
523
+ .limit(limit),
524
+ scopedDb
525
+ .select()
526
+ .from(schema.workspaceLearningPolicyActivationEvents)
527
+ .where(eq(schema.workspaceLearningPolicyActivationEvents.workspaceId, input.workspaceId))
528
+ .orderBy(
529
+ desc(schema.workspaceLearningPolicyActivationEvents.activationVersion),
530
+ asc(schema.workspaceLearningPolicyActivationEvents.id),
531
+ )
532
+ .limit(limit),
533
+ ]);
534
+ return {
535
+ head: heads[0] ? headFromRow(heads[0]) : null,
536
+ revisions: revisions.map(revisionFromRow),
537
+ events: events.map(eventFromRow),
538
+ };
539
+ },
540
+ );
541
+ }
542
+
543
+ export async function getOrCreateWorkspaceLearningPolicySnapshot(
544
+ db: Database,
545
+ claims: WorkspaceLearningPolicyAttemptClaims,
546
+ ): Promise<WorkspaceLearningPolicySnapshot> {
547
+ try {
548
+ return await withRlsContext(
549
+ db,
550
+ { accountId: claims.accountId, workspaceId: claims.workspaceId },
551
+ async (scopedDb) => {
552
+ const [receipt] = await rawRows<{ snapshotId: string }>(
553
+ scopedDb,
554
+ sql`select snapshot_id as "snapshotId"
555
+ from workspace_learning_policy_get_or_create_snapshot(
556
+ ${claims.accountId}::uuid,
557
+ ${claims.workspaceId}::uuid,
558
+ ${claims.sessionId}::uuid,
559
+ ${claims.turnId}::uuid,
560
+ ${claims.attemptId}::uuid,
561
+ ${claims.executionGeneration}::integer
562
+ )`,
563
+ );
564
+ if (!receipt) throw new Error("Workspace learning-policy snapshot returned no receipt");
565
+ const [row] = await scopedDb
566
+ .select()
567
+ .from(schema.workspaceLearningPolicySnapshots)
568
+ .where(
569
+ and(
570
+ eq(schema.workspaceLearningPolicySnapshots.id, receipt.snapshotId),
571
+ eq(schema.workspaceLearningPolicySnapshots.accountId, claims.accountId),
572
+ eq(schema.workspaceLearningPolicySnapshots.workspaceId, claims.workspaceId),
573
+ eq(schema.workspaceLearningPolicySnapshots.attemptId, claims.attemptId),
574
+ ),
575
+ )
576
+ .limit(1);
577
+ if (!row) throw new Error("Workspace learning-policy snapshot was not recorded");
578
+ return snapshotFromRow(row);
579
+ },
580
+ );
581
+ } catch (error) {
582
+ const state = nestedPostgresSqlState(error);
583
+ if (state === "42501") {
584
+ throw new WorkspaceLearningPolicyAuthorityError(
585
+ "Workspace learning-policy snapshot was not authorized",
586
+ );
587
+ }
588
+ if (state === "23514") {
589
+ throw new WorkspaceLearningPolicyInvalidOperationError(
590
+ "Workspace learning-policy snapshot requires the exact active attempt",
591
+ );
592
+ }
593
+ throw error;
594
+ }
595
+ }