@opengeni/db 0.9.3 → 0.10.7

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 (33) hide show
  1. package/dist/{chunk-4LG5NBTC.js → chunk-P6PKXY5W.js} +93 -1
  2. package/dist/chunk-P6PKXY5W.js.map +1 -0
  3. package/dist/index.d.ts +3 -2
  4. package/dist/index.js +1332 -178
  5. package/dist/index.js.map +1 -1
  6. package/dist/provision-roles.d.ts +406 -32
  7. package/dist/{schema-CdPGTHlD.d.ts → schema-CqkzrBRS.d.ts} +513 -2
  8. package/dist/schema.d.ts +1 -1
  9. package/dist/schema.js +3 -1
  10. package/drizzle/0053_codex_credential_leases.sql +2 -2
  11. package/drizzle/0057_durable_queue_control.sql +1 -1
  12. package/drizzle/0061_session_workflow_wake_outbox.sql +1 -1
  13. package/drizzle/0062_session_list_snapshot_reaper.sql +1 -1
  14. package/drizzle/0063_session_control_mega_foundation.sql +1 -1
  15. package/drizzle/0064_rotation_strategy_sharded_backfill.sql +1 -1
  16. package/drizzle/0065_codex_subscription_overview.sql +168 -0
  17. package/drizzle/0065_session_tool_policy.sql +38 -0
  18. package/drizzle/0067_session_event_payload_bounds.sql +2 -2
  19. package/drizzle/0068_workspace_control_event_bounds.sql +2 -2
  20. package/drizzle/0069_session_event_history_backfill.sql +2 -2
  21. package/drizzle/0074_session_activity_revisions.sql +2 -2
  22. package/drizzle/0106_session_attempt_mcp_approval_policies.sql +29 -0
  23. package/drizzle/0107_host_export_lineage_contract.sql +381 -0
  24. package/drizzle/0108_fence_invalidated_warming_epochs.sql +76 -0
  25. package/package.json +5 -4
  26. package/src/codex-token-resolver.ts +175 -14
  27. package/src/connection-token-resolver.ts +143 -120
  28. package/src/event-payload-sanitizer.ts +32 -2
  29. package/src/index.ts +1888 -205
  30. package/src/schema.ts +107 -1
  31. package/src/session-control.ts +2 -0
  32. package/src/session-queue-commands.ts +94 -21
  33. package/dist/chunk-4LG5NBTC.js.map +0 -1
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  capabilityInstallations,
8
8
  codexCapacityWaiters,
9
9
  codexCredentialLeases,
10
+ codexResetRedemptionAttempts,
10
11
  codexRotationSettings,
11
12
  codexSubscriptionCredentials,
12
13
  composerDrafts,
@@ -65,7 +66,7 @@ import {
65
66
  workspaceVariableSetVariables,
66
67
  workspaceVariableSets,
67
68
  workspaces
68
- } from "./chunk-4LG5NBTC.js";
69
+ } from "./chunk-P6PKXY5W.js";
69
70
  import {
70
71
  migrate,
71
72
  runMigrations
@@ -87,6 +88,9 @@ import {
87
88
  SESSION_EVENT_ENVELOPE_MAX_BYTES,
88
89
  SESSION_EVENT_TYPE_MAX_BYTES,
89
90
  resolveSessionEventTypeFilters,
91
+ capabilityCatalogItemIsTrustedForExposure,
92
+ metadataWithTurnExecutionPolicyV1 as metadataWithTurnExecutionPolicyV12,
93
+ readTurnExecutionPolicyV1,
90
94
  reasoningEffortForMetadata,
91
95
  resolveWorkspaceMemoryEnabled,
92
96
  RigChange as RigChangeContract,
@@ -98,7 +102,8 @@ import {
98
102
  HostUsageExportBatch as HostUsageExportBatchContract,
99
103
  OPENGENI_HOST_EXPORT_SCHEMA_REVISION,
100
104
  HumanInputQuestion as HumanInputQuestionContract,
101
- SubmitHumanInputResponseRequest
105
+ SubmitHumanInputResponseRequest,
106
+ TurnExecutionPolicyV1
102
107
  } from "@opengeni/contracts";
103
108
  import { environmentsEncryptionKeyBytes as environmentsEncryptionKeyBytes3 } from "@opengeni/config";
104
109
  import { boundModelToolOutputItem as boundModelToolOutputItem2, isCodexBilledModel } from "@opengeni/codex";
@@ -327,8 +332,21 @@ function sanitizeEventString(value) {
327
332
  }
328
333
  return out;
329
334
  }
330
- function sanitizeEventPayload(payload) {
331
- return sanitizeEventPayloadDeep(boundSessionEventPayload(payload));
335
+ function sanitizeEventPayload(payload, options = {}) {
336
+ const bounded = boundSessionEventPayload(payload, {
337
+ fullEvidence: options.fullEvidence
338
+ });
339
+ return sanitizeEventPayloadDeep(
340
+ bounded === payload ? removeProducerTruncationMetadata(bounded) : bounded
341
+ );
342
+ }
343
+ function removeProducerTruncationMetadata(payload) {
344
+ if (!isPlainObject(payload)) return payload;
345
+ const descriptor = Object.getOwnPropertyDescriptor(payload, "truncation");
346
+ if (!descriptor?.enumerable) return payload;
347
+ const cleaned = { ...payload };
348
+ delete cleaned.truncation;
349
+ return cleaned;
332
350
  }
333
351
  function sanitizeEventPayloadDeep(payload) {
334
352
  if (typeof payload === "string") {
@@ -2478,8 +2496,10 @@ import { sql as sql5 } from "drizzle-orm";
2478
2496
 
2479
2497
  // src/session-queue-commands.ts
2480
2498
  import {
2499
+ metadataWithTurnExecutionPolicyV1,
2481
2500
  mergeResourceRefs,
2482
- mergeToolRefs
2501
+ mergeToolRefs,
2502
+ turnExecutionPolicyAuditMetadata
2483
2503
  } from "@opengeni/contracts";
2484
2504
  import { and as and4, asc as asc2, eq as eq4, inArray as inArray2, sql as sql3 } from "drizzle-orm";
2485
2505
  var QueueCommandConflictError = class extends Error {
@@ -2684,7 +2704,7 @@ async function normalizeQueuePositions(db, workspaceId, sessionId, orderedIds) {
2684
2704
  }).where(and4(eq4(sessions.workspaceId, workspaceId), eq4(sessions.id, sessionId)));
2685
2705
  }
2686
2706
  function draftIsNonEmpty(draft) {
2687
- return draft.text.length > 0 || draft.resources.length > 0 || draft.tools.length > 0 || draft.sourceTurnId !== null;
2707
+ return draft.text.length > 0 || draft.resources.length > 0 || draft.tools.length > 0 || draft.toolsProvided || draft.sourceTurnId !== null;
2688
2708
  }
2689
2709
  async function getComposerDraftInTransaction(db, input) {
2690
2710
  const query = db.select().from(composerDrafts).where(
@@ -2725,6 +2745,7 @@ async function saveComposerDraftInTransaction(db, input) {
2725
2745
  text: input.text,
2726
2746
  resources: input.resources,
2727
2747
  tools: input.tools,
2748
+ toolsProvided: input.toolsProvided,
2728
2749
  model: input.model,
2729
2750
  reasoningEffort: input.reasoningEffort,
2730
2751
  // A queue edit is still the same accepted work item. Preserve its frozen
@@ -3024,6 +3045,7 @@ async function editQueuedTurnInTransaction(db, input) {
3024
3045
  text: turn.prompt,
3025
3046
  resources: turn.resources,
3026
3047
  tools: turn.tools,
3048
+ toolsProvided: turn.toolsProvided,
3027
3049
  model: turn.model,
3028
3050
  reasoningEffort: turn.reasoningEffort,
3029
3051
  sourceTurnId: turn.id,
@@ -3290,6 +3312,7 @@ async function submitHumanPromptInTransaction(db, input) {
3290
3312
  turnInstructions: input.turnInstructions ?? null,
3291
3313
  resources: input.resources,
3292
3314
  tools: input.tools,
3315
+ toolsProvided: input.toolsProvided === true,
3293
3316
  model: input.model ?? null,
3294
3317
  reasoningEffort: input.reasoningEffort ?? null,
3295
3318
  source: input.source,
@@ -3373,12 +3396,14 @@ async function submitHumanPromptInTransaction(db, input) {
3373
3396
  text: draft.text,
3374
3397
  resources: draft.resources,
3375
3398
  tools: draft.tools,
3399
+ toolsProvided: draft.toolsProvided,
3376
3400
  model: draft.model,
3377
3401
  reasoningEffort: draft.reasoningEffort
3378
3402
  }) !== canonicalSessionCommandHash({
3379
3403
  text: input.text,
3380
3404
  resources: input.resources,
3381
3405
  tools: input.tools,
3406
+ toolsProvided: input.toolsProvided === true,
3382
3407
  model: input.model ?? session.model,
3383
3408
  reasoningEffort: input.reasoningEffort ?? input.reasoningEffortFallback
3384
3409
  })) {
@@ -3392,6 +3417,33 @@ async function submitHumanPromptInTransaction(db, input) {
3392
3417
  );
3393
3418
  }
3394
3419
  }
3420
+ let editedSourceTurn;
3421
+ let editedSourceTurnInstructions;
3422
+ if (draft?.sourceTurnId) {
3423
+ const sourceLocks = await lockSessionEventWriteRows(db, {
3424
+ workspaceId: input.workspaceId,
3425
+ controlLock: "already_locked",
3426
+ workspaceLock: "already_locked",
3427
+ turnIds: [draft.sourceTurnId]
3428
+ });
3429
+ const sourceTurn = sourceLocks.turns[0];
3430
+ const sourceTurnVersion = draft.sourceTurnVersion;
3431
+ const sourceMetadata = sourceTurn?.metadata ?? {};
3432
+ const sourceIsExactWithdrawnRevision = sourceTurn !== void 0 && sourceTurn.accountId === input.accountId && sourceTurn.workspaceId === input.workspaceId && sourceTurn.sessionId === input.sessionId && (sourceTurn.source === "user" || sourceTurn.source === "api") && sourceTurn.status === "withdrawn_for_edit" && sourceTurnVersion !== null && sourceTurn.version === sourceTurnVersion + 1 && sourceTurn.cancelledBy === input.subjectId && sourceTurn.cancelReason === "withdrawn_for_edit" && sourceTurn.activeAttemptId === null && sourceMetadata.delivery !== "steer";
3433
+ if (!sourceIsExactWithdrawnRevision) {
3434
+ throw new QueueCommandConflictError(
3435
+ "EDIT_SOURCE_CHANGED",
3436
+ "Edited prompt source changed or is no longer withdrawn for edit",
3437
+ {
3438
+ queueVersion: session.queueVersion,
3439
+ draftRevision: draft.revision,
3440
+ ...sourceTurn ? { turnVersion: sourceTurn.version } : {}
3441
+ }
3442
+ );
3443
+ }
3444
+ editedSourceTurn = sourceTurn;
3445
+ editedSourceTurnInstructions = sourceTurn.turnInstructions ?? null;
3446
+ }
3395
3447
  for (const update of input.mcpCredentialUpdates ?? []) {
3396
3448
  const [server] = await db.update(sessionMcpServers).set({
3397
3449
  headersEncrypted: update.headersEncrypted,
@@ -3409,18 +3461,7 @@ async function submitHumanPromptInTransaction(db, input) {
3409
3461
  const now = /* @__PURE__ */ new Date();
3410
3462
  let frozenInitiator;
3411
3463
  if (input.delivery === "send" && draft?.sourceTurnId) {
3412
- const [sourceTurn] = await db.select({
3413
- sessionId: sessionTurns.sessionId,
3414
- initiatorKind: sessionTurns.initiatorKind,
3415
- initiatorSubjectId: sessionTurns.initiatorSubjectId,
3416
- initiatorContext: sessionTurns.initiatorContext
3417
- }).from(sessionTurns).where(
3418
- and4(
3419
- eq4(sessionTurns.workspaceId, input.workspaceId),
3420
- eq4(sessionTurns.sessionId, input.sessionId),
3421
- eq4(sessionTurns.id, draft.sourceTurnId)
3422
- )
3423
- ).limit(1);
3464
+ const sourceTurn = editedSourceTurn;
3424
3465
  if (!sourceTurn) {
3425
3466
  throw new SessionControlInvariantError("Edited prompt source turn is missing");
3426
3467
  }
@@ -3456,7 +3497,7 @@ async function submitHumanPromptInTransaction(db, input) {
3456
3497
  payload: sanitizeEventPayload({
3457
3498
  text: input.text,
3458
3499
  ...input.resources.length ? { resources: input.resources } : {},
3459
- ...input.tools.length ? { tools: input.tools } : {},
3500
+ ...input.toolsProvided === true ? { tools: input.tools } : input.tools.length ? { tools: input.tools } : {},
3460
3501
  ...input.model ? { model: input.model } : {},
3461
3502
  ...input.reasoningEffort ? { reasoningEffort: input.reasoningEffort } : {},
3462
3503
  delivery: input.delivery,
@@ -3477,13 +3518,14 @@ async function submitHumanPromptInTransaction(db, input) {
3477
3518
  source: input.source,
3478
3519
  position: input.delivery === "steer" ? 0 : existingQueued.length + 1,
3479
3520
  prompt: input.text,
3480
- turnInstructions: input.turnInstructions ?? null,
3521
+ turnInstructions: editedSourceTurnInstructions !== void 0 ? editedSourceTurnInstructions : input.turnInstructions ?? null,
3481
3522
  resources: input.resources,
3482
3523
  tools: input.tools,
3524
+ toolsProvided: input.toolsProvided === true,
3483
3525
  model: input.model ?? session.model,
3484
3526
  reasoningEffort: input.reasoningEffort ?? input.reasoningEffortFallback,
3485
3527
  sandboxBackend: session.sandboxBackend,
3486
- metadata: {},
3528
+ metadata: input.turnExecutionPolicy ? metadataWithTurnExecutionPolicyV1({}, input.turnExecutionPolicy) : {},
3487
3529
  lineage: { actor: input.actor.type },
3488
3530
  ...initiatorColumns(frozenInitiator)
3489
3531
  }).returning();
@@ -3527,6 +3569,7 @@ async function submitHumanPromptInTransaction(db, input) {
3527
3569
  if (input.delivery === "steer") {
3528
3570
  await db.update(sessionTurns).set({
3529
3571
  metadata: {
3572
+ ...turn.metadata,
3530
3573
  delivery: "steer",
3531
3574
  replacedTurnId,
3532
3575
  replacedAttemptId,
@@ -3598,7 +3641,7 @@ async function submitHumanPromptInTransaction(db, input) {
3598
3641
  const queueVersion = session.queueVersion + 1;
3599
3642
  await db.update(sessions).set({
3600
3643
  resources: mergeResourceRefs(session.resources, input.resources),
3601
- tools: mergeToolRefs(session.tools, input.tools),
3644
+ tools: sessionToolPolicyIsFixed(session.toolPolicy) ? session.tools : mergeToolRefs(session.tools, input.tools),
3602
3645
  activeTurnId: input.delivery === "steer" ? liveCurrentTurnId : session.activeTurnId,
3603
3646
  status: nextStatus,
3604
3647
  queueVersion,
@@ -3627,7 +3670,8 @@ async function submitHumanPromptInTransaction(db, input) {
3627
3670
  metadata: {
3628
3671
  operationId: reserved.receipt.id,
3629
3672
  replacedTurnId,
3630
- interruptionCount
3673
+ interruptionCount,
3674
+ ...input.turnExecutionPolicy ? turnExecutionPolicyAuditMetadata(input.turnExecutionPolicy, turnId) : {}
3631
3675
  }
3632
3676
  });
3633
3677
  const eventIds = eventRows.map((event) => event.id);
@@ -3643,7 +3687,10 @@ async function submitHumanPromptInTransaction(db, input) {
3643
3687
  wakeRevision,
3644
3688
  interruptionCount,
3645
3689
  replacedTurnId,
3646
- workspaceControlEventId: resumed.workspaceControlEventId
3690
+ workspaceControlEventId: resumed.workspaceControlEventId,
3691
+ ...input.turnExecutionPolicy ? {
3692
+ executionPolicy: turnExecutionPolicyAuditMetadata(input.turnExecutionPolicy, turnId)
3693
+ } : {}
3647
3694
  }
3648
3695
  });
3649
3696
  return {
@@ -3658,6 +3705,11 @@ async function submitHumanPromptInTransaction(db, input) {
3658
3705
  replay: false
3659
3706
  };
3660
3707
  }
3708
+ function sessionToolPolicyIsFixed(value) {
3709
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
3710
+ const mode = value.mode;
3711
+ return mode === "workspace_default" || mode === "explicit" || mode === "inherited";
3712
+ }
3661
3713
  async function sendAgentMessageInTransaction(db, input) {
3662
3714
  const workspaceControl = await lockWorkspaceInferenceControl(db, input.workspaceId, "share");
3663
3715
  await lockSessionEventWriteRows(db, {
@@ -4021,11 +4073,65 @@ import {
4021
4073
  CODEX_REFRESH_FALLBACK_MS,
4022
4074
  CODEX_REFRESH_WINDOW_MS,
4023
4075
  CodexReloginRequired,
4076
+ fetchCodexRateLimitResetCredits,
4024
4077
  fetchCodexUsage,
4025
4078
  normalizeCodexUsage,
4026
4079
  refreshCodexToken
4027
4080
  } from "@opengeni/codex";
4028
4081
  var inflight = /* @__PURE__ */ new Map();
4082
+ var CODEX_TOKEN_REFRESH_TIMEOUT_MS = 6e3;
4083
+ var systemCodexTokenDeadlineClock = {
4084
+ setTimeout: (callback, delayMs) => globalThis.setTimeout(callback, delayMs),
4085
+ clearTimeout: (handle) => globalThis.clearTimeout(handle)
4086
+ };
4087
+ async function withCodexTokenDeadline(operation, options = {}) {
4088
+ const timeoutMs = options.timeoutMs ?? CODEX_TOKEN_REFRESH_TIMEOUT_MS;
4089
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
4090
+ throw new Error("Codex token refresh timeout must be positive");
4091
+ }
4092
+ const clock = options.clock ?? systemCodexTokenDeadlineClock;
4093
+ const signal = options.signal;
4094
+ return await new Promise((resolve, reject) => {
4095
+ let settled = false;
4096
+ let timeout;
4097
+ const cleanup = () => {
4098
+ if (timeout !== void 0) {
4099
+ clock.clearTimeout(timeout);
4100
+ timeout = void 0;
4101
+ }
4102
+ signal?.removeEventListener("abort", onAbort);
4103
+ };
4104
+ const settle = (outcome) => {
4105
+ if (settled) return;
4106
+ settled = true;
4107
+ cleanup();
4108
+ if (outcome.kind === "resolve") {
4109
+ resolve(outcome.value);
4110
+ } else {
4111
+ reject(outcome.error);
4112
+ }
4113
+ };
4114
+ const onAbort = () => {
4115
+ settle({
4116
+ kind: "reject",
4117
+ error: signal?.reason ?? new Error("Codex token refresh cancelled")
4118
+ });
4119
+ };
4120
+ if (signal?.aborted) {
4121
+ onAbort();
4122
+ } else {
4123
+ signal?.addEventListener("abort", onAbort, { once: true });
4124
+ timeout = clock.setTimeout(
4125
+ () => settle({ kind: "reject", error: new Error("Codex token refresh timed out") }),
4126
+ timeoutMs
4127
+ );
4128
+ }
4129
+ void Promise.resolve(operation).then(
4130
+ (value) => settle({ kind: "resolve", value }),
4131
+ (error) => settle({ kind: "reject", error })
4132
+ );
4133
+ });
4134
+ }
4029
4135
  var defaultDeps = {
4030
4136
  loadCredential: loadCodexCredentialForRun,
4031
4137
  recordRefresh: recordCodexTokenRefresh,
@@ -4043,7 +4149,7 @@ function buildCodexTokenResolver(db, settings, workspaceId, credentialId, deps =
4043
4149
  });
4044
4150
  const performRefresh = async (refreshDb, cred) => {
4045
4151
  try {
4046
- const next = await deps.refresh(cred.tokens.refreshToken);
4152
+ const next = await withCodexTokenDeadline(deps.refresh(cred.tokens.refreshToken));
4047
4153
  const tokens = {
4048
4154
  access_token: next.accessToken ?? cred.tokens.accessToken,
4049
4155
  refresh_token: next.refreshToken ?? cred.tokens.refreshToken,
@@ -4136,10 +4242,11 @@ function errorUsagePayload(reason) {
4136
4242
  weekly: null,
4137
4243
  limitReached: false,
4138
4244
  fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
4245
+ rateLimitResetCredits: null,
4139
4246
  ...reason ? { reason } : {}
4140
4247
  };
4141
4248
  }
4142
- async function fetchCodexUsageForAccount(db, settings, workspaceId, credentialId) {
4249
+ async function fetchCodexUsageForAccount(db, settings, workspaceId, credentialId, fetchImpl = fetch) {
4143
4250
  const resolver = buildCodexTokenResolver(db, settings, workspaceId, credentialId);
4144
4251
  let token;
4145
4252
  try {
@@ -4149,34 +4256,74 @@ async function fetchCodexUsageForAccount(db, settings, workspaceId, credentialId
4149
4256
  }
4150
4257
  let normalized;
4151
4258
  try {
4152
- const usage = await fetchCodexUsage({
4153
- accessToken: token.accessToken,
4154
- chatgptAccountId: token.chatgptAccountId,
4155
- isFedramp: token.isFedramp,
4156
- clientVersion: CODEX_CLIENT_VERSION
4157
- });
4259
+ const usage = await fetchCodexUsage(
4260
+ {
4261
+ accessToken: token.accessToken,
4262
+ chatgptAccountId: token.chatgptAccountId,
4263
+ isFedramp: token.isFedramp,
4264
+ clientVersion: CODEX_CLIENT_VERSION
4265
+ },
4266
+ fetchImpl
4267
+ );
4158
4268
  normalized = normalizeCodexUsage(usage.status, usage.payload);
4159
4269
  } catch {
4160
4270
  return errorUsagePayload();
4161
4271
  }
4162
- if (normalized.fiveHour || normalized.weekly) {
4272
+ const parsedQuota = normalized.status !== "error" && (normalized.fiveHour != null || normalized.weekly != null);
4273
+ if (parsedQuota || normalized.rateLimitResetCredits) {
4274
+ const checkedAt = /* @__PURE__ */ new Date();
4163
4275
  await recordCodexAccountUsage(db, workspaceId, credentialId, {
4164
- primaryUsedPercent: normalized.fiveHour?.percent ?? null,
4165
- primaryResetAt: normalized.fiveHour?.resetAt ? new Date(normalized.fiveHour.resetAt) : null,
4166
- secondaryUsedPercent: normalized.weekly?.percent ?? null,
4167
- secondaryResetAt: normalized.weekly?.resetAt ? new Date(normalized.weekly.resetAt) : null,
4168
- checkedAt: /* @__PURE__ */ new Date()
4276
+ ...parsedQuota ? {
4277
+ primaryUsedPercent: normalized.fiveHour?.percent ?? null,
4278
+ primaryResetAt: normalized.fiveHour?.resetAt ? new Date(normalized.fiveHour.resetAt) : null,
4279
+ secondaryUsedPercent: normalized.weekly?.percent ?? null,
4280
+ secondaryResetAt: normalized.weekly?.resetAt ? new Date(normalized.weekly.resetAt) : null,
4281
+ checkedAt
4282
+ } : {},
4283
+ ...normalized.rateLimitResetCredits ? {
4284
+ resetCreditAvailableCount: normalized.rateLimitResetCredits.availableCount,
4285
+ resetCreditsCheckedAt: checkedAt
4286
+ } : {}
4169
4287
  }).catch(() => void 0);
4170
4288
  }
4171
4289
  return normalized;
4172
4290
  }
4291
+ async function fetchCodexRateLimitResetCreditsForAccount(db, settings, workspaceId, credentialId, fetchImpl = fetch) {
4292
+ const resolver = buildCodexTokenResolver(db, settings, workspaceId, credentialId);
4293
+ let token;
4294
+ try {
4295
+ token = await resolver.getToken();
4296
+ } catch (error) {
4297
+ return {
4298
+ ok: false,
4299
+ status: 0,
4300
+ reason: error instanceof CodexReloginRequired ? "needs_relogin" : "network_error"
4301
+ };
4302
+ }
4303
+ return await fetchCodexRateLimitResetCredits(
4304
+ {
4305
+ accessToken: token.accessToken,
4306
+ chatgptAccountId: token.chatgptAccountId,
4307
+ isFedramp: token.isFedramp,
4308
+ clientVersion: CODEX_CLIENT_VERSION
4309
+ },
4310
+ fetchImpl
4311
+ );
4312
+ }
4173
4313
 
4174
4314
  // src/connection-token-resolver.ts
4175
4315
  import {
4176
4316
  environmentsEncryptionKeyBytes as environmentsEncryptionKeyBytes2
4177
4317
  } from "@opengeni/config";
4318
+ import {
4319
+ OAUTH_MAX_RESPONSE_BYTES,
4320
+ pinnedFetch,
4321
+ readResponseJsonBounded,
4322
+ undiciFetch,
4323
+ validateHttpUrl
4324
+ } from "@opengeni/network";
4325
+ import { isPrivateAddress } from "@opengeni/network";
4178
4326
  import { Buffer as Buffer2 } from "buffer";
4179
- import { lookup } from "dns/promises";
4180
4327
  import { isIP } from "net";
4181
4328
  var HostMcpCredentialScopeError = class extends Error {
4182
4329
  constructor(field) {
@@ -4195,6 +4342,10 @@ function buildHostConnectionTokenResolver(resolve, context) {
4195
4342
  if (input.workspaceId !== context.workspaceId) {
4196
4343
  throw new HostMcpCredentialScopeError("workspaceId");
4197
4344
  }
4345
+ const destinationUrl = canonicalHttpUrl(input.destinationUrl);
4346
+ if (!destinationUrl || !destinationHostMatchesProvider(destinationUrl, input.connectionRef.providerDomain)) {
4347
+ throw new HostMcpCredentialBindingError("destinationUrl");
4348
+ }
4198
4349
  const toolName = input.toolName ?? input.toolId;
4199
4350
  const request = {
4200
4351
  accountId: context.accountId,
@@ -4207,6 +4358,7 @@ function buildHostConnectionTokenResolver(resolve, context) {
4207
4358
  initiator: context.initiator,
4208
4359
  initiatorContext: { ...context.initiatorContext },
4209
4360
  surface: context.surface,
4361
+ destinationUrl,
4210
4362
  serverId: input.serverId,
4211
4363
  connectionRef: {
4212
4364
  providerDomain: input.connectionRef.providerDomain,
@@ -4384,10 +4536,13 @@ function buildConnectionTokenResolver(db, settings, deps = defaultDeps2) {
4384
4536
  }
4385
4537
  return deps.loadCredential(db, settings, request);
4386
4538
  };
4387
- const snapshot = async (cred, ref) => {
4539
+ const snapshot = async (cred, ref, destinationUrl) => {
4388
4540
  if (cred.status !== "active") {
4389
4541
  return authNeededForStatus(cred, ref);
4390
4542
  }
4543
+ if (!connectionBindingMatches(cred, ref, destinationUrl)) {
4544
+ return authNeeded(ref, "missing_connection", cred.id);
4545
+ }
4391
4546
  const missingScopes = missingRequestedScopes(ref.scopes, cred.grantedScopes);
4392
4547
  if (missingScopes.length > 0) {
4393
4548
  return {
@@ -4443,7 +4598,6 @@ function buildConnectionTokenResolver(db, settings, deps = defaultDeps2) {
4443
4598
  if (persisted) {
4444
4599
  const current = await load({
4445
4600
  workspaceId: cred.workspaceId,
4446
- serverId: "",
4447
4601
  connectionRef: { ...ref, connectionId: cred.id }
4448
4602
  });
4449
4603
  if (current) {
@@ -4452,7 +4606,6 @@ function buildConnectionTokenResolver(db, settings, deps = defaultDeps2) {
4452
4606
  }
4453
4607
  const winner = await load({
4454
4608
  workspaceId: cred.workspaceId,
4455
- serverId: "",
4456
4609
  connectionRef: { ...ref, connectionId: cred.id }
4457
4610
  });
4458
4611
  if (winner?.status === "active") {
@@ -4491,6 +4644,9 @@ function buildConnectionTokenResolver(db, settings, deps = defaultDeps2) {
4491
4644
  if (cred.status !== "active") {
4492
4645
  return authNeededForStatus(cred, ref);
4493
4646
  }
4647
+ if (!connectionBindingMatches(cred, ref, input.destinationUrl)) {
4648
+ return authNeeded(ref, "missing_connection", cred.id);
4649
+ }
4494
4650
  if (shouldRefresh(cred, input.forceRefresh === true, deps.now())) {
4495
4651
  try {
4496
4652
  cred = await refreshSingleFlight(cred, ref);
@@ -4510,9 +4666,54 @@ function buildConnectionTokenResolver(db, settings, deps = defaultDeps2) {
4510
4666
  return authNeeded(ref, "refresh_failed", cred.id);
4511
4667
  }
4512
4668
  }
4513
- return await snapshot(cred, ref);
4669
+ return await snapshot(cred, ref, input.destinationUrl);
4514
4670
  };
4515
4671
  }
4672
+ function connectionBindingMatches(cred, ref, destinationUrl) {
4673
+ if (cred.providerDomain.toLowerCase() !== ref.providerDomain.toLowerCase()) return false;
4674
+ if (ref.kind && cred.kind !== ref.kind) return false;
4675
+ const credential = cred.credential;
4676
+ const metadata = cred.metadata;
4677
+ const boundMcpUrl = stringValue(credential.mcp_url) ?? stringValue(metadata.mcpUrl);
4678
+ const destination = canonicalHttpUrl(destinationUrl);
4679
+ if (!destination) return false;
4680
+ if (boundMcpUrl) {
4681
+ const binding = canonicalHttpUrl(boundMcpUrl);
4682
+ if (!binding || destination !== binding) return false;
4683
+ } else if (!destinationHostMatchesProvider(destination, cred.providerDomain)) {
4684
+ return false;
4685
+ }
4686
+ if (cred.kind !== "oauth2") return true;
4687
+ const boundResource = stringValue(credential.resource) ?? stringValue(metadata.resource);
4688
+ if (ref.resource) {
4689
+ if (!boundResource) return false;
4690
+ if (canonicalResource(ref.resource) !== canonicalResource(boundResource)) return false;
4691
+ }
4692
+ return true;
4693
+ }
4694
+ function destinationHostMatchesProvider(destinationUrl, providerDomain) {
4695
+ const destinationHost = new URL(destinationUrl).hostname.toLowerCase();
4696
+ const provider = providerDomain.trim().toLowerCase().replace(/^\.+|\.+$/g, "");
4697
+ return Boolean(provider) && (destinationHost === provider || destinationHost.endsWith(`.${provider}`));
4698
+ }
4699
+ function canonicalHttpUrl(value) {
4700
+ try {
4701
+ const url = new URL(value);
4702
+ if (url.protocol !== "http:" && url.protocol !== "https:") return null;
4703
+ url.hash = "";
4704
+ url.hostname = url.hostname.toLowerCase();
4705
+ if (url.protocol === "https:" && url.port === "443" || url.protocol === "http:" && url.port === "80") {
4706
+ url.port = "";
4707
+ }
4708
+ url.pathname = url.pathname.replace(/\/+$/, "") || "/";
4709
+ return url.toString();
4710
+ } catch {
4711
+ return null;
4712
+ }
4713
+ }
4714
+ function canonicalResource(value) {
4715
+ return canonicalHttpUrl(value) ?? value.trim();
4716
+ }
4516
4717
  var ConnectionRefreshHttpError = class extends Error {
4517
4718
  httpStatus;
4518
4719
  constructor(httpStatus) {
@@ -4583,7 +4784,7 @@ function headersForCredential(cred) {
4583
4784
  }
4584
4785
  return stringRecord(cred.credential.headers);
4585
4786
  }
4586
- async function refreshOAuthConnectionCredential(cred, ref, settings) {
4787
+ async function refreshOAuthConnectionCredential(cred, ref, settings, transportOptions = {}) {
4587
4788
  if (cred.kind !== "oauth2") {
4588
4789
  return {
4589
4790
  credential: cred.credential,
@@ -4596,8 +4797,14 @@ async function refreshOAuthConnectionCredential(cred, ref, settings) {
4596
4797
  if (!refreshToken || !tokenEndpoint) {
4597
4798
  throw new Error("connection has no refresh token endpoint");
4598
4799
  }
4599
- if (settings) {
4600
- await assertOAuthEndpointAllowed(tokenEndpoint, settings);
4800
+ let validatedTokenEndpoint;
4801
+ try {
4802
+ validatedTokenEndpoint = validateHttpUrl(tokenEndpoint, {
4803
+ label: "OAuth refresh token endpoint",
4804
+ allowLoopbackHttp: settings.environment === "local" || settings.environment === "test"
4805
+ });
4806
+ } catch {
4807
+ throw new Error("connection has an invalid refresh token endpoint");
4601
4808
  }
4602
4809
  const body = new URLSearchParams();
4603
4810
  body.set("grant_type", "refresh_token");
@@ -4623,20 +4830,35 @@ async function refreshOAuthConnectionCredential(cred, ref, settings) {
4623
4830
  if (ref.scopes?.length) {
4624
4831
  body.set("scope", ref.scopes.join(" "));
4625
4832
  }
4626
- const response = await fetch(tokenEndpoint, {
4627
- method: "POST",
4628
- headers,
4629
- body,
4630
- redirect: "manual",
4631
- signal: AbortSignal.timeout(CONNECTION_REFRESH_TIMEOUT_MS)
4632
- });
4833
+ const response = await pinnedFetch(
4834
+ validatedTokenEndpoint,
4835
+ {
4836
+ method: "POST",
4837
+ headers,
4838
+ body,
4839
+ signal: AbortSignal.timeout(CONNECTION_REFRESH_TIMEOUT_MS)
4840
+ },
4841
+ settings,
4842
+ {
4843
+ fetchImpl: transportOptions.fetchImpl ?? undiciFetch,
4844
+ ...transportOptions.dnsLookup ? { dnsLookup: transportOptions.dnsLookup } : {},
4845
+ label: "OAuth token endpoint",
4846
+ requireHttpsOutsideLocalTest: true
4847
+ }
4848
+ );
4633
4849
  if (response.status >= 300 && response.status < 400) {
4850
+ await cancelResponseBody(response);
4634
4851
  throw new ConnectionRefreshHttpError(response.status);
4635
4852
  }
4636
4853
  if (!response.ok) {
4854
+ await cancelResponseBody(response);
4637
4855
  throw new ConnectionRefreshHttpError(response.status);
4638
4856
  }
4639
- const payload = await response.json();
4857
+ const payload = await readResponseJsonBounded(
4858
+ response,
4859
+ OAUTH_MAX_RESPONSE_BYTES,
4860
+ "OAuth refresh token response"
4861
+ );
4640
4862
  const accessToken = stringValue(payload.access_token);
4641
4863
  if (!accessToken) {
4642
4864
  throw new Error("connection refresh response did not include access_token");
@@ -4687,74 +4909,8 @@ function stringRecord(value) {
4687
4909
  function stringValue(value) {
4688
4910
  return typeof value === "string" && value.length > 0 ? value : void 0;
4689
4911
  }
4690
- async function assertOAuthEndpointAllowed(rawUrl, settings) {
4691
- if (settings.integrationsAllowPrivateNetworkTargets || ["local", "test"].includes(settings.environment)) {
4692
- return;
4693
- }
4694
- const url = new URL(rawUrl);
4695
- if (url.protocol !== "https:") {
4696
- throw new Error("OAuth token endpoint must use https outside local/test");
4697
- }
4698
- const hostname = url.hostname.toLowerCase();
4699
- if (hostname === "localhost" || hostname.endsWith(".localhost")) {
4700
- throw new Error("OAuth token endpoint may not target localhost");
4701
- }
4702
- const literal = isIP(hostname);
4703
- const addresses = literal ? [hostname] : (await lookup(hostname, { all: true })).map((entry) => entry.address);
4704
- if (addresses.some(isPrivateAddress)) {
4705
- throw new Error("OAuth token endpoint may not target a private network address");
4706
- }
4707
- }
4708
- function isPrivateAddress(address) {
4709
- const normalized = normalizeAddress(address);
4710
- const mapped = ipv4FromMappedIpv6(normalized);
4711
- if (mapped) {
4712
- return isPrivateIpv4Address(mapped);
4713
- }
4714
- if (normalized.includes(":")) {
4715
- if (isIP(normalized) !== 6) {
4716
- return true;
4717
- }
4718
- return normalized === "::1" || normalized === "::" || normalized.startsWith("fc") || normalized.startsWith("fd") || normalized.startsWith("fe8") || normalized.startsWith("fe9") || normalized.startsWith("fea") || normalized.startsWith("feb");
4719
- }
4720
- return isPrivateIpv4Address(normalized);
4721
- }
4722
- function normalizeAddress(address) {
4723
- const trimmed = address.trim().toLowerCase();
4724
- if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
4725
- return trimmed.slice(1, -1);
4726
- }
4727
- return trimmed;
4728
- }
4729
- function ipv4FromMappedIpv6(address) {
4730
- if (!address.startsWith("::ffff:")) {
4731
- return null;
4732
- }
4733
- const embedded = address.slice("::ffff:".length);
4734
- if (embedded.includes(".")) {
4735
- return embedded;
4736
- }
4737
- const parts = embedded.split(":");
4738
- if (parts.length !== 2 || parts.some((part) => !/^[0-9a-f]{1,4}$/.test(part))) {
4739
- return null;
4740
- }
4741
- const high = Number.parseInt(parts[0], 16);
4742
- const low = Number.parseInt(parts[1], 16);
4743
- if (!Number.isInteger(high) || !Number.isInteger(low) || high < 0 || high > 65535 || low < 0 || low > 65535) {
4744
- return null;
4745
- }
4746
- return `${high >> 8 & 255}.${high & 255}.${low >> 8 & 255}.${low & 255}`;
4747
- }
4748
- function isPrivateIpv4Address(address) {
4749
- if (isIP(address) !== 4) {
4750
- return true;
4751
- }
4752
- const parts = address.split(".").map((part) => Number(part));
4753
- if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) {
4754
- return true;
4755
- }
4756
- const [a, b] = parts;
4757
- return a === 0 || a === 10 || a === 127 || a === 169 && b === 254 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168;
4912
+ async function cancelResponseBody(response) {
4913
+ await response.body?.cancel().catch(() => void 0);
4758
4914
  }
4759
4915
 
4760
4916
  // src/index.ts
@@ -6171,6 +6327,27 @@ async function requireFile(db, workspaceId, fileId) {
6171
6327
  }
6172
6328
  return file;
6173
6329
  }
6330
+ async function getRetainedFileArtifact(db, workspaceId, fileId) {
6331
+ return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
6332
+ const [row] = await scopedDb.select({
6333
+ file: files,
6334
+ uploadStatus: fileUploads.status,
6335
+ uploadExpiresAt: fileUploads.expiresAt
6336
+ }).from(files).leftJoin(
6337
+ fileUploads,
6338
+ and5(
6339
+ eq5(fileUploads.workspaceId, files.workspaceId),
6340
+ eq5(fileUploads.fileId, files.id)
6341
+ )
6342
+ ).where(and5(eq5(files.workspaceId, workspaceId), eq5(files.id, fileId))).orderBy(desc(fileUploads.createdAt)).limit(1);
6343
+ if (!row) return null;
6344
+ return {
6345
+ file: mapFile(row.file),
6346
+ uploadStatus: row.uploadStatus,
6347
+ uploadExpiresAt: row.uploadExpiresAt
6348
+ };
6349
+ });
6350
+ }
6174
6351
  async function getFileUpload(db, workspaceId, uploadId) {
6175
6352
  return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
6176
6353
  const [row] = await scopedDb.select({
@@ -6728,7 +6905,14 @@ async function listCapabilityCatalogItems(db, workspaceId) {
6728
6905
  )
6729
6906
  )
6730
6907
  ).orderBy(asc3(capabilityCatalogItems.kind), asc3(capabilityCatalogItems.name));
6731
- return rows.map(mapCapabilityCatalogItem);
6908
+ const installations = await scopedDb.select().from(capabilityInstallations).where(eq5(capabilityInstallations.workspaceId, workspaceId));
6909
+ const installationByCapabilityId = new Map(
6910
+ installations.map((installation) => [installation.capabilityId, installation])
6911
+ );
6912
+ return rows.flatMap((row) => {
6913
+ const exposure = catalogExposureState(row, installationByCapabilityId.get(row.id) ?? null);
6914
+ return exposure === "blocked" ? [] : [mapCapabilityCatalogItem(row, exposure)];
6915
+ });
6732
6916
  });
6733
6917
  }
6734
6918
  async function getCapabilityCatalogItem(db, workspaceId, capabilityId) {
@@ -6742,7 +6926,17 @@ async function getCapabilityCatalogItem(db, workspaceId, capabilityId) {
6742
6926
  )
6743
6927
  )
6744
6928
  ).orderBy(asc3(sql4`(${capabilityCatalogItems.workspaceId} is null)`)).limit(1);
6745
- return row ? mapCapabilityCatalogItem(row) : null;
6929
+ if (!row) {
6930
+ return null;
6931
+ }
6932
+ const [installation] = await scopedDb.select().from(capabilityInstallations).where(
6933
+ and5(
6934
+ eq5(capabilityInstallations.workspaceId, workspaceId),
6935
+ eq5(capabilityInstallations.capabilityId, capabilityId)
6936
+ )
6937
+ ).limit(1);
6938
+ const exposure = catalogExposureState(row, installation ?? null);
6939
+ return exposure === "blocked" ? null : mapCapabilityCatalogItem(row, exposure);
6746
6940
  });
6747
6941
  }
6748
6942
  async function enableCapabilityInstallation(db, input) {
@@ -6862,7 +7056,7 @@ async function listEnabledMcpCapabilityServers(db, workspaceId) {
6862
7056
  }
6863
7057
  }
6864
7058
  return [...preferredByInstallation.values()].flatMap(({ item, installation }) => {
6865
- if (!item.endpointUrl || !mcpConnectivityOk(installation.metadata)) {
7059
+ if (catalogExposureState(item, installation) === "blocked" || !item.endpointUrl || !mcpConnectivityOk(installation.metadata)) {
6866
7060
  return [];
6867
7061
  }
6868
7062
  const headersEncrypted = encryptedHeadersConfig(installation.config.headersEncrypted);
@@ -9403,6 +9597,30 @@ async function upsertCodexSubscriptionCredential(db, input) {
9403
9597
  db,
9404
9598
  { accountId: input.accountId, workspaceId: input.workspaceId },
9405
9599
  async (scopedDb) => {
9600
+ await scopedDb.execute(
9601
+ sql4`select pg_advisory_xact_lock(hashtextextended(${`codex-credential-upsert:${input.workspaceId}:${input.chatgptAccountId ?? "null"}`}, 0))`
9602
+ );
9603
+ const [existing] = input.chatgptAccountId ? await scopedDb.select({
9604
+ id: codexSubscriptionCredentials.id,
9605
+ connectedBySubjectId: codexSubscriptionCredentials.connectedBySubjectId
9606
+ }).from(codexSubscriptionCredentials).where(
9607
+ and5(
9608
+ eq5(codexSubscriptionCredentials.workspaceId, input.workspaceId),
9609
+ eq5(codexSubscriptionCredentials.chatgptAccountId, input.chatgptAccountId)
9610
+ )
9611
+ ).for("update").limit(1) : [];
9612
+ if (existing && existing.connectedBySubjectId !== (input.connectedBySubjectId ?? null)) {
9613
+ const [unresolved] = await scopedDb.select({ id: codexResetRedemptionAttempts.id }).from(codexResetRedemptionAttempts).where(
9614
+ and5(
9615
+ eq5(codexResetRedemptionAttempts.workspaceId, input.workspaceId),
9616
+ eq5(codexResetRedemptionAttempts.credentialId, existing.id),
9617
+ eq5(codexResetRedemptionAttempts.status, "provider_started")
9618
+ )
9619
+ ).limit(1);
9620
+ if (unresolved) {
9621
+ return { kind: "unresolved_redemption", id: existing.id, isNew: false };
9622
+ }
9623
+ }
9406
9624
  const now = /* @__PURE__ */ new Date();
9407
9625
  const [row] = await scopedDb.insert(codexSubscriptionCredentials).values({
9408
9626
  accountId: input.accountId,
@@ -9416,6 +9634,7 @@ async function upsertCodexSubscriptionCredential(db, input) {
9416
9634
  lastRefreshAt: input.lastRefreshAt,
9417
9635
  accountEmail: input.accountEmail ?? null,
9418
9636
  label: input.label ?? null,
9637
+ connectedBySubjectId: input.connectedBySubjectId ?? null,
9419
9638
  status: "active",
9420
9639
  lastError: null
9421
9640
  }).onConflictDoUpdate({
@@ -9443,6 +9662,11 @@ async function upsertCodexSubscriptionCredential(db, input) {
9443
9662
  // it when still null) so a re-connect never clobbers a rename.
9444
9663
  accountEmail: input.accountEmail ?? null,
9445
9664
  label: sql4`coalesce(${codexSubscriptionCredentials.label}, ${input.label ?? null})`,
9665
+ // Ownership follows the most recent connection exactly. A
9666
+ // configured/delegated/API-key reconnect is intentionally
9667
+ // nonhuman and clears the prior human owner, making the row
9668
+ // view-only until a direct managed-cookie human reconnects it.
9669
+ connectedBySubjectId: input.connectedBySubjectId ?? null,
9446
9670
  status: "active",
9447
9671
  lastError: null,
9448
9672
  version: sql4`${codexSubscriptionCredentials.version} + 1`,
@@ -9457,7 +9681,7 @@ async function upsertCodexSubscriptionCredential(db, input) {
9457
9681
  throw new Error("upsertCodexSubscriptionCredential returned no row");
9458
9682
  }
9459
9683
  const isNew = row.createdAt.getTime() === row.updatedAt.getTime();
9460
- return { id: row.id, isNew };
9684
+ return { kind: "upserted", id: row.id, isNew };
9461
9685
  }
9462
9686
  );
9463
9687
  }
@@ -10766,6 +10990,12 @@ async function listCodexAccountStatuses(db, workspaceId) {
10766
10990
  planType: codexSubscriptionCredentials.planType,
10767
10991
  status: codexSubscriptionCredentials.status,
10768
10992
  allocatorEnabled: codexSubscriptionCredentials.allocatorEnabled,
10993
+ allocatorVersion: codexSubscriptionCredentials.allocatorVersion,
10994
+ allocatorUpdatedBySubjectId: codexSubscriptionCredentials.allocatorUpdatedBySubjectId,
10995
+ allocatorUpdatedAt: codexSubscriptionCredentials.allocatorUpdatedAt,
10996
+ resetCreditAvailableCount: codexSubscriptionCredentials.resetCreditAvailableCount,
10997
+ resetCreditsCheckedAt: codexSubscriptionCredentials.resetCreditsCheckedAt,
10998
+ connectedBySubjectId: codexSubscriptionCredentials.connectedBySubjectId,
10769
10999
  expiresAt: codexSubscriptionCredentials.expiresAt,
10770
11000
  lastRefreshAt: codexSubscriptionCredentials.lastRefreshAt,
10771
11001
  lastError: codexSubscriptionCredentials.lastError,
@@ -10787,6 +11017,8 @@ async function listCodexAccountStatuses(db, workspaceId) {
10787
11017
  ...row,
10788
11018
  expiresAt: codexMetadataDate(row.expiresAt),
10789
11019
  lastRefreshAt: codexMetadataDate(row.lastRefreshAt),
11020
+ allocatorUpdatedAt: codexMetadataDate(row.allocatorUpdatedAt),
11021
+ resetCreditsCheckedAt: codexMetadataDate(row.resetCreditsCheckedAt),
10790
11022
  primaryResetAt: codexMetadataDate(row.primaryResetAt),
10791
11023
  secondaryResetAt: codexMetadataDate(row.secondaryResetAt),
10792
11024
  usageCheckedAt: codexMetadataDate(row.usageCheckedAt),
@@ -10796,6 +11028,519 @@ async function listCodexAccountStatuses(db, workspaceId) {
10796
11028
  }));
10797
11029
  });
10798
11030
  }
11031
+ async function updateCodexAllocatorEligibility(db, input) {
11032
+ return await withCodexCapacityMutation(
11033
+ db,
11034
+ { workspaceId: input.workspaceId, reason: "codex_allocator_eligibility_changed" },
11035
+ async (tx) => {
11036
+ const [row] = await tx.select({
11037
+ allocatorEnabled: codexSubscriptionCredentials.allocatorEnabled,
11038
+ allocatorVersion: codexSubscriptionCredentials.allocatorVersion,
11039
+ allocatorUpdatedBySubjectId: codexSubscriptionCredentials.allocatorUpdatedBySubjectId,
11040
+ allocatorUpdatedAt: codexSubscriptionCredentials.allocatorUpdatedAt
11041
+ }).from(codexSubscriptionCredentials).where(
11042
+ and5(
11043
+ eq5(codexSubscriptionCredentials.accountId, input.accountId),
11044
+ eq5(codexSubscriptionCredentials.workspaceId, input.workspaceId),
11045
+ eq5(codexSubscriptionCredentials.id, input.credentialId)
11046
+ )
11047
+ ).for("update").limit(1);
11048
+ if (!row) return { result: { kind: "not_found" }, changed: false };
11049
+ const current = {
11050
+ allocatorEnabled: row.allocatorEnabled,
11051
+ allocatorVersion: row.allocatorVersion,
11052
+ allocatorUpdatedBySubjectId: row.allocatorUpdatedBySubjectId,
11053
+ allocatorUpdatedAt: codexMetadataDate(row.allocatorUpdatedAt)
11054
+ };
11055
+ if (row.allocatorEnabled === input.enabled) {
11056
+ return { result: { kind: "unchanged", ...current }, changed: false };
11057
+ }
11058
+ if (row.allocatorVersion !== input.expectedVersion) {
11059
+ return { result: { kind: "conflict", ...current }, changed: false };
11060
+ }
11061
+ const changedAt = /* @__PURE__ */ new Date();
11062
+ const [updated] = await tx.update(codexSubscriptionCredentials).set({
11063
+ allocatorEnabled: input.enabled,
11064
+ allocatorVersion: sql4`${codexSubscriptionCredentials.allocatorVersion} + 1`,
11065
+ allocatorUpdatedBySubjectId: input.subjectId,
11066
+ allocatorUpdatedAt: changedAt
11067
+ // Deliberately no credential version/updatedAt write.
11068
+ }).where(
11069
+ and5(
11070
+ eq5(codexSubscriptionCredentials.accountId, input.accountId),
11071
+ eq5(codexSubscriptionCredentials.workspaceId, input.workspaceId),
11072
+ eq5(codexSubscriptionCredentials.id, input.credentialId),
11073
+ eq5(codexSubscriptionCredentials.allocatorVersion, input.expectedVersion)
11074
+ )
11075
+ ).returning({
11076
+ allocatorEnabled: codexSubscriptionCredentials.allocatorEnabled,
11077
+ allocatorVersion: codexSubscriptionCredentials.allocatorVersion,
11078
+ allocatorUpdatedBySubjectId: codexSubscriptionCredentials.allocatorUpdatedBySubjectId,
11079
+ allocatorUpdatedAt: codexSubscriptionCredentials.allocatorUpdatedAt
11080
+ });
11081
+ if (!updated) {
11082
+ throw new Error("Codex allocator row changed while locked");
11083
+ }
11084
+ await tx.insert(auditEvents).values({
11085
+ accountId: input.accountId,
11086
+ workspaceId: input.workspaceId,
11087
+ subjectId: input.subjectId,
11088
+ action: "codex.allocator.updated",
11089
+ targetType: "codex_subscription_credential",
11090
+ targetId: input.credentialId,
11091
+ metadata: {
11092
+ allocatorEnabled: updated.allocatorEnabled,
11093
+ allocatorVersion: updated.allocatorVersion
11094
+ }
11095
+ });
11096
+ return {
11097
+ result: {
11098
+ kind: "updated",
11099
+ allocatorEnabled: updated.allocatorEnabled,
11100
+ allocatorVersion: updated.allocatorVersion,
11101
+ allocatorUpdatedBySubjectId: updated.allocatorUpdatedBySubjectId,
11102
+ allocatorUpdatedAt: codexMetadataDate(updated.allocatorUpdatedAt)
11103
+ },
11104
+ changed: true
11105
+ };
11106
+ }
11107
+ );
11108
+ }
11109
+ var CODEX_RESET_REDEMPTION_OUTCOMES = [
11110
+ "reset",
11111
+ "nothingToReset",
11112
+ "noCredit",
11113
+ "alreadyRedeemed"
11114
+ ];
11115
+ function mapCodexResetRedemptionAttempt(row) {
11116
+ return {
11117
+ id: row.id,
11118
+ accountId: row.accountId,
11119
+ workspaceId: row.workspaceId,
11120
+ credentialId: row.credentialId,
11121
+ subjectId: row.subjectId,
11122
+ browserSessionHash: row.browserSessionHash,
11123
+ creditId: row.creditId,
11124
+ upstreamIdempotencyKey: row.upstreamIdempotencyKey,
11125
+ status: row.status,
11126
+ outcome: row.outcome,
11127
+ claimHolderId: row.claimHolderId,
11128
+ claimExpiresAt: codexMetadataDate(row.claimExpiresAt),
11129
+ confirmationExpiresAt: codexMetadataDate(row.confirmationExpiresAt),
11130
+ providerStartedAt: codexMetadataDate(row.providerStartedAt),
11131
+ completedAt: codexMetadataDate(row.completedAt),
11132
+ lastFailureKind: row.lastFailureKind,
11133
+ retryCount: row.retryCount,
11134
+ createdAt: codexMetadataDate(row.createdAt),
11135
+ updatedAt: codexMetadataDate(row.updatedAt)
11136
+ };
11137
+ }
11138
+ async function getCodexResetRedemptionAttempt(db, workspaceId, attemptId) {
11139
+ return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
11140
+ const [row] = await scopedDb.select().from(codexResetRedemptionAttempts).where(
11141
+ and5(
11142
+ eq5(codexResetRedemptionAttempts.workspaceId, workspaceId),
11143
+ eq5(codexResetRedemptionAttempts.id, attemptId)
11144
+ )
11145
+ ).limit(1);
11146
+ return row ? mapCodexResetRedemptionAttempt(row) : null;
11147
+ });
11148
+ }
11149
+ async function listCodexResetRedemptionRecoveries(db, input) {
11150
+ return await withRlsContext(
11151
+ db,
11152
+ { accountId: input.accountId, workspaceId: input.workspaceId },
11153
+ async (scopedDb) => {
11154
+ const rows = await scopedDb.select({
11155
+ attemptId: codexResetRedemptionAttempts.id,
11156
+ credentialId: codexResetRedemptionAttempts.credentialId,
11157
+ creditId: codexResetRedemptionAttempts.creditId,
11158
+ status: codexResetRedemptionAttempts.status,
11159
+ outcome: codexResetRedemptionAttempts.outcome,
11160
+ providerStartedAt: codexResetRedemptionAttempts.providerStartedAt,
11161
+ completedAt: codexResetRedemptionAttempts.completedAt,
11162
+ createdAt: codexResetRedemptionAttempts.createdAt,
11163
+ updatedAt: codexResetRedemptionAttempts.updatedAt
11164
+ }).from(codexResetRedemptionAttempts).innerJoin(
11165
+ codexSubscriptionCredentials,
11166
+ and5(
11167
+ eq5(
11168
+ codexSubscriptionCredentials.id,
11169
+ codexResetRedemptionAttempts.credentialId
11170
+ ),
11171
+ eq5(
11172
+ codexSubscriptionCredentials.workspaceId,
11173
+ codexResetRedemptionAttempts.workspaceId
11174
+ )
11175
+ )
11176
+ ).where(
11177
+ and5(
11178
+ eq5(codexResetRedemptionAttempts.workspaceId, input.workspaceId),
11179
+ eq5(codexResetRedemptionAttempts.subjectId, input.subjectId),
11180
+ eq5(codexSubscriptionCredentials.connectedBySubjectId, input.subjectId),
11181
+ inArray3(codexResetRedemptionAttempts.status, ["provider_started", "completed"])
11182
+ )
11183
+ ).orderBy(desc(codexResetRedemptionAttempts.createdAt));
11184
+ return rows.map((row) => ({
11185
+ attemptId: row.attemptId,
11186
+ credentialId: row.credentialId,
11187
+ creditId: row.creditId,
11188
+ status: row.status,
11189
+ outcome: row.outcome,
11190
+ providerStartedAt: codexMetadataDate(row.providerStartedAt),
11191
+ completedAt: codexMetadataDate(row.completedAt),
11192
+ createdAt: codexMetadataDate(row.createdAt),
11193
+ updatedAt: codexMetadataDate(row.updatedAt)
11194
+ }));
11195
+ }
11196
+ );
11197
+ }
11198
+ async function adoptCodexResetRedemptionAttempt(db, input) {
11199
+ return await withRlsContext(
11200
+ db,
11201
+ { accountId: input.accountId, workspaceId: input.workspaceId },
11202
+ async (scopedDb) => await scopedDb.transaction(async (tx) => {
11203
+ const [credential] = await tx.select({
11204
+ connectedBySubjectId: codexSubscriptionCredentials.connectedBySubjectId
11205
+ }).from(codexSubscriptionCredentials).where(
11206
+ and5(
11207
+ eq5(codexSubscriptionCredentials.id, input.credentialId),
11208
+ eq5(codexSubscriptionCredentials.workspaceId, input.workspaceId)
11209
+ )
11210
+ ).for("share").limit(1);
11211
+ if (!credential) return { kind: "not_found" };
11212
+ if (credential.connectedBySubjectId !== input.subjectId) {
11213
+ return { kind: "forbidden" };
11214
+ }
11215
+ const [attempt] = await tx.select().from(codexResetRedemptionAttempts).where(
11216
+ and5(
11217
+ eq5(codexResetRedemptionAttempts.workspaceId, input.workspaceId),
11218
+ eq5(codexResetRedemptionAttempts.id, input.attemptId)
11219
+ )
11220
+ ).for("update").limit(1);
11221
+ if (!attempt) return { kind: "not_found" };
11222
+ if (attempt.accountId !== input.accountId || attempt.credentialId !== input.credentialId || attempt.creditId !== input.creditId || attempt.subjectId !== input.subjectId) {
11223
+ return { kind: "conflict" };
11224
+ }
11225
+ if (attempt.browserSessionHash === input.browserSessionHash) {
11226
+ return { kind: "current", attempt: mapCodexResetRedemptionAttempt(attempt) };
11227
+ }
11228
+ if (attempt.status !== "provider_started" && attempt.status !== "completed") {
11229
+ return { kind: "conflict" };
11230
+ }
11231
+ const claim = await tx.execute(sql4`
11232
+ select claim_expires_at > now() as claim_live
11233
+ from codex_reset_redemption_attempts
11234
+ where workspace_id = ${input.workspaceId} and id = ${input.attemptId}
11235
+ `);
11236
+ if (claim[0]?.claim_live) return { kind: "in_progress" };
11237
+ const [adopted] = await tx.update(codexResetRedemptionAttempts).set({
11238
+ browserSessionHash: input.browserSessionHash,
11239
+ claimHolderId: null,
11240
+ claimExpiresAt: null,
11241
+ updatedAt: sql4`now()`
11242
+ }).where(eq5(codexResetRedemptionAttempts.id, input.attemptId)).returning();
11243
+ if (!adopted) throw new Error("Codex redemption adoption returned no row");
11244
+ return { kind: "adopted", attempt: mapCodexResetRedemptionAttempt(adopted) };
11245
+ })
11246
+ );
11247
+ }
11248
+ async function claimCodexResetRedemption(db, input) {
11249
+ const claimTtlMs = input.claimTtlMs ?? 6e4;
11250
+ if (!Number.isFinite(claimTtlMs) || claimTtlMs <= 0) {
11251
+ throw new Error("Codex redemption claim TTL must be positive");
11252
+ }
11253
+ if (!Number.isFinite(input.confirmationExpiresAt.getTime()) || input.confirmationExpiresAt.getTime() <= Date.now()) {
11254
+ return { kind: "forbidden" };
11255
+ }
11256
+ return await withRlsContext(
11257
+ db,
11258
+ { accountId: input.accountId, workspaceId: input.workspaceId },
11259
+ async (scopedDb) => await scopedDb.transaction(async (tx) => {
11260
+ await tx.execute(
11261
+ sql4`select pg_advisory_xact_lock(hashtextextended(${`codex-reset-attempt:${input.id}`}, 0))`
11262
+ );
11263
+ await tx.execute(
11264
+ sql4`select pg_advisory_xact_lock(hashtextextended(${`codex-reset-credit:${input.workspaceId}:${input.credentialId}:${input.creditId}`}, 0))`
11265
+ );
11266
+ const [credential] = await tx.select({
11267
+ connectedBySubjectId: codexSubscriptionCredentials.connectedBySubjectId,
11268
+ status: codexSubscriptionCredentials.status
11269
+ }).from(codexSubscriptionCredentials).where(
11270
+ and5(
11271
+ eq5(codexSubscriptionCredentials.accountId, input.accountId),
11272
+ eq5(codexSubscriptionCredentials.workspaceId, input.workspaceId),
11273
+ eq5(codexSubscriptionCredentials.id, input.credentialId)
11274
+ )
11275
+ ).for("share").limit(1);
11276
+ if (!credential) return { kind: "not_found" };
11277
+ const [existing] = await tx.select().from(codexResetRedemptionAttempts).where(
11278
+ and5(
11279
+ eq5(codexResetRedemptionAttempts.workspaceId, input.workspaceId),
11280
+ eq5(codexResetRedemptionAttempts.id, input.id)
11281
+ )
11282
+ ).for("update").limit(1);
11283
+ const now = /* @__PURE__ */ new Date();
11284
+ if (existing) {
11285
+ if (existing.accountId !== input.accountId || existing.credentialId !== input.credentialId || existing.subjectId !== input.subjectId || existing.browserSessionHash !== input.browserSessionHash || existing.creditId !== input.creditId) {
11286
+ return { kind: "conflict" };
11287
+ }
11288
+ if (credential.connectedBySubjectId !== input.subjectId) {
11289
+ return { kind: "forbidden" };
11290
+ }
11291
+ const mapped = mapCodexResetRedemptionAttempt(existing);
11292
+ if (mapped.status === "completed") return { kind: "completed", attempt: mapped };
11293
+ if (credential.status !== "active") {
11294
+ return { kind: "forbidden" };
11295
+ }
11296
+ const claimState = await tx.execute(sql4`
11297
+ select claim_expires_at > now() as claim_live
11298
+ from codex_reset_redemption_attempts
11299
+ where workspace_id = ${input.workspaceId} and id = ${input.id}
11300
+ `);
11301
+ if (claimState[0]?.claim_live) {
11302
+ return { kind: "in_progress", attempt: mapped };
11303
+ }
11304
+ const [reclaimed] = await tx.update(codexResetRedemptionAttempts).set({
11305
+ claimHolderId: input.claimHolderId,
11306
+ claimExpiresAt: sql4`now() + (${claimTtlMs} * interval '1 millisecond')`,
11307
+ confirmationExpiresAt: input.confirmationExpiresAt,
11308
+ lastFailureKind: null,
11309
+ retryCount: sql4`${codexResetRedemptionAttempts.retryCount} + 1`,
11310
+ updatedAt: now
11311
+ }).where(eq5(codexResetRedemptionAttempts.id, input.id)).returning();
11312
+ if (!reclaimed) throw new Error("Codex redemption reclaim returned no row");
11313
+ return {
11314
+ kind: "claimed",
11315
+ attempt: mapCodexResetRedemptionAttempt(reclaimed)
11316
+ };
11317
+ }
11318
+ if (credential.status !== "active" || credential.connectedBySubjectId !== input.subjectId) {
11319
+ return { kind: "forbidden" };
11320
+ }
11321
+ const [creditAttempt] = await tx.execute(sql4`
11322
+ select id, status, claim_expires_at > now() as claim_live
11323
+ from codex_reset_redemption_attempts
11324
+ where workspace_id = ${input.workspaceId}
11325
+ and credential_id = ${input.credentialId}
11326
+ and credit_id = ${input.creditId}
11327
+ and (status <> 'completed' or outcome in ('reset', 'alreadyRedeemed'))
11328
+ limit 1
11329
+ for update
11330
+ `);
11331
+ if (creditAttempt) {
11332
+ if (creditAttempt.status !== "processing" || creditAttempt.claim_live) {
11333
+ return { kind: "conflict" };
11334
+ }
11335
+ const removed = await tx.delete(codexResetRedemptionAttempts).where(
11336
+ and5(
11337
+ eq5(codexResetRedemptionAttempts.workspaceId, input.workspaceId),
11338
+ eq5(codexResetRedemptionAttempts.id, creditAttempt.id),
11339
+ eq5(codexResetRedemptionAttempts.status, "processing"),
11340
+ sql4`(${codexResetRedemptionAttempts.claimExpiresAt} is null or ${codexResetRedemptionAttempts.claimExpiresAt} <= now())`
11341
+ )
11342
+ ).returning({ id: codexResetRedemptionAttempts.id });
11343
+ if (removed.length !== 1) return { kind: "conflict" };
11344
+ }
11345
+ const [created] = await tx.insert(codexResetRedemptionAttempts).values({
11346
+ id: input.id,
11347
+ accountId: input.accountId,
11348
+ workspaceId: input.workspaceId,
11349
+ credentialId: input.credentialId,
11350
+ subjectId: input.subjectId,
11351
+ browserSessionHash: input.browserSessionHash,
11352
+ creditId: input.creditId,
11353
+ status: "processing",
11354
+ claimHolderId: input.claimHolderId,
11355
+ claimExpiresAt: sql4`now() + (${claimTtlMs} * interval '1 millisecond')`,
11356
+ confirmationExpiresAt: input.confirmationExpiresAt
11357
+ }).returning();
11358
+ if (!created) throw new Error("Codex redemption claim returned no row");
11359
+ return {
11360
+ kind: "claimed",
11361
+ attempt: mapCodexResetRedemptionAttempt(created)
11362
+ };
11363
+ })
11364
+ );
11365
+ }
11366
+ async function fenceCodexResetRedemptionSend(db, input) {
11367
+ const sendLeaseMs = input.sendLeaseMs ?? 3e4;
11368
+ if (!Number.isFinite(sendLeaseMs) || sendLeaseMs <= 1e4) {
11369
+ throw new Error("Codex redemption send lease must exceed the bounded provider call");
11370
+ }
11371
+ return await withRlsContext(
11372
+ db,
11373
+ { accountId: input.accountId, workspaceId: input.workspaceId },
11374
+ async (scopedDb) => await scopedDb.transaction(async (tx) => {
11375
+ const [credential] = await tx.select({
11376
+ connectedBySubjectId: codexSubscriptionCredentials.connectedBySubjectId,
11377
+ status: codexSubscriptionCredentials.status
11378
+ }).from(codexSubscriptionCredentials).where(
11379
+ and5(
11380
+ eq5(codexSubscriptionCredentials.accountId, input.accountId),
11381
+ eq5(codexSubscriptionCredentials.workspaceId, input.workspaceId),
11382
+ eq5(codexSubscriptionCredentials.id, input.credentialId)
11383
+ )
11384
+ ).for("share").limit(1);
11385
+ const [attempt] = await tx.select().from(codexResetRedemptionAttempts).where(
11386
+ and5(
11387
+ eq5(codexResetRedemptionAttempts.accountId, input.accountId),
11388
+ eq5(codexResetRedemptionAttempts.workspaceId, input.workspaceId),
11389
+ eq5(codexResetRedemptionAttempts.id, input.attemptId)
11390
+ )
11391
+ ).for("update").limit(1);
11392
+ if (!attempt) return { kind: "not_ready", reason: "not_found" };
11393
+ if (attempt.credentialId !== input.credentialId || attempt.subjectId !== input.subjectId || attempt.browserSessionHash !== input.browserSessionHash || attempt.claimHolderId !== input.claimHolderId) {
11394
+ return { kind: "not_ready", reason: "identity_mismatch" };
11395
+ }
11396
+ if (attempt.status === "completed") {
11397
+ return { kind: "not_ready", reason: "already_completed" };
11398
+ }
11399
+ const [liveness] = await tx.execute(sql4`
11400
+ select claim_expires_at > now() as claim_live,
11401
+ confirmation_expires_at > now() as confirmation_live
11402
+ from codex_reset_redemption_attempts
11403
+ where workspace_id = ${input.workspaceId} and id = ${input.attemptId}
11404
+ `);
11405
+ let reason;
11406
+ if (!liveness?.claim_live) reason = "claim_expired";
11407
+ else if (!liveness.confirmation_live) reason = "confirmation_expired";
11408
+ else if (!credential || credential.status !== "active" || credential.connectedBySubjectId !== input.subjectId) {
11409
+ reason = "credential_unavailable";
11410
+ } else {
11411
+ const [ready] = await tx.update(codexResetRedemptionAttempts).set({
11412
+ status: "provider_started",
11413
+ providerStartedAt: sql4`coalesce(${codexResetRedemptionAttempts.providerStartedAt}, now())`,
11414
+ claimExpiresAt: sql4`now() + (${sendLeaseMs} * interval '1 millisecond')`,
11415
+ lastFailureKind: null,
11416
+ updatedAt: sql4`now()`
11417
+ }).where(eq5(codexResetRedemptionAttempts.id, input.attemptId)).returning();
11418
+ if (!ready) throw new Error("Codex redemption send fence returned no row");
11419
+ return { kind: "ready", attempt: mapCodexResetRedemptionAttempt(ready) };
11420
+ }
11421
+ if (attempt.status === "processing") {
11422
+ await tx.delete(codexResetRedemptionAttempts).where(eq5(codexResetRedemptionAttempts.id, input.attemptId));
11423
+ } else {
11424
+ await tx.update(codexResetRedemptionAttempts).set({
11425
+ claimHolderId: null,
11426
+ claimExpiresAt: null,
11427
+ lastFailureKind: `send_fence_${reason}`,
11428
+ updatedAt: sql4`now()`
11429
+ }).where(eq5(codexResetRedemptionAttempts.id, input.attemptId));
11430
+ }
11431
+ return { kind: "not_ready", reason };
11432
+ })
11433
+ );
11434
+ }
11435
+ async function abandonCodexResetRedemptionBeforeProvider(db, input) {
11436
+ return await withRlsContext(
11437
+ db,
11438
+ { accountId: input.accountId, workspaceId: input.workspaceId },
11439
+ async (scopedDb) => {
11440
+ const deleted = await scopedDb.delete(codexResetRedemptionAttempts).where(
11441
+ and5(
11442
+ eq5(codexResetRedemptionAttempts.workspaceId, input.workspaceId),
11443
+ eq5(codexResetRedemptionAttempts.id, input.attemptId),
11444
+ eq5(codexResetRedemptionAttempts.status, "processing"),
11445
+ eq5(codexResetRedemptionAttempts.claimHolderId, input.claimHolderId)
11446
+ )
11447
+ ).returning({ id: codexResetRedemptionAttempts.id });
11448
+ return deleted.length === 1;
11449
+ }
11450
+ );
11451
+ }
11452
+ async function releaseCodexResetRedemptionClaim(db, input) {
11453
+ return await withRlsContext(
11454
+ db,
11455
+ { accountId: input.accountId, workspaceId: input.workspaceId },
11456
+ async (scopedDb) => {
11457
+ const rows = await scopedDb.update(codexResetRedemptionAttempts).set({
11458
+ claimHolderId: null,
11459
+ claimExpiresAt: null,
11460
+ lastFailureKind: input.failureKind.slice(0, 100),
11461
+ updatedAt: /* @__PURE__ */ new Date()
11462
+ }).where(
11463
+ and5(
11464
+ eq5(codexResetRedemptionAttempts.workspaceId, input.workspaceId),
11465
+ eq5(codexResetRedemptionAttempts.id, input.attemptId),
11466
+ eq5(codexResetRedemptionAttempts.claimHolderId, input.claimHolderId),
11467
+ sql4`${codexResetRedemptionAttempts.status} <> 'completed'`
11468
+ )
11469
+ ).returning({ id: codexResetRedemptionAttempts.id });
11470
+ return rows.length === 1;
11471
+ }
11472
+ );
11473
+ }
11474
+ async function completeCodexResetRedemption(db, input) {
11475
+ if (!CODEX_RESET_REDEMPTION_OUTCOMES.includes(input.outcome)) {
11476
+ throw new Error("Unknown Codex redemption outcome");
11477
+ }
11478
+ return await withCodexCapacityMutation(
11479
+ db,
11480
+ { workspaceId: input.workspaceId, reason: "codex_reset_credit_redeemed" },
11481
+ async (tx) => {
11482
+ await tx.execute(
11483
+ sql4`select pg_advisory_xact_lock(hashtextextended(${`codex-reset-attempt:${input.attemptId}`}, 0))`
11484
+ );
11485
+ const [current] = await tx.select().from(codexResetRedemptionAttempts).where(
11486
+ and5(
11487
+ eq5(codexResetRedemptionAttempts.accountId, input.accountId),
11488
+ eq5(codexResetRedemptionAttempts.workspaceId, input.workspaceId),
11489
+ eq5(codexResetRedemptionAttempts.id, input.attemptId)
11490
+ )
11491
+ ).for("update").limit(1);
11492
+ if (!current) return { result: null, changed: false };
11493
+ if (current.status === "completed") {
11494
+ return { result: mapCodexResetRedemptionAttempt(current), changed: false };
11495
+ }
11496
+ if (current.status !== "provider_started" || current.claimHolderId !== input.claimHolderId) {
11497
+ return { result: null, changed: false };
11498
+ }
11499
+ const completedAt = /* @__PURE__ */ new Date();
11500
+ const [completed] = await tx.update(codexResetRedemptionAttempts).set({
11501
+ status: "completed",
11502
+ outcome: input.outcome,
11503
+ completedAt,
11504
+ claimHolderId: null,
11505
+ claimExpiresAt: null,
11506
+ lastFailureKind: null,
11507
+ updatedAt: completedAt
11508
+ }).where(
11509
+ and5(
11510
+ eq5(codexResetRedemptionAttempts.accountId, input.accountId),
11511
+ eq5(codexResetRedemptionAttempts.workspaceId, input.workspaceId),
11512
+ eq5(codexResetRedemptionAttempts.id, input.attemptId)
11513
+ )
11514
+ ).returning();
11515
+ if (!completed) throw new Error("Codex redemption completion returned no row");
11516
+ const restoresCapacity = input.outcome === "reset" || input.outcome === "alreadyRedeemed";
11517
+ if (restoresCapacity) {
11518
+ await tx.update(codexSubscriptionCredentials).set({ exhaustedUntil: null }).where(
11519
+ and5(
11520
+ eq5(codexSubscriptionCredentials.accountId, input.accountId),
11521
+ eq5(codexSubscriptionCredentials.workspaceId, input.workspaceId),
11522
+ eq5(codexSubscriptionCredentials.id, current.credentialId)
11523
+ )
11524
+ );
11525
+ }
11526
+ await tx.insert(auditEvents).values({
11527
+ accountId: input.accountId,
11528
+ workspaceId: input.workspaceId,
11529
+ subjectId: current.subjectId,
11530
+ action: "codex.reset_credit.redemption.completed",
11531
+ targetType: "codex_reset_redemption_attempt",
11532
+ targetId: input.attemptId,
11533
+ metadata: { outcome: input.outcome }
11534
+ });
11535
+ return {
11536
+ result: mapCodexResetRedemptionAttempt(completed),
11537
+ // A successful/already-applied upstream reset can make durable waiters
11538
+ // eligible immediately even when the local cooldown was already null.
11539
+ changed: restoresCapacity
11540
+ };
11541
+ }
11542
+ );
11543
+ }
10799
11544
  async function recordCodexAccountUsage(db, workspaceId, credentialId, snapshot) {
10800
11545
  return (await recordCodexAccountUsageWithWakeTargets(db, workspaceId, credentialId, snapshot)).result;
10801
11546
  }
@@ -10805,11 +11550,17 @@ async function recordCodexAccountUsageWithWakeTargets(db, workspaceId, credentia
10805
11550
  { workspaceId, reason: "codex_usage_refreshed" },
10806
11551
  async (tx) => {
10807
11552
  const updated = await tx.update(codexSubscriptionCredentials).set({
10808
- primaryUsedPercent: snapshot.primaryUsedPercent,
10809
- primaryResetAt: snapshot.primaryResetAt,
10810
- secondaryUsedPercent: snapshot.secondaryUsedPercent,
10811
- secondaryResetAt: snapshot.secondaryResetAt,
10812
- usageCheckedAt: snapshot.checkedAt
11553
+ ...snapshot.checkedAt !== void 0 ? {
11554
+ primaryUsedPercent: snapshot.primaryUsedPercent ?? null,
11555
+ primaryResetAt: snapshot.primaryResetAt ?? null,
11556
+ secondaryUsedPercent: snapshot.secondaryUsedPercent ?? null,
11557
+ secondaryResetAt: snapshot.secondaryResetAt ?? null,
11558
+ usageCheckedAt: snapshot.checkedAt
11559
+ } : {},
11560
+ ...snapshot.resetCreditAvailableCount !== void 0 ? {
11561
+ resetCreditAvailableCount: snapshot.resetCreditAvailableCount,
11562
+ resetCreditsCheckedAt: snapshot.resetCreditsCheckedAt ?? null
11563
+ } : {}
10813
11564
  // NB: no `version` bump and no `updatedAt` touch — usage is non-credential
10814
11565
  // metadata and must NOT race the (id, version) refresh CAS in
10815
11566
  // recordCodexTokenRefresh / setCodexCredentialStatus.
@@ -11091,6 +11842,34 @@ async function disconnectCodexAccount(db, workspaceId, credentialId) {
11091
11842
  where workspace_id = ${workspaceId}
11092
11843
  for update
11093
11844
  `);
11845
+ const [credential] = await scopedDb.select({ id: codexSubscriptionCredentials.id }).from(codexSubscriptionCredentials).where(
11846
+ and5(
11847
+ eq5(codexSubscriptionCredentials.id, credentialId),
11848
+ eq5(codexSubscriptionCredentials.workspaceId, workspaceId)
11849
+ )
11850
+ ).for("update").limit(1);
11851
+ const [settingsBefore] = await scopedDb.select({ activeCredentialId: codexRotationSettings.activeCredentialId }).from(codexRotationSettings).where(eq5(codexRotationSettings.workspaceId, workspaceId)).limit(1);
11852
+ if (!credential) {
11853
+ return {
11854
+ removed: false,
11855
+ newActiveCredentialId: settingsBefore?.activeCredentialId ?? null,
11856
+ blockedByUnresolvedRedemption: false
11857
+ };
11858
+ }
11859
+ const [unresolved] = await scopedDb.select({ id: codexResetRedemptionAttempts.id }).from(codexResetRedemptionAttempts).where(
11860
+ and5(
11861
+ eq5(codexResetRedemptionAttempts.workspaceId, workspaceId),
11862
+ eq5(codexResetRedemptionAttempts.credentialId, credentialId),
11863
+ eq5(codexResetRedemptionAttempts.status, "provider_started")
11864
+ )
11865
+ ).limit(1);
11866
+ if (unresolved) {
11867
+ return {
11868
+ removed: false,
11869
+ newActiveCredentialId: settingsBefore?.activeCredentialId ?? null,
11870
+ blockedByUnresolvedRedemption: true
11871
+ };
11872
+ }
11094
11873
  const removedRows = await scopedDb.delete(codexSubscriptionCredentials).where(
11095
11874
  and5(
11096
11875
  eq5(codexSubscriptionCredentials.id, credentialId),
@@ -11103,7 +11882,8 @@ async function disconnectCodexAccount(db, workspaceId, credentialId) {
11103
11882
  if (removedRows.length === 0) {
11104
11883
  return {
11105
11884
  removed: false,
11106
- newActiveCredentialId: settingsRow?.activeCredentialId ?? null
11885
+ newActiveCredentialId: settingsRow?.activeCredentialId ?? null,
11886
+ blockedByUnresolvedRedemption: false
11107
11887
  };
11108
11888
  }
11109
11889
  let newActive = settingsRow?.activeCredentialId ?? null;
@@ -11114,13 +11894,31 @@ async function disconnectCodexAccount(db, workspaceId, credentialId) {
11114
11894
  await scopedDb.update(codexRotationSettings).set({ activeCredentialId: newActive, updatedAt: /* @__PURE__ */ new Date() }).where(eq5(codexRotationSettings.workspaceId, workspaceId));
11115
11895
  }
11116
11896
  }
11117
- return { removed: true, newActiveCredentialId: newActive };
11897
+ return {
11898
+ removed: true,
11899
+ newActiveCredentialId: newActive,
11900
+ blockedByUnresolvedRedemption: false
11901
+ };
11118
11902
  });
11119
11903
  }
11120
11904
  async function disconnectAllCodexAccounts(db, workspaceId) {
11121
11905
  return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
11906
+ const credentials = await scopedDb.select({ id: codexSubscriptionCredentials.id }).from(codexSubscriptionCredentials).where(eq5(codexSubscriptionCredentials.workspaceId, workspaceId)).orderBy(asc3(codexSubscriptionCredentials.id)).for("update");
11907
+ if (credentials.length === 0) return { removed: 0, blockedCredentialIds: [] };
11908
+ const blocked = await scopedDb.selectDistinct({ credentialId: codexResetRedemptionAttempts.credentialId }).from(codexResetRedemptionAttempts).where(
11909
+ and5(
11910
+ eq5(codexResetRedemptionAttempts.workspaceId, workspaceId),
11911
+ eq5(codexResetRedemptionAttempts.status, "provider_started")
11912
+ )
11913
+ );
11914
+ if (blocked.length > 0) {
11915
+ return {
11916
+ removed: 0,
11917
+ blockedCredentialIds: blocked.map((row) => row.credentialId).sort()
11918
+ };
11919
+ }
11122
11920
  const rows = await scopedDb.delete(codexSubscriptionCredentials).where(eq5(codexSubscriptionCredentials.workspaceId, workspaceId)).returning({ id: codexSubscriptionCredentials.id });
11123
- return rows.length;
11921
+ return { removed: rows.length, blockedCredentialIds: [] };
11124
11922
  });
11125
11923
  }
11126
11924
  async function recordAuditEvent(db, input) {
@@ -11181,6 +11979,7 @@ function mapSessionMcpServerMetadata(row) {
11181
11979
  url: row.url,
11182
11980
  headerNames: Object.keys(row.headersEncrypted ?? {}).sort(),
11183
11981
  credentialVersion: Number(row.credentialVersion),
11982
+ requireApproval: row.requireApproval ?? false,
11184
11983
  connectionRef: row.connectionRef ?? null
11185
11984
  };
11186
11985
  }
@@ -11290,8 +12089,60 @@ async function updateSessionMcpServerCredentialsInTransaction(tx, input) {
11290
12089
  }
11291
12090
  return { servers, missingIds };
11292
12091
  }
11293
- async function listSessionMcpServersForRun(db, workspaceId, sessionId, encryptionKey) {
12092
+ async function updateSessionMcpApprovalPolicy(db, input) {
12093
+ return await withWorkspaceRls(
12094
+ db,
12095
+ input.workspaceId,
12096
+ async (scopedDb) => await scopedDb.transaction(
12097
+ async (tx) => await updateSessionMcpApprovalPolicyInTransaction(tx, input)
12098
+ )
12099
+ );
12100
+ }
12101
+ async function updateSessionMcpApprovalPolicyInTransaction(tx, input) {
12102
+ const [existing] = await tx.select().from(sessionMcpServers).where(
12103
+ and5(
12104
+ eq5(sessionMcpServers.workspaceId, input.workspaceId),
12105
+ eq5(sessionMcpServers.sessionId, input.sessionId),
12106
+ eq5(sessionMcpServers.serverId, input.serverId)
12107
+ )
12108
+ ).for("update").limit(1);
12109
+ if (!existing) {
12110
+ return { server: null, changed: false };
12111
+ }
12112
+ const current = existing.requireApproval ?? false;
12113
+ if (JSON.stringify(current) === JSON.stringify(input.requireApproval)) {
12114
+ return { server: mapSessionMcpServerMetadata(existing), changed: false };
12115
+ }
12116
+ const [updated] = await tx.update(sessionMcpServers).set({
12117
+ requireApproval: input.requireApproval,
12118
+ updatedAt: /* @__PURE__ */ new Date()
12119
+ }).where(
12120
+ and5(
12121
+ eq5(sessionMcpServers.workspaceId, input.workspaceId),
12122
+ eq5(sessionMcpServers.sessionId, input.sessionId),
12123
+ eq5(sessionMcpServers.serverId, input.serverId)
12124
+ )
12125
+ ).returning();
12126
+ if (!updated) {
12127
+ throw new Error(`Session MCP server disappeared during policy update: ${input.serverId}`);
12128
+ }
12129
+ return { server: mapSessionMcpServerMetadata(updated), changed: true };
12130
+ }
12131
+ async function listSessionMcpServersForRun(db, workspaceId, sessionId, attemptId, encryptionKey) {
11294
12132
  return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
12133
+ const [attempt] = await scopedDb.select({
12134
+ sessionId: sessionTurnAttempts.sessionId,
12135
+ mcpApprovalPolicies: sessionTurnAttempts.mcpApprovalPolicies
12136
+ }).from(sessionTurnAttempts).where(
12137
+ and5(
12138
+ eq5(sessionTurnAttempts.workspaceId, workspaceId),
12139
+ eq5(sessionTurnAttempts.id, attemptId),
12140
+ inArray3(sessionTurnAttempts.state, ["claimed", "running"])
12141
+ )
12142
+ ).limit(1);
12143
+ if (!attempt || attempt.sessionId !== sessionId) {
12144
+ throw new Error(`session MCP policy snapshot is unavailable for attempt ${attemptId}`);
12145
+ }
11295
12146
  const rows = await scopedDb.select().from(sessionMcpServers).where(
11296
12147
  and5(
11297
12148
  eq5(sessionMcpServers.workspaceId, workspaceId),
@@ -11299,6 +12150,11 @@ async function listSessionMcpServersForRun(db, workspaceId, sessionId, encryptio
11299
12150
  )
11300
12151
  ).orderBy(asc3(sessionMcpServers.createdAt), asc3(sessionMcpServers.serverId));
11301
12152
  return rows.map((row) => {
12153
+ if (!Object.hasOwn(attempt.mcpApprovalPolicies, row.serverId)) {
12154
+ throw new Error(
12155
+ `session MCP policy snapshot is missing server ${row.serverId} for attempt ${attemptId}`
12156
+ );
12157
+ }
11302
12158
  let headers;
11303
12159
  try {
11304
12160
  if (!encryptionKey && Object.keys(row.headersEncrypted ?? {}).length > 0) {
@@ -11318,7 +12174,7 @@ async function listSessionMcpServersForRun(db, workspaceId, sessionId, encryptio
11318
12174
  ...row.allowedTools ? { allowedTools: row.allowedTools } : {},
11319
12175
  ...row.timeoutMs ? { timeoutMs: row.timeoutMs } : {},
11320
12176
  ...row.cacheToolsList ? { cacheToolsList: row.cacheToolsList } : {},
11321
- ...row.requireApproval != null ? { requireApproval: row.requireApproval } : {},
12177
+ requireApproval: attempt.mcpApprovalPolicies[row.serverId],
11322
12178
  headers
11323
12179
  };
11324
12180
  });
@@ -11369,6 +12225,7 @@ async function createSession(db, input) {
11369
12225
  initialTurnInstructions: input.initialTurnInstructions ?? null,
11370
12226
  resources: input.resources,
11371
12227
  tools: input.tools ?? [],
12228
+ toolPolicy: input.toolPolicy ?? null,
11372
12229
  metadata: input.metadata,
11373
12230
  ...creatorColumns(frozenCreator),
11374
12231
  model: input.model,
@@ -11416,6 +12273,7 @@ async function createSessionWithIdempotencyKey(db, input) {
11416
12273
  initialTurnInstructions: input.initialTurnInstructions ?? null,
11417
12274
  resources: input.resources,
11418
12275
  tools: input.tools ?? [],
12276
+ toolPolicy: input.toolPolicy ?? null,
11419
12277
  metadata: input.metadata,
11420
12278
  ...creatorColumns(frozenCreator),
11421
12279
  model: input.model,
@@ -12695,6 +13553,17 @@ async function listSessionEventPage(db, workspaceId, sessionId, options = {}) {
12695
13553
  eq5(sessionEvents.sessionId, sessionId),
12696
13554
  gt(sessionEvents.sequence, after)
12697
13555
  ];
13556
+ if (options.authoritativeLatest) {
13557
+ filters.push(
13558
+ and5(
13559
+ or(
13560
+ isNull(sessionEvents.turnAssociation),
13561
+ eq5(sessionEvents.turnAssociation, "current")
13562
+ ),
13563
+ isNull(sessionEvents.duplicateOfEventId)
13564
+ )
13565
+ );
13566
+ }
12698
13567
  if (typeFilters.includeTypes.length > 0) {
12699
13568
  filters.push(inArray3(sessionEvents.type, typeFilters.includeTypes));
12700
13569
  }
@@ -12712,7 +13581,7 @@ async function listSessionEventPage(db, workspaceId, sessionId, options = {}) {
12712
13581
  filters.push(lt(sessionEvents.sequence, before));
12713
13582
  }
12714
13583
  const rows = await scopedDb.select(sessionEventProjectionSelect(payloadMode)).from(sessionEvents).where(and5(...filters)).orderBy(
12715
- direction === "before" ? desc(sessionEvents.sequence) : asc3(sessionEvents.sequence)
13584
+ options.authoritativeLatest ? desc(sessionEvents.sequence) : direction === "before" ? desc(sessionEvents.sequence) : asc3(sessionEvents.sequence)
12716
13585
  ).limit(queryLimit);
12717
13586
  if (rows.length === 0) break;
12718
13587
  for (const row of rows) {
@@ -12906,21 +13775,67 @@ async function listSessionEvents(db, workspaceId, sessionId, afterOrOptions = 0,
12906
13775
  return (direction === "before" ? rows.reverse() : rows).map(mapProjectedEvent);
12907
13776
  });
12908
13777
  }
12909
- async function reserveToolspaceCallForTurn(db, workspaceId, sessionId, turnId, limit) {
13778
+ async function admitToolspaceTurnAttempt(db, workspaceId, claims) {
12910
13779
  return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
12911
- const [row] = await scopedDb.update(sessionTurns).set({
12912
- toolspaceCallCount: sql4`${sessionTurns.toolspaceCallCount} + 1`
12913
- }).where(
12914
- and5(
12915
- eq5(sessionTurns.workspaceId, workspaceId),
12916
- eq5(sessionTurns.sessionId, sessionId),
12917
- eq5(sessionTurns.id, turnId),
12918
- sql4`${sessionTurns.toolspaceCallCount} < ${limit}`
12919
- )
12920
- ).returning({ count: sessionTurns.toolspaceCallCount });
12921
- return row ? { reserved: true, count: Number(row.count) } : { reserved: false };
13780
+ return await scopedDb.transaction(async (tx) => {
13781
+ const fence = await lockTurnAttemptWriteFenceTx(tx, {
13782
+ workspaceId,
13783
+ sessionId: claims.sessionId,
13784
+ turnId: claims.turnId,
13785
+ attemptId: claims.attemptId,
13786
+ executionGeneration: claims.executionGeneration
13787
+ });
13788
+ return fence.allowed && fence.turn.status === "running";
13789
+ });
12922
13790
  });
12923
13791
  }
13792
+ async function reserveToolspaceCallForAttempt(db, input) {
13793
+ return await withRlsContext(
13794
+ db,
13795
+ { accountId: input.accountId, workspaceId: input.workspaceId },
13796
+ async (scopedDb) => await scopedDb.transaction(async (tx) => {
13797
+ const fence = await lockTurnAttemptWriteFenceTx(tx, {
13798
+ workspaceId: input.workspaceId,
13799
+ sessionId: input.sessionId,
13800
+ turnId: input.turnId,
13801
+ executionGeneration: input.executionGeneration,
13802
+ attemptId: input.attemptId
13803
+ });
13804
+ if (!fence.allowed) {
13805
+ return { reserved: false, reason: fence.reason };
13806
+ }
13807
+ if (fence.turn.status !== "running") {
13808
+ return { reserved: false, reason: "turn_terminal" };
13809
+ }
13810
+ if (Number(fence.turn.toolspaceCallCount) >= input.limit) {
13811
+ return { reserved: false, reason: "budget_exhausted" };
13812
+ }
13813
+ const [row] = await tx.update(sessionTurns).set({
13814
+ toolspaceCallCount: sql4`${sessionTurns.toolspaceCallCount} + 1`
13815
+ }).where(
13816
+ and5(
13817
+ eq5(sessionTurns.workspaceId, input.workspaceId),
13818
+ eq5(sessionTurns.sessionId, input.sessionId),
13819
+ eq5(sessionTurns.id, input.turnId),
13820
+ eq5(sessionTurns.executionGeneration, input.executionGeneration),
13821
+ eq5(sessionTurns.activeAttemptId, input.attemptId),
13822
+ sql4`${sessionTurns.toolspaceCallCount} < ${input.limit}`
13823
+ )
13824
+ ).returning({ count: sessionTurns.toolspaceCallCount });
13825
+ if (!row) {
13826
+ throw new Error("Toolspace call reservation lost its locked turn");
13827
+ }
13828
+ return {
13829
+ reserved: true,
13830
+ count: Number(row.count),
13831
+ turn: mapSessionTurnForExecution({
13832
+ ...fence.turn,
13833
+ toolspaceCallCount: Number(row.count)
13834
+ })
13835
+ };
13836
+ })
13837
+ );
13838
+ }
12924
13839
  function normalizeEventSequence(value, fallback) {
12925
13840
  if (value === void 0 || !Number.isFinite(value)) {
12926
13841
  return fallback;
@@ -13441,6 +14356,56 @@ async function lockTurnAttemptWriteFenceTx(tx, input) {
13441
14356
  }
13442
14357
  return { allowed: true, workspace, session, turn, attempt };
13443
14358
  }
14359
+ async function installOrReadTurnExecutionPolicyForAttempt(db, input) {
14360
+ return await withRlsContext(
14361
+ db,
14362
+ { accountId: input.accountId, workspaceId: input.workspaceId },
14363
+ async (scopedDb) => await scopedDb.transaction(async (tx) => {
14364
+ const fence = await lockTurnAttemptWriteFenceTx(tx, {
14365
+ workspaceId: input.workspaceId,
14366
+ sessionId: input.sessionId,
14367
+ turnId: input.turnId,
14368
+ executionGeneration: input.executionGeneration,
14369
+ attemptId: input.attemptId
14370
+ });
14371
+ if (!fence.allowed) {
14372
+ return { accepted: false, reason: fence.reason };
14373
+ }
14374
+ const existing = readTurnExecutionPolicyV1(fence.turn.metadata);
14375
+ if (existing.kind === "valid") {
14376
+ return {
14377
+ accepted: true,
14378
+ installed: false,
14379
+ policy: existing.policy,
14380
+ turn: mapSessionTurn(fence.turn)
14381
+ };
14382
+ }
14383
+ const policy = TurnExecutionPolicyV1.parse(input.policyForAbsent);
14384
+ const [updated] = await tx.update(sessionTurns).set({
14385
+ metadata: metadataWithTurnExecutionPolicyV12(fence.turn.metadata, policy),
14386
+ updatedAt: /* @__PURE__ */ new Date()
14387
+ }).where(
14388
+ and5(
14389
+ eq5(sessionTurns.workspaceId, input.workspaceId),
14390
+ eq5(sessionTurns.sessionId, input.sessionId),
14391
+ eq5(sessionTurns.id, input.turnId),
14392
+ eq5(sessionTurns.status, "running"),
14393
+ eq5(sessionTurns.executionGeneration, input.executionGeneration),
14394
+ eq5(sessionTurns.activeAttemptId, input.attemptId)
14395
+ )
14396
+ ).returning();
14397
+ if (!updated) {
14398
+ throw new Error("Turn execution policy owner changed while its row was locked");
14399
+ }
14400
+ return {
14401
+ accepted: true,
14402
+ installed: true,
14403
+ policy,
14404
+ turn: mapSessionTurn(updated)
14405
+ };
14406
+ })
14407
+ );
14408
+ }
13444
14409
  async function appendSessionHistoryItems(db, input) {
13445
14410
  if (input.items.length === 0) {
13446
14411
  return true;
@@ -13519,6 +14484,33 @@ async function registerPendingSessionToolCall(db, input) {
13519
14484
  })
13520
14485
  );
13521
14486
  }
14487
+ async function clearPendingSessionToolspaceCall(db, input) {
14488
+ return await withRlsContext(
14489
+ db,
14490
+ { accountId: input.accountId, workspaceId: input.workspaceId },
14491
+ async (scopedDb) => await scopedDb.transaction(async (tx) => {
14492
+ const fence = await lockTurnAttemptWriteFenceTx(tx, {
14493
+ workspaceId: input.workspaceId,
14494
+ sessionId: input.sessionId,
14495
+ turnId: input.turnId,
14496
+ executionGeneration: input.executionGeneration,
14497
+ attemptId: input.attemptId
14498
+ });
14499
+ if (!fence.allowed) return { accepted: false, cleared: false };
14500
+ const deleted = await tx.delete(sessionPendingToolCalls).where(
14501
+ and5(
14502
+ eq5(sessionPendingToolCalls.workspaceId, input.workspaceId),
14503
+ eq5(sessionPendingToolCalls.sessionId, input.sessionId),
14504
+ eq5(sessionPendingToolCalls.turnId, input.turnId),
14505
+ eq5(sessionPendingToolCalls.attemptId, input.attemptId),
14506
+ eq5(sessionPendingToolCalls.callId, input.callId),
14507
+ eq5(sessionPendingToolCalls.callType, "toolspace_call")
14508
+ )
14509
+ ).returning({ id: sessionPendingToolCalls.id });
14510
+ return { accepted: true, cleared: deleted.length === 1 };
14511
+ })
14512
+ );
14513
+ }
13522
14514
  async function recordPendingSessionToolCallResult(db, input) {
13523
14515
  return await withRlsContext(
13524
14516
  db,
@@ -14385,13 +15377,17 @@ async function acquireLease(db, input) {
14385
15377
  on conflict (workspace_id, sandbox_group_id) do nothing
14386
15378
  `);
14387
15379
  const rows = await tx.execute(sql4`
14388
- select * from sandbox_leases
15380
+ select *, (liveness = 'draining' and expires_at <= now()) as draining_expired
15381
+ from sandbox_leases
14389
15382
  where workspace_id = ${workspaceId} and sandbox_group_id = ${sandboxGroupId}
14390
15383
  for update
14391
15384
  `);
14392
15385
  const row = rows[0];
14393
15386
  if (!row) throw new Error(`Lease row vanished post-insert: ${sandboxGroupId}`);
14394
15387
  let liveness = row.liveness;
15388
+ if (liveness === "draining" && row.draining_expired) {
15389
+ return { role: "fenced", lease: mapLeaseRow(row) };
15390
+ }
14395
15391
  const imageConflict = image !== null && row.image !== null && row.image !== image;
14396
15392
  const rigConflict = rigVersionId !== null && row.rig_version_id !== null && row.rig_version_id !== rigVersionId;
14397
15393
  if (liveness !== "cold" && (imageConflict || rigConflict)) {
@@ -14570,6 +15566,7 @@ async function failWarmingToCold(db, input) {
14570
15566
  update sandbox_leases set
14571
15567
  liveness = 'cold', instance_id = null,
14572
15568
  data_plane_url = null, terminal_data_plane_url = null, updated_at = now(),
15569
+ lease_epoch = lease_epoch + 1,
14573
15570
  resume_state = case
14574
15571
  when (resume_state #>> '{sessionState,workspaceArchive}') is not null
14575
15572
  then jsonb_build_object(
@@ -14719,7 +15716,8 @@ async function reapStaleLeaseHolders(db, input) {
14719
15716
  update sandbox_leases set
14720
15717
  liveness = 'cold', instance_id = null,
14721
15718
  resume_backend_id = null, resume_state = null,
14722
- data_plane_url = null, terminal_data_plane_url = null, updated_at = now()
15719
+ data_plane_url = null, terminal_data_plane_url = null,
15720
+ lease_epoch = lease_epoch + 1, updated_at = now()
14723
15721
  where workspace_id = ${input.workspaceId}
14724
15722
  and liveness = 'warming' and expires_at < now() and instance_id is null
14725
15723
  returning id
@@ -14732,6 +15730,7 @@ async function reapStaleLeaseHolders(db, input) {
14732
15730
  viewer_holders = 0,
14733
15731
  data_plane_url = null,
14734
15732
  terminal_data_plane_url = null,
15733
+ lease_epoch = lease_epoch + 1,
14735
15734
  expires_at = now() - interval '1 millisecond',
14736
15735
  updated_at = now()
14737
15736
  where workspace_id = ${input.workspaceId}
@@ -14882,6 +15881,7 @@ async function reArmDrainingLease(db, input) {
14882
15881
  updated_at = now()
14883
15882
  where workspace_id = ${input.workspaceId} and sandbox_group_id = ${input.sandboxGroupId}
14884
15883
  and liveness = 'draining'
15884
+ and expires_at > now()
14885
15885
  returning id
14886
15886
  `);
14887
15887
  return { rearmed: rows.length > 0 };
@@ -16998,6 +17998,7 @@ async function initializeSessionStartAtomically(db, input) {
16998
17998
  turnInstructions: session.initialTurnInstructions ?? null,
16999
17999
  resources: session.resources,
17000
18000
  tools: session.tools,
18001
+ toolsProvided: session.toolPolicy?.mode === "explicit",
17001
18002
  model: session.model,
17002
18003
  reasoningEffort: reasoningEffortForMetadata(
17003
18004
  session.metadata,
@@ -17005,7 +18006,7 @@ async function initializeSessionStartAtomically(db, input) {
17005
18006
  ),
17006
18007
  sandboxBackend: session.sandboxBackend,
17007
18008
  sandboxOs: session.sandboxOs,
17008
- metadata: {},
18009
+ metadata: input.turnExecutionPolicy ? metadataWithTurnExecutionPolicyV12({}, input.turnExecutionPolicy) : {},
17009
18010
  lineage: {},
17010
18011
  ...initiatorColumns(creator)
17011
18012
  }).returning();
@@ -17114,6 +18115,7 @@ async function enqueueSessionTurn(db, input) {
17114
18115
  turnInstructions: input.turnInstructions ?? null,
17115
18116
  resources: input.resources,
17116
18117
  tools: input.tools,
18118
+ toolsProvided: input.toolsProvided ?? false,
17117
18119
  model: input.model,
17118
18120
  reasoningEffort: input.reasoningEffort,
17119
18121
  sandboxBackend: input.sandboxBackend,
@@ -17325,18 +18327,33 @@ async function claimSessionWorkForAttempt(db, workspaceId, input) {
17325
18327
  if (unquiescedInterruption) {
17326
18328
  return { action: "unclaimed", reason: "control-pending" };
17327
18329
  }
17328
- const registerAttempt = async (turn) => await registerSessionTurnAttemptClaim(tx, {
17329
- id: input.attemptId,
17330
- accountId: session.accountId,
17331
- workspaceId,
17332
- sessionId,
17333
- turnId: turn.id,
17334
- executionGeneration: turn.executionGeneration,
17335
- temporalWorkflowId: workflowId,
17336
- temporalWorkflowRunId: input.workflowRunId,
17337
- temporalActivityId: input.dispatchId,
17338
- verifiedControlRevision: Number(workspaceControl.revision)
17339
- });
18330
+ const registerAttempt = async (turn) => {
18331
+ const policyRows = await tx.select({
18332
+ serverId: sessionMcpServers.serverId,
18333
+ requireApproval: sessionMcpServers.requireApproval
18334
+ }).from(sessionMcpServers).where(
18335
+ and5(
18336
+ eq5(sessionMcpServers.workspaceId, workspaceId),
18337
+ eq5(sessionMcpServers.sessionId, sessionId)
18338
+ )
18339
+ ).orderBy(asc3(sessionMcpServers.serverId));
18340
+ const mcpApprovalPolicies = Object.fromEntries(
18341
+ policyRows.map((row2) => [row2.serverId, row2.requireApproval ?? false])
18342
+ );
18343
+ return await registerSessionTurnAttemptClaim(tx, {
18344
+ id: input.attemptId,
18345
+ accountId: session.accountId,
18346
+ workspaceId,
18347
+ sessionId,
18348
+ turnId: turn.id,
18349
+ executionGeneration: turn.executionGeneration,
18350
+ temporalWorkflowId: workflowId,
18351
+ temporalWorkflowRunId: input.workflowRunId,
18352
+ temporalActivityId: input.dispatchId,
18353
+ verifiedControlRevision: Number(workspaceControl.revision),
18354
+ mcpApprovalPolicies
18355
+ });
18356
+ };
17340
18357
  if (session.activeTurnId !== null) {
17341
18358
  const [activeTurnPreview] = await tx.select().from(sessionTurns).where(
17342
18359
  and5(
@@ -19083,7 +20100,8 @@ async function applySessionTurnSettlement(db, workspaceId, input) {
19083
20100
  sequence: ++sequence,
19084
20101
  type: event.type,
19085
20102
  payload: sanitizeEventPayload(
19086
- event.type === "session.status.changed" && payload.status === input.sessionStatus && effectiveSessionStatus !== input.sessionStatus ? { ...payload, status: effectiveSessionStatus } : payload
20103
+ event.type === "session.status.changed" && payload.status === input.sessionStatus && effectiveSessionStatus !== input.sessionStatus ? { ...payload, status: effectiveSessionStatus } : payload,
20104
+ { fullEvidence: event.retainedOutputEvidence }
19087
20105
  ),
19088
20106
  clientEventId: event.clientEventId ?? null,
19089
20107
  turnId: input.turnId,
@@ -19876,6 +20894,41 @@ async function getSessionTurn(db, workspaceId, turnId) {
19876
20894
  return row ? mapSessionTurn(row) : null;
19877
20895
  });
19878
20896
  }
20897
+ async function getActiveSessionTurnForExecution(db, workspaceId, sessionId) {
20898
+ return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
20899
+ const [row] = await scopedDb.select({ turn: sessionTurns }).from(sessions).innerJoin(
20900
+ sessionTurns,
20901
+ and5(
20902
+ eq5(sessionTurns.workspaceId, sessions.workspaceId),
20903
+ eq5(sessionTurns.id, sessions.activeTurnId)
20904
+ )
20905
+ ).innerJoin(
20906
+ sessionTurnAttempts,
20907
+ and5(
20908
+ eq5(sessionTurnAttempts.workspaceId, sessionTurns.workspaceId),
20909
+ eq5(sessionTurnAttempts.id, sessionTurns.activeAttemptId)
20910
+ )
20911
+ ).where(
20912
+ and5(
20913
+ eq5(sessions.workspaceId, workspaceId),
20914
+ eq5(sessions.id, sessionId),
20915
+ eq5(sessionTurns.sessionId, sessionId),
20916
+ eq5(sessionTurnAttempts.sessionId, sessionId),
20917
+ eq5(sessionTurnAttempts.turnId, sessionTurns.id),
20918
+ inArray3(sessionTurnAttempts.state, ["claimed", "running"]),
20919
+ inArray3(sessionTurns.status, ["running", "recovering", "waiting_capacity"]),
20920
+ sql4`not exists (
20921
+ select 1
20922
+ from ${sessionAttemptInterruptions} interruption
20923
+ where interruption.workspace_id = ${workspaceId}
20924
+ and interruption.attempt_id = ${sessionTurnAttempts.id}
20925
+ and interruption.state in ('pending', 'delivered', 'acknowledged')
20926
+ )`
20927
+ )
20928
+ ).limit(1);
20929
+ return row ? mapSessionTurnForExecution(row.turn) : null;
20930
+ });
20931
+ }
19879
20932
  async function getSessionTurnForAttempt(db, workspaceId, sessionId, attemptId) {
19880
20933
  return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
19881
20934
  const [row] = await scopedDb.select({ turn: sessionTurns }).from(sessionTurnAttempts).innerJoin(
@@ -20146,11 +21199,37 @@ async function claimPendingSessionWorkflowWakes(db, limit = 100) {
20146
21199
  }));
20147
21200
  }
20148
21201
  async function markSessionWorkflowWakeDelivered(db, input) {
20149
- await withRlsContext(
21202
+ return await withRlsContext(
20150
21203
  db,
20151
21204
  { accountId: input.accountId, workspaceId: input.workspaceId },
20152
- async (scopedDb) => {
20153
- const [row] = await scopedDb.update(sessionWorkflowWakeOutbox).set({
21205
+ async (scopedDb) => await scopedDb.transaction(async (tx) => {
21206
+ const locks = await lockSessionEventWriteRows(tx, {
21207
+ workspaceId: input.workspaceId,
21208
+ controlLock: "share",
21209
+ sessionIds: [input.sessionId]
21210
+ });
21211
+ const session = locks.sessions[0];
21212
+ if (!session) throw new Error(`Session not found: ${input.sessionId}`);
21213
+ const effectiveControl = await evaluateSessionControl(
21214
+ tx,
21215
+ input.workspaceId,
21216
+ input.sessionId,
21217
+ { workspaceControl: locks.control ?? void 0 }
21218
+ );
21219
+ if (effectiveControl.state === "active") {
21220
+ const [pendingAgentSteer] = await tx.select({ id: sessionSystemUpdates.id }).from(sessionSystemUpdates).where(
21221
+ and5(
21222
+ eq5(sessionSystemUpdates.workspaceId, input.workspaceId),
21223
+ eq5(sessionSystemUpdates.sessionId, input.sessionId),
21224
+ eq5(sessionSystemUpdates.kind, "agent_steer_instruction"),
21225
+ eq5(sessionSystemUpdates.state, "pending")
21226
+ )
21227
+ ).limit(1);
21228
+ if (pendingAgentSteer) {
21229
+ return { action: "pending_admission", blocker: "pending_agent_steer" };
21230
+ }
21231
+ }
21232
+ const [row] = await tx.update(sessionWorkflowWakeOutbox).set({
20154
21233
  deliveredRevision: sql4`greatest(${sessionWorkflowWakeOutbox.deliveredRevision}, ${input.wakeRevision})`,
20155
21234
  attempts: sql4`case when ${sessionWorkflowWakeOutbox.wakeRevision} = ${input.wakeRevision} then 0 else ${sessionWorkflowWakeOutbox.attempts} end`,
20156
21235
  lastError: sql4`case when ${sessionWorkflowWakeOutbox.wakeRevision} = ${input.wakeRevision} then null else ${sessionWorkflowWakeOutbox.lastError} end`,
@@ -20167,7 +21246,8 @@ async function markSessionWorkflowWakeDelivered(db, input) {
20167
21246
  `Workflow wake revision ${input.wakeRevision} is not current for session ${input.sessionId}`
20168
21247
  );
20169
21248
  }
20170
- }
21249
+ return { action: "acknowledged" };
21250
+ })
20171
21251
  );
20172
21252
  }
20173
21253
  async function markSessionWorkflowWakeFailed(db, input, error) {
@@ -20528,7 +21608,9 @@ async function appendSessionEvents(db, workspaceId, sessionId, inputs) {
20528
21608
  sessionId,
20529
21609
  sequence: ++sequence,
20530
21610
  type: input.type,
20531
- payload: sanitizeEventPayload(input.payload ?? {}),
21611
+ payload: sanitizeEventPayload(input.payload ?? {}, {
21612
+ fullEvidence: input.retainedOutputEvidence
21613
+ }),
20532
21614
  clientEventId: input.clientEventId ?? null,
20533
21615
  turnId: input.turnId ?? null,
20534
21616
  turnGeneration: input.turnGeneration ?? null,
@@ -20780,7 +21862,9 @@ async function appendSessionEventsForTurnAttempt(db, workspaceId, sessionId, tur
20780
21862
  sessionId,
20781
21863
  sequence: ++sequence,
20782
21864
  type: input.type,
20783
- payload: sanitizeEventPayload(input.payload ?? {}),
21865
+ payload: sanitizeEventPayload(input.payload ?? {}, {
21866
+ fullEvidence: input.retainedOutputEvidence
21867
+ }),
20784
21868
  clientEventId: input.clientEventId ?? null,
20785
21869
  turnId,
20786
21870
  turnGeneration: executionGeneration,
@@ -20838,7 +21922,9 @@ async function appendSessionEventToSandboxGroup(db, workspaceId, sandboxGroupId,
20838
21922
  sessionId: row.id,
20839
21923
  sequence: row.lastSequence + 1,
20840
21924
  type: input.type,
20841
- payload: sanitizeEventPayload(input.payload ?? {}),
21925
+ payload: sanitizeEventPayload(input.payload ?? {}, {
21926
+ fullEvidence: input.retainedOutputEvidence
21927
+ }),
20842
21928
  clientEventId: input.clientEventId ?? null,
20843
21929
  turnId: input.turnId ?? null,
20844
21930
  turnGeneration: input.turnGeneration ?? null,
@@ -20893,7 +21979,9 @@ async function appendSessionEventsAndUpdateSession(db, workspaceId, sessionId, i
20893
21979
  sessionId,
20894
21980
  sequence: ++sequence,
20895
21981
  type: input.type,
20896
- payload: sanitizeEventPayload(input.payload ?? {}),
21982
+ payload: sanitizeEventPayload(input.payload ?? {}, {
21983
+ fullEvidence: input.retainedOutputEvidence
21984
+ }),
20897
21985
  clientEventId: input.clientEventId ?? null,
20898
21986
  turnId: input.turnId ?? null,
20899
21987
  turnGeneration: input.turnGeneration ?? null,
@@ -20946,6 +22034,12 @@ async function appendSessionEventsWithLockedSessionUpdate(db, workspaceId, sessi
20946
22034
  sessionId,
20947
22035
  updates
20948
22036
  }),
22037
+ updateSessionMcpApprovalPolicy: async (serverId, requireApproval) => await updateSessionMcpApprovalPolicyInTransaction(tx, {
22038
+ workspaceId,
22039
+ sessionId,
22040
+ serverId,
22041
+ requireApproval
22042
+ }),
20949
22043
  listPendingSessionTurns: async () => {
20950
22044
  const rows = await tx.select().from(sessionTurns).where(
20951
22045
  and5(
@@ -20975,7 +22069,9 @@ async function appendSessionEventsWithLockedSessionUpdate(db, workspaceId, sessi
20975
22069
  sessionId,
20976
22070
  sequence: ++sequence,
20977
22071
  type: input.type,
20978
- payload: sanitizeEventPayload(input.payload ?? {}),
22072
+ payload: sanitizeEventPayload(input.payload ?? {}, {
22073
+ fullEvidence: input.retainedOutputEvidence
22074
+ }),
20979
22075
  clientEventId: input.clientEventId ?? null,
20980
22076
  turnId: input.turnId ?? null,
20981
22077
  turnGeneration: input.turnGeneration ?? null,
@@ -21034,6 +22130,10 @@ function mapSession(row, effectiveControl, mcpServers = [], pin = mapSessionPin(
21034
22130
  instructions: row.instructions ?? null,
21035
22131
  resources: row.resources,
21036
22132
  tools: row.tools,
22133
+ toolPolicy: row.toolPolicy ?? {
22134
+ mode: "legacy",
22135
+ inheritedFromSessionId: null
22136
+ },
21037
22137
  metadata: row.metadata,
21038
22138
  createdBy: initiatorFromStorage(
21039
22139
  row.createdByKind,
@@ -21127,6 +22227,7 @@ function mapSessionTurn(row) {
21127
22227
  prompt: row.prompt,
21128
22228
  resources: row.resources,
21129
22229
  tools: row.tools,
22230
+ toolsProvided: row.toolsProvided,
21130
22231
  model: row.model,
21131
22232
  reasoningEffort: row.reasoningEffort,
21132
22233
  sandboxBackend: row.sandboxBackend,
@@ -21277,15 +22378,50 @@ function mapImportBatch(row) {
21277
22378
  updatedAt: row.updatedAt.toISOString()
21278
22379
  };
21279
22380
  }
21280
- function mapCapabilityCatalogItem(row) {
22381
+ function catalogExposureState(item, installation) {
22382
+ if (capabilityCatalogItemIsTrustedForExposure({
22383
+ source: item.source,
22384
+ stale: item.stale,
22385
+ authKind: item.authKind,
22386
+ metadata: item.metadata
22387
+ })) {
22388
+ return "trusted";
22389
+ }
22390
+ if (item.source !== registryCapabilitySource || item.stale || Object.prototype.hasOwnProperty.call(item.metadata, "mcpProbe") || !item.authKind || item.authKind === "unknown" || installation?.status !== "active" || !mcpConnectivityOk(installation.metadata)) {
22391
+ return "blocked";
22392
+ }
22393
+ const hasCredentialBinding = !!encryptedHeadersConfig(installation.config.headersEncrypted) || !!connectionRefConfig(installation.config.connectionRef);
22394
+ if (item.authModel && !hasCredentialBinding) {
22395
+ return "blocked";
22396
+ }
22397
+ return "legacy_active";
22398
+ }
22399
+ function mapCapabilityCatalogItem(row, exposure = capabilityCatalogItemIsTrustedForExposure({
22400
+ source: row.source,
22401
+ stale: row.stale,
22402
+ authKind: row.authKind,
22403
+ metadata: row.metadata
22404
+ }) ? "trusted" : "unverified") {
22405
+ const catalogTrust = exposure === "legacy_active" ? {
22406
+ state: "legacy_active",
22407
+ reason: "active_installation_compatibility"
22408
+ } : exposure === "trusted" ? {
22409
+ state: "trusted",
22410
+ reason: row.source === registryCapabilitySource ? "verified_probe" : "trusted_source"
22411
+ } : {
22412
+ state: "unverified",
22413
+ reason: "missing_verification"
22414
+ };
21281
22415
  const runtime = row.kind === "mcp" && row.endpointUrl ? {
21282
22416
  available: true,
21283
22417
  mcpServerId: mcpServerIdForCapability(row.id, row.metadata),
21284
22418
  transport: row.transport ?? "streamable-http",
21285
- notes: row.authModel ? "Requires credential headers supplied in the enable request." : null
22419
+ notes: row.authModel ? "Requires credential headers supplied in the enable request." : null,
22420
+ catalogTrust
21286
22421
  } : {
21287
22422
  available: false,
21288
- notes: row.kind === "mcp" ? "Remote streamable HTTP endpoint is required for runtime use." : null
22423
+ notes: row.kind === "mcp" ? "Remote streamable HTTP endpoint is required for runtime use." : null,
22424
+ catalogTrust
21289
22425
  };
21290
22426
  return {
21291
22427
  id: row.id,
@@ -21628,6 +22764,7 @@ export {
21628
22764
  CODEX_CAPACITY_REFRESH_MAX_MS,
21629
22765
  CODEX_CAPACITY_REFRESH_MIN_MS,
21630
22766
  CODEX_CREDENTIAL_LEASE_TTL_MS,
22767
+ CODEX_RESET_REDEMPTION_OUTCOMES,
21631
22768
  CODEX_ROTATION_STRATEGIES,
21632
22769
  ConnectionRefreshHttpError,
21633
22770
  HostExportPayloadError,
@@ -21678,6 +22815,7 @@ export {
21678
22815
  WORKSPACE_MEMORY_BLOCK_EMPTY,
21679
22816
  WORKSPACE_MEMORY_BLOCK_HEADER_POPULATED,
21680
22817
  WORKSPACE_MEMORY_BLOCK_TOKEN_BUDGET,
22818
+ abandonCodexResetRedemptionBeforeProvider,
21681
22819
  abandonRecordingForTurnAttempt,
21682
22820
  acceptSessionApprovalDecision,
21683
22821
  acceptSessionHumanInputResponse,
@@ -21688,6 +22826,8 @@ export {
21688
22826
  activateRigVersion,
21689
22827
  addSessionSystemUpdate,
21690
22828
  addSessionSystemUpdateWithSourceMutation,
22829
+ admitToolspaceTurnAttempt,
22830
+ adoptCodexResetRedemptionAttempt,
21691
22831
  allAccountPermissions,
21692
22832
  allWorkspacePermissions,
21693
22833
  appendSessionEventToSandboxGroup,
@@ -21713,6 +22853,7 @@ export {
21713
22853
  buildConnectionTokenResolver,
21714
22854
  buildHostConnectionTokenResolver,
21715
22855
  canonicalSessionCommandHash,
22856
+ claimCodexResetRedemption,
21716
22857
  claimExpiredFileUploadCleanup,
21717
22858
  claimFileUploadCleanup,
21718
22859
  claimHostExportBatch,
@@ -21721,6 +22862,7 @@ export {
21721
22862
  claimSessionWorkForAttempt,
21722
22863
  clearDurablePendingSessionToolCalls,
21723
22864
  clearEnrollmentWentOffline,
22865
+ clearPendingSessionToolspaceCall,
21724
22866
  clearSessionContext,
21725
22867
  clearSessionGoal,
21726
22868
  clearedContextMarkerItem,
@@ -21728,6 +22870,7 @@ export {
21728
22870
  closeSessionTurnAttemptInTransaction,
21729
22871
  codexCapacityRefreshBackoffMs,
21730
22872
  commitWarmingToWarm,
22873
+ completeCodexResetRedemption,
21731
22874
  completeExpiredFileUploadCleanup,
21732
22875
  completeFileUpload,
21733
22876
  completeFileUploadCleanup,
@@ -21822,12 +22965,15 @@ export {
21822
22965
  expireSessionHumanInputRequest,
21823
22966
  failHostExportBatch,
21824
22967
  failWarmingToCold,
22968
+ fenceCodexResetRedemptionSend,
22969
+ fetchCodexRateLimitResetCreditsForAccount,
21825
22970
  fetchCodexUsageForAccount,
21826
22971
  finalizeEnrollmentByToken,
21827
22972
  findActiveApiKeyByHash,
21828
22973
  forceDrainOverLimitViewerOnlyBoxes,
21829
22974
  frozenInitiatorForCommandActor,
21830
22975
  getActiveSessionHistoryItems,
22976
+ getActiveSessionTurnForExecution,
21831
22977
  getAnySessionInGroup,
21832
22978
  getBillingBalance,
21833
22979
  getBillingCustomer,
@@ -21835,6 +22981,7 @@ export {
21835
22981
  getCapabilityInstallation,
21836
22982
  getCodexCapacityWaitForSession,
21837
22983
  getCodexCredentialStatus,
22984
+ getCodexResetRedemptionAttempt,
21838
22985
  getCodexRotationSettings,
21839
22986
  getComposerDraftInTransaction,
21840
22987
  getConnectionMetadata,
@@ -21856,6 +23003,7 @@ export {
21856
23003
  getPendingDeviceEnrollmentRequestByUserCode,
21857
23004
  getPendingDeviceEnrollmentRequestByUserCodeGlobal,
21858
23005
  getRecording,
23006
+ getRetainedFileArtifact,
21859
23007
  getRig,
21860
23008
  getRigByName,
21861
23009
  getRigChange,
@@ -21908,6 +23056,7 @@ export {
21908
23056
  insertPtySession,
21909
23057
  insertRecording,
21910
23058
  insertWorkspaceCapture,
23059
+ installOrReadTurnExecutionPolicyForAttempt,
21911
23060
  interruptedToolCallResult,
21912
23061
  isCodexBilledModel2 as isCodexBilledModel,
21913
23062
  isCodexBilledTurn,
@@ -21923,6 +23072,7 @@ export {
21923
23072
  listCapabilityCatalogItems,
21924
23073
  listCapabilityInstallations,
21925
23074
  listCodexAccountStatuses,
23075
+ listCodexResetRedemptionRecoveries,
21926
23076
  listConnectionsMetadata,
21927
23077
  listCreditBalancesByAccount,
21928
23078
  listDistinctRigVersionIdsInGroup,
@@ -22044,6 +23194,7 @@ export {
22044
23194
  registerSessionWorkflowWakeInTransaction,
22045
23195
  registerWorkspacePack,
22046
23196
  releaseCodexCredentialLease,
23197
+ releaseCodexResetRedemptionClaim,
22047
23198
  releaseLeaseHolder,
22048
23199
  removeWorkspaceMember,
22049
23200
  renameCodexAccount,
@@ -22057,7 +23208,7 @@ export {
22057
23208
  requireSocialConnection,
22058
23209
  requireWorkspace,
22059
23210
  reserveSessionCommandReceipt,
22060
- reserveToolspaceCallForTurn,
23211
+ reserveToolspaceCallForAttempt,
22061
23212
  resolveWorkspaceMemoryBlock,
22062
23213
  resumeHostExportConsumer,
22063
23214
  retireHostExportConsumer,
@@ -22120,6 +23271,7 @@ export {
22120
23271
  supersedeSessionCurrentDirectionInTransaction,
22121
23272
  touchEnrollmentLastSeen,
22122
23273
  touchLeaseHolder,
23274
+ updateCodexAllocatorEligibility,
22123
23275
  updateCodexRotationSettings,
22124
23276
  updateConnection,
22125
23277
  updateImportBatchCounts,
@@ -22133,6 +23285,7 @@ export {
22133
23285
  updateScheduledTaskRun,
22134
23286
  updateSessionCommandReceiptResult,
22135
23287
  updateSessionGoal,
23288
+ updateSessionMcpApprovalPolicy,
22136
23289
  updateSessionMcpServerCredentials,
22137
23290
  updateSessionTitle,
22138
23291
  updateVariableSet,
@@ -22152,6 +23305,7 @@ export {
22152
23305
  withAccountRls,
22153
23306
  withCodexCapacityMutation,
22154
23307
  withCodexCredentialRefreshLock,
23308
+ withCodexTokenDeadline,
22155
23309
  withRlsContext,
22156
23310
  withWorkspaceRls,
22157
23311
  withWorkspaceSubjectRls,