@opengeni/db 5.0.1 → 6.0.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.
Files changed (212) hide show
  1. package/dist/automations.d.ts +0 -2
  2. package/dist/canonical-human-identities.d.ts +4 -0
  3. package/dist/canonical-human-identities.js +3 -3
  4. package/dist/capability-components.d.ts +5 -13
  5. package/dist/capability-integrations.d.ts +23 -8
  6. package/dist/capability-reconciliation.d.ts +5 -0
  7. package/dist/capability-reconciliation.js +21 -0
  8. package/dist/capability-reconciliation.js.map +1 -0
  9. package/dist/{chunk-6T2GGYWE.js → chunk-4UJCXBR2.js} +4 -5800
  10. package/dist/chunk-4UJCXBR2.js.map +1 -0
  11. package/dist/{chunk-MWMXWHJD.js → chunk-7JCDSMX2.js} +2 -2
  12. package/dist/chunk-AJA2ZIUW.js +219 -0
  13. package/dist/chunk-AJA2ZIUW.js.map +1 -0
  14. package/dist/chunk-CDAT6EKW.js +247 -0
  15. package/dist/chunk-CDAT6EKW.js.map +1 -0
  16. package/dist/{chunk-DPONPYJD.js → chunk-CJI6G254.js} +70 -18
  17. package/dist/chunk-CJI6G254.js.map +1 -0
  18. package/dist/chunk-G32NVMRJ.js +276 -0
  19. package/dist/chunk-G32NVMRJ.js.map +1 -0
  20. package/dist/chunk-GABXCG3N.js +5811 -0
  21. package/dist/chunk-GABXCG3N.js.map +1 -0
  22. package/dist/{chunk-ERKVSPDX.js → chunk-LVY33EWL.js} +3 -3
  23. package/dist/{chunk-45CP2YKY.js → chunk-MAMBI2HR.js} +9 -3
  24. package/dist/{chunk-45CP2YKY.js.map → chunk-MAMBI2HR.js.map} +1 -1
  25. package/dist/{chunk-F73UKJY6.js → chunk-MN5PZMEK.js} +91 -108
  26. package/dist/chunk-MN5PZMEK.js.map +1 -0
  27. package/dist/chunk-PJI4MUZ7.js +131 -0
  28. package/dist/chunk-PJI4MUZ7.js.map +1 -0
  29. package/dist/{chunk-T6BWIGWJ.js → chunk-RIYHP2WU.js} +2 -2
  30. package/dist/{chunk-7HMK3B45.js → chunk-RL2MUXM3.js} +16 -7
  31. package/dist/chunk-RL2MUXM3.js.map +1 -0
  32. package/dist/{chunk-HQDVI2DB.js → chunk-SYKHLPTK.js} +4 -3
  33. package/dist/{chunk-HQDVI2DB.js.map → chunk-SYKHLPTK.js.map} +1 -1
  34. package/dist/{chunk-T6YLHJLS.js → chunk-XIXVVBW2.js} +198 -51
  35. package/dist/chunk-XIXVVBW2.js.map +1 -0
  36. package/dist/chunk-ZPJ6V2VG.js +156 -0
  37. package/dist/chunk-ZPJ6V2VG.js.map +1 -0
  38. package/dist/codex-capacity-recovery.d.ts +12 -0
  39. package/dist/codex-failure-eligibility.d.ts +9 -0
  40. package/dist/company-profile-schema.d.ts +4 -2
  41. package/dist/connect-attempts.d.ts +15 -0
  42. package/dist/connect-attempts.js +25 -0
  43. package/dist/connect-attempts.js.map +1 -0
  44. package/dist/connection-authority.d.ts +7 -63
  45. package/dist/connection-refresh-lock.d.ts +4 -0
  46. package/dist/connection-token-resolver.d.ts +8 -43
  47. package/dist/database-timing.d.ts +10 -0
  48. package/dist/database.d.ts +4 -0
  49. package/dist/editable-artifact-durable-export.js +2 -2
  50. package/dist/editable-artifacts.d.ts +9 -0
  51. package/dist/editable-artifacts.js +7 -5
  52. package/dist/index.d.ts +447 -78
  53. package/dist/index.js +12705 -12872
  54. package/dist/index.js.map +1 -1
  55. package/dist/insights-usage-bundle.d.ts +3 -0
  56. package/dist/integration-bindings.d.ts +1 -1
  57. package/dist/integration-facets.d.ts +18 -1
  58. package/dist/knowledge-entries-schema.d.ts +2 -0
  59. package/dist/knowledge-entries.d.ts +1 -0
  60. package/dist/knowledge-indexing.d.ts +1 -0
  61. package/dist/live-session-attempt.d.ts +35 -137
  62. package/dist/managed-auth-session-sets.js +3 -3
  63. package/dist/mcp-account-bindings.d.ts +4 -0
  64. package/dist/mcp-oauth.d.ts +11 -0
  65. package/dist/mcp-operations.js +2 -2
  66. package/dist/migrate.js +1 -1
  67. package/dist/organization-integration-policy.d.ts +51 -0
  68. package/dist/organization-integration-policy.js +18 -0
  69. package/dist/organization-integration-policy.js.map +1 -0
  70. package/dist/organization-usage.d.ts +20 -0
  71. package/dist/plugin-packages.d.ts +9 -12
  72. package/dist/pr-review.d.ts +0 -11
  73. package/dist/preference-registry-schema.d.ts +17 -0
  74. package/dist/provision-roles.js +1 -1
  75. package/dist/retained-provider-commands.d.ts +124 -6
  76. package/dist/retained-provider-commands.js +29 -97
  77. package/dist/retained-provider-commands.js.map +1 -1
  78. package/dist/retired-session-create-metadata.d.ts +4 -0
  79. package/dist/runtime-posture.d.ts +7 -5
  80. package/dist/schema.d.ts +876 -656
  81. package/dist/schema.js +5 -7
  82. package/dist/session-admission-block.d.ts +32 -0
  83. package/dist/session-attempt-writers.d.ts +4 -0
  84. package/dist/session-background-commands.js +5 -4
  85. package/dist/session-command-output.js +8 -126
  86. package/dist/session-command-output.js.map +1 -1
  87. package/dist/session-control.d.ts +5 -1
  88. package/dist/session-event-slices.d.ts +10 -0
  89. package/dist/session-event-slices.js +8 -222
  90. package/dist/session-event-slices.js.map +1 -1
  91. package/dist/session-file-attachments.d.ts +28 -0
  92. package/dist/session-goal-reports.d.ts +43 -0
  93. package/dist/session-mcp-approval.d.ts +4 -0
  94. package/dist/session-mcp-credential-rotation.js +5 -4
  95. package/dist/session-mcp-credential-rotation.js.map +1 -1
  96. package/dist/session-message-search.d.ts +22 -0
  97. package/dist/session-queue-commands.d.ts +2 -6
  98. package/dist/session-retry.d.ts +36 -0
  99. package/dist/session-tenancy.js +3 -3
  100. package/dist/skill-source-release-impact.d.ts +18 -0
  101. package/dist/skill-source-release.d.ts +9 -0
  102. package/dist/skills.d.ts +7 -1
  103. package/dist/user-resource-authority.d.ts +0 -8
  104. package/dist/video-generation.js +3 -3
  105. package/dist/workspace-tool-defaults.js +5 -4
  106. package/dist/workspace-tool-defaults.js.map +1 -1
  107. package/drizzle/0468_knowledge_relationship_projection.sql +30 -0
  108. package/drizzle/0469_knowledge_source_discovery.sql +85 -0
  109. package/drizzle/0470_organization_integration_policy.sql +151 -0
  110. package/drizzle/0471_usage_events_account_recent_index.sql +4 -0
  111. package/drizzle/0472_usage_events_workspace_recent_index.sql +4 -0
  112. package/drizzle/0473_organization_usage_analytical_capability.sql +237 -0
  113. package/drizzle/0474_goal_report_requirements.sql +156 -0
  114. package/drizzle/0475_session_event_history_statistics.sql +15 -0
  115. package/drizzle/0476_session_event_history_policy_planning.sql +29 -0
  116. package/drizzle/0477_managed_sign_in_methods.sql +303 -0
  117. package/drizzle/0478_sender_owned_connections.sql +2772 -0
  118. package/drizzle/0479_connection_create_idempotency.sql +16 -0
  119. package/drizzle/0480_private_session_organization_key_administration.sql +154 -0
  120. package/drizzle/0481_personal_connection_visibility_cleanup.sql +45 -0
  121. package/drizzle/0482_remove_packs.sql +417 -0
  122. package/drizzle/0483_preclaim_admission_block.sql +85 -0
  123. package/drizzle/0484_insights_usage_projection.sql +211 -0
  124. package/drizzle/0485_inherited_mcp_approval_policies.sql +6 -0
  125. package/drizzle/0486_reasoning_configuration_message_forks.sql +35 -0
  126. package/drizzle/0487_skill_catalog_message_forks.sql +26 -0
  127. package/drizzle/0488_permanent_skill_removal.sql +207 -0
  128. package/drizzle/0489_modal_command_byte_offsets.sql +17 -0
  129. package/drizzle/0490_periodic_capture_attempt_cadence.sql +6 -0
  130. package/drizzle/0491_warm_capture_holder_reclamation.sql +57 -0
  131. package/drizzle/0492_codex_accepted_source_authority.sql +249 -0
  132. package/drizzle/0493_stopping_command_error_containment.sql +58 -0
  133. package/drizzle/0494_mcp_account_bindings.sql +334 -0
  134. package/drizzle/0495_consented_sandbox_recovery.sql +220 -0
  135. package/drizzle/0496_supervised_command_settlement.sql +386 -0
  136. package/drizzle/0497_modal_inventory_owner_read_capability.sql +112 -0
  137. package/drizzle/0498_enrollment_membership_fence.sql +87 -0
  138. package/drizzle/0499_session_attachment_access.sql +224 -0
  139. package/drizzle/0500_session_execution_epoch.sql +37 -0
  140. package/drizzle/0501_session_sharing_execution.sql +591 -0
  141. package/drizzle/0502_active_message_boundary_forks.sql +30 -0
  142. package/package.json +17 -5
  143. package/src/automations.ts +17 -73
  144. package/src/canonical-human-identities.ts +17 -5
  145. package/src/capability-components.ts +7 -24
  146. package/src/capability-integrations.ts +301 -21
  147. package/src/capability-reconciliation.ts +19 -0
  148. package/src/codex-capacity-recovery.ts +47 -0
  149. package/src/codex-failure-eligibility.ts +93 -0
  150. package/src/connect-attempts.ts +49 -18
  151. package/src/connection-authority.ts +12 -158
  152. package/src/connection-refresh-lock.ts +22 -0
  153. package/src/connection-token-resolver.ts +58 -530
  154. package/src/database-timing.ts +43 -0
  155. package/src/database.ts +61 -26
  156. package/src/editable-artifacts.ts +1 -1
  157. package/src/index.ts +3578 -1273
  158. package/src/insights-model-bundle.ts +19 -7
  159. package/src/insights-usage-bundle.ts +16 -19
  160. package/src/integration-bindings.ts +2 -7
  161. package/src/integration-facets.ts +73 -6
  162. package/src/knowledge-entries.ts +8 -1
  163. package/src/live-session-attempt.ts +0 -22
  164. package/src/mcp-account-bindings.ts +12 -0
  165. package/src/mcp-oauth.ts +26 -0
  166. package/src/organization-integration-policy.ts +235 -0
  167. package/src/organization-usage.ts +83 -0
  168. package/src/plugin-packages.ts +226 -42
  169. package/src/pr-review.ts +7 -69
  170. package/src/preference-registry-schema.ts +1 -0
  171. package/src/provision-roles.ts +50 -37
  172. package/src/retained-provider-commands.ts +286 -9
  173. package/src/retired-session-create-metadata.ts +16 -0
  174. package/src/runtime-posture.ts +217 -13
  175. package/src/schema.ts +104 -120
  176. package/src/session-admission-block.ts +29 -0
  177. package/src/session-attempt-writers.ts +48 -0
  178. package/src/session-control.ts +21 -5
  179. package/src/session-event-slices.ts +76 -26
  180. package/src/session-file-attachments.ts +66 -0
  181. package/src/session-goal-reports.ts +255 -0
  182. package/src/session-mcp-approval.ts +30 -0
  183. package/src/session-message-search.ts +348 -0
  184. package/src/session-queue-commands.ts +60 -36
  185. package/src/session-retry.ts +319 -0
  186. package/src/skill-config-migration.ts +11 -2
  187. package/src/skill-source-release-impact.ts +33 -0
  188. package/src/skill-source-release.ts +67 -26
  189. package/src/skills.ts +44 -6
  190. package/src/user-resource-authority.ts +0 -56
  191. package/dist/chunk-6T2GGYWE.js.map +0 -1
  192. package/dist/chunk-7HMK3B45.js.map +0 -1
  193. package/dist/chunk-DPONPYJD.js.map +0 -1
  194. package/dist/chunk-F73UKJY6.js.map +0 -1
  195. package/dist/chunk-T6YLHJLS.js.map +0 -1
  196. package/dist/host-mcp-binding-match.d.ts +0 -5
  197. package/dist/host-mcp-bindings.d.ts +0 -79
  198. package/dist/host-mcp-resolvers.d.ts +0 -47
  199. package/dist/host-mcp-task-authority.d.ts +0 -43
  200. package/dist/host-selection-identity.d.ts +0 -6
  201. package/dist/pack-components.d.ts +0 -83
  202. package/dist/pack-installations.d.ts +0 -86
  203. package/src/host-mcp-binding-match.ts +0 -34
  204. package/src/host-mcp-bindings.ts +0 -777
  205. package/src/host-mcp-resolvers.ts +0 -266
  206. package/src/host-mcp-task-authority.ts +0 -384
  207. package/src/host-selection-identity.ts +0 -29
  208. package/src/pack-components.ts +0 -980
  209. package/src/pack-installations.ts +0 -950
  210. /package/dist/{chunk-MWMXWHJD.js.map → chunk-7JCDSMX2.js.map} +0 -0
  211. /package/dist/{chunk-ERKVSPDX.js.map → chunk-LVY33EWL.js.map} +0 -0
  212. /package/dist/{chunk-T6BWIGWJ.js.map → chunk-RIYHP2WU.js.map} +0 -0
@@ -1,950 +0,0 @@
1
- import { createHash } from "node:crypto";
2
-
3
- import {
4
- CapabilityPack as CapabilityPackSchema,
5
- stableJson,
6
- type CapabilityPack,
7
- type PackInstallation,
8
- type PackInstallationStatus,
9
- } from "@opengeni/contracts";
10
- import { and, eq, inArray, ne, sql } from "drizzle-orm";
11
-
12
- import { withRlsContext, type Database } from "./database";
13
- import * as schema from "./schema";
14
- import {
15
- lockSkillPublication,
16
- prepareSkillPublication,
17
- readSkillPublications,
18
- } from "./skill-publication";
19
-
20
- export class PackOperationIdempotencyError extends Error {
21
- readonly name = "PackOperationIdempotencyError";
22
- }
23
-
24
- export class PackOperationInProgressError extends Error {
25
- readonly name = "PackOperationInProgressError";
26
- }
27
-
28
- export class PackOperationClaimLostError extends Error {
29
- readonly name = "PackOperationClaimLostError";
30
- }
31
-
32
- export class PackManifestChangedError extends Error {
33
- readonly name = "PackManifestChangedError";
34
- }
35
-
36
- export class PackInstallationVersionConflictError extends Error {
37
- readonly name = "PackInstallationVersionConflictError";
38
-
39
- constructor(
40
- readonly expectedVersion: number,
41
- readonly actualVersion: number,
42
- ) {
43
- super(
44
- `Pack installation changed: expected version ${expectedVersion}, current version ${actualVersion}`,
45
- );
46
- }
47
- }
48
-
49
- export class PackInstallationVersionRequiredError extends Error {
50
- readonly name = "PackInstallationVersionRequiredError";
51
- }
52
-
53
- export type PreparedPackInstallation = {
54
- installation: PackInstallation;
55
- operationId: string;
56
- operationVersion: number;
57
- replayResult: Record<string, unknown> | null;
58
- };
59
-
60
- export async function preparePackInstallationOperation(
61
- db: Database,
62
- input: {
63
- accountId: string;
64
- workspaceId: string;
65
- subjectId: string;
66
- pack: CapabilityPack;
67
- manifestDigest: string;
68
- selectedRigId: string | null;
69
- metadata: Record<string, unknown>;
70
- idempotencyKey: string;
71
- requestDigest: string;
72
- registeredManifestDigest?: string;
73
- expectedInstallationVersion?: number;
74
- },
75
- ): Promise<PreparedPackInstallation> {
76
- return await withRlsContext(
77
- db,
78
- { accountId: input.accountId, workspaceId: input.workspaceId },
79
- async (scopedDb) =>
80
- await scopedDb.transaction(async (tx) => {
81
- await lockSkillPublication(tx as unknown as Database, input.workspaceId);
82
- await tx.execute(
83
- sql`select pg_advisory_xact_lock(hashtextextended(${`capability-operation:${input.workspaceId}:${input.idempotencyKey}`}, 0))`,
84
- );
85
- await tx.execute(
86
- sql`select pg_advisory_xact_lock(hashtextextended(${`pack-installation:${input.workspaceId}:${input.pack.id}`}, 0))`,
87
- );
88
- let [operation] = await tx
89
- .select()
90
- .from(schema.capabilityOperations)
91
- .where(
92
- and(
93
- eq(schema.capabilityOperations.workspaceId, input.workspaceId),
94
- eq(schema.capabilityOperations.idempotencyKey, input.idempotencyKey),
95
- ),
96
- )
97
- .for("update")
98
- .limit(1);
99
- let [existing] = await tx
100
- .select()
101
- .from(schema.packInstallations)
102
- .where(
103
- and(
104
- eq(schema.packInstallations.workspaceId, input.workspaceId),
105
- eq(schema.packInstallations.packId, input.pack.id),
106
- ),
107
- )
108
- .for("update")
109
- .limit(1);
110
- if (operation && operation.requestDigest !== input.requestDigest) {
111
- throw new PackOperationIdempotencyError("Pack idempotency key was reused");
112
- }
113
- if (operation?.status === "completed" && operation.result) {
114
- if (!existing) throw new Error("Completed Pack operation lost its installation");
115
- return {
116
- installation: mapPackInstallation(existing),
117
- operationId: operation.id,
118
- operationVersion: operation.version,
119
- replayResult: operation.result,
120
- };
121
- }
122
- if (input.registeredManifestDigest) {
123
- await assertRegisteredPackManifestCurrent(tx as unknown as Database, {
124
- workspaceId: input.workspaceId,
125
- packId: input.pack.id,
126
- expectedDigest: input.registeredManifestDigest,
127
- admittedDigest: input.manifestDigest,
128
- });
129
- }
130
- assertPackOperationRetryableStatus(operation);
131
- const resuming = Boolean(operation);
132
- if (!resuming && existing) {
133
- if (input.expectedInstallationVersion === undefined) {
134
- throw new PackInstallationVersionRequiredError(
135
- "Updating or repairing a Pack requires the previewed installation version",
136
- );
137
- }
138
- if (existing.version !== input.expectedInstallationVersion) {
139
- throw new PackInstallationVersionConflictError(
140
- input.expectedInstallationVersion,
141
- existing.version,
142
- );
143
- }
144
- }
145
- const otherOperation = await findOtherPackOperationInProgress(tx as unknown as Database, {
146
- workspaceId: input.workspaceId,
147
- packId: input.pack.id,
148
- ...(operation ? { operationId: operation.id } : {}),
149
- });
150
- if (operation && otherOperation) {
151
- throw new PackOperationInProgressError("Another Pack operation is already in progress");
152
- }
153
- if (!operation) {
154
- if (otherOperation?.status === "running") {
155
- if (
156
- !existing ||
157
- !["install", "update", "repair"].includes(otherOperation.kind) ||
158
- !samePackInstallationIntent(existing, input)
159
- ) {
160
- throw new PackOperationInProgressError(
161
- "Another Pack operation is already in progress",
162
- );
163
- }
164
- await supersedeStaleRunningPackOperation(tx as unknown as Database, otherOperation);
165
- } else if (otherOperation?.status === "pending") {
166
- await supersedePendingPackOperation(tx as unknown as Database, otherOperation);
167
- }
168
- [operation] = await tx
169
- .insert(schema.capabilityOperations)
170
- .values({
171
- accountId: input.accountId,
172
- workspaceId: input.workspaceId,
173
- idempotencyKey: input.idempotencyKey,
174
- requestDigest: input.requestDigest,
175
- kind: !existing
176
- ? "install"
177
- : existing.manifestDigest === input.manifestDigest
178
- ? "repair"
179
- : "update",
180
- targetKind: "pack",
181
- targetId: input.pack.id,
182
- status: "running",
183
- phase: "admitted",
184
- createdBySubjectId: input.subjectId,
185
- })
186
- .returning();
187
- } else {
188
- operation = await resumePackOperation(tx as unknown as Database, operation);
189
- }
190
- if (!operation) throw new Error("Failed to admit Pack operation");
191
- const now = new Date();
192
- if (!existing) {
193
- [existing] = await tx
194
- .insert(schema.packInstallations)
195
- .values({
196
- accountId: input.accountId,
197
- workspaceId: input.workspaceId,
198
- packId: input.pack.id,
199
- status: "installing",
200
- manifestSnapshot: input.pack as unknown as Record<string, unknown>,
201
- manifestDigest: input.manifestDigest,
202
- selectedRigId: input.selectedRigId,
203
- installedBySubjectId: input.subjectId,
204
- metadata: input.metadata,
205
- })
206
- .returning();
207
- } else if (!resuming) {
208
- [existing] = await tx
209
- .update(schema.packInstallations)
210
- .set({
211
- status: "needs_attention",
212
- version: existing.version + 1,
213
- manifestSnapshot: input.pack as unknown as Record<string, unknown>,
214
- manifestDigest: input.manifestDigest,
215
- selectedRigId: input.selectedRigId,
216
- installedBySubjectId: input.subjectId,
217
- metadata: input.metadata,
218
- enabledAt: now,
219
- updatedAt: now,
220
- })
221
- .where(eq(schema.packInstallations.id, existing.id))
222
- .returning();
223
- } else {
224
- [existing] = await tx
225
- .update(schema.packInstallations)
226
- .set({
227
- status: existing.status === "installing" ? "installing" : "needs_attention",
228
- updatedAt: now,
229
- })
230
- .where(eq(schema.packInstallations.id, existing.id))
231
- .returning();
232
- }
233
- if (!existing) throw new Error("Failed to prepare Pack installation");
234
- return {
235
- installation: mapPackInstallation(existing),
236
- operationId: operation.id,
237
- operationVersion: operation.version,
238
- replayResult: null,
239
- };
240
- }),
241
- );
242
- }
243
-
244
- export async function finalizePackInstallationOperation(
245
- db: Database,
246
- input: {
247
- accountId: string;
248
- workspaceId: string;
249
- operationId: string;
250
- operationVersion: number;
251
- packInstallationId: string;
252
- packId: string;
253
- result: Record<string, unknown>;
254
- },
255
- ): Promise<PackInstallation> {
256
- return await withRlsContext(
257
- db,
258
- { accountId: input.accountId, workspaceId: input.workspaceId },
259
- async (scopedDb) =>
260
- await scopedDb.transaction(async (tx) => {
261
- await prepareSkillPublication(
262
- tx as unknown as Database,
263
- input.workspaceId,
264
- input.operationId,
265
- );
266
- const [priorFinalization] = await tx
267
- .select()
268
- .from(schema.capabilityOperations)
269
- .where(
270
- and(
271
- eq(schema.capabilityOperations.id, input.operationId),
272
- eq(schema.capabilityOperations.accountId, input.accountId),
273
- eq(schema.capabilityOperations.workspaceId, input.workspaceId),
274
- eq(schema.capabilityOperations.targetKind, "pack"),
275
- eq(schema.capabilityOperations.targetId, input.packId),
276
- ),
277
- )
278
- .for("update")
279
- .limit(1);
280
- if (priorFinalization?.status === "completed") {
281
- const { skillPublications: _storedPublications, ...originalResult } =
282
- (priorFinalization.result ?? {}) as Record<string, unknown>;
283
- const { skillPublications: _submittedPublications, ...requestedResult } = input.result;
284
- if (
285
- priorFinalization.version !== input.operationVersion + 1 ||
286
- stableJson(originalResult) !== stableJson(requestedResult)
287
- )
288
- throw new PackOperationIdempotencyError("Pack finalization request changed");
289
- const [priorInstallation] = await tx
290
- .select()
291
- .from(schema.packInstallations)
292
- .where(
293
- and(
294
- eq(schema.packInstallations.id, input.packInstallationId),
295
- eq(schema.packInstallations.accountId, input.accountId),
296
- eq(schema.packInstallations.workspaceId, input.workspaceId),
297
- eq(schema.packInstallations.packId, input.packId),
298
- ),
299
- )
300
- .limit(1);
301
- if (!priorInstallation)
302
- throw new PackOperationClaimLostError("Pack finalization installation unavailable");
303
- const skillPublications = await readSkillPublications(
304
- tx as unknown as Database,
305
- input.workspaceId,
306
- input.operationId,
307
- );
308
- return {
309
- ...mapPackInstallation(priorInstallation),
310
- ...(skillPublications.length ? { skillPublications } : {}),
311
- };
312
- }
313
- await assertActivePackOperationClaim(tx as unknown as Database, {
314
- accountId: input.accountId,
315
- workspaceId: input.workspaceId,
316
- operationId: input.operationId,
317
- operationVersion: input.operationVersion,
318
- packInstallationId: input.packInstallationId,
319
- });
320
- const now = new Date();
321
- const [installation] = await tx
322
- .update(schema.packInstallations)
323
- .set({ status: "active", updatedAt: now })
324
- .where(
325
- and(
326
- eq(schema.packInstallations.workspaceId, input.workspaceId),
327
- eq(schema.packInstallations.id, input.packInstallationId),
328
- eq(schema.packInstallations.packId, input.packId),
329
- ),
330
- )
331
- .returning();
332
- if (!installation) throw new Error("Pack installation was not found during finalize");
333
- const skillPublications = await readSkillPublications(
334
- tx as unknown as Database,
335
- input.workspaceId,
336
- input.operationId,
337
- );
338
- const [completed] = await tx
339
- .update(schema.capabilityOperations)
340
- .set({
341
- status: "completed",
342
- phase: "completed",
343
- result: { ...input.result, ...(skillPublications.length ? { skillPublications } : {}) },
344
- errorCode: null,
345
- version: sql`${schema.capabilityOperations.version} + 1`,
346
- completedAt: now,
347
- updatedAt: now,
348
- })
349
- .where(
350
- and(
351
- eq(schema.capabilityOperations.id, input.operationId),
352
- eq(schema.capabilityOperations.status, "running"),
353
- eq(schema.capabilityOperations.version, input.operationVersion),
354
- ),
355
- )
356
- .returning({ id: schema.capabilityOperations.id });
357
- if (!completed) throw new PackOperationClaimLostError("Pack operation claim was lost");
358
- return {
359
- ...mapPackInstallation(installation),
360
- ...(skillPublications.length ? { skillPublications } : {}),
361
- };
362
- }),
363
- );
364
- }
365
-
366
- export async function deferPackInstallationOperation(
367
- db: Database,
368
- input: {
369
- accountId: string;
370
- workspaceId: string;
371
- operationId: string;
372
- operationVersion: number;
373
- packInstallationId: string;
374
- phase: string;
375
- errorCode: string;
376
- },
377
- ): Promise<void> {
378
- await withRlsContext(
379
- db,
380
- { accountId: input.accountId, workspaceId: input.workspaceId },
381
- async (scopedDb) =>
382
- await scopedDb.transaction(async (tx) => {
383
- const operation = await assertActivePackOperationClaim(tx as unknown as Database, {
384
- accountId: input.accountId,
385
- workspaceId: input.workspaceId,
386
- operationId: input.operationId,
387
- operationVersion: input.operationVersion,
388
- packInstallationId: input.packInstallationId,
389
- });
390
- await tx
391
- .update(schema.packInstallations)
392
- .set({ status: "needs_attention", updatedAt: new Date() })
393
- .where(
394
- and(
395
- eq(schema.packInstallations.workspaceId, input.workspaceId),
396
- eq(schema.packInstallations.id, input.packInstallationId),
397
- eq(schema.packInstallations.packId, operation.targetId),
398
- ),
399
- );
400
- const [deferred] = await tx
401
- .update(schema.capabilityOperations)
402
- .set({
403
- status: "pending",
404
- phase: input.phase.slice(0, 120),
405
- errorCode: input.errorCode.slice(0, 120),
406
- version: sql`${schema.capabilityOperations.version} + 1`,
407
- updatedAt: new Date(),
408
- })
409
- .where(
410
- and(
411
- eq(schema.capabilityOperations.id, input.operationId),
412
- eq(schema.capabilityOperations.status, "running"),
413
- eq(schema.capabilityOperations.version, input.operationVersion),
414
- ),
415
- )
416
- .returning({ id: schema.capabilityOperations.id });
417
- if (!deferred) throw new PackOperationClaimLostError("Pack operation claim was lost");
418
- }),
419
- );
420
- }
421
-
422
- export async function touchPackInstallationOperation(
423
- db: Database,
424
- input: {
425
- accountId: string;
426
- workspaceId: string;
427
- operationId: string;
428
- operationVersion: number;
429
- },
430
- ): Promise<void> {
431
- await withRlsContext(
432
- db,
433
- { accountId: input.accountId, workspaceId: input.workspaceId },
434
- async (scopedDb) => {
435
- const [touched] = await scopedDb
436
- .update(schema.capabilityOperations)
437
- .set({ updatedAt: sql`now()` })
438
- .where(
439
- and(
440
- eq(schema.capabilityOperations.workspaceId, input.workspaceId),
441
- eq(schema.capabilityOperations.id, input.operationId),
442
- eq(schema.capabilityOperations.status, "running"),
443
- eq(schema.capabilityOperations.version, input.operationVersion),
444
- ),
445
- )
446
- .returning({ id: schema.capabilityOperations.id });
447
- if (!touched) throw new PackOperationClaimLostError("Pack operation claim was lost");
448
- },
449
- );
450
- }
451
-
452
- export async function preparePackUninstallOperation(
453
- db: Database,
454
- input: {
455
- accountId: string;
456
- workspaceId: string;
457
- subjectId: string;
458
- packId: string;
459
- expectedInstallationVersion: number;
460
- idempotencyKey: string;
461
- requestDigest: string;
462
- },
463
- ): Promise<PreparedPackInstallation | { replayResult: Record<string, unknown> }> {
464
- return await withRlsContext(
465
- db,
466
- { accountId: input.accountId, workspaceId: input.workspaceId },
467
- async (scopedDb) =>
468
- await scopedDb.transaction(async (tx) => {
469
- await tx.execute(
470
- sql`select pg_advisory_xact_lock(hashtextextended(${`capability-operation:${input.workspaceId}:${input.idempotencyKey}`}, 0))`,
471
- );
472
- await tx.execute(
473
- sql`select pg_advisory_xact_lock(hashtextextended(${`pack-installation:${input.workspaceId}:${input.packId}`}, 0))`,
474
- );
475
- const [operation] = await tx
476
- .select()
477
- .from(schema.capabilityOperations)
478
- .where(
479
- and(
480
- eq(schema.capabilityOperations.workspaceId, input.workspaceId),
481
- eq(schema.capabilityOperations.idempotencyKey, input.idempotencyKey),
482
- ),
483
- )
484
- .for("update")
485
- .limit(1);
486
- const [installation] = await tx
487
- .select()
488
- .from(schema.packInstallations)
489
- .where(
490
- and(
491
- eq(schema.packInstallations.workspaceId, input.workspaceId),
492
- eq(schema.packInstallations.packId, input.packId),
493
- ),
494
- )
495
- .for("update")
496
- .limit(1);
497
- if (operation) {
498
- if (operation.requestDigest !== input.requestDigest) {
499
- throw new PackOperationIdempotencyError("Pack idempotency key was reused");
500
- }
501
- if (operation.status === "completed" && operation.result) {
502
- return { replayResult: operation.result };
503
- }
504
- assertPackOperationRetryableStatus(operation);
505
- const otherOperation = await findOtherPackOperationInProgress(tx as unknown as Database, {
506
- workspaceId: input.workspaceId,
507
- packId: input.packId,
508
- operationId: operation.id,
509
- });
510
- if (otherOperation) {
511
- throw new PackOperationInProgressError("Another Pack operation is already in progress");
512
- }
513
- const resumed = await resumePackOperation(tx as unknown as Database, operation);
514
- if (!installation || installation.status === "disabled") {
515
- const result = { status: "not_installed" };
516
- await completePackOperation(
517
- tx as unknown as Database,
518
- operation.id,
519
- resumed.version,
520
- result,
521
- );
522
- return { replayResult: result };
523
- }
524
- return {
525
- installation: mapPackInstallation(installation),
526
- operationId: operation.id,
527
- operationVersion: resumed.version,
528
- replayResult: null,
529
- };
530
- }
531
- if (!installation || installation.status === "disabled") {
532
- const result = { status: "not_installed" };
533
- const otherOperation = await findOtherPackOperationInProgress(tx as unknown as Database, {
534
- workspaceId: input.workspaceId,
535
- packId: input.packId,
536
- });
537
- if (otherOperation?.status === "running") {
538
- if (otherOperation.kind !== "uninstall") {
539
- throw new PackOperationInProgressError(
540
- "Another Pack operation is already in progress",
541
- );
542
- }
543
- await supersedeStaleRunningPackOperation(tx as unknown as Database, otherOperation);
544
- } else if (otherOperation?.status === "pending") {
545
- await supersedePendingPackOperation(tx as unknown as Database, otherOperation);
546
- }
547
- await tx.insert(schema.capabilityOperations).values({
548
- accountId: input.accountId,
549
- workspaceId: input.workspaceId,
550
- idempotencyKey: input.idempotencyKey,
551
- requestDigest: input.requestDigest,
552
- kind: "uninstall",
553
- targetKind: "pack",
554
- targetId: input.packId,
555
- status: "completed",
556
- phase: "completed",
557
- result,
558
- createdBySubjectId: input.subjectId,
559
- completedAt: new Date(),
560
- });
561
- return { replayResult: result };
562
- }
563
- if (installation.version !== input.expectedInstallationVersion) {
564
- throw new PackInstallationVersionConflictError(
565
- input.expectedInstallationVersion,
566
- installation.version,
567
- );
568
- }
569
- const otherOperation = await findOtherPackOperationInProgress(tx as unknown as Database, {
570
- workspaceId: input.workspaceId,
571
- packId: input.packId,
572
- });
573
- if (otherOperation?.status === "running") {
574
- if (otherOperation.kind !== "uninstall") {
575
- throw new PackOperationInProgressError("Another Pack operation is already in progress");
576
- }
577
- await supersedeStaleRunningPackOperation(tx as unknown as Database, otherOperation);
578
- } else if (otherOperation?.status === "pending") {
579
- await supersedePendingPackOperation(tx as unknown as Database, otherOperation);
580
- }
581
- const [createdOperation] = await tx
582
- .insert(schema.capabilityOperations)
583
- .values({
584
- accountId: input.accountId,
585
- workspaceId: input.workspaceId,
586
- idempotencyKey: input.idempotencyKey,
587
- requestDigest: input.requestDigest,
588
- kind: "uninstall",
589
- targetKind: "pack",
590
- targetId: input.packId,
591
- status: "running",
592
- phase: "admitted",
593
- createdBySubjectId: input.subjectId,
594
- })
595
- .returning();
596
- if (!createdOperation) throw new Error("Failed to admit Pack uninstall");
597
- const [prepared] = await tx
598
- .update(schema.packInstallations)
599
- .set({
600
- status: "needs_attention",
601
- version: installation.version + 1,
602
- updatedAt: new Date(),
603
- })
604
- .where(eq(schema.packInstallations.id, installation.id))
605
- .returning();
606
- if (!prepared) throw new Error("Failed to prepare Pack uninstall");
607
- return {
608
- installation: mapPackInstallation(prepared),
609
- operationId: createdOperation.id,
610
- operationVersion: createdOperation.version,
611
- replayResult: null,
612
- };
613
- }),
614
- );
615
- }
616
-
617
- export async function finalizePackUninstallOperation(
618
- db: Database,
619
- input: {
620
- accountId: string;
621
- workspaceId: string;
622
- operationId: string;
623
- operationVersion: number;
624
- packInstallationId: string;
625
- packId: string;
626
- result: Record<string, unknown>;
627
- },
628
- ): Promise<void> {
629
- await withRlsContext(
630
- db,
631
- { accountId: input.accountId, workspaceId: input.workspaceId },
632
- async (scopedDb) =>
633
- await scopedDb.transaction(async (tx) => {
634
- await assertActivePackOperationClaim(tx as unknown as Database, {
635
- accountId: input.accountId,
636
- workspaceId: input.workspaceId,
637
- operationId: input.operationId,
638
- operationVersion: input.operationVersion,
639
- packInstallationId: input.packInstallationId,
640
- });
641
- const now = new Date();
642
- await tx
643
- .update(schema.packInstallations)
644
- .set({ status: "disabled", updatedAt: now })
645
- .where(
646
- and(
647
- eq(schema.packInstallations.workspaceId, input.workspaceId),
648
- eq(schema.packInstallations.id, input.packInstallationId),
649
- eq(schema.packInstallations.packId, input.packId),
650
- ),
651
- );
652
- const [completed] = await tx
653
- .update(schema.capabilityOperations)
654
- .set({
655
- status: "completed",
656
- phase: "completed",
657
- result: input.result,
658
- errorCode: null,
659
- version: sql`${schema.capabilityOperations.version} + 1`,
660
- completedAt: now,
661
- updatedAt: now,
662
- })
663
- .where(
664
- and(
665
- eq(schema.capabilityOperations.id, input.operationId),
666
- eq(schema.capabilityOperations.status, "running"),
667
- eq(schema.capabilityOperations.version, input.operationVersion),
668
- ),
669
- )
670
- .returning({ id: schema.capabilityOperations.id });
671
- if (!completed) throw new PackOperationClaimLostError("Pack operation claim was lost");
672
- }),
673
- );
674
- }
675
-
676
- async function findOtherPackOperationInProgress(
677
- db: Database,
678
- input: { workspaceId: string; packId: string; operationId?: string },
679
- ): Promise<Pick<
680
- typeof schema.capabilityOperations.$inferSelect,
681
- "id" | "kind" | "status" | "version" | "updatedAt"
682
- > | null> {
683
- const [operation] = await db
684
- .select({
685
- id: schema.capabilityOperations.id,
686
- kind: schema.capabilityOperations.kind,
687
- status: schema.capabilityOperations.status,
688
- version: schema.capabilityOperations.version,
689
- updatedAt: schema.capabilityOperations.updatedAt,
690
- })
691
- .from(schema.capabilityOperations)
692
- .where(
693
- and(
694
- eq(schema.capabilityOperations.workspaceId, input.workspaceId),
695
- eq(schema.capabilityOperations.targetKind, "pack"),
696
- eq(schema.capabilityOperations.targetId, input.packId),
697
- inArray(schema.capabilityOperations.status, ["running", "pending"]),
698
- input.operationId ? ne(schema.capabilityOperations.id, input.operationId) : sql`true`,
699
- ),
700
- )
701
- .orderBy(schema.capabilityOperations.createdAt)
702
- .limit(1);
703
- return operation ?? null;
704
- }
705
-
706
- async function resumePackOperation(
707
- db: Database,
708
- operation: typeof schema.capabilityOperations.$inferSelect,
709
- ): Promise<typeof schema.capabilityOperations.$inferSelect> {
710
- if (operation.status === "completed") {
711
- if (operation.result) return operation;
712
- throw new Error("Completed Pack operation has no replay result");
713
- }
714
- if (operation.status !== "pending" && operation.status !== "running") {
715
- throw new PackOperationIdempotencyError("Pack idempotency key is no longer retryable");
716
- }
717
- const [resumed] = await db
718
- .update(schema.capabilityOperations)
719
- .set({
720
- status: "running",
721
- phase: operation.status === "running" ? "recovering_stale" : "resuming",
722
- errorCode: null,
723
- version: operation.version + 1,
724
- updatedAt: sql`now()`,
725
- })
726
- .where(
727
- and(
728
- eq(schema.capabilityOperations.id, operation.id),
729
- eq(schema.capabilityOperations.status, operation.status),
730
- eq(schema.capabilityOperations.version, operation.version),
731
- operation.status === "running"
732
- ? sql`${schema.capabilityOperations.updatedAt} <= now() - interval '15 minutes'`
733
- : sql`true`,
734
- ),
735
- )
736
- .returning();
737
- if (!resumed) {
738
- throw new PackOperationInProgressError(
739
- operation.status === "running"
740
- ? "This Pack operation is still running"
741
- : "This Pack operation changed before retry admission",
742
- );
743
- }
744
- return resumed;
745
- }
746
-
747
- function assertPackOperationRetryableStatus(
748
- operation: typeof schema.capabilityOperations.$inferSelect | undefined,
749
- ): void {
750
- if (!operation || operation.status === "pending" || operation.status === "running") return;
751
- if (operation.status === "completed") {
752
- throw new Error("Completed Pack operation has no replay result");
753
- }
754
- throw new PackOperationIdempotencyError("Pack idempotency key is no longer retryable");
755
- }
756
-
757
- async function supersedePendingPackOperation(
758
- db: Database,
759
- operation: Pick<typeof schema.capabilityOperations.$inferSelect, "id" | "status" | "version">,
760
- ): Promise<void> {
761
- const now = new Date();
762
- const [superseded] = await db
763
- .update(schema.capabilityOperations)
764
- .set({
765
- status: "failed",
766
- phase: "superseded",
767
- errorCode: "superseded_by_retry",
768
- version: sql`${schema.capabilityOperations.version} + 1`,
769
- completedAt: now,
770
- updatedAt: now,
771
- })
772
- .where(
773
- and(
774
- eq(schema.capabilityOperations.id, operation.id),
775
- eq(schema.capabilityOperations.status, "pending"),
776
- eq(schema.capabilityOperations.version, operation.version),
777
- ),
778
- )
779
- .returning({ id: schema.capabilityOperations.id });
780
- if (!superseded) {
781
- throw new PackOperationInProgressError("Another Pack operation changed before retry admission");
782
- }
783
- }
784
-
785
- async function supersedeStaleRunningPackOperation(
786
- db: Database,
787
- operation: Pick<typeof schema.capabilityOperations.$inferSelect, "id" | "status" | "version">,
788
- ): Promise<void> {
789
- const now = new Date();
790
- const [superseded] = await db
791
- .update(schema.capabilityOperations)
792
- .set({
793
- status: "failed",
794
- phase: "superseded_stale",
795
- errorCode: "superseded_by_recovery",
796
- version: operation.version + 1,
797
- completedAt: now,
798
- updatedAt: now,
799
- })
800
- .where(
801
- and(
802
- eq(schema.capabilityOperations.id, operation.id),
803
- eq(schema.capabilityOperations.status, "running"),
804
- eq(schema.capabilityOperations.version, operation.version),
805
- sql`${schema.capabilityOperations.updatedAt} <= now() - interval '15 minutes'`,
806
- ),
807
- )
808
- .returning({ id: schema.capabilityOperations.id });
809
- if (!superseded) {
810
- throw new PackOperationInProgressError("Another Pack operation is still running");
811
- }
812
- }
813
-
814
- async function assertActivePackOperationClaim(
815
- db: Database,
816
- input: {
817
- accountId: string;
818
- workspaceId: string;
819
- operationId: string;
820
- operationVersion: number;
821
- packInstallationId: string;
822
- },
823
- ): Promise<typeof schema.capabilityOperations.$inferSelect> {
824
- const [operation] = await db
825
- .select()
826
- .from(schema.capabilityOperations)
827
- .where(
828
- and(
829
- eq(schema.capabilityOperations.id, input.operationId),
830
- eq(schema.capabilityOperations.accountId, input.accountId),
831
- eq(schema.capabilityOperations.workspaceId, input.workspaceId),
832
- eq(schema.capabilityOperations.targetKind, "pack"),
833
- eq(schema.capabilityOperations.status, "running"),
834
- eq(schema.capabilityOperations.version, input.operationVersion),
835
- ),
836
- )
837
- .for("update")
838
- .limit(1);
839
- if (!operation) throw new PackOperationClaimLostError("Pack operation claim was lost");
840
- const [installation] = await db
841
- .select({ id: schema.packInstallations.id })
842
- .from(schema.packInstallations)
843
- .where(
844
- and(
845
- eq(schema.packInstallations.id, input.packInstallationId),
846
- eq(schema.packInstallations.accountId, input.accountId),
847
- eq(schema.packInstallations.workspaceId, input.workspaceId),
848
- eq(schema.packInstallations.packId, operation.targetId),
849
- ),
850
- )
851
- .limit(1);
852
- if (!installation) {
853
- throw new PackOperationClaimLostError("Pack operation no longer matches its installation");
854
- }
855
- return operation;
856
- }
857
-
858
- function samePackInstallationIntent(
859
- existing: typeof schema.packInstallations.$inferSelect,
860
- input: {
861
- manifestDigest: string;
862
- selectedRigId: string | null;
863
- metadata: Record<string, unknown>;
864
- },
865
- ): boolean {
866
- return (
867
- existing.manifestDigest === input.manifestDigest &&
868
- existing.selectedRigId === input.selectedRigId &&
869
- stableJson(existing.metadata) === stableJson(input.metadata)
870
- );
871
- }
872
-
873
- async function assertRegisteredPackManifestCurrent(
874
- db: Database,
875
- input: {
876
- workspaceId: string;
877
- packId: string;
878
- expectedDigest: string;
879
- admittedDigest: string;
880
- },
881
- ): Promise<void> {
882
- const [registration] = await db
883
- .select({ manifest: schema.workspacePacks.manifest })
884
- .from(schema.workspacePacks)
885
- .where(
886
- and(
887
- eq(schema.workspacePacks.workspaceId, input.workspaceId),
888
- eq(schema.workspacePacks.packId, input.packId),
889
- ),
890
- )
891
- .for("update")
892
- .limit(1);
893
- const parsed = registration ? CapabilityPackSchema.safeParse(registration.manifest) : null;
894
- const currentDigest =
895
- parsed?.success === true
896
- ? createHash("sha256").update(stableJson(parsed.data)).digest("hex")
897
- : null;
898
- if (input.expectedDigest !== input.admittedDigest || currentDigest !== input.expectedDigest) {
899
- throw new PackManifestChangedError(
900
- "Pack manifest changed after preview; review the current installation plan",
901
- );
902
- }
903
- }
904
-
905
- async function completePackOperation(
906
- db: Database,
907
- operationId: string,
908
- operationVersion: number,
909
- result: Record<string, unknown>,
910
- ): Promise<void> {
911
- const now = new Date();
912
- const [completed] = await db
913
- .update(schema.capabilityOperations)
914
- .set({
915
- status: "completed",
916
- phase: "completed",
917
- result,
918
- errorCode: null,
919
- version: sql`${schema.capabilityOperations.version} + 1`,
920
- completedAt: now,
921
- updatedAt: now,
922
- })
923
- .where(
924
- and(
925
- eq(schema.capabilityOperations.id, operationId),
926
- eq(schema.capabilityOperations.status, "running"),
927
- eq(schema.capabilityOperations.version, operationVersion),
928
- ),
929
- )
930
- .returning({ id: schema.capabilityOperations.id });
931
- if (!completed) throw new PackOperationClaimLostError("Pack operation claim was lost");
932
- }
933
-
934
- function mapPackInstallation(row: typeof schema.packInstallations.$inferSelect): PackInstallation {
935
- return {
936
- id: row.id,
937
- accountId: row.accountId,
938
- workspaceId: row.workspaceId,
939
- packId: row.packId,
940
- status: row.status as PackInstallationStatus,
941
- version: row.version,
942
- manifestSnapshot: row.manifestSnapshot,
943
- manifestDigest: row.manifestDigest,
944
- selectedRigId: row.selectedRigId,
945
- installedBySubjectId: row.installedBySubjectId,
946
- metadata: row.metadata,
947
- enabledAt: row.enabledAt.toISOString(),
948
- updatedAt: row.updatedAt.toISOString(),
949
- };
950
- }