@opengeni/db 6.0.1 → 6.0.3

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 (87) hide show
  1. package/dist/browser-sessions.d.ts +26 -0
  2. package/dist/canonical-human-identities.js +3 -3
  3. package/dist/capability-reconciliation.js +2 -2
  4. package/dist/{chunk-4RDUZ5UV.js → chunk-4ZQFT5II.js} +2 -2
  5. package/dist/{chunk-6ORJPCYW.js → chunk-5UOWNKPP.js} +2 -2
  6. package/dist/{chunk-4CCFFLDZ.js → chunk-BG46J5HF.js} +13 -2
  7. package/dist/chunk-BG46J5HF.js.map +1 -0
  8. package/dist/{chunk-CSVHCF3R.js → chunk-IB3SW3JW.js} +11 -4
  9. package/dist/{chunk-CSVHCF3R.js.map → chunk-IB3SW3JW.js.map} +1 -1
  10. package/dist/{chunk-GSB4ISHE.js → chunk-IIPWNNVR.js} +4 -1
  11. package/dist/chunk-IIPWNNVR.js.map +1 -0
  12. package/dist/{chunk-MAMBI2HR.js → chunk-JPYN5XKV.js} +39 -3
  13. package/dist/chunk-JPYN5XKV.js.map +1 -0
  14. package/dist/{chunk-LSLNMENV.js → chunk-KZXWUXWV.js} +4 -4
  15. package/dist/{chunk-LO6BVXXH.js → chunk-LD52IFS3.js} +2 -2
  16. package/dist/{chunk-JILAWE26.js → chunk-M5Z6II2P.js} +2 -2
  17. package/dist/{chunk-PGIFHEBA.js → chunk-RVEOGRMF.js} +2 -2
  18. package/dist/{chunk-H7BC444C.js → chunk-SRAMNVHT.js} +17 -1
  19. package/dist/chunk-SRAMNVHT.js.map +1 -0
  20. package/dist/{chunk-XK6HXON2.js → chunk-UXFBXCHM.js} +2 -2
  21. package/dist/{chunk-RXH5CFG3.js → chunk-VE2XK2KD.js} +12 -8
  22. package/dist/{chunk-RXH5CFG3.js.map → chunk-VE2XK2KD.js.map} +1 -1
  23. package/dist/{chunk-FULLE5CF.js → chunk-WT7HG43Y.js} +3 -3
  24. package/dist/{chunk-I7HQL6FK.js → chunk-ZM5BA3TD.js} +3 -3
  25. package/dist/connect-attempts.js +3 -3
  26. package/dist/database.d.ts +8 -0
  27. package/dist/editable-artifact-durable-export.js +2 -2
  28. package/dist/editable-artifacts.js +3 -3
  29. package/dist/index.d.ts +69 -20
  30. package/dist/index.js +633 -202
  31. package/dist/index.js.map +1 -1
  32. package/dist/knowledge-entries.d.ts +2 -0
  33. package/dist/knowledge-indexing.d.ts +17 -0
  34. package/dist/managed-auth-session-sets.js +3 -3
  35. package/dist/managed-user-setup.d.ts +1 -0
  36. package/dist/mcp-operations.js +2 -2
  37. package/dist/migrate.d.ts +10 -1
  38. package/dist/migrate.js +3 -1
  39. package/dist/organization-integration-policy.js +3 -3
  40. package/dist/organization-membership-lifecycle.d.ts +1 -0
  41. package/dist/provision-roles.js +1 -1
  42. package/dist/retained-provider-commands.d.ts +4 -2
  43. package/dist/retained-provider-commands.js +6 -6
  44. package/dist/runtime-posture.d.ts +1 -1
  45. package/dist/schema.d.ts +25 -8
  46. package/dist/schema.js +1 -1
  47. package/dist/session-background-commands.js +4 -4
  48. package/dist/session-command-output.js +5 -5
  49. package/dist/session-event-slices.js +3 -3
  50. package/dist/session-mcp-credential-rotation.js +4 -4
  51. package/dist/session-tenancy.js +3 -3
  52. package/dist/video-generation.js +3 -3
  53. package/dist/workspace-tool-defaults.js +4 -4
  54. package/drizzle/0508_deadline_command_workspace_capture.sql +75 -0
  55. package/drizzle/0509_verified_signup_trial_credits.sql +76 -0
  56. package/drizzle/0510_knowledge_index_funding_wait.sql +174 -0
  57. package/drizzle/0511_knowledge_visible_index_status.sql +56 -0
  58. package/drizzle/0512_insights_fact_reads_hash_join.sql +31 -0
  59. package/drizzle/0513_message_fork_prefix_compaction.sql +85 -0
  60. package/drizzle/0514_failed_session_variable_set_attach.sql +25 -0
  61. package/package.json +5 -5
  62. package/src/browser-sessions.ts +71 -1
  63. package/src/database.ts +18 -0
  64. package/src/index.ts +706 -252
  65. package/src/insights.ts +44 -36
  66. package/src/knowledge-entries.ts +43 -2
  67. package/src/knowledge-indexing.ts +79 -1
  68. package/src/managed-user-setup.ts +18 -1
  69. package/src/migrate.ts +58 -1
  70. package/src/organization-membership-lifecycle.ts +11 -0
  71. package/src/provision-roles.ts +12 -0
  72. package/src/retained-provider-commands.ts +16 -6
  73. package/src/runtime-posture.ts +4 -0
  74. package/src/schema.ts +3 -0
  75. package/dist/chunk-4CCFFLDZ.js.map +0 -1
  76. package/dist/chunk-GSB4ISHE.js.map +0 -1
  77. package/dist/chunk-H7BC444C.js.map +0 -1
  78. package/dist/chunk-MAMBI2HR.js.map +0 -1
  79. /package/dist/{chunk-4RDUZ5UV.js.map → chunk-4ZQFT5II.js.map} +0 -0
  80. /package/dist/{chunk-6ORJPCYW.js.map → chunk-5UOWNKPP.js.map} +0 -0
  81. /package/dist/{chunk-LSLNMENV.js.map → chunk-KZXWUXWV.js.map} +0 -0
  82. /package/dist/{chunk-LO6BVXXH.js.map → chunk-LD52IFS3.js.map} +0 -0
  83. /package/dist/{chunk-JILAWE26.js.map → chunk-M5Z6II2P.js.map} +0 -0
  84. /package/dist/{chunk-PGIFHEBA.js.map → chunk-RVEOGRMF.js.map} +0 -0
  85. /package/dist/{chunk-XK6HXON2.js.map → chunk-UXFBXCHM.js.map} +0 -0
  86. /package/dist/{chunk-FULLE5CF.js.map → chunk-WT7HG43Y.js.map} +0 -0
  87. /package/dist/{chunk-I7HQL6FK.js.map → chunk-ZM5BA3TD.js.map} +0 -0
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  retainedProviderCommandPersistence
3
- } from "./chunk-RXH5CFG3.js";
3
+ } from "./chunk-VE2XK2KD.js";
4
4
  import {
5
5
  readSessionMessageSliceInScope
6
- } from "./chunk-FULLE5CF.js";
6
+ } from "./chunk-WT7HG43Y.js";
7
7
  import {
8
8
  FORCE_RLS_TABLES,
9
9
  NON_RLS_RUNTIME_TABLES,
@@ -28,7 +28,7 @@ import {
28
28
  isRetryableRuntimeDatabaseStartupError,
29
29
  provisionRoles,
30
30
  runtimeDatabaseReadyCheck
31
- } from "./chunk-H7BC444C.js";
31
+ } from "./chunk-SRAMNVHT.js";
32
32
  import {
33
33
  CanonicalHumanIdentityAuthorityError,
34
34
  CanonicalHumanIdentityConflictError,
@@ -42,7 +42,7 @@ import {
42
42
  getCanonicalHumanIdentityProjection,
43
43
  synchronizeCanonicalHumanLoginBindings,
44
44
  validateCanonicalHumanSession
45
- } from "./chunk-PGIFHEBA.js";
45
+ } from "./chunk-RVEOGRMF.js";
46
46
  import {
47
47
  EDITABLE_ARTIFACT_INTENT_MAX_BYTES,
48
48
  EDITABLE_ARTIFACT_KERNEL_TAIL_MAX_BYTES,
@@ -54,7 +54,7 @@ import {
54
54
  listEditableArtifactIdsForSession,
55
55
  touchEditableArtifactSessionLink,
56
56
  transactionallyAuthorizeEditableArtifactActor
57
- } from "./chunk-6ORJPCYW.js";
57
+ } from "./chunk-5UOWNKPP.js";
58
58
  import {
59
59
  SessionTenancyAccessError,
60
60
  SessionTenancyConflictError,
@@ -71,8 +71,8 @@ import {
71
71
  replayAppliedSessionFork,
72
72
  sessionTenancyProductActivated,
73
73
  transitionSessionVisibility
74
- } from "./chunk-JILAWE26.js";
75
- import "./chunk-LSLNMENV.js";
74
+ } from "./chunk-M5Z6II2P.js";
75
+ import "./chunk-KZXWUXWV.js";
76
76
  import {
77
77
  ConnectAttemptConflictError,
78
78
  ConnectAttemptNotFoundError,
@@ -82,11 +82,11 @@ import {
82
82
  getConnectAttempt,
83
83
  getConnectBeginReplay,
84
84
  listPendingConnectAttempts
85
- } from "./chunk-XK6HXON2.js";
85
+ } from "./chunk-UXFBXCHM.js";
86
86
  import {
87
87
  withOrganizationIntegrationAcquisition,
88
88
  withOrganizationIntegrationPolicyFence
89
- } from "./chunk-LO6BVXXH.js";
89
+ } from "./chunk-LD52IFS3.js";
90
90
  import {
91
91
  AgentCommandAuthorityError,
92
92
  CHILD_LIFECYCLE_DEDUPE_PREFIX,
@@ -220,7 +220,7 @@ import {
220
220
  updateSessionCommandReceiptResult,
221
221
  workspaceControlLockBudget,
222
222
  workspaceControlRequestLockTimeoutMs
223
- } from "./chunk-CSVHCF3R.js";
223
+ } from "./chunk-IB3SW3JW.js";
224
224
  import {
225
225
  ACTIVE_VIDEO_GENERATION_STATUSES,
226
226
  VideoGenerationCapacityError,
@@ -252,7 +252,7 @@ import {
252
252
  settleVideoGenerationFailure,
253
253
  settleVideoGenerationReady,
254
254
  updateWorkspaceVideoGenerationPolicy
255
- } from "./chunk-I7HQL6FK.js";
255
+ } from "./chunk-ZM5BA3TD.js";
256
256
  import {
257
257
  external_exports
258
258
  } from "./chunk-4UJCXBR2.js";
@@ -278,12 +278,13 @@ import {
278
278
  reapManagedAuthIsolatedSessions,
279
279
  releaseManagedAuthActorMutationLease,
280
280
  validateManagedAuthActorMutationLease
281
- } from "./chunk-4RDUZ5UV.js";
281
+ } from "./chunk-4ZQFT5II.js";
282
282
  import {
283
283
  SandboxProviderReadLockUnavailableError,
284
284
  SessionEventPersistenceError,
285
285
  createDb,
286
286
  currentSessionAttachmentReadAccess,
287
+ currentSessionRlsActorIdentityKey,
287
288
  databaseFailureCode,
288
289
  dbBindingFor,
289
290
  isDatabasePersistenceFailure,
@@ -316,7 +317,7 @@ import {
316
317
  withWorkspaceSubjectRls,
317
318
  withWorkspaceSubjectSessionActivityRls,
318
319
  withWorkspaceUsageLock
319
- } from "./chunk-4CCFFLDZ.js";
320
+ } from "./chunk-BG46J5HF.js";
320
321
  import {
321
322
  agentRunStates,
322
323
  apiKeys,
@@ -552,11 +553,11 @@ import {
552
553
  xaiRotationSettings,
553
554
  xaiSessionAccountPins,
554
555
  xaiSubscriptionCredentials
555
- } from "./chunk-GSB4ISHE.js";
556
+ } from "./chunk-IIPWNNVR.js";
556
557
  import {
557
558
  migrate,
558
559
  runMigrations
559
- } from "./chunk-MAMBI2HR.js";
560
+ } from "./chunk-JPYN5XKV.js";
560
561
  import {
561
562
  LOSSLESS_CONTENT_CODEC_VERSION,
562
563
  LOSSLESS_JSON_STRING_PREFIX,
@@ -8407,6 +8408,7 @@ import {
8407
8408
  import {
8408
8409
  environmentsEncryptionKeyBytes as environmentsEncryptionKeyBytes2,
8409
8410
  SANDBOX_LIFECYCLE_TRANSITION_MAX_WAIT_MS as SANDBOX_LIFECYCLE_TRANSITION_MAX_WAIT_MS2,
8411
+ SANDBOX_DEADLINE_COMMAND_STOP_GRACE_MS,
8410
8412
  WORKSPACE_OPENROUTER_CONNECTION_DOMAIN as WORKSPACE_OPENROUTER_CONNECTION_DOMAIN2,
8411
8413
  WORKSPACE_OPENROUTER_CONNECTION_ROLE,
8412
8414
  VERCEL_AI_GATEWAY_CONNECTION_DOMAIN as VERCEL_AI_GATEWAY_CONNECTION_DOMAIN2,
@@ -24422,10 +24424,23 @@ async function completeSelfServiceOrganizationSetup(db, input) {
24422
24424
  return await db.transaction(async (tx) => {
24423
24425
  const txDb = tx;
24424
24426
  await setSubjectRlsContext(txDb, input.actorSubjectId);
24427
+ await rawRows(
24428
+ txDb,
24429
+ sql47`select pg_catalog.set_config(
24430
+ 'opengeni.verified_signup_trial_enabled',
24431
+ ${input.trialCreditsEnabled === true ? "on" : "off"}, true
24432
+ )`
24433
+ );
24425
24434
  const [row] = await rawRows(
24426
24435
  txDb,
24427
24436
  sql47`select complete_self_service_organization_setup(
24428
- ${JSON.stringify(input)}::jsonb
24437
+ ${JSON.stringify({
24438
+ authUserId: input.authUserId,
24439
+ actorSubjectId: input.actorSubjectId,
24440
+ organizationName: input.organizationName,
24441
+ operationId: input.operationId,
24442
+ requestFingerprint: input.requestFingerprint
24443
+ })}::jsonb
24429
24444
  ) as result`
24430
24445
  );
24431
24446
  return CompleteSelfServiceOrganizationSetupResponse.parse(row?.result);
@@ -29496,36 +29511,36 @@ async function countScheduledTaskFires(db, input) {
29496
29511
  async function aggregateSessionDepth(db, workspaceId) {
29497
29512
  const context = await rlsContextForWorkspace(db, workspaceId);
29498
29513
  return await withRlsContext(db, context, async (scopedDb) => {
29499
- const buckets = await scopedDb.select({
29514
+ const buckets = (await scopedDb.select({
29500
29515
  depth: sessions.nestedAgentDepth,
29501
29516
  sessions: sql59`count(*)::int`
29502
- }).from(sessions).where(eq41(sessions.workspaceId, workspaceId)).groupBy(sessions.nestedAgentDepth).orderBy(sessions.nestedAgentDepth);
29503
- const [stats] = await scopedDb.select({
29504
- sessionsTouched: sql59`count(*)::int`,
29505
- rootSessions: sql59`count(*) filter (where ${sessions.nestedAgentDepth} = 0)::int`,
29506
- deepestDepth: sql59`coalesce(max(${sessions.nestedAgentDepth}), 0)`,
29507
- avgDepth: sql59`coalesce(avg(${sessions.nestedAgentDepth}), 0)`
29508
- }).from(sessions).where(eq41(sessions.workspaceId, workspaceId));
29509
- const [deepest] = await scopedDb.select({
29517
+ }).from(sessions).where(eq41(sessions.workspaceId, workspaceId)).groupBy(sessions.nestedAgentDepth).orderBy(sessions.nestedAgentDepth)).map((row) => ({ depth: row.depth, sessions: Number(row.sessions) }));
29518
+ const sessionsTouched = buckets.reduce((sum, bucket) => sum + bucket.sessions, 0);
29519
+ const rootSessions = buckets.find((bucket) => bucket.depth === 0)?.sessions ?? 0;
29520
+ const deepestDepth = buckets.reduce((max, bucket) => Math.max(max, bucket.depth), 0);
29521
+ const avgDepth = sessionsTouched > 0 ? buckets.reduce((sum, bucket) => sum + bucket.depth * bucket.sessions, 0) / sessionsTouched : 0;
29522
+ const [deepest] = sessionsTouched > 0 ? await scopedDb.select({
29510
29523
  id: sessions.id,
29511
29524
  title: sessions.title,
29512
29525
  depth: sessions.nestedAgentDepth
29513
- }).from(sessions).where(eq41(sessions.workspaceId, workspaceId)).orderBy(desc13(sessions.nestedAgentDepth), desc13(sessions.updatedAt)).limit(1);
29526
+ }).from(sessions).where(
29527
+ and40(
29528
+ eq41(sessions.workspaceId, workspaceId),
29529
+ eq41(sessions.nestedAgentDepth, deepestDepth)
29530
+ )
29531
+ ).orderBy(desc13(sessions.updatedAt)).limit(1) : [];
29514
29532
  const [goals] = await scopedDb.select({
29515
29533
  active: sql59`count(*) filter (where ${sessionGoals.status} = 'active')::int`,
29516
29534
  completed: sql59`count(*) filter (where ${sessionGoals.status} = 'completed')::int`
29517
29535
  }).from(sessionGoals).where(eq41(sessionGoals.workspaceId, workspaceId));
29518
29536
  return {
29519
- buckets: buckets.map((row) => ({
29520
- depth: row.depth,
29521
- sessions: Number(row.sessions)
29522
- })),
29523
- sessionsTouched: Number(stats?.sessionsTouched ?? 0),
29524
- rootSessions: Number(stats?.rootSessions ?? 0),
29525
- deepestDepth: Number(stats?.deepestDepth ?? 0),
29537
+ buckets,
29538
+ sessionsTouched,
29539
+ rootSessions,
29540
+ deepestDepth,
29526
29541
  deepestSessionId: deepest?.id ?? null,
29527
29542
  deepestSessionTitle: deepest?.title?.trim() || "",
29528
- avgDepth: Number(stats?.avgDepth ?? 0),
29543
+ avgDepth,
29529
29544
  goalsActive: Number(goals?.active ?? 0),
29530
29545
  goalsCompleted: Number(goals?.completed ?? 0)
29531
29546
  };
@@ -36849,6 +36864,9 @@ function associationFromRow(row) {
36849
36864
  lastUsedAt: iso13(row.lastUsedAt)
36850
36865
  };
36851
36866
  }
36867
+ function readStoredBrowserCapabilities(value) {
36868
+ return BrowserSessionCapabilities.parse({ permissions: false, ...value });
36869
+ }
36852
36870
  function browserSessionFromRows(row, associations) {
36853
36871
  return BrowserSession.parse({
36854
36872
  id: row.id,
@@ -36866,7 +36884,7 @@ function browserSessionFromRows(row, associations) {
36866
36884
  baseRevisionId: row.baseRevisionId,
36867
36885
  networkRouteId: row.networkRouteId,
36868
36886
  linkedComputerSessionId: row.linkedComputerSessionId,
36869
- capabilities: row.capabilities,
36887
+ capabilities: readStoredBrowserCapabilities(row.capabilities),
36870
36888
  associations: associations.map(associationFromRow),
36871
36889
  createdBySubjectId: row.createdBySubjectId,
36872
36890
  createdAt: iso13(row.createdAt),
@@ -37681,6 +37699,50 @@ async function prepareBrowserSessionSuspend(db, input) {
37681
37699
  }
37682
37700
  });
37683
37701
  }
37702
+ async function failPreparedBrowserSessionSuspend(db, input) {
37703
+ const error = InteractionError.parse(input.error);
37704
+ return await withRlsContext(
37705
+ db,
37706
+ input,
37707
+ async (scopedDb) => await scopedDb.transaction(async (txRaw) => {
37708
+ const tx = txRaw;
37709
+ await lockOperation3(tx, input.workspaceId, input.operationId);
37710
+ const operation = await loadOperation2(tx, input.workspaceId, input.operationId);
37711
+ assertOperationResource(operation, input.browserSessionId, "suspend");
37712
+ if (operation.state !== "prepared" || operation.dispatchedAt) return null;
37713
+ await lockBrowserSession(tx, input.workspaceId, input.browserSessionId);
37714
+ const now = /* @__PURE__ */ new Date();
37715
+ const [sessionRow] = await tx.update(browserSessions).set({ lifecycle: "active", failureCode: null, updatedAt: now }).where(
37716
+ and50(
37717
+ eq51(browserSessions.workspaceId, input.workspaceId),
37718
+ eq51(browserSessions.id, input.browserSessionId),
37719
+ eq51(browserSessions.lifecycle, "suspending")
37720
+ )
37721
+ ).returning();
37722
+ if (!sessionRow) {
37723
+ throw new BrowserSessionStateError("Prepared BrowserSession suspension changed state");
37724
+ }
37725
+ const [operationRow] = await tx.update(interactionOperations).set({
37726
+ state: "failed",
37727
+ errorCode: error.code,
37728
+ errorMessage: error.message,
37729
+ errorRetryable: error.retryable,
37730
+ errorDetails: error.details ?? null,
37731
+ settledAt: now,
37732
+ updatedAt: now
37733
+ }).where(eq51(interactionOperations.operationId, input.operationId)).returning();
37734
+ if (!operationRow) throw new Error("BrowserSession failure receipt was lost");
37735
+ const associations = await loadAssociations(tx, input.workspaceId, [
37736
+ input.browserSessionId
37737
+ ]);
37738
+ await advanceWorkspaceInteractionRevision(tx, input.accountId, input.workspaceId);
37739
+ return BrowserSessionMutationResponse.parse({
37740
+ session: browserSessionFromRows(sessionRow, associations),
37741
+ operation: operationReceipt(operationRow, false)
37742
+ });
37743
+ })
37744
+ );
37745
+ }
37684
37746
  async function prepareBrowserSessionResume(db, input) {
37685
37747
  return await prepareBrowserSessionLifecycleTransition(db, input, {
37686
37748
  kind: "resume",
@@ -42164,6 +42226,7 @@ import {
42164
42226
  KnowledgeEntryRestoreRequest,
42165
42227
  KnowledgeEntrySaveRequest,
42166
42228
  KnowledgeEntrySummary,
42229
+ KnowledgeIndexStatus,
42167
42230
  KnowledgeEntryWriteReceipt
42168
42231
  } from "@opengeni/contracts";
42169
42232
  import { sql as sql84 } from "drizzle-orm";
@@ -42351,6 +42414,27 @@ function decodeProjection(value, full) {
42351
42414
  }
42352
42415
  });
42353
42416
  }
42417
+ async function withIndexStatus(db, context, entries, view) {
42418
+ if (view === "archived" || view === "rejected") return entries;
42419
+ const items = entries.filter(
42420
+ (entry) => ("entry" in entry.revision ? entry.revision.entry.kind : entry.revision.kind) === "source"
42421
+ ).map((entry) => ({ entryId: entry.id, revisionId: entry.revision.id }));
42422
+ if (!items.length) return entries;
42423
+ const statuses = await inKnowledgeContext(db, context, async (tx) => {
42424
+ const [row] = await rawRows(
42425
+ tx,
42426
+ sql84`SELECT knowledge_visible_index_status(${context.accountId}::uuid,
42427
+ ${context.workspaceId}::uuid,${JSON.stringify(context.actor)}::jsonb,
42428
+ ${JSON.stringify(items)}::jsonb,${view}) AS result`
42429
+ );
42430
+ return external_exports.array(external_exports.object({ entryId: external_exports.uuid(), revisionId: external_exports.uuid(), status: KnowledgeIndexStatus })).parse(row?.result);
42431
+ });
42432
+ const byRevision = new Map(statuses.map((item) => [item.revisionId, item.status]));
42433
+ return entries.map((entry) => {
42434
+ const indexStatus = byRevision.get(entry.revision.id);
42435
+ return indexStatus ? { ...entry, indexStatus } : entry;
42436
+ });
42437
+ }
42354
42438
  async function read(db, context, request) {
42355
42439
  return inKnowledgeContext(db, context, async (tx) => {
42356
42440
  const [row] = await rawRows(
@@ -42369,7 +42453,9 @@ async function getKnowledgeEntry(db, context, entryId, options = {}) {
42369
42453
  view: external_exports.enum(["published", "needs_review", "archived", "rejected"]).optional()
42370
42454
  }).strict().parse({ entryId, ...options });
42371
42455
  const rows = await read(db, context, { ...request, operation: "get", limit: 1 });
42372
- return rows[0] ? decodeProjection(rows[0], true) : null;
42456
+ if (!rows[0]) return null;
42457
+ const record4 = decodeProjection(rows[0], true);
42458
+ return (await withIndexStatus(db, context, [record4], request.view ?? "published"))[0];
42373
42459
  }
42374
42460
  var Cursor2 = external_exports.object({
42375
42461
  v: external_exports.literal(2),
@@ -42402,7 +42488,12 @@ async function listKnowledgeEntries(db, context, input = {}, embedding) {
42402
42488
  embeddingModel: embedding.model
42403
42489
  } : {}
42404
42490
  });
42405
- const entries = rows.slice(0, request.limit).map((row) => decodeProjection(row, false));
42491
+ const entries = await withIndexStatus(
42492
+ db,
42493
+ context,
42494
+ rows.slice(0, request.limit).map((row) => decodeProjection(row, false)),
42495
+ request.view
42496
+ );
42406
42497
  return {
42407
42498
  entries,
42408
42499
  nextCursor: rows.length > request.limit && entries.length ? Buffer.from(
@@ -42719,6 +42810,37 @@ var Claim = external_exports.object({
42719
42810
  generation: external_exports.number().int().positive(),
42720
42811
  nextIndex: external_exports.number().int().nonnegative()
42721
42812
  });
42813
+ async function knowledgeIndexBillingActivationTime(db) {
42814
+ const [row] = await rawRows(
42815
+ db,
42816
+ sql85`SELECT clock_timestamp() AS "activatedAt"`
42817
+ );
42818
+ return external_exports.coerce.date().parse(row?.activatedAt);
42819
+ }
42820
+ async function freezeKnowledgeIndexBillingMode(db, raw2, requested, activatedAt, rateMicrosPerMillionBytes) {
42821
+ const claim = Claim.parse(raw2);
42822
+ return withRlsContext(db, { accountId: claim.accountId }, async (tx) => {
42823
+ const [row] = await rawRows(
42824
+ tx,
42825
+ sql85`SELECT knowledge_index_billing_policy(${claim.accountId}::uuid,
42826
+ ${claim.revisionId}::uuid,${claim.leaseId}::uuid,${requested},
42827
+ ${activatedAt.toISOString()}::timestamptz,
42828
+ ${rateMicrosPerMillionBytes}::bigint) AS policy`
42829
+ );
42830
+ return external_exports.object({
42831
+ mode: external_exports.enum(["usage_only", "shadow", "credits", "awaiting_review", "obsolete"]),
42832
+ rateMicrosPerMillionBytes: external_exports.number().int().nonnegative()
42833
+ }).parse(row?.policy);
42834
+ });
42835
+ }
42836
+ async function withKnowledgeQueryAccountLock(db, accountId, workspaceId, fn) {
42837
+ return withWorkspaceUsageLock(db, workspaceId, async (tx) => {
42838
+ await tx.execute(
42839
+ sql85`SELECT pg_advisory_xact_lock(hashtext(${`knowledge-query-account:${accountId}`}))`
42840
+ );
42841
+ return fn(tx);
42842
+ });
42843
+ }
42722
42844
  async function claimKnowledgeIndexJobs(db, input) {
42723
42845
  const request = external_exports.object({
42724
42846
  model: Claim.shape.model,
@@ -42785,6 +42907,28 @@ async function completeKnowledgeIndexJob(db, claim, expectedNextIndex) {
42785
42907
  async function deferKnowledgeIndexJob(db, claim) {
42786
42908
  return work(db, claim, { operation: "fail" });
42787
42909
  }
42910
+ async function waitKnowledgeIndexForFunding(db, raw2) {
42911
+ const claim = Claim.parse(raw2);
42912
+ return withRlsContext(db, { accountId: claim.accountId }, async (tx) => {
42913
+ const [row] = await rawRows(
42914
+ tx,
42915
+ sql85`SELECT knowledge_index_wait_for_funding(${claim.accountId}::uuid,
42916
+ ${claim.revisionId}::uuid,${claim.leaseId}::uuid) AS result`
42917
+ );
42918
+ return external_exports.object({ status: external_exports.enum(["pending", "obsolete"]) }).parse(row?.result);
42919
+ });
42920
+ }
42921
+ async function guardPaidKnowledgeIndexPublication(db, raw2) {
42922
+ const claim = Claim.parse(raw2);
42923
+ return withRlsContext(db, { accountId: claim.accountId }, async (tx) => {
42924
+ const [row] = await rawRows(
42925
+ tx,
42926
+ sql85`SELECT knowledge_index_paid_publication_guard(${claim.accountId}::uuid,
42927
+ ${claim.revisionId}::uuid,${claim.leaseId}::uuid) AS status`
42928
+ );
42929
+ return external_exports.enum(["published", "awaiting_review", "obsolete"]).parse(row?.status);
42930
+ });
42931
+ }
42788
42932
  async function continueKnowledgeIndexJob(db, claim) {
42789
42933
  return work(db, claim, { operation: "continue" });
42790
42934
  }
@@ -45702,6 +45846,47 @@ async function applyCreditDebitUpToBalance(db, input) {
45702
45846
  }
45703
45847
  );
45704
45848
  }
45849
+ async function applyCreditDebitAfterUse(db, input) {
45850
+ if (!Number.isSafeInteger(input.amountMicros) || input.amountMicros <= 0) {
45851
+ throw new Error("post-use credit debit requires a positive, safe integer micro amount");
45852
+ }
45853
+ return await withRlsContext(
45854
+ db,
45855
+ { accountId: input.accountId, workspaceId: input.workspaceId ?? null },
45856
+ async (scopedDb) => {
45857
+ const inserted = await scopedDb.insert(creditLedgerEntries).values({
45858
+ accountId: input.accountId,
45859
+ workspaceId: input.workspaceId ?? null,
45860
+ type: input.type,
45861
+ amountMicros: -input.amountMicros,
45862
+ sourceType: input.sourceType,
45863
+ sourceId: input.sourceId,
45864
+ idempotencyKey: input.idempotencyKey,
45865
+ metadata: input.metadata ?? {},
45866
+ occurredAt: input.occurredAt ?? /* @__PURE__ */ new Date()
45867
+ }).onConflictDoNothing({ target: creditLedgerEntries.idempotencyKey }).returning({ id: creditLedgerEntries.id });
45868
+ if (inserted.length === 0) {
45869
+ const [prior] = await scopedDb.select({
45870
+ accountId: creditLedgerEntries.accountId,
45871
+ workspaceId: creditLedgerEntries.workspaceId,
45872
+ type: creditLedgerEntries.type,
45873
+ amountMicros: creditLedgerEntries.amountMicros,
45874
+ sourceType: creditLedgerEntries.sourceType,
45875
+ sourceId: creditLedgerEntries.sourceId
45876
+ }).from(creditLedgerEntries).where(eq59(creditLedgerEntries.idempotencyKey, input.idempotencyKey));
45877
+ if (!prior || prior.accountId !== input.accountId || prior.workspaceId !== (input.workspaceId ?? null) || prior.type !== input.type || prior.amountMicros !== -input.amountMicros || prior.sourceType !== input.sourceType || prior.sourceId !== input.sourceId) {
45878
+ throw new Error(
45879
+ "post-use credit debit idempotency key conflicts with a different charge"
45880
+ );
45881
+ }
45882
+ }
45883
+ return {
45884
+ balance: await getBillingBalance(scopedDb, input.accountId),
45885
+ debitedMicros: inserted.length ? input.amountMicros : 0
45886
+ };
45887
+ }
45888
+ );
45889
+ }
45705
45890
  async function hasCreditLedgerEntry(db, accountId, idempotencyKey) {
45706
45891
  return await withAccountRls(db, accountId, async (scopedDb) => {
45707
45892
  const [row] = await scopedDb.select({ id: creditLedgerEntries.id }).from(creditLedgerEntries).where(
@@ -46420,6 +46605,24 @@ async function getRetainedScreenshotArtifact(db, workspaceId, sessionId, artifac
46420
46605
  return row ? mapRetainedScreenshotArtifact(row.artifact, row.file) : null;
46421
46606
  });
46422
46607
  }
46608
+ async function getRetainedScreenshotArtifactForToolCall(db, workspaceId, sessionId, toolCallId) {
46609
+ return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
46610
+ const rows = await scopedDb.select({ artifact: retainedScreenshotArtifacts, file: files }).from(retainedScreenshotArtifacts).innerJoin(
46611
+ files,
46612
+ and58(
46613
+ eq59(files.workspaceId, retainedScreenshotArtifacts.workspaceId),
46614
+ eq59(files.id, retainedScreenshotArtifacts.artifactId)
46615
+ )
46616
+ ).where(
46617
+ and58(
46618
+ eq59(retainedScreenshotArtifacts.workspaceId, workspaceId),
46619
+ eq59(retainedScreenshotArtifacts.sessionId, sessionId),
46620
+ eq59(retainedScreenshotArtifacts.toolCallId, toolCallId)
46621
+ )
46622
+ ).limit(2);
46623
+ return rows.length === 1 ? mapRetainedScreenshotArtifact(rows[0].artifact, rows[0].file) : null;
46624
+ });
46625
+ }
46423
46626
  async function claimRetainedScreenshotMaintenance(db, input) {
46424
46627
  const rows = await rawRows(
46425
46628
  db,
@@ -67818,74 +68021,129 @@ function assertPendingToolOutputPolicyMatches(stored, supplied, callId) {
67818
68021
  );
67819
68022
  }
67820
68023
  }
68024
+ function pendingToolCallItemsEqual(left, right) {
68025
+ if (left === right) return true;
68026
+ if (left === null || right === null || typeof left !== "object" || typeof right !== "object")
68027
+ return false;
68028
+ if (Array.isArray(left) || Array.isArray(right)) {
68029
+ return Array.isArray(left) && Array.isArray(right) && left.length === right.length && left.every((value, index) => pendingToolCallItemsEqual(value, right[index]));
68030
+ }
68031
+ const keys = Object.keys(left);
68032
+ return keys.length === Object.keys(right).length && keys.every(
68033
+ (key2) => Object.hasOwn(right, key2) && pendingToolCallItemsEqual(
68034
+ left[key2],
68035
+ right[key2]
68036
+ )
68037
+ );
68038
+ }
67821
68039
  async function registerPendingSessionToolCall(db, input) {
67822
- return await withRlsContext(
67823
- db,
67824
- { accountId: input.accountId, workspaceId: input.workspaceId },
67825
- async (scopedDb) => await scopedDb.transaction(async (tx) => {
67826
- const fence = await lockTurnAttemptWriteFenceTx(tx, {
67827
- workspaceId: input.workspaceId,
67828
- sessionId: input.sessionId,
67829
- turnId: input.turnId,
67830
- executionGeneration: input.executionGeneration,
67831
- attemptId: input.attemptId
67832
- });
67833
- if (!fence.allowed) return { accepted: false, registered: false };
67834
- const inserted = await tx.insert(sessionPendingToolCalls).values(
67835
- withLosslessContentWriteVersion(
67836
- {
67837
- accountId: input.accountId,
68040
+ const correlationId = crypto.randomUUID();
68041
+ const registerOnce = async (attempt) => {
68042
+ try {
68043
+ return await withRlsContext(
68044
+ db,
68045
+ { accountId: input.accountId, workspaceId: input.workspaceId },
68046
+ async (scopedDb) => await scopedDb.transaction(async (tx) => {
68047
+ const fence = await lockTurnAttemptWriteFenceTx(tx, {
67838
68048
  workspaceId: input.workspaceId,
67839
68049
  sessionId: input.sessionId,
67840
68050
  turnId: input.turnId,
67841
68051
  executionGeneration: input.executionGeneration,
67842
- attemptId: input.attemptId,
67843
- callId: input.callId,
67844
- callType: input.callType,
67845
- callItem: input.callItem,
67846
- modelToolOutputTruncationTokens: input.modelToolOutputTruncationTokens ?? null
67847
- },
67848
- "callItem",
67849
- "callItemCodecVersion"
67850
- )
67851
- ).onConflictDoNothing({
67852
- target: [
67853
- sessionPendingToolCalls.workspaceId,
67854
- sessionPendingToolCalls.turnId,
67855
- sessionPendingToolCalls.callId
67856
- ]
67857
- }).returning({ id: sessionPendingToolCalls.id });
67858
- if (inserted.length === 0) {
67859
- const [pending] = await tx.select({
67860
- id: sessionPendingToolCalls.id,
67861
- modelToolOutputTruncationTokens: sessionPendingToolCalls.modelToolOutputTruncationTokens
67862
- }).from(sessionPendingToolCalls).where(
67863
- and58(
67864
- eq59(sessionPendingToolCalls.workspaceId, input.workspaceId),
67865
- eq59(sessionPendingToolCalls.sessionId, input.sessionId),
67866
- eq59(sessionPendingToolCalls.turnId, input.turnId),
67867
- eq59(sessionPendingToolCalls.callId, input.callId)
67868
- )
67869
- ).for("update").limit(1);
67870
- if (!pending) {
67871
- throw new Error(
67872
- `Pending SDK tool call disappeared during registration: ${input.callId}`
67873
- );
67874
- }
67875
- assertPendingToolOutputPolicyMatches(
67876
- pending.modelToolOutputTruncationTokens,
67877
- input.modelToolOutputTruncationTokens,
67878
- input.callId
67879
- );
67880
- if (pending.modelToolOutputTruncationTokens === null && input.modelToolOutputTruncationTokens !== void 0) {
67881
- await tx.update(sessionPendingToolCalls).set({
67882
- modelToolOutputTruncationTokens: input.modelToolOutputTruncationTokens
67883
- }).where(eq59(sessionPendingToolCalls.id, pending.id));
67884
- }
68052
+ attemptId: input.attemptId
68053
+ });
68054
+ if (!fence.allowed) return { accepted: false, registered: false };
68055
+ const inserted = await tx.insert(sessionPendingToolCalls).values(
68056
+ withLosslessContentWriteVersion(
68057
+ {
68058
+ accountId: input.accountId,
68059
+ workspaceId: input.workspaceId,
68060
+ sessionId: input.sessionId,
68061
+ turnId: input.turnId,
68062
+ executionGeneration: input.executionGeneration,
68063
+ attemptId: input.attemptId,
68064
+ callId: input.callId,
68065
+ callType: input.callType,
68066
+ callItem: input.callItem,
68067
+ modelToolOutputTruncationTokens: input.modelToolOutputTruncationTokens ?? null
68068
+ },
68069
+ "callItem",
68070
+ "callItemCodecVersion"
68071
+ )
68072
+ ).onConflictDoNothing({
68073
+ target: [
68074
+ sessionPendingToolCalls.workspaceId,
68075
+ sessionPendingToolCalls.turnId,
68076
+ sessionPendingToolCalls.callId
68077
+ ]
68078
+ }).returning({ id: sessionPendingToolCalls.id });
68079
+ if (inserted.length === 0) {
68080
+ const [pending] = await tx.select({
68081
+ id: sessionPendingToolCalls.id,
68082
+ accountId: sessionPendingToolCalls.accountId,
68083
+ callType: sessionPendingToolCalls.callType,
68084
+ callItem: sessionPendingToolCalls.callItem,
68085
+ callItemCodecVersion: sessionPendingToolCalls.callItemCodecVersion,
68086
+ modelToolOutputTruncationTokens: sessionPendingToolCalls.modelToolOutputTruncationTokens
68087
+ }).from(sessionPendingToolCalls).where(
68088
+ and58(
68089
+ eq59(sessionPendingToolCalls.workspaceId, input.workspaceId),
68090
+ eq59(sessionPendingToolCalls.sessionId, input.sessionId),
68091
+ eq59(sessionPendingToolCalls.turnId, input.turnId),
68092
+ eq59(sessionPendingToolCalls.callId, input.callId)
68093
+ )
68094
+ ).for("update").limit(1);
68095
+ if (!pending || pending.accountId !== input.accountId || pending.callType !== input.callType || !pendingToolCallItemsEqual(
68096
+ fromPostgresLosslessJson(pending.callItem, pending.callItemCodecVersion),
68097
+ input.callItem
68098
+ )) {
68099
+ throw new Error("Pending tool receipt conflicts with the registered call");
68100
+ }
68101
+ assertPendingToolOutputPolicyMatches(
68102
+ pending.modelToolOutputTruncationTokens,
68103
+ input.modelToolOutputTruncationTokens,
68104
+ input.callId
68105
+ );
68106
+ if (pending.modelToolOutputTruncationTokens === null && input.modelToolOutputTruncationTokens !== void 0) {
68107
+ await tx.update(sessionPendingToolCalls).set({
68108
+ modelToolOutputTruncationTokens: input.modelToolOutputTruncationTokens
68109
+ }).where(eq59(sessionPendingToolCalls.id, pending.id));
68110
+ }
68111
+ }
68112
+ return { accepted: true, registered: inserted.length === 1 };
68113
+ })
68114
+ );
68115
+ } catch (error) {
68116
+ if (!isRetryableDatabaseTransportFailure(error)) throw error;
68117
+ return { transportFailure: error, attempt };
68118
+ }
68119
+ };
68120
+ const result = await runIdempotentPersistenceTransaction(
68121
+ {
68122
+ stage: "pending_tool_registration",
68123
+ correlationId,
68124
+ maxAttempts: 3,
68125
+ onRetry: async ({ attempt }) => {
68126
+ await new Promise((resolve) => setTimeout(resolve, 25 * attempt));
67885
68127
  }
67886
- return { accepted: true, registered: inserted.length === 1 };
67887
- })
68128
+ },
68129
+ registerOnce
67888
68130
  );
68131
+ if ("transportFailure" in result) {
68132
+ throw new SessionEventPersistenceError(
68133
+ {
68134
+ code: "db_failure",
68135
+ sqlState: null,
68136
+ stage: "pending_tool_registration",
68137
+ eventTypes: [],
68138
+ correlationId,
68139
+ attempts: result.attempt,
68140
+ retryOutcome: "not_retryable",
68141
+ database: safeDatabaseErrorFacts(result.transportFailure)
68142
+ },
68143
+ result.transportFailure
68144
+ );
68145
+ }
68146
+ return result;
67889
68147
  }
67890
68148
  async function recordPendingSessionToolCallResult(db, input) {
67891
68149
  return await withRlsContext(
@@ -69482,6 +69740,54 @@ var SandboxViewerAdmissionBlockedError = class extends Error {
69482
69740
  }
69483
69741
  name = "SandboxViewerAdmissionBlockedError";
69484
69742
  };
69743
+ var SandboxPaidComputeAdmissionError = class extends Error {
69744
+ constructor(workspaceId, sandboxGroupId) {
69745
+ super("Insufficient OpenGeni credits to admit paid sandbox compute");
69746
+ this.workspaceId = workspaceId;
69747
+ this.sandboxGroupId = sandboxGroupId;
69748
+ }
69749
+ name = "SandboxPaidComputeAdmissionError";
69750
+ };
69751
+ function warmBillingSnapshot(row) {
69752
+ const value = row.resume_state?.opengeniWarmBilling;
69753
+ if (!value || typeof value !== "object") return null;
69754
+ const snapshot = value;
69755
+ return (snapshot.mode === "usage_only" || snapshot.mode === "shadow" || snapshot.mode === "credits") && Number.isFinite(snapshot.rateMicrosPerSecond) && snapshot.rateMicrosPerSecond >= 0 && (snapshot.stopChargeAt === void 0 || typeof snapshot.stopChargeAt === "string" && Number.isFinite(Date.parse(snapshot.stopChargeAt))) && snapshot.accountId === row.account_id && snapshot.workspaceId === row.workspace_id ? snapshot : null;
69756
+ }
69757
+ async function markWarmBillingStopCutoff(db, input) {
69758
+ return withRlsContext(
69759
+ db,
69760
+ { accountId: input.accountId, workspaceId: input.workspaceId },
69761
+ async (scopedDb) => scopedDb.transaction(async (txRaw) => {
69762
+ const tx = txRaw;
69763
+ const [row] = await tx.execute(sql87`
69764
+ select * from sandbox_leases where workspace_id = ${input.workspaceId}
69765
+ and sandbox_group_id = ${input.sandboxGroupId} for update
69766
+ `);
69767
+ if (!row || Number(row.lease_epoch) !== input.expectedEpoch || row.liveness !== "draining" || Number(row.refcount) !== 0 && !row.unobservable_command_drain_ids?.length)
69768
+ return null;
69769
+ const snapshot = warmBillingSnapshot(row);
69770
+ if (row.resume_state?.opengeniWarmBilling && !snapshot)
69771
+ throw new Error("sandbox warm billing snapshot is invalid");
69772
+ if (snapshot?.stopChargeAt) return new Date(snapshot.stopChargeAt);
69773
+ const [stamped] = await tx.execute(sql87`
69774
+ update sandbox_leases set
69775
+ resume_state = jsonb_set(coalesce(resume_state, '{}'::jsonb),
69776
+ '{opengeniWarmBilling}',
69777
+ coalesce(resume_state->'opengeniWarmBilling',
69778
+ jsonb_build_object('mode', 'usage_only', 'rateMicrosPerSecond', 0,
69779
+ 'accountId', account_id, 'workspaceId', workspace_id)) ||
69780
+ jsonb_build_object('stopChargeAt', clock_timestamp()), true),
69781
+ updated_at = now()
69782
+ where id = ${row.id}
69783
+ returning resume_state #>> '{opengeniWarmBilling,stopChargeAt}' as cutoff
69784
+ `);
69785
+ if (!stamped || !Number.isFinite(Date.parse(stamped.cutoff)))
69786
+ throw new Error("sandbox warm billing stop cutoff was not saved");
69787
+ return new Date(stamped.cutoff);
69788
+ })
69789
+ );
69790
+ }
69485
69791
  var SandboxLeaseRecoveryBlockedError = class extends Error {
69486
69792
  constructor(sandboxGroupId, leaseEpoch, code, recovery) {
69487
69793
  super(
@@ -69892,6 +70198,10 @@ async function acquireLeaseOnce(db, input) {
69892
70198
  const os = input.os ?? "linux";
69893
70199
  const subjectId = input.subjectId ?? null;
69894
70200
  const warmingLeaseTtlMs = input.warmingLeaseTtlMs ?? input.leaseTtlMs;
70201
+ const warmPolicy = input.warmBilling ?? { mode: "usage_only", rateMicrosPerSecond: 0 };
70202
+ if (!Number.isFinite(warmPolicy.rateMicrosPerSecond) || warmPolicy.rateMicrosPerSecond < 0) {
70203
+ throw new Error("sandbox warm rate must be finite and non-negative");
70204
+ }
69895
70205
  return await withRlsContext(
69896
70206
  db,
69897
70207
  { accountId, workspaceId },
@@ -69927,6 +70237,24 @@ async function acquireLeaseOnce(db, input) {
69927
70237
  const row = rows[0];
69928
70238
  if (!row) throw new Error(`Lease row vanished post-insert: ${sandboxGroupId}`);
69929
70239
  const liveness = row.liveness;
70240
+ const existingSnapshot = warmBillingSnapshot(row);
70241
+ if (row.resume_state?.opengeniWarmBilling && !existingSnapshot) {
70242
+ throw new Error("sandbox warm billing snapshot is invalid");
70243
+ }
70244
+ const activeMode = input.warmBilling?.mode ?? existingSnapshot?.mode ?? "usage_only";
70245
+ const activeRate = liveness !== "cold" && existingSnapshot?.mode === "credits" ? existingSnapshot.rateMicrosPerSecond : input.warmBilling?.rateMicrosPerSecond ?? existingSnapshot?.rateMicrosPerSecond ?? 0;
70246
+ if (activeMode === "credits" && activeRate > 0 && (liveness === "cold" || existingSnapshot?.mode === "credits" && existingSnapshot.rateMicrosPerSecond > 0)) {
70247
+ const alreadyHeld = await tx.execute(sql87`
70248
+ select exists (select 1 from sandbox_lease_holders
70249
+ where lease_id = ${row.id} and kind = ${kind} and holder_id = ${holderId}) as held
70250
+ `);
70251
+ if (!alreadyHeld[0]?.held) {
70252
+ const balance = await getBillingBalance(tx, accountId);
70253
+ if (balance.balanceMicros <= 0) {
70254
+ throw new SandboxPaidComputeAdmissionError(workspaceId, sandboxGroupId);
70255
+ }
70256
+ }
70257
+ }
69930
70258
  if ((kind === "viewer" || kind === "interaction") && Number(row.turn_holders) === 0) {
69931
70259
  const workspaceRows = await tx.execute(sql87`
69932
70260
  select sandbox_viewer_force_drain_reason
@@ -69934,7 +70262,7 @@ async function acquireLeaseOnce(db, input) {
69934
70262
  where id = ${workspaceId}
69935
70263
  `);
69936
70264
  const reason = workspaceRows[0]?.sandbox_viewer_force_drain_reason ?? null;
69937
- if (reason !== null) {
70265
+ if (reason === "warm_cap") {
69938
70266
  throw new SandboxViewerAdmissionBlockedError(workspaceId, sandboxGroupId, reason);
69939
70267
  }
69940
70268
  }
@@ -69999,6 +70327,14 @@ async function acquireLeaseOnce(db, input) {
69999
70327
  };
70000
70328
  }
70001
70329
  if (liveness === "draining") {
70330
+ if (warmBillingSnapshot(row)?.stopChargeAt) {
70331
+ await tx.execute(sql87`
70332
+ update sandbox_leases
70333
+ set resume_state = resume_state #- '{opengeniWarmBilling,stopChargeAt}',
70334
+ updated_at = now()
70335
+ where id = ${row.id}
70336
+ `);
70337
+ }
70002
70338
  await upsertLeaseHolder(tx, row.id, accountId, workspaceId, kind, holderId, subjectId, {
70003
70339
  subjectId: input.viewerSubjectId ?? null,
70004
70340
  authorityEpoch: input.viewerAuthorityEpoch ?? null
@@ -70018,6 +70354,13 @@ async function acquireLeaseOnce(db, input) {
70018
70354
  const casRows = await tx.execute(sql87`
70019
70355
  update sandbox_leases set
70020
70356
  liveness = 'warming',
70357
+ ${input.warmBilling ? sql87`resume_state = coalesce(resume_state, '{}'::jsonb) ||
70358
+ jsonb_build_object('opengeniWarmBilling', ${JSON.stringify({
70359
+ mode: warmPolicy.mode,
70360
+ rateMicrosPerSecond: warmPolicy.rateMicrosPerSecond,
70361
+ accountId,
70362
+ workspaceId
70363
+ })}::jsonb),` : sql87``}
70021
70364
  ${image !== null ? sql87`image = ${image},` : sql87``}
70022
70365
  ${rigVersionId !== null ? sql87`rig_version_id = ${rigVersionId},` : sql87``}
70023
70366
  updated_at = now()
@@ -70986,7 +71329,11 @@ async function commitWarmingToWarm(db, input) {
70986
71329
  reason: "archive_generation_mismatch"
70987
71330
  };
70988
71331
  }
70989
- const completedAt = (/* @__PURE__ */ new Date()).toISOString();
71332
+ const [warmClock] = await tx.execute(sql87`
71333
+ select clock_timestamp() as completed_at
71334
+ `);
71335
+ if (!warmClock) throw new Error("sandbox warm-transition clock unavailable");
71336
+ const completedAt = new Date(warmClock.completed_at).toISOString();
70990
71337
  const recovery = rematerialization ? {
70991
71338
  provider: {
70992
71339
  status: "exists",
@@ -71030,7 +71377,15 @@ async function commitWarmingToWarm(db, input) {
71030
71377
  input.resumeState ?? null,
71031
71378
  row.resume_state
71032
71379
  );
71033
- const resumeStateJson = JSON.stringify(resumeStateWithRecovery(withArchives, recovery));
71380
+ const resumeStateJson = JSON.stringify(
71381
+ resumeStateWithRecovery(
71382
+ {
71383
+ ...withArchives ?? {},
71384
+ ...row.resume_state?.opengeniWarmBilling ? { opengeniWarmBilling: row.resume_state.opengeniWarmBilling } : {}
71385
+ },
71386
+ recovery
71387
+ )
71388
+ );
71034
71389
  const updated = await tx.execute(sql87`
71035
71390
  update sandbox_leases set
71036
71391
  liveness = 'warm',
@@ -71118,7 +71473,15 @@ async function recordWarmingSandboxCreated(db, input) {
71118
71473
  input.resumeState ?? null,
71119
71474
  row.resume_state
71120
71475
  );
71121
- const resumeStateJson = JSON.stringify(resumeStateWithRecovery(withArchives, recovery));
71476
+ const resumeStateJson = JSON.stringify(
71477
+ resumeStateWithRecovery(
71478
+ {
71479
+ ...withArchives ?? {},
71480
+ ...row.resume_state?.opengeniWarmBilling ? { opengeniWarmBilling: row.resume_state.opengeniWarmBilling } : {}
71481
+ },
71482
+ recovery
71483
+ )
71484
+ );
71122
71485
  const warmingTtlMs = input.warmingLeaseTtlMs ?? input.leaseTtlMs;
71123
71486
  const providerCreatedAt = input.providerCreatedAt ?? null;
71124
71487
  const providerDeadlineAt = input.providerDeadlineAt ?? null;
@@ -72638,7 +73001,7 @@ async function heartbeatLeaseHolderStatus(db, input) {
72638
73001
  async (scopedDb) => await scopedDb.transaction(async (txRaw) => {
72639
73002
  const tx = txRaw;
72640
73003
  const leases = await tx.execute(sql87`
72641
- select id, lease_epoch, liveness, rotation_requested_at from sandbox_leases
73004
+ select id, account_id, workspace_id, resume_state, lease_epoch, liveness, rotation_requested_at from sandbox_leases
72642
73005
  where workspace_id = ${input.workspaceId}
72643
73006
  and sandbox_group_id = ${input.sandboxGroupId}
72644
73007
  for update
@@ -72663,6 +73026,19 @@ async function heartbeatLeaseHolderStatus(db, input) {
72663
73026
  fence: "holder_gone"
72664
73027
  };
72665
73028
  }
73029
+ const snapshot = warmBillingSnapshot(lease);
73030
+ if (lease.resume_state?.opengeniWarmBilling && !snapshot) {
73031
+ throw new Error("sandbox warm billing snapshot is invalid");
73032
+ }
73033
+ if (Number(lease.lease_epoch) !== input.expectedEpoch)
73034
+ return { holderAlive: true, leaseExtended: false, fence: "epoch" };
73035
+ if (lease.liveness !== "warm" && lease.liveness !== "warming")
73036
+ return { holderAlive: true, leaseExtended: false, fence: "liveness" };
73037
+ if (lease.rotation_requested_at !== null)
73038
+ return { holderAlive: true, leaseExtended: false, fence: "rotation_requested" };
73039
+ if ((input.billingMode ?? snapshot?.mode) === "credits" && snapshot?.mode === "credits" && snapshot.rateMicrosPerSecond > 0 && (await getBillingBalance(tx, input.accountId)).balanceMicros <= 0) {
73040
+ return { holderAlive: true, leaseExtended: false, fence: "funding" };
73041
+ }
72666
73042
  const leaseRows = await tx.execute(sql87`
72667
73043
  update sandbox_leases set
72668
73044
  expires_at = now() + (${String(input.leaseTtlMs)} || ' milliseconds')::interval,
@@ -73125,6 +73501,8 @@ async function enrollUnobservableCommandIdleDrain(db, input) {
73125
73501
  'quarantined_provider_binding_mismatch')
73126
73502
  `);
73127
73503
  }
73504
+ const deadlineStopGraceMs = SANDBOX_DEADLINE_COMMAND_STOP_GRACE_MS;
73505
+ const deadlineRotation = initial.rotationReason === "provider_deadline" && initial.rotationRequestedAt !== null;
73128
73506
  const processes = await rawRows(
73129
73507
  tx,
73130
73508
  sql87`
@@ -73151,10 +73529,43 @@ async function enrollUnobservableCommandIdleDrain(db, input) {
73151
73529
  (${input.idleGraceMs}::bigint * interval '1 millisecond')
73152
73530
  )
73153
73531
  )
73532
+ or (
73533
+ ${deadlineRotation}
73534
+ and process.deadline_cancellation_requested_at < now() -
73535
+ (${deadlineStopGraceMs}::bigint * interval '1 millisecond')
73536
+ and process.reconcile_attempts >= 1
73537
+ and (
73538
+ (process.owner_actor_kind = 'turn' and attempt.state = 'closed'
73539
+ and attempt.quiesced_at is not null
73540
+ and greatest(attempt.quiesced_at, process.started_at) < now() -
73541
+ (${deadlineStopGraceMs}::bigint * interval '1 millisecond'))
73542
+ or (process.owner_actor_kind = 'direct' and process.owner_attempt_id is null
73543
+ and process.started_at < now() -
73544
+ (${deadlineStopGraceMs}::bigint * interval '1 millisecond'))
73545
+ )
73546
+ )
73154
73547
  )
73155
- and attempt.state = 'closed'
73156
- and greatest(coalesce(attempt.quiesced_at, attempt.closed_at, attempt.updated_at), process.started_at) < now() -
73157
- (${input.idleGraceMs}::bigint * interval '1 millisecond')) as eligible
73548
+ and (attempt.state = 'closed' or (
73549
+ ${deadlineRotation}
73550
+ and process.owner_actor_kind = 'direct' and process.owner_attempt_id is null))
73551
+ and (
73552
+ (attempt.state = 'closed' and
73553
+ greatest(coalesce(attempt.quiesced_at, attempt.closed_at, attempt.updated_at), process.started_at) < now() -
73554
+ (${input.idleGraceMs}::bigint * interval '1 millisecond'))
73555
+ or (
73556
+ ${deadlineRotation}
73557
+ and process.deadline_cancellation_requested_at < now() -
73558
+ (${deadlineStopGraceMs}::bigint * interval '1 millisecond')
73559
+ and (
73560
+ (process.owner_actor_kind = 'turn' and attempt.quiesced_at is not null
73561
+ and greatest(attempt.quiesced_at, process.started_at) < now() -
73562
+ (${deadlineStopGraceMs}::bigint * interval '1 millisecond'))
73563
+ or (process.owner_actor_kind = 'direct' and process.owner_attempt_id is null
73564
+ and process.started_at < now() -
73565
+ (${deadlineStopGraceMs}::bigint * interval '1 millisecond'))
73566
+ )
73567
+ )
73568
+ )) as eligible
73158
73569
  from sandbox_retained_processes process
73159
73570
  left join session_turn_attempts attempt on attempt.id = process.owner_attempt_id
73160
73571
  and attempt.workspace_id = process.workspace_id and attempt.session_id = process.session_id
@@ -73192,7 +73603,10 @@ async function enrollUnobservableCommandIdleDrain(db, input) {
73192
73603
  ))
73193
73604
  return null;
73194
73605
  if (!enrolled) {
73195
- if (lease.archive_capture_id !== null || await hasSandboxGroupAttemptActivityTx(tx, input))
73606
+ if (lease.archive_capture_id !== null || await hasSandboxGroupAttemptActivityTx(tx, {
73607
+ ...input,
73608
+ idleGraceMs: deadlineRotation ? deadlineStopGraceMs : input.idleGraceMs
73609
+ }))
73196
73610
  return null;
73197
73611
  await tx.execute(sql87`
73198
73612
  update sandbox_leases set unobservable_command_drain_ids = ${`{${ids.join(",")}}`}::uuid[],
@@ -73385,10 +73799,26 @@ async function reArmDrainingLease(db, input) {
73385
73799
  return await withRlsContext(
73386
73800
  db,
73387
73801
  { accountId: input.accountId, workspaceId: input.workspaceId },
73388
- async (scopedDb) => {
73389
- const rows = await scopedDb.execute(sql87`
73802
+ async (scopedDb) => await scopedDb.transaction(async (txRaw) => {
73803
+ const tx = txRaw;
73804
+ const [lease] = await tx.execute(sql87`
73805
+ select * from sandbox_leases
73806
+ where workspace_id = ${input.workspaceId} and sandbox_group_id = ${input.sandboxGroupId}
73807
+ for update
73808
+ `);
73809
+ if (!lease || lease.liveness !== "draining") return { rearmed: false };
73810
+ const snapshot = warmBillingSnapshot(lease);
73811
+ if (lease.resume_state?.opengeniWarmBilling && !snapshot) {
73812
+ throw new Error("sandbox warm billing snapshot is invalid");
73813
+ }
73814
+ const activeMode = input.warmBilling?.mode ?? snapshot?.mode ?? "usage_only";
73815
+ if (activeMode === "credits" && (snapshot ? snapshot.mode === "credits" && snapshot.rateMicrosPerSecond > 0 : input.warmBilling?.rateMicrosPerSecond !== void 0 && input.warmBilling.rateMicrosPerSecond > 0) && (await getBillingBalance(tx, input.accountId)).balanceMicros <= 0) {
73816
+ throw new SandboxPaidComputeAdmissionError(input.workspaceId, input.sandboxGroupId);
73817
+ }
73818
+ const rows = await tx.execute(sql87`
73390
73819
  update sandbox_leases set
73391
73820
  liveness = 'warm',
73821
+ resume_state = resume_state #- '{opengeniWarmBilling,stopChargeAt}',
73392
73822
  expires_at = now() + (${String(input.leaseTtlMs)} || ' milliseconds')::interval,
73393
73823
  updated_at = now()
73394
73824
  where workspace_id = ${input.workspaceId} and sandbox_group_id = ${input.sandboxGroupId}
@@ -73415,7 +73845,7 @@ async function reArmDrainingLease(db, input) {
73415
73845
  returning id
73416
73846
  `);
73417
73847
  return { rearmed: rows.length > 0 };
73418
- }
73848
+ })
73419
73849
  );
73420
73850
  }
73421
73851
  async function confirmDrainCold(db, input) {
@@ -73706,6 +74136,9 @@ function mapRetainedProcess(row) {
73706
74136
  reconcileClaimedAt: row.reconcileClaimedAt?.toISOString() ?? null,
73707
74137
  reconcileAttempts: row.reconcileAttempts,
73708
74138
  lastReconcileOutcome: row.lastReconcileOutcome ?? null,
74139
+ cancellationRequestedAt: row.cancellationRequestedAt?.toISOString() ?? null,
74140
+ cancellationReason: row.cancellationReason ?? null,
74141
+ deadlineCancellationRequestedAt: row.deadlineCancellationRequestedAt?.toISOString() ?? null,
73709
74142
  reconcileProofOutcome: row.reconcileProofOutcome ?? null,
73710
74143
  reconcileProofExitCode: row.reconcileProofExitCode ?? null,
73711
74144
  reconcileProofReason: row.reconcileProofReason ?? null,
@@ -79312,45 +79745,71 @@ async function accrueWarmSeconds(db, input) {
79312
79745
  async (scopedDb) => await scopedDb.transaction(async (txRaw) => {
79313
79746
  const tx = txRaw;
79314
79747
  const rows = await tx.execute(sql87`
79315
- select *,
79316
- case when last_meter_at is null then null
79317
- else floor(extract(epoch from (now() - last_meter_at)))::int end as meter_elapsed_s
79748
+ select *
79318
79749
  from sandbox_leases
79319
79750
  where workspace_id = ${input.workspaceId} and sandbox_group_id = ${input.sandboxGroupId}
79320
79751
  for update
79321
79752
  `);
79322
79753
  const row = rows[0];
79323
79754
  if (!row) return none;
79324
- if (Number(row.lease_epoch) !== input.expectedEpoch || row.liveness !== "warm") {
79755
+ if (Number(row.lease_epoch) !== input.expectedEpoch || row.liveness !== "warm" && !(input.finalDrain && row.liveness === "draining")) {
79325
79756
  return none;
79326
79757
  }
79327
- if (row.last_meter_at == null) {
79758
+ const recovery = row.resume_state?.opengeniRecovery;
79759
+ const warmStartedAt = recovery?.restore?.completedAt;
79760
+ const warmStartMs = typeof warmStartedAt === "string" ? Date.parse(warmStartedAt) : NaN;
79761
+ const snapshot = warmBillingSnapshot(row);
79762
+ if (row.resume_state?.opengeniWarmBilling && !snapshot) {
79763
+ throw new Error("sandbox warm billing snapshot is invalid");
79764
+ }
79765
+ const mode = input.billingMode === "credits" ? snapshot?.mode ?? "usage_only" : input.billingMode ?? snapshot?.mode ?? "usage_only";
79766
+ const rate = snapshot?.rateMicrosPerSecond ?? input.warmRateMicrosPerSecond;
79767
+ if (!Number.isFinite(warmStartMs) && mode === "credits" && rate > 0) {
79768
+ throw new Error("paid warm lease has no valid warm-transition timestamp");
79769
+ }
79770
+ if (!Number.isFinite(warmStartMs) && row.last_meter_at == null) {
79328
79771
  await tx.execute(sql87`
79329
- update sandbox_leases set last_meter_at = now(), updated_at = now()
79330
- where id = ${row.id}
79331
- `);
79772
+ update sandbox_leases set last_meter_at = now(), updated_at = now()
79773
+ where id = ${row.id}
79774
+ `);
79332
79775
  return none;
79333
79776
  }
79334
- const elapsedS = Number(row.meter_elapsed_s ?? 0);
79335
- if (elapsedS <= 0) {
79777
+ const cursorMs = row.last_meter_at ? new Date(row.last_meter_at).getTime() : 0;
79778
+ const startMs = Number.isFinite(warmStartMs) ? Math.max(cursorMs, warmStartMs) : cursorMs;
79779
+ if (input.finalDrain && !snapshot?.stopChargeAt) {
79780
+ throw new Error("sandbox final tick has no durable stop cutoff");
79781
+ }
79782
+ const upperBound = snapshot?.stopChargeAt && input.finalDrain ? sql87`${snapshot.stopChargeAt}::timestamptz` : sql87`clock_timestamp()`;
79783
+ const elapsedRows = await tx.execute(sql87`
79784
+ select greatest(0, floor(extract(epoch from (${upperBound} -
79785
+ ${new Date(startMs).toISOString()}::timestamptz)) * 1000))::bigint as elapsed_ms
79786
+ `);
79787
+ const elapsedMs = Number(elapsedRows[0]?.elapsed_ms ?? 0);
79788
+ const elapsedS = Math.floor(elapsedMs / 1e3);
79789
+ if (elapsedS <= 0 && !(input.finalDrain && elapsedMs > 0)) {
79336
79790
  return none;
79337
79791
  }
79338
79792
  const tick = Number(row.last_meter_tick) + 1;
79339
- const costMicros = Math.round(elapsedS * Math.max(0, input.warmRateMicrosPerSecond));
79793
+ const costMicros = mode === "usage_only" ? 0 : Math.round((input.finalDrain ? elapsedMs / 1e3 : elapsedS) * rate);
79794
+ if (!Number.isSafeInteger(costMicros) || costMicros < 0) {
79795
+ throw new Error("warm-cost estimate exceeds safe integer micros");
79796
+ }
79340
79797
  await tx.execute(sql87`
79341
79798
  insert into usage_events
79342
79799
  (account_id, workspace_id, subject_id, event_type, quantity, unit,
79343
- source_resource_type, source_resource_id, idempotency_key, occurred_at)
79800
+ source_resource_type, source_resource_id, idempotency_key, initiator_context, occurred_at)
79344
79801
  values
79345
79802
  (${input.accountId}, ${input.workspaceId}, ${input.subjectId ?? null},
79346
79803
  'sandbox.warm_seconds', ${elapsedS}, 'seconds',
79347
79804
  'sandbox_lease', ${`${input.sandboxGroupId}:${input.expectedEpoch}`},
79348
79805
  ${`usage:sandbox.warm_seconds:${input.sandboxGroupId}:${input.expectedEpoch}:${tick}`},
79349
- now())
79806
+ ${JSON.stringify(mode === "shadow" ? { warmCostShadowMicros: costMicros } : {})}::jsonb, now())
79350
79807
  on conflict (idempotency_key) do nothing
79351
79808
  `);
79352
- if (costMicros > 0) {
79353
- await tx.execute(sql87`
79809
+ let settledCostMicros = costMicros;
79810
+ if (costMicros > 0 && mode === "credits") {
79811
+ const costKey = `usage:sandbox.warm_cost:${input.sandboxGroupId}:${input.expectedEpoch}:${tick}`;
79812
+ const inserted = await tx.execute(sql87`
79354
79813
  insert into usage_events
79355
79814
  (account_id, workspace_id, subject_id, event_type, quantity, unit,
79356
79815
  source_resource_type, source_resource_id, idempotency_key, occurred_at)
@@ -79358,30 +79817,43 @@ async function accrueWarmSeconds(db, input) {
79358
79817
  (${input.accountId}, ${input.workspaceId}, ${input.subjectId ?? null},
79359
79818
  'sandbox.warm_cost', ${costMicros}, 'usd_micros',
79360
79819
  'sandbox_lease', ${`${input.sandboxGroupId}:${input.expectedEpoch}`},
79361
- ${`usage:sandbox.warm_cost:${input.sandboxGroupId}:${input.expectedEpoch}:${tick}`},
79820
+ ${costKey},
79362
79821
  now())
79363
79822
  on conflict (idempotency_key) do nothing
79823
+ returning quantity
79364
79824
  `);
79825
+ const previous = inserted[0] ? null : await tx.execute(sql87`
79826
+ select quantity from usage_events
79827
+ where idempotency_key = ${costKey}
79828
+ and account_id = ${input.accountId}
79829
+ and workspace_id = ${input.workspaceId}
79830
+ and event_type = 'sandbox.warm_cost'
79831
+ `);
79832
+ settledCostMicros = Number((inserted[0] ?? previous?.[0])?.quantity ?? 0);
79833
+ if (!Number.isSafeInteger(settledCostMicros) || settledCostMicros <= 0) {
79834
+ throw new Error("warm-cost tick idempotency key conflicts with another usage event");
79835
+ }
79836
+ }
79837
+ if (costMicros > 0 && mode === "credits") {
79838
+ await applyCreditDebitAfterUse(tx, {
79839
+ accountId: input.accountId,
79840
+ workspaceId: input.workspaceId,
79841
+ type: "sandbox.warm_cost",
79842
+ amountMicros: settledCostMicros,
79843
+ sourceType: "sandbox_lease",
79844
+ sourceId: `${input.sandboxGroupId}:${input.expectedEpoch}`,
79845
+ idempotencyKey: `debit:sandbox.warm_cost:${input.sandboxGroupId}:${input.expectedEpoch}:${tick}`
79846
+ });
79365
79847
  }
79366
79848
  await tx.execute(sql87`
79367
79849
  update sandbox_leases set
79368
- last_meter_at = now(), last_meter_tick = ${tick}, updated_at = now()
79850
+ last_meter_at = ${new Date(startMs).toISOString()}::timestamptz + (${String(input.finalDrain ? elapsedMs : elapsedS * 1e3)} || ' milliseconds')::interval,
79851
+ last_meter_tick = ${tick}, updated_at = now()
79369
79852
  where id = ${row.id}
79370
79853
  `);
79371
- return { accrued: true, seconds: elapsedS, tick, costMicros };
79854
+ return { accrued: true, seconds: elapsedS, tick, costMicros: settledCostMicros };
79372
79855
  })
79373
79856
  );
79374
- if (result.accrued && result.costMicros > 0 && (input.debitCredits ?? true)) {
79375
- await applyCreditDebitUpToBalance(db, {
79376
- accountId: input.accountId,
79377
- workspaceId: input.workspaceId,
79378
- type: "sandbox.warm_cost",
79379
- requestedAmountMicros: result.costMicros,
79380
- sourceType: "sandbox_lease",
79381
- sourceId: `${input.sandboxGroupId}:${input.expectedEpoch}`,
79382
- idempotencyKey: `debit:sandbox.warm_cost:${input.sandboxGroupId}:${input.expectedEpoch}:${result.tick}`
79383
- }).catch(() => void 0);
79384
- }
79385
79857
  return result;
79386
79858
  }
79387
79859
  function startOfUtcMonthDefault(date = /* @__PURE__ */ new Date()) {
@@ -79390,9 +79862,7 @@ function startOfUtcMonthDefault(date = /* @__PURE__ */ new Date()) {
79390
79862
  async function forceDrainOverLimitViewerOnlyBoxes(db, input) {
79391
79863
  return await withWorkspaceUsageLock(db, input.workspaceId, async (scopedDb) => {
79392
79864
  let reason = null;
79393
- if (input.enforceBalance && input.balanceMicros <= 0) {
79394
- reason = "balance";
79395
- } else if (input.maxWarmSecondsPerWorkspace > 0) {
79865
+ if (input.maxWarmSecondsPerWorkspace > 0) {
79396
79866
  const since = input.capWindowStart ?? startOfUtcMonthDefault();
79397
79867
  const [{ total } = { total: 0 }] = await scopedDb.select({
79398
79868
  total: sql87`coalesce(sum(${usageEvents.quantity}), 0)`
@@ -79435,76 +79905,25 @@ async function forceDrainOverLimitViewerOnlyBoxes(db, input) {
79435
79905
  )
79436
79906
  `);
79437
79907
  await scopedDb.execute(sql87`
79438
- select id from sandbox_leases
79439
- where workspace_id = ${input.workspaceId}
79440
- and liveness = 'warm' and turn_holders = 0
79908
+ select lease.id from sandbox_leases lease
79909
+ where lease.workspace_id = ${input.workspaceId}
79910
+ and lease.liveness = 'warm' and lease.refcount = 0
79911
+ and not exists (select 1 from sandbox_lease_holders h where h.lease_id = lease.id)
79441
79912
  order by id
79442
79913
  for update
79443
79914
  `);
79444
- const lostBrowsers = await scopedDb.execute(sql87`
79445
- update browser_sessions browser set
79446
- lifecycle = 'lost',
79447
- controller_id = null,
79448
- controller_generation = null,
79449
- placement_instance_id = null,
79450
- controller_heartbeat_at = null,
79451
- failure_code = 'workspace_force_drained',
79452
- updated_at = now()
79453
- where browser.workspace_id = ${input.workspaceId}
79454
- and browser.lifecycle in ('starting', 'active', 'suspending', 'restoring', 'ending')
79455
- and browser.controller_host_sandbox_group_id in (
79456
- select sandbox_group_id from sandbox_leases
79457
- where workspace_id = ${input.workspaceId}
79458
- and liveness = 'warm' and turn_holders = 0
79459
- )
79460
- returning browser.id
79461
- `);
79462
- const lostComputers = await scopedDb.execute(sql87`
79463
- update computer_sessions computer set
79464
- lifecycle = 'lost',
79465
- controller_id = null,
79466
- controller_generation = null,
79467
- placement_instance_id = null,
79468
- controller_heartbeat_at = null,
79469
- failure_code = 'workspace_force_drained',
79470
- updated_at = now()
79471
- where computer.workspace_id = ${input.workspaceId}
79472
- and computer.lifecycle in ('starting', 'active', 'suspending', 'restoring', 'ending')
79473
- and computer.sandbox_group_id in (
79474
- select sandbox_group_id from sandbox_leases
79475
- where workspace_id = ${input.workspaceId}
79476
- and liveness = 'warm' and turn_holders = 0
79477
- )
79478
- returning computer.id
79479
- `);
79480
- if (lostBrowsers.length > 0 || lostComputers.length > 0) {
79481
- await scopedDb.execute(sql87`
79482
- update workspace_interaction_revisions set
79483
- revision = revision + 1,
79484
- updated_at = now()
79485
- where workspace_id = ${input.workspaceId}
79486
- `);
79487
- }
79488
- await scopedDb.execute(sql87`
79489
- delete from sandbox_lease_holders h
79490
- where h.kind in ('viewer', 'interaction')
79491
- and h.lease_id in (
79492
- select id from sandbox_leases
79493
- where workspace_id = ${input.workspaceId}
79494
- and liveness = 'warm' and turn_holders = 0
79495
- )
79496
- `);
79497
79915
  const drained = await rawRows(
79498
79916
  scopedDb,
79499
79917
  sql87`
79500
- update sandbox_leases set
79918
+ update sandbox_leases lease set
79501
79919
  liveness = 'draining',
79502
79920
  refcount = 0, turn_holders = 0, viewer_holders = 0,
79503
79921
  expires_at = now() + (${String(input.idleGraceMs)} || ' milliseconds')::interval,
79504
79922
  updated_at = now()
79505
- where workspace_id = ${input.workspaceId}
79506
- and liveness = 'warm' and turn_holders = 0
79507
- returning sandbox_group_id
79923
+ where lease.workspace_id = ${input.workspaceId}
79924
+ and lease.liveness = 'warm' and lease.refcount = 0
79925
+ and not exists (select 1 from sandbox_lease_holders h where h.lease_id = lease.id)
79926
+ returning lease.sandbox_group_id
79508
79927
  `
79509
79928
  );
79510
79929
  return {
@@ -93800,6 +94219,7 @@ export {
93800
94219
  SandboxLeaseRecoveryBlockedError,
93801
94220
  SandboxLeaseSupersededError,
93802
94221
  SandboxLeaseTransitionError,
94222
+ SandboxPaidComputeAdmissionError,
93803
94223
  SandboxProviderReadLockUnavailableError,
93804
94224
  SandboxRecoveryConflictError,
93805
94225
  SandboxRetainedProcessPromotionFencedError,
@@ -93960,6 +94380,7 @@ export {
93960
94380
  appendSessionRealtimeOutboundInTransaction,
93961
94381
  applyCanonicalHumanIdentityOperation,
93962
94382
  applyContextCompaction,
94383
+ applyCreditDebitAfterUse,
93963
94384
  applyCreditDebitUpToBalance,
93964
94385
  applyCreditLedgerEntry,
93965
94386
  applyKnowledgeMemoryOperation,
@@ -94273,6 +94694,7 @@ export {
94273
94694
  createWorkspaceOpenRouterCustomModel,
94274
94695
  createXaiSubscriptionCredential,
94275
94696
  currentMemorySlackConfigurationMatches,
94697
+ currentSessionRlsActorIdentityKey,
94276
94698
  databaseFailureCode,
94277
94699
  sql88 as dbSql,
94278
94700
  deactivatePreferenceRegistry,
@@ -94397,6 +94819,7 @@ export {
94397
94819
  failHostExportBatch,
94398
94820
  failMemorySlackPublication,
94399
94821
  failOrganizationRetentionDeletion,
94822
+ failPreparedBrowserSessionSuspend,
94400
94823
  failSandboxRematerialization,
94401
94824
  failScheduledGeneratedSessionRoute,
94402
94825
  failSessionRealtimeConnectionInTransaction,
@@ -94424,6 +94847,7 @@ export {
94424
94847
  forceDrainOverLimitViewerOnlyBoxes,
94425
94848
  forkSessionContent,
94426
94849
  freezeAgentLearningPolicy,
94850
+ freezeKnowledgeIndexBillingMode,
94427
94851
  frozenInitiatorForCommandActor,
94428
94852
  getActiveSessionHistoryItems,
94429
94853
  getActiveSessionHistoryItemsPaged,
@@ -94560,6 +94984,7 @@ export {
94560
94984
  getRetainedFileArtifact,
94561
94985
  getRetainedProcess,
94562
94986
  getRetainedScreenshotArtifact,
94987
+ getRetainedScreenshotArtifactForToolCall,
94563
94988
  getRig,
94564
94989
  getRigByName,
94565
94990
  getRigChange,
@@ -94682,6 +95107,7 @@ export {
94682
95107
  getXaiSubscriptionAccountAuthoritySnapshot,
94683
95108
  getXaiSubscriptionAccountMetadata,
94684
95109
  grantWorkspaceAccess,
95110
+ guardPaidKnowledgeIndexPublication,
94685
95111
  hasAuditableGitHubInstallationAuthority,
94686
95112
  hasCreditLedgerEntry,
94687
95113
  hasPendingSteerAfterContextCompaction,
@@ -94731,6 +95157,7 @@ export {
94731
95157
  isStripeWebhookProcessed,
94732
95158
  issueSelfUserResourceGrant,
94733
95159
  issueToolGatewayApproval,
95160
+ knowledgeIndexBillingActivationTime,
94734
95161
  knowledgeOriginalFileAsset,
94735
95162
  latestWorkspaceCapture,
94736
95163
  linkKnowledgeClaims,
@@ -94946,6 +95373,7 @@ export {
94946
95373
  markVideoGenerationSubmissionUncertain,
94947
95374
  markVideoGenerationTerminalUpdate,
94948
95375
  markVideoGenerationTerminalUpdateInTransaction,
95376
+ markWarmBillingStopCutoff,
94949
95377
  markWarmLeaseInstanceLost,
94950
95378
  materializeConfirmedRememberKnowledgeMemory,
94951
95379
  materializeGoalContinuation,
@@ -95064,6 +95492,7 @@ export {
95064
95492
  readScheduledKnowledgeSource,
95065
95493
  readSessionFileAttachments,
95066
95494
  readSkillRemovalScope,
95495
+ readStoredBrowserCapabilities,
95067
95496
  readVariableSetSecretAtomically,
95068
95497
  readWorkspaceArchiveCapturePreflight,
95069
95498
  readWorkspaceInsightsModelBundle,
@@ -95528,6 +95957,7 @@ export {
95528
95957
  verifyRetainedProcessMutationSettlement,
95529
95958
  verifyWorkspaceMutationSettlement,
95530
95959
  waitForSessionInputWithEvent,
95960
+ waitKnowledgeIndexForFunding,
95531
95961
  wakeXaiCapacityWaiters,
95532
95962
  withAccountRls,
95533
95963
  withCodexAppsRequestAuthorization,
@@ -95536,6 +95966,7 @@ export {
95536
95966
  withCodexTokenDeadline,
95537
95967
  withDatabaseStatementTimeout,
95538
95968
  withDatabaseTimingObserver,
95969
+ withKnowledgeQueryAccountLock,
95539
95970
  withOrganizationModelProviderCustomModelReadLock,
95540
95971
  withOrganizationXaiCapacityMutation,
95541
95972
  withRlsContext,