@opengeni/db 3.7.4 → 3.8.2-canary.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/dist/automations.d.ts +4 -0
  2. package/dist/canonical-human-identities.js +3 -3
  3. package/dist/{chunk-O56DM5GZ.js → chunk-53A2PEHT.js} +328 -31
  4. package/dist/chunk-53A2PEHT.js.map +1 -0
  5. package/dist/{chunk-SZAWDXQC.js → chunk-5S4BQAHX.js} +3 -3
  6. package/dist/{chunk-S3TDDCS2.js → chunk-CITYUGMW.js} +3 -3
  7. package/dist/{chunk-4NAR7FBA.js → chunk-F5YOZEYQ.js} +49 -1
  8. package/dist/chunk-F5YOZEYQ.js.map +1 -0
  9. package/dist/{chunk-F6PBS6WE.js → chunk-HZV7O6JR.js} +2 -2
  10. package/dist/{chunk-5NNCGFQY.js → chunk-RLQVPOVR.js} +2 -2
  11. package/dist/{chunk-DZCYW2WT.js → chunk-UKOGHUGC.js} +2 -2
  12. package/dist/{chunk-US4IS6IV.js → chunk-X6TO5HII.js} +2 -2
  13. package/dist/{chunk-U4WPPEBH.js → chunk-Y53WEFYB.js} +2 -2
  14. package/dist/connection-token-resolver.d.ts +4 -0
  15. package/dist/editable-artifact-durable-export.js +2 -2
  16. package/dist/editable-artifacts.js +3 -3
  17. package/dist/index.d.ts +164 -14
  18. package/dist/index.js +7327 -5442
  19. package/dist/index.js.map +1 -1
  20. package/dist/insights.d.ts +9 -0
  21. package/dist/managed-auth-session-sets.js +3 -3
  22. package/dist/model-catalog.d.ts +153 -0
  23. package/dist/new-session-drafts.d.ts +12 -0
  24. package/dist/organization-membership-lifecycle.d.ts +8 -1
  25. package/dist/organization-model-providers.d.ts +101 -0
  26. package/dist/pack-components.d.ts +1 -0
  27. package/dist/pr-review.d.ts +9 -0
  28. package/dist/provision-roles.js +1 -1
  29. package/dist/runtime-posture.d.ts +7 -7
  30. package/dist/schema.d.ts +989 -2
  31. package/dist/schema.js +11 -1
  32. package/dist/session-background-commands.js +3 -3
  33. package/dist/session-control.d.ts +35 -0
  34. package/dist/session-queue-commands.d.ts +17 -0
  35. package/dist/session-tenancy.js +3 -3
  36. package/dist/video-generation.js +3 -3
  37. package/dist/xai-subscription.d.ts +8 -0
  38. package/drizzle/0387_bound_host_export_session_payloads.sql +165 -0
  39. package/drizzle/0388_sandbox_provider_deadline_interactions.sql +491 -0
  40. package/drizzle/0389_model_catalog_and_gateway_custom_models.sql +241 -0
  41. package/drizzle/0390_organization_model_provider_connections.sql +197 -0
  42. package/drizzle/0391_sandbox_provider_deadline_interaction_followup.sql +295 -0
  43. package/drizzle/0392_context_compaction_pending_observability.sql +198 -0
  44. package/drizzle/0393_workspace_memory_and_learning_defaults.sql +102 -0
  45. package/drizzle/0394_session_selected_skill_activation.sql +20 -0
  46. package/drizzle/0395_scheduled_task_unclaimed_occurrence_invalidation.sql +154 -0
  47. package/drizzle/0396_model_call_equivalent_credit_cost.sql +28 -0
  48. package/drizzle/0397_sandbox_deadline_rotation_preemption.sql +113 -0
  49. package/drizzle/0398_organization_workspace_management_entry.sql +206 -0
  50. package/drizzle/0399_additional_managed_organization_creation.sql +691 -0
  51. package/package.json +6 -6
  52. package/src/automations.ts +9 -1
  53. package/src/connection-token-resolver.ts +12 -0
  54. package/src/index.ts +1551 -272
  55. package/src/insights-model-bundle.ts +54 -5
  56. package/src/insights.ts +53 -7
  57. package/src/model-catalog.ts +682 -0
  58. package/src/new-session-drafts.ts +66 -21
  59. package/src/organization-membership-lifecycle.ts +47 -5
  60. package/src/organization-model-providers.ts +614 -0
  61. package/src/pack-components.ts +15 -2
  62. package/src/pr-review.ts +19 -0
  63. package/src/provision-roles.ts +2 -0
  64. package/src/runtime-posture.ts +48 -0
  65. package/src/schema.ts +348 -32
  66. package/src/session-control.ts +170 -0
  67. package/src/session-queue-commands.ts +157 -82
  68. package/src/xai-subscription.ts +66 -14
  69. package/dist/chunk-4NAR7FBA.js.map +0 -1
  70. package/dist/chunk-O56DM5GZ.js.map +0 -1
  71. /package/dist/{chunk-SZAWDXQC.js.map → chunk-5S4BQAHX.js.map} +0 -0
  72. /package/dist/{chunk-S3TDDCS2.js.map → chunk-CITYUGMW.js.map} +0 -0
  73. /package/dist/{chunk-F6PBS6WE.js.map → chunk-HZV7O6JR.js.map} +0 -0
  74. /package/dist/{chunk-5NNCGFQY.js.map → chunk-RLQVPOVR.js.map} +0 -0
  75. /package/dist/{chunk-DZCYW2WT.js.map → chunk-UKOGHUGC.js.map} +0 -0
  76. /package/dist/{chunk-US4IS6IV.js.map → chunk-X6TO5HII.js.map} +0 -0
  77. /package/dist/{chunk-U4WPPEBH.js.map → chunk-Y53WEFYB.js.map} +0 -0
package/src/index.ts CHANGED
@@ -217,8 +217,12 @@ import {
217
217
  } from "@opengeni/contracts";
218
218
 
219
219
  import {
220
+ approximateSessionEventTokens,
220
221
  approvalIdentifier,
222
+ boundSessionEvent,
221
223
  boundWorkspaceControlEvent,
224
+ sessionEventJsonBytes,
225
+ sessionEventPayloadTruncation,
222
226
  workspaceControlUtf8Bytes,
223
227
  WORKSPACE_STATE_MEMORY_SAMPLE_LIMIT,
224
228
  SESSION_EVENT_RAW_DELTA_TYPES,
@@ -251,9 +255,17 @@ import {
251
255
  HumanInputQuestion as HumanInputQuestionContract,
252
256
  SubmitHumanInputResponseRequest,
253
257
  TurnExecutionPolicyV1,
258
+ OPENROUTER_CREDENTIAL_OPERATION_DIGEST_METADATA_KEY,
259
+ OPENROUTER_CREDENTIAL_OPERATION_ID_METADATA_KEY,
260
+ VERCEL_AI_GATEWAY_CREDENTIAL_OPERATION_DIGEST_METADATA_KEY,
261
+ VERCEL_AI_GATEWAY_CREDENTIAL_OPERATION_ID_METADATA_KEY,
254
262
  } from "@opengeni/contracts";
255
263
  import {
256
264
  environmentsEncryptionKeyBytes,
265
+ SANDBOX_LIFECYCLE_RETRY_HANDOFF_GRACE_MS,
266
+ SANDBOX_LIFECYCLE_TRANSITION_MAX_WAIT_MS,
267
+ WORKSPACE_OPENROUTER_CONNECTION_DOMAIN,
268
+ WORKSPACE_OPENROUTER_CONNECTION_ROLE,
257
269
  VERCEL_AI_GATEWAY_CONNECTION_DOMAIN,
258
270
  VERCEL_AI_GATEWAY_CONNECTION_ROLE,
259
271
  type Settings,
@@ -343,6 +355,7 @@ import {
343
355
  lockSessionEventWriteRows,
344
356
  lockWorkspaceInferenceControl,
345
357
  registerInternalUpdateWakeInTransaction,
358
+ registerSessionWorkflowWakeInTransaction,
346
359
  SESSION_DISCOVERY_CONTROL_TITLE_MAX_CHARS,
347
360
  assertSessionAuthoritySnapshot,
348
361
  registerSessionTurnAttemptClaim,
@@ -509,6 +522,7 @@ export {
509
522
  } from "./provision-roles";
510
523
  // Workspace Memory V1 pure domain surface (gates, render, canonical prompt text).
511
524
  export * from "./memory-domain";
525
+ export * from "./model-catalog";
512
526
 
513
527
  import {
514
528
  dbBindingFor,
@@ -3142,6 +3156,24 @@ async function lockBackgroundCommandWorkspaceLifecycle(
3142
3156
  }
3143
3157
  }
3144
3158
 
3159
+ async function authorizeOrganizationWorkspaceDeletion(
3160
+ tx: Database,
3161
+ input: {
3162
+ accountId: string;
3163
+ workspaceId: string;
3164
+ subjectId: string;
3165
+ },
3166
+ ): Promise<void> {
3167
+ await setSubjectRlsContext(tx, input.subjectId);
3168
+ await tx.execute(sql`
3169
+ select authorize_organization_shared_workspace_administration(
3170
+ ${input.accountId}::uuid,
3171
+ ${input.workspaceId}::uuid,
3172
+ ${input.subjectId}
3173
+ )
3174
+ `);
3175
+ }
3176
+
3145
3177
  /**
3146
3178
  * Atomically prove a workspace has no live runtime ownership and delete it.
3147
3179
  *
@@ -3153,10 +3185,33 @@ async function lockBackgroundCommandWorkspaceLifecycle(
3153
3185
  */
3154
3186
  export async function deleteWorkspaceIfQuiescent(
3155
3187
  db: Database,
3156
- input: { accountId: string; workspaceId: string; observer?: WorkspaceDeleteObserver },
3188
+ input: {
3189
+ accountId: string;
3190
+ workspaceId: string;
3191
+ observer?: WorkspaceDeleteObserver;
3192
+ organizationAdministratorSubjectId?: string;
3193
+ },
3157
3194
  ): Promise<DeleteWorkspaceIfQuiescentResult> {
3158
3195
  const transactionStartedAt = performance.now();
3159
3196
  try {
3197
+ if (input.organizationAdministratorSubjectId) {
3198
+ // Reject a clearly unauthorized caller before it can acquire or wait on
3199
+ // the exclusive workspace lifecycle lock. This preflight is deliberately
3200
+ // not the mutation authority: the deletion transaction repeats the same
3201
+ // check after taking the lifecycle lock so a concurrent demotion still
3202
+ // fails closed without reintroducing the account/lifecycle lock cycle.
3203
+ await withRlsContext(
3204
+ db,
3205
+ { accountId: input.accountId, workspaceId: input.workspaceId },
3206
+ async (scopedDb) =>
3207
+ await authorizeOrganizationWorkspaceDeletion(scopedDb, {
3208
+ accountId: input.accountId,
3209
+ workspaceId: input.workspaceId,
3210
+ subjectId: input.organizationAdministratorSubjectId!,
3211
+ }),
3212
+ );
3213
+ }
3214
+
3160
3215
  const result = await withRlsContext(
3161
3216
  db,
3162
3217
  { accountId: input.accountId, workspaceId: input.workspaceId },
@@ -3166,7 +3221,10 @@ export async function deleteWorkspaceIfQuiescent(
3166
3221
  // Adoption takes the matching shared prefix before its canonical
3167
3222
  // session/attempt or retained-process locks. Taking the exclusive
3168
3223
  // prefix first prevents parent-FK deletion from deadlocking with an
3169
- // already-started cross-workspace adoption.
3224
+ // already-started cross-workspace adoption. It must also precede the
3225
+ // organization-administration seam: that seam takes the account row
3226
+ // FOR KEY SHARE, while ordinary deletion takes this lifecycle lock
3227
+ // before upgrading the account row to FOR UPDATE.
3170
3228
  const lifecycleLockStartedAt = performance.now();
3171
3229
  await lockBackgroundCommandWorkspaceLifecycle(tx, [input.workspaceId], "update");
3172
3230
  observeWorkspaceDeletePhase(input.observer, {
@@ -3175,6 +3233,14 @@ export async function deleteWorkspaceIfQuiescent(
3175
3233
  durationSeconds: workspaceDeletePhaseDuration(lifecycleLockStartedAt),
3176
3234
  });
3177
3235
 
3236
+ if (input.organizationAdministratorSubjectId) {
3237
+ await authorizeOrganizationWorkspaceDeletion(tx, {
3238
+ accountId: input.accountId,
3239
+ workspaceId: input.workspaceId,
3240
+ subjectId: input.organizationAdministratorSubjectId,
3241
+ });
3242
+ }
3243
+
3178
3244
  const accountWorkspaceLockStartedAt = performance.now();
3179
3245
  const [account] = await tx
3180
3246
  .select({ id: schema.managedAccounts.id })
@@ -4623,6 +4689,7 @@ export type ModelCallFact = {
4623
4689
  totalTokens: number | null;
4624
4690
  pricedCostMicros: number;
4625
4691
  estimatedProviderCostMicros: number | null;
4692
+ equivalentCreditCostMicros: number | null;
4626
4693
  pricingSource: "configured_list_price" | "gateway_reported" | null;
4627
4694
  contextContributions: readonly ModelContextContributionSummary[] | null;
4628
4695
  occurredAt: Date;
@@ -4658,6 +4725,7 @@ function mapModelCallFact(row: typeof schema.modelCallFacts.$inferSelect): Model
4658
4725
  totalTokens: row.totalTokens,
4659
4726
  pricedCostMicros: row.pricedCostMicros,
4660
4727
  estimatedProviderCostMicros: row.estimatedProviderCostMicros,
4728
+ equivalentCreditCostMicros: row.equivalentCreditCostMicros,
4661
4729
  pricingSource:
4662
4730
  row.pricingSource === "configured_list_price" || row.pricingSource === "gateway_reported"
4663
4731
  ? row.pricingSource
@@ -4724,6 +4792,7 @@ export async function recordModelCallFact(
4724
4792
  billingPath: ModelCallBillingPath;
4725
4793
  pricedCostMicros: number;
4726
4794
  estimatedProviderCostMicros?: number | null;
4795
+ equivalentCreditCostMicros?: number | null;
4727
4796
  pricingSource?: "configured_list_price" | "gateway_reported" | null;
4728
4797
  contextContributions?: readonly ModelContextContributionSummary[] | null;
4729
4798
  inputTokens?: number | null;
@@ -4747,6 +4816,20 @@ export async function recordModelCallFact(
4747
4816
  "recordModelCallFact: estimatedProviderCostMicros must be null or a non-negative safe integer",
4748
4817
  );
4749
4818
  }
4819
+ if (
4820
+ input.equivalentCreditCostMicros != null &&
4821
+ (input.equivalentCreditCostMicros < 0 ||
4822
+ !Number.isSafeInteger(input.equivalentCreditCostMicros))
4823
+ ) {
4824
+ throw new Error(
4825
+ "recordModelCallFact: equivalentCreditCostMicros must be null or a non-negative safe integer",
4826
+ );
4827
+ }
4828
+ if (input.equivalentCreditCostMicros != null && input.estimatedProviderCostMicros == null) {
4829
+ throw new Error(
4830
+ "recordModelCallFact: equivalentCreditCostMicros requires estimatedProviderCostMicros",
4831
+ );
4832
+ }
4750
4833
  if ((input.estimatedProviderCostMicros == null) !== (input.pricingSource == null)) {
4751
4834
  throw new Error(
4752
4835
  "recordModelCallFact: estimatedProviderCostMicros and pricingSource must be present together",
@@ -4808,6 +4891,7 @@ export async function recordModelCallFact(
4808
4891
  totalTokens: input.totalTokens ?? null,
4809
4892
  pricedCostMicros: input.pricedCostMicros,
4810
4893
  estimatedProviderCostMicros: input.estimatedProviderCostMicros ?? null,
4894
+ equivalentCreditCostMicros: input.equivalentCreditCostMicros ?? null,
4811
4895
  pricingSource: input.pricingSource ?? null,
4812
4896
  contextContributions,
4813
4897
  occurredAt,
@@ -4825,6 +4909,7 @@ export async function recordModelCallFact(
4825
4909
  initiatorSubjectId: sql`coalesce(${schema.modelCallFacts.initiatorSubjectId}, excluded.initiator_subject_id)`,
4826
4910
  turnSource: sql`coalesce(${schema.modelCallFacts.turnSource}, excluded.turn_source)`,
4827
4911
  estimatedProviderCostMicros: sql`coalesce(${schema.modelCallFacts.estimatedProviderCostMicros}, excluded.estimated_provider_cost_micros)`,
4912
+ equivalentCreditCostMicros: sql`coalesce(${schema.modelCallFacts.equivalentCreditCostMicros}, excluded.equivalent_credit_cost_micros)`,
4828
4913
  pricingSource: sql`coalesce(${schema.modelCallFacts.pricingSource}, excluded.pricing_source)`,
4829
4914
  contextContributions: sql`coalesce(${schema.modelCallFacts.contextContributions}, excluded.context_contributions)`,
4830
4915
  },
@@ -5202,6 +5287,8 @@ export type CreateScheduledTaskInput = {
5202
5287
  // The rig each run binds to (M3); active version resolved per fire at dispatch.
5203
5288
  rigId?: string | null;
5204
5289
  metadata: Record<string, unknown>;
5290
+ /** Trusted database-only admission seam. Throwing rolls the task creation back. */
5291
+ beforeCreateCommit?: (tx: Database) => Promise<void>;
5205
5292
  };
5206
5293
 
5207
5294
  export type UpdateScheduledTaskInput = Partial<{
@@ -5224,6 +5311,8 @@ export type UpdateScheduledTaskInput = Partial<{
5224
5311
  authorityUpdatedBy: TurnInitiator;
5225
5312
  authorityUpdatedByContext: TurnInitiatorContext;
5226
5313
  authorityUpdatedByActor: AgentSessionCreationActor | null;
5314
+ /** Trusted database-only admission seam. Throwing rolls the task update back. */
5315
+ beforeUpdateCommit: (tx: Database) => Promise<void>;
5227
5316
  }>;
5228
5317
 
5229
5318
  export type CreatePackInstallationInput = {
@@ -5358,6 +5447,51 @@ export type CreateConnectionInput = {
5358
5447
  updatedBySubjectId?: string | null;
5359
5448
  };
5360
5449
 
5450
+ type UpsertWorkspaceProviderApiKeyConnectionInput = {
5451
+ accountId: string;
5452
+ workspaceId: string;
5453
+ operationId: string;
5454
+ requestDigest: string;
5455
+ credentialEncrypted: string;
5456
+ grantedScopes?: string[];
5457
+ expiresAt?: Date | null;
5458
+ metadata?: Record<string, unknown>;
5459
+ updatedBySubjectId: string;
5460
+ };
5461
+
5462
+ type RotateWorkspaceProviderApiKeyConnectionInput = {
5463
+ accountId: string;
5464
+ workspaceId: string;
5465
+ connectionId: string;
5466
+ expectedVersion: number;
5467
+ operationId: string;
5468
+ requestDigest: string;
5469
+ credentialEncrypted: string;
5470
+ grantedScopes?: string[];
5471
+ expiresAt?: Date | null;
5472
+ metadata?: Record<string, unknown>;
5473
+ updatedBySubjectId: string;
5474
+ };
5475
+
5476
+ type RevokeWorkspaceProviderApiKeyConnectionsInput = {
5477
+ accountId: string;
5478
+ workspaceId: string;
5479
+ connectionId: string;
5480
+ expectedVersion: number;
5481
+ updatedBySubjectId: string;
5482
+ };
5483
+
5484
+ export type UpsertWorkspaceVercelAiGatewayConnectionInput =
5485
+ UpsertWorkspaceProviderApiKeyConnectionInput;
5486
+ export type RotateWorkspaceVercelAiGatewayConnectionInput =
5487
+ RotateWorkspaceProviderApiKeyConnectionInput;
5488
+ export type RevokeWorkspaceVercelAiGatewayConnectionsInput =
5489
+ RevokeWorkspaceProviderApiKeyConnectionsInput;
5490
+ export type UpsertWorkspaceOpenRouterConnectionInput = UpsertWorkspaceProviderApiKeyConnectionInput;
5491
+ export type RotateWorkspaceOpenRouterConnectionInput = RotateWorkspaceProviderApiKeyConnectionInput;
5492
+ export type RevokeWorkspaceOpenRouterConnectionsInput =
5493
+ RevokeWorkspaceProviderApiKeyConnectionsInput;
5494
+
5361
5495
  export type UpdateConnectionInput = {
5362
5496
  workspaceId: string;
5363
5497
  connectionId: string;
@@ -5756,6 +5890,7 @@ export type InstallPortableSkillInput = {
5756
5890
  sourcePath: string;
5757
5891
  name: string;
5758
5892
  description: string;
5893
+ activationMode?: "workspace_managed" | "session_selected";
5759
5894
  category?: string;
5760
5895
  tags?: string[];
5761
5896
  provenance?: "platform" | "deployment" | "registry" | "workspace";
@@ -5796,6 +5931,7 @@ export type PortableSkillRuntime = {
5796
5931
  version: string;
5797
5932
  name: string;
5798
5933
  description: string;
5934
+ activationMode: "workspace_managed" | "session_selected";
5799
5935
  sourceUrl: string;
5800
5936
  sourceCommit: string;
5801
5937
  sourcePath: string;
@@ -8232,6 +8368,7 @@ export async function installPortableSkill(
8232
8368
  }
8233
8369
  if (!pluginVersion) throw new Error("Failed to create portable Skill plugin version");
8234
8370
 
8371
+ const activationMode = input.activationMode ?? "workspace_managed";
8235
8372
  let [facet] = await tx
8236
8373
  .select()
8237
8374
  .from(schema.capabilityFacets)
@@ -8249,12 +8386,17 @@ export async function installPortableSkill(
8249
8386
  pluginVersionId: pluginVersion.id,
8250
8387
  facetKey: "skill",
8251
8388
  kind: "skill",
8252
- activationMode: "workspace_managed",
8389
+ activationMode,
8253
8390
  required: true,
8254
8391
  })
8255
8392
  .returning();
8256
8393
  }
8257
8394
  if (!facet) throw new Error("Failed to create portable Skill facet");
8395
+ if (facet.activationMode !== activationMode) {
8396
+ throw new Error(
8397
+ `Portable Skill ${input.capabilityId} activation mode conflicts with immutable stored content`,
8398
+ );
8399
+ }
8258
8400
 
8259
8401
  const [existingSkill] = await tx
8260
8402
  .select()
@@ -8458,16 +8600,22 @@ export async function installPortableSkill(
8458
8600
  );
8459
8601
  }
8460
8602
 
8461
- /** Return exact text artifacts for active authoritative Skill installations. */
8603
+ /**
8604
+ * Return exact text artifacts for active authoritative Skill installations.
8605
+ * Session-selected Skills are excluded from ambient runtime resolution unless
8606
+ * the caller is explicitly materializing one during session admission.
8607
+ */
8462
8608
  export async function listInstalledPortableSkills(
8463
8609
  db: Database,
8464
8610
  workspaceId: string,
8611
+ options: { includeSessionSelected?: boolean } = {},
8465
8612
  ): Promise<PortableSkillRuntime[]> {
8466
8613
  return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
8467
8614
  const rows = await scopedDb
8468
8615
  .select({
8469
8616
  capabilityId: schema.capabilitySkillFacets.capabilityId,
8470
8617
  facetId: schema.capabilitySkillFacets.facetId,
8618
+ activationMode: schema.capabilityFacets.activationMode,
8471
8619
  manifest: schema.capabilityPluginVersions.manifest,
8472
8620
  version: schema.capabilityPluginVersions.version,
8473
8621
  name: schema.capabilitySkillFacets.name,
@@ -8494,6 +8642,10 @@ export async function listInstalledPortableSkills(
8494
8642
  schema.capabilityPluginInstallations.id,
8495
8643
  ),
8496
8644
  )
8645
+ .innerJoin(
8646
+ schema.capabilityFacets,
8647
+ eq(schema.capabilityFacets.id, schema.capabilityFacetInstallations.facetId),
8648
+ )
8497
8649
  .innerJoin(
8498
8650
  schema.capabilitySkillFacets,
8499
8651
  eq(schema.capabilitySkillFacets.facetId, schema.capabilityFacetInstallations.facetId),
@@ -8518,6 +8670,14 @@ export async function listInstalledPortableSkills(
8518
8670
  .orderBy(asc(schema.capabilitySkillFacets.name), asc(schema.capabilitySkillFiles.path));
8519
8671
  const skills = new Map<string, PortableSkillRuntime>();
8520
8672
  for (const row of rows) {
8673
+ if (row.activationMode !== "workspace_managed" && row.activationMode !== "session_selected") {
8674
+ throw new Error(
8675
+ `Installed Skill ${row.capabilityId} has invalid activation mode ${row.activationMode}`,
8676
+ );
8677
+ }
8678
+ if (row.activationMode === "session_selected" && !options.includeSessionSelected) {
8679
+ continue;
8680
+ }
8521
8681
  const source = skillSourceFromManifest(row.manifest, row.capabilityId);
8522
8682
  const existing = skills.get(row.facetId);
8523
8683
  if (existing) {
@@ -8530,6 +8690,7 @@ export async function listInstalledPortableSkills(
8530
8690
  version: row.version,
8531
8691
  name: row.name,
8532
8692
  description: row.description,
8693
+ activationMode: row.activationMode,
8533
8694
  sourceUrl: row.sourceUrl,
8534
8695
  sourceCommit: row.sourceCommit,
8535
8696
  sourcePath: row.sourcePath,
@@ -9458,6 +9619,318 @@ export async function createConnection(
9458
9619
  );
9459
9620
  }
9460
9621
 
9622
+ type WorkspaceProviderApiKeyConnectionKind = "vercel_gateway" | "openrouter";
9623
+
9624
+ type WorkspaceProviderApiKeyConnectionSpec = {
9625
+ providerDomain: string;
9626
+ credentialRole: string;
9627
+ operationIdMetadataKey: string;
9628
+ operationDigestMetadataKey: string;
9629
+ label: string;
9630
+ };
9631
+
9632
+ function workspaceProviderApiKeyConnectionSpec(
9633
+ providerKind: WorkspaceProviderApiKeyConnectionKind,
9634
+ ): WorkspaceProviderApiKeyConnectionSpec {
9635
+ return providerKind === "vercel_gateway"
9636
+ ? {
9637
+ providerDomain: VERCEL_AI_GATEWAY_CONNECTION_DOMAIN,
9638
+ credentialRole: VERCEL_AI_GATEWAY_CONNECTION_ROLE,
9639
+ operationIdMetadataKey: VERCEL_AI_GATEWAY_CREDENTIAL_OPERATION_ID_METADATA_KEY,
9640
+ operationDigestMetadataKey: VERCEL_AI_GATEWAY_CREDENTIAL_OPERATION_DIGEST_METADATA_KEY,
9641
+ label: "Vercel AI Gateway",
9642
+ }
9643
+ : {
9644
+ providerDomain: WORKSPACE_OPENROUTER_CONNECTION_DOMAIN,
9645
+ credentialRole: WORKSPACE_OPENROUTER_CONNECTION_ROLE,
9646
+ operationIdMetadataKey: OPENROUTER_CREDENTIAL_OPERATION_ID_METADATA_KEY,
9647
+ operationDigestMetadataKey: OPENROUTER_CREDENTIAL_OPERATION_DIGEST_METADATA_KEY,
9648
+ label: "OpenRouter",
9649
+ };
9650
+ }
9651
+
9652
+ async function lockWorkspaceProviderApiKeyConnection(
9653
+ db: Database,
9654
+ workspaceId: string,
9655
+ providerKind: WorkspaceProviderApiKeyConnectionKind,
9656
+ ): Promise<void> {
9657
+ const lockKey =
9658
+ providerKind === "vercel_gateway"
9659
+ ? `workspace-vercel-ai-gateway:${workspaceId}`
9660
+ : `workspace-openrouter:${workspaceId}`;
9661
+ await db.execute(sql`select pg_advisory_xact_lock(hashtextextended(${lockKey}, 0))`);
9662
+ }
9663
+
9664
+ async function upsertWorkspaceProviderApiKeyConnection(
9665
+ db: Database,
9666
+ providerKind: WorkspaceProviderApiKeyConnectionKind,
9667
+ input: UpsertWorkspaceProviderApiKeyConnectionInput,
9668
+ ): Promise<ConnectionMetadataWithVerification | null> {
9669
+ const spec = workspaceProviderApiKeyConnectionSpec(providerKind);
9670
+ return await withRlsContext(
9671
+ db,
9672
+ { accountId: input.accountId, workspaceId: input.workspaceId },
9673
+ async (scopedDb) =>
9674
+ await scopedDb.transaction(async (txRaw) => {
9675
+ const tx = txRaw as unknown as Database;
9676
+ await lockWorkspaceProviderApiKeyConnection(tx, input.workspaceId, providerKind);
9677
+ const rows = await tx
9678
+ .select(connectionMetadataColumns)
9679
+ .from(schema.connections)
9680
+ .where(
9681
+ and(
9682
+ eq(schema.connections.accountId, input.accountId),
9683
+ eq(schema.connections.workspaceId, input.workspaceId),
9684
+ isNull(schema.connections.subjectId),
9685
+ sql`lower(${schema.connections.providerDomain}) = lower(${spec.providerDomain})`,
9686
+ eq(schema.connections.kind, "api_key"),
9687
+ sql`${schema.connections.metadata} ->> 'credentialRole' = ${spec.credentialRole}`,
9688
+ ),
9689
+ )
9690
+ .orderBy(
9691
+ desc(sql`(${schema.connections.status} = 'active')`),
9692
+ desc(schema.connections.updatedAt),
9693
+ desc(schema.connections.createdAt),
9694
+ desc(schema.connections.id),
9695
+ )
9696
+ .for("update");
9697
+ const metadata = {
9698
+ ...(input.metadata ?? {}),
9699
+ credentialRole: spec.credentialRole,
9700
+ [spec.operationIdMetadataKey]: input.operationId,
9701
+ [spec.operationDigestMetadataKey]: input.requestDigest,
9702
+ };
9703
+ const operationRow = rows.find(
9704
+ (row) => row.metadata[spec.operationIdMetadataKey] === input.operationId,
9705
+ );
9706
+ if (operationRow) {
9707
+ return operationRow.metadata[spec.operationDigestMetadataKey] === input.requestDigest
9708
+ ? mapConnectionMetadata(operationRow)
9709
+ : null;
9710
+ }
9711
+ if (rows.some((row) => row.status !== "revoked")) return null;
9712
+ return await createConnectionInScope(tx, {
9713
+ accountId: input.accountId,
9714
+ workspaceId: input.workspaceId,
9715
+ subjectId: null,
9716
+ providerDomain: spec.providerDomain,
9717
+ kind: "api_key",
9718
+ status: "active",
9719
+ credentialEncrypted: input.credentialEncrypted,
9720
+ grantedScopes: input.grantedScopes ?? [],
9721
+ expiresAt: input.expiresAt ?? null,
9722
+ metadata,
9723
+ createdBySubjectId: input.updatedBySubjectId,
9724
+ updatedBySubjectId: input.updatedBySubjectId,
9725
+ });
9726
+ }),
9727
+ );
9728
+ }
9729
+
9730
+ async function rotateWorkspaceProviderApiKeyConnection(
9731
+ db: Database,
9732
+ providerKind: WorkspaceProviderApiKeyConnectionKind,
9733
+ input: RotateWorkspaceProviderApiKeyConnectionInput,
9734
+ ): Promise<ConnectionMetadataWithVerification | null> {
9735
+ const spec = workspaceProviderApiKeyConnectionSpec(providerKind);
9736
+ return await withRlsContext(
9737
+ db,
9738
+ { accountId: input.accountId, workspaceId: input.workspaceId },
9739
+ async (scopedDb) =>
9740
+ await scopedDb.transaction(async (txRaw) => {
9741
+ const tx = txRaw as unknown as Database;
9742
+ await lockWorkspaceProviderApiKeyConnection(tx, input.workspaceId, providerKind);
9743
+ const rows = await tx
9744
+ .select(connectionMetadataColumns)
9745
+ .from(schema.connections)
9746
+ .where(
9747
+ and(
9748
+ eq(schema.connections.accountId, input.accountId),
9749
+ eq(schema.connections.workspaceId, input.workspaceId),
9750
+ isNull(schema.connections.subjectId),
9751
+ sql`lower(${schema.connections.providerDomain}) = lower(${spec.providerDomain})`,
9752
+ eq(schema.connections.kind, "api_key"),
9753
+ sql`${schema.connections.metadata} ->> 'credentialRole' = ${spec.credentialRole}`,
9754
+ ),
9755
+ )
9756
+ .orderBy(desc(schema.connections.updatedAt), desc(schema.connections.id))
9757
+ .for("update");
9758
+ const operationRow = rows.find(
9759
+ (row) => row.metadata[spec.operationIdMetadataKey] === input.operationId,
9760
+ );
9761
+ if (operationRow) {
9762
+ return operationRow.status !== "revoked" &&
9763
+ operationRow.metadata[spec.operationDigestMetadataKey] === input.requestDigest
9764
+ ? mapConnectionMetadata(operationRow)
9765
+ : null;
9766
+ }
9767
+ const targetRow = rows.find((row) => row.id === input.connectionId);
9768
+ if (
9769
+ !targetRow ||
9770
+ targetRow.status === "revoked" ||
9771
+ targetRow.version !== input.expectedVersion
9772
+ ) {
9773
+ return null;
9774
+ }
9775
+ const metadata = {
9776
+ ...(input.metadata ?? {}),
9777
+ credentialRole: spec.credentialRole,
9778
+ [spec.operationIdMetadataKey]: input.operationId,
9779
+ [spec.operationDigestMetadataKey]: input.requestDigest,
9780
+ };
9781
+ for (const row of rows.filter((candidate) => candidate.status !== "revoked")) {
9782
+ const revoked = await revokeConnectionInScope(
9783
+ tx,
9784
+ input.workspaceId,
9785
+ row.id,
9786
+ input.updatedBySubjectId,
9787
+ row.version,
9788
+ );
9789
+ if (!revoked) {
9790
+ throw new Error(`${spec.label} connection changed during rotation`);
9791
+ }
9792
+ }
9793
+ return await createConnectionInScope(tx, {
9794
+ accountId: input.accountId,
9795
+ workspaceId: input.workspaceId,
9796
+ subjectId: null,
9797
+ providerDomain: spec.providerDomain,
9798
+ kind: "api_key",
9799
+ status: "active",
9800
+ credentialEncrypted: input.credentialEncrypted,
9801
+ grantedScopes: input.grantedScopes ?? [],
9802
+ expiresAt: input.expiresAt ?? null,
9803
+ metadata,
9804
+ createdBySubjectId: input.updatedBySubjectId,
9805
+ updatedBySubjectId: input.updatedBySubjectId,
9806
+ });
9807
+ }),
9808
+ );
9809
+ }
9810
+
9811
+ async function revokeWorkspaceProviderApiKeyConnections(
9812
+ db: Database,
9813
+ providerKind: WorkspaceProviderApiKeyConnectionKind,
9814
+ input: RevokeWorkspaceProviderApiKeyConnectionsInput,
9815
+ ): Promise<ConnectionMetadataWithVerification | null> {
9816
+ const spec = workspaceProviderApiKeyConnectionSpec(providerKind);
9817
+ return await withRlsContext(
9818
+ db,
9819
+ { accountId: input.accountId, workspaceId: input.workspaceId },
9820
+ async (scopedDb) =>
9821
+ await scopedDb.transaction(async (txRaw) => {
9822
+ const tx = txRaw as unknown as Database;
9823
+ await lockWorkspaceProviderApiKeyConnection(tx, input.workspaceId, providerKind);
9824
+ const [targetRow] = await tx
9825
+ .select(connectionMetadataColumns)
9826
+ .from(schema.connections)
9827
+ .where(
9828
+ and(
9829
+ eq(schema.connections.accountId, input.accountId),
9830
+ eq(schema.connections.workspaceId, input.workspaceId),
9831
+ eq(schema.connections.id, input.connectionId),
9832
+ isNull(schema.connections.subjectId),
9833
+ ),
9834
+ )
9835
+ .for("update")
9836
+ .limit(1);
9837
+ if (
9838
+ !targetRow ||
9839
+ targetRow.providerDomain.toLowerCase() !== spec.providerDomain ||
9840
+ targetRow.kind !== "api_key" ||
9841
+ targetRow.metadata.credentialRole !== spec.credentialRole
9842
+ ) {
9843
+ return null;
9844
+ }
9845
+ // A replay of an already-committed DELETE is idempotent for that exact
9846
+ // connection generation. It must not sweep a newer provider connection
9847
+ // that was established after the original response was lost.
9848
+ if (targetRow.status === "revoked") {
9849
+ return mapConnectionMetadata(targetRow);
9850
+ }
9851
+ if (targetRow.version !== input.expectedVersion) {
9852
+ return null;
9853
+ }
9854
+ const providerRows = await tx
9855
+ .select(connectionMetadataColumns)
9856
+ .from(schema.connections)
9857
+ .where(
9858
+ and(
9859
+ eq(schema.connections.accountId, input.accountId),
9860
+ eq(schema.connections.workspaceId, input.workspaceId),
9861
+ isNull(schema.connections.subjectId),
9862
+ sql`lower(${schema.connections.providerDomain}) = lower(${spec.providerDomain})`,
9863
+ eq(schema.connections.kind, "api_key"),
9864
+ ne(schema.connections.status, "revoked"),
9865
+ sql`${schema.connections.metadata} ->> 'credentialRole' = ${spec.credentialRole}`,
9866
+ ),
9867
+ )
9868
+ .orderBy(desc(schema.connections.updatedAt), desc(schema.connections.id))
9869
+ .for("update");
9870
+ let result = mapConnectionMetadata(targetRow);
9871
+ for (const row of providerRows) {
9872
+ const revoked = await revokeConnectionInScope(
9873
+ tx,
9874
+ input.workspaceId,
9875
+ row.id,
9876
+ input.updatedBySubjectId,
9877
+ row.version,
9878
+ );
9879
+ if (!revoked) {
9880
+ throw new Error(`${spec.label} connection changed during disconnect`);
9881
+ }
9882
+ if (row.id === input.connectionId) result = revoked;
9883
+ }
9884
+ return result;
9885
+ }),
9886
+ );
9887
+ }
9888
+
9889
+ /** Compatibility wrapper for the existing Vercel AI Gateway create/replay contract. */
9890
+ export async function upsertWorkspaceVercelAiGatewayConnection(
9891
+ db: Database,
9892
+ input: UpsertWorkspaceVercelAiGatewayConnectionInput,
9893
+ ): Promise<ConnectionMetadataWithVerification | null> {
9894
+ return await upsertWorkspaceProviderApiKeyConnection(db, "vercel_gateway", input);
9895
+ }
9896
+
9897
+ export async function upsertWorkspaceOpenRouterConnection(
9898
+ db: Database,
9899
+ input: UpsertWorkspaceOpenRouterConnectionInput,
9900
+ ): Promise<ConnectionMetadataWithVerification | null> {
9901
+ return await upsertWorkspaceProviderApiKeyConnection(db, "openrouter", input);
9902
+ }
9903
+
9904
+ /** Compatibility wrapper for the existing Vercel AI Gateway rotation contract. */
9905
+ export async function rotateWorkspaceVercelAiGatewayConnection(
9906
+ db: Database,
9907
+ input: RotateWorkspaceVercelAiGatewayConnectionInput,
9908
+ ): Promise<ConnectionMetadataWithVerification | null> {
9909
+ return await rotateWorkspaceProviderApiKeyConnection(db, "vercel_gateway", input);
9910
+ }
9911
+
9912
+ export async function rotateWorkspaceOpenRouterConnection(
9913
+ db: Database,
9914
+ input: RotateWorkspaceOpenRouterConnectionInput,
9915
+ ): Promise<ConnectionMetadataWithVerification | null> {
9916
+ return await rotateWorkspaceProviderApiKeyConnection(db, "openrouter", input);
9917
+ }
9918
+
9919
+ /** Compatibility wrapper for the existing Vercel AI Gateway revoke contract. */
9920
+ export async function revokeWorkspaceVercelAiGatewayConnections(
9921
+ db: Database,
9922
+ input: RevokeWorkspaceVercelAiGatewayConnectionsInput,
9923
+ ): Promise<ConnectionMetadataWithVerification | null> {
9924
+ return await revokeWorkspaceProviderApiKeyConnections(db, "vercel_gateway", input);
9925
+ }
9926
+
9927
+ export async function revokeWorkspaceOpenRouterConnections(
9928
+ db: Database,
9929
+ input: RevokeWorkspaceOpenRouterConnectionsInput,
9930
+ ): Promise<ConnectionMetadataWithVerification | null> {
9931
+ return await revokeWorkspaceProviderApiKeyConnections(db, "openrouter", input);
9932
+ }
9933
+
9461
9934
  /**
9462
9935
  * Serialize one provider-principal/owner connection generation. Distinct OAuth
9463
9936
  * starts for the same principal converge on one row, while an explicit
@@ -13493,11 +13966,21 @@ export async function loadConnectionCredentialForBroker(
13493
13966
  );
13494
13967
  }
13495
13968
 
13496
- export async function workspaceVercelAiGatewayConnectionActive(
13969
+ async function getWorkspaceProviderApiKeyConnectionMetadata(
13497
13970
  db: Database,
13498
13971
  workspaceId: string,
13499
- ): Promise<boolean> {
13500
- return (await getWorkspaceVercelAiGatewayConnectionMetadata(db, workspaceId)) !== null;
13972
+ providerKind: WorkspaceProviderApiKeyConnectionKind,
13973
+ ): Promise<{ connectionId: string; version: number } | null> {
13974
+ const spec = workspaceProviderApiKeyConnectionSpec(providerKind);
13975
+ const connection = (await listConnectionsMetadata(db, workspaceId, null)).find(
13976
+ (candidate) =>
13977
+ candidate.subjectId === null &&
13978
+ candidate.providerDomain.toLowerCase() === spec.providerDomain &&
13979
+ candidate.kind === "api_key" &&
13980
+ candidate.status === "active" &&
13981
+ candidate.metadata.credentialRole === spec.credentialRole,
13982
+ );
13983
+ return connection ? { connectionId: connection.id, version: connection.version } : null;
13501
13984
  }
13502
13985
 
13503
13986
  /** Metadata-only capability revision input; never selects/decrypts the API key. */
@@ -13505,32 +13988,44 @@ export async function getWorkspaceVercelAiGatewayConnectionMetadata(
13505
13988
  db: Database,
13506
13989
  workspaceId: string,
13507
13990
  ): Promise<{ connectionId: string; version: number } | null> {
13508
- const gatewayConnection = (await listConnectionsMetadata(db, workspaceId, null)).find(
13509
- (candidate) =>
13510
- candidate.subjectId === null &&
13511
- candidate.providerDomain === VERCEL_AI_GATEWAY_CONNECTION_DOMAIN &&
13512
- candidate.kind === "api_key" &&
13513
- candidate.status === "active" &&
13514
- candidate.metadata.credentialRole === VERCEL_AI_GATEWAY_CONNECTION_ROLE,
13515
- );
13516
- return gatewayConnection
13517
- ? { connectionId: gatewayConnection.id, version: gatewayConnection.version }
13518
- : null;
13991
+ return await getWorkspaceProviderApiKeyConnectionMetadata(db, workspaceId, "vercel_gateway");
13519
13992
  }
13520
13993
 
13521
- /** Resolve only the reviewed workspace-shared AI Gateway credential shape. */
13522
- export async function loadWorkspaceVercelAiGatewayApiKey(
13994
+ export async function getWorkspaceOpenRouterConnectionMetadata(
13995
+ db: Database,
13996
+ workspaceId: string,
13997
+ ): Promise<{ connectionId: string; version: number } | null> {
13998
+ return await getWorkspaceProviderApiKeyConnectionMetadata(db, workspaceId, "openrouter");
13999
+ }
14000
+
14001
+ export async function workspaceVercelAiGatewayConnectionActive(
14002
+ db: Database,
14003
+ workspaceId: string,
14004
+ ): Promise<boolean> {
14005
+ return (await getWorkspaceVercelAiGatewayConnectionMetadata(db, workspaceId)) !== null;
14006
+ }
14007
+
14008
+ export async function workspaceOpenRouterConnectionActive(
14009
+ db: Database,
14010
+ workspaceId: string,
14011
+ ): Promise<boolean> {
14012
+ return (await getWorkspaceOpenRouterConnectionMetadata(db, workspaceId)) !== null;
14013
+ }
14014
+
14015
+ async function loadWorkspaceProviderApiKey(
13523
14016
  db: Database,
13524
14017
  settings: Settings,
13525
14018
  workspaceId: string,
14019
+ providerKind: WorkspaceProviderApiKeyConnectionKind,
13526
14020
  ): Promise<string | null> {
14021
+ const spec = workspaceProviderApiKeyConnectionSpec(providerKind);
13527
14022
  const metadata = (await listConnectionsMetadata(db, workspaceId, null)).find(
13528
14023
  (connection) =>
13529
14024
  connection.subjectId === null &&
13530
- connection.providerDomain === VERCEL_AI_GATEWAY_CONNECTION_DOMAIN &&
14025
+ connection.providerDomain.toLowerCase() === spec.providerDomain &&
13531
14026
  connection.kind === "api_key" &&
13532
14027
  connection.status === "active" &&
13533
- connection.metadata.credentialRole === VERCEL_AI_GATEWAY_CONNECTION_ROLE,
14028
+ connection.metadata.credentialRole === spec.credentialRole,
13534
14029
  );
13535
14030
  if (!metadata) {
13536
14031
  return null;
@@ -13538,14 +14033,14 @@ export async function loadWorkspaceVercelAiGatewayApiKey(
13538
14033
  const connection = await loadConnectionCredentialForBroker(db, settings, {
13539
14034
  workspaceId,
13540
14035
  connectionId: metadata.id,
13541
- providerDomain: VERCEL_AI_GATEWAY_CONNECTION_DOMAIN,
14036
+ providerDomain: spec.providerDomain,
13542
14037
  kind: "api_key",
13543
14038
  allowSubjectOwned: false,
13544
14039
  });
13545
14040
  if (
13546
14041
  !connection ||
13547
14042
  connection.status !== "active" ||
13548
- connection.metadata.credentialRole !== VERCEL_AI_GATEWAY_CONNECTION_ROLE
14043
+ connection.metadata.credentialRole !== spec.credentialRole
13549
14044
  ) {
13550
14045
  return null;
13551
14046
  }
@@ -13553,6 +14048,24 @@ export async function loadWorkspaceVercelAiGatewayApiKey(
13553
14048
  return typeof apiKey === "string" && apiKey.trim().length > 0 ? apiKey : null;
13554
14049
  }
13555
14050
 
14051
+ /** Resolve only the reviewed workspace-shared AI Gateway credential shape. */
14052
+ export async function loadWorkspaceVercelAiGatewayApiKey(
14053
+ db: Database,
14054
+ settings: Settings,
14055
+ workspaceId: string,
14056
+ ): Promise<string | null> {
14057
+ return await loadWorkspaceProviderApiKey(db, settings, workspaceId, "vercel_gateway");
14058
+ }
14059
+
14060
+ /** Resolve only the reviewed workspace-shared OpenRouter credential shape. */
14061
+ export async function loadWorkspaceOpenRouterApiKey(
14062
+ db: Database,
14063
+ settings: Settings,
14064
+ workspaceId: string,
14065
+ ): Promise<string | null> {
14066
+ return await loadWorkspaceProviderApiKey(db, settings, workspaceId, "openrouter");
14067
+ }
14068
+
13556
14069
  /**
13557
14070
  * Worker-only immutable credential lease for an asynchronous video operation.
13558
14071
  * The operation copies the already-encrypted credential envelope before its
@@ -15546,6 +16059,7 @@ export async function createScheduledTask(
15546
16059
  async (scopedDb) => {
15547
16060
  const frozenCreator = await frozenSessionCreatorForInsert(scopedDb, input);
15548
16061
  await setScheduledTaskAuthorityRlsContext(scopedDb, frozenCreator);
16062
+ await input.beforeCreateCommit?.(scopedDb);
15549
16063
  const [row] = await scopedDb
15550
16064
  .insert(schema.scheduledTasks)
15551
16065
  .values({
@@ -15623,6 +16137,7 @@ export async function updateScheduledTask(
15623
16137
  });
15624
16138
  await setScheduledTaskAuthorityRlsContext(scopedDb, frozenUpdater);
15625
16139
  }
16140
+ await input.beforeUpdateCommit?.(scopedDb);
15626
16141
  const [row] = await scopedDb
15627
16142
  .update(schema.scheduledTasks)
15628
16143
  .set({
@@ -16209,23 +16724,69 @@ export async function createScheduledTaskRun(
16209
16724
  scheduledAt?: Date | null;
16210
16725
  firedAt?: Date;
16211
16726
  acceptedExecutionSnapshot?: ScheduledTaskRunAcceptedExecutionType | null;
16727
+ /**
16728
+ * Trusted database-only admission seam for a fresh agent occurrence.
16729
+ * Throwing rolls back the run insert; exact producer replay skips it.
16730
+ */
16731
+ beforeFreshAgentRunCommit?: (tx: Database) => Promise<void>;
16212
16732
  },
16213
16733
  ): Promise<ScheduledTaskRun> {
16214
16734
  return await withWorkspaceRls(db, input.workspaceId, async (scopedDb) => {
16215
16735
  if ((input.taskAuthorityRevision == null) !== (input.taskExecutionDigest == null)) {
16216
16736
  throw new Error("scheduled task run execution binding is incomplete");
16217
16737
  }
16218
- const [taskRow] = await scopedDb
16219
- .select()
16220
- .from(schema.scheduledTasks)
16221
- .where(
16222
- and(
16223
- eq(schema.scheduledTasks.workspaceId, input.workspaceId),
16224
- eq(schema.scheduledTasks.id, input.taskId),
16225
- isNull(schema.scheduledTasks.deletedAt),
16226
- ),
16227
- )
16228
- .limit(1);
16738
+ let guardedAcceptedExecution: ScheduledTaskRunAcceptedExecutionType | null = null;
16739
+ let replayedProducerRow: typeof schema.scheduledTaskRuns.$inferSelect | undefined;
16740
+ if (input.beforeFreshAgentRunCommit) {
16741
+ if (!input.producerKey || !input.acceptedExecutionSnapshot) {
16742
+ throw new Error(
16743
+ "scheduled fresh agent run commit guard requires a producer-bound accepted execution",
16744
+ );
16745
+ }
16746
+ guardedAcceptedExecution = ScheduledTaskRunAcceptedExecution.parse(
16747
+ input.acceptedExecutionSnapshot,
16748
+ );
16749
+ const readProducerReplay = async () => {
16750
+ const [row] = await scopedDb
16751
+ .select()
16752
+ .from(schema.scheduledTaskRuns)
16753
+ .where(
16754
+ and(
16755
+ eq(schema.scheduledTaskRuns.workspaceId, input.workspaceId),
16756
+ eq(schema.scheduledTaskRuns.producerKey, input.producerKey!),
16757
+ ),
16758
+ )
16759
+ .limit(1);
16760
+ return row;
16761
+ };
16762
+ replayedProducerRow = await readProducerReplay();
16763
+ // Fresh custom-model admission must precede the task row lock. Material
16764
+ // task updates take the same advisory lock before updating that row, and
16765
+ // acquiring them in the opposite order can deadlock behind a queued
16766
+ // exclusive custom-model mutation. An exact durable producer replay
16767
+ // still skips the live-model check so retirement cannot invalidate work
16768
+ // that was already accepted.
16769
+ if (!replayedProducerRow) {
16770
+ try {
16771
+ await input.beforeFreshAgentRunCommit(scopedDb);
16772
+ } catch (error) {
16773
+ // An overlapping producer can commit after the unlocked pre-read
16774
+ // while this retry waits behind a queued retirement. Re-read before
16775
+ // surfacing the mutable-model denial so the committed winner remains
16776
+ // replayable instead of becoming a false terminal rejection.
16777
+ replayedProducerRow = await readProducerReplay();
16778
+ if (!replayedProducerRow) throw error;
16779
+ }
16780
+ }
16781
+ }
16782
+ const taskFilter = and(
16783
+ eq(schema.scheduledTasks.workspaceId, input.workspaceId),
16784
+ eq(schema.scheduledTasks.id, input.taskId),
16785
+ isNull(schema.scheduledTasks.deletedAt),
16786
+ );
16787
+ const [taskRow] = input.beforeFreshAgentRunCommit
16788
+ ? await scopedDb.select().from(schema.scheduledTasks).where(taskFilter).for("update").limit(1)
16789
+ : await scopedDb.select().from(schema.scheduledTasks).where(taskFilter).limit(1);
16229
16790
  if (!taskRow) {
16230
16791
  throw new Error(`Scheduled task not found: ${input.taskId}`);
16231
16792
  }
@@ -16233,8 +16794,14 @@ export async function createScheduledTaskRun(
16233
16794
  "agent_turn") satisfies ScheduledTaskActionKind;
16234
16795
  const acceptedExecutionSnapshot =
16235
16796
  actionKind === "agent_turn"
16236
- ? ScheduledTaskRunAcceptedExecution.parse(input.acceptedExecutionSnapshot)
16797
+ ? (guardedAcceptedExecution ??
16798
+ ScheduledTaskRunAcceptedExecution.parse(input.acceptedExecutionSnapshot))
16237
16799
  : null;
16800
+ if (input.beforeFreshAgentRunCommit && actionKind !== "agent_turn") {
16801
+ throw new Error(
16802
+ "scheduled fresh agent run commit guard requires a producer-bound accepted execution",
16803
+ );
16804
+ }
16238
16805
  if (
16239
16806
  acceptedExecutionSnapshot &&
16240
16807
  (acceptedExecutionSnapshot.task.id !== input.taskId ||
@@ -16245,6 +16812,27 @@ export async function createScheduledTaskRun(
16245
16812
  ) {
16246
16813
  throw new Error("scheduled task accepted execution binding changed");
16247
16814
  }
16815
+ if (replayedProducerRow) {
16816
+ if (
16817
+ replayedProducerRow.actionKind !== "agent_turn" ||
16818
+ replayedProducerRow.taskId !== input.taskId ||
16819
+ replayedProducerRow.triggerType !== input.triggerType
16820
+ ) {
16821
+ throw new Error("scheduled task run producer identity changed");
16822
+ }
16823
+ if (
16824
+ replayedProducerRow.taskAuthorityRevision !== input.taskAuthorityRevision ||
16825
+ replayedProducerRow.taskExecutionDigest !== input.taskExecutionDigest
16826
+ ) {
16827
+ throw new Error("scheduled task run execution binding changed");
16828
+ }
16829
+ if (
16830
+ stableJson(replayedProducerRow.acceptedExecutionSnapshot) !==
16831
+ stableJson(acceptedExecutionSnapshot)
16832
+ ) {
16833
+ throw new Error("scheduled task run accepted execution changed");
16834
+ }
16835
+ }
16248
16836
  const values = {
16249
16837
  ...(input.runId ? { id: input.runId } : {}),
16250
16838
  accountId: taskRow.accountId,
@@ -16265,42 +16853,47 @@ export async function createScheduledTaskRun(
16265
16853
  )}::jsonb::text, 'UTF8'), 'sha256'), 'hex')`
16266
16854
  : null,
16267
16855
  };
16268
- let inserted: typeof schema.scheduledTaskRuns.$inferSelect | undefined;
16269
- if (actionKind === "agent_turn" && input.producerKey && acceptedExecutionSnapshot) {
16270
- const [created] = await rawRows<{ id: string }>(
16271
- scopedDb,
16272
- sql`select create_scheduled_agent_run_with_admission(
16273
- ${input.runId ?? randomUUID()}::uuid,
16274
- ${taskRow.accountId}::uuid,
16275
- ${taskRow.workspaceId}::uuid,
16276
- ${input.taskId}::uuid,
16277
- ${input.taskAuthorityRevision}::bigint,
16278
- ${input.taskExecutionDigest}::text,
16279
- ${input.triggerType}::text,
16280
- ${input.producerKey}::text,
16281
- ${input.scheduledAt?.toISOString() ?? null}::timestamptz,
16282
- ${(input.firedAt ?? new Date()).toISOString()}::timestamptz,
16283
- ${JSON.stringify(acceptedExecutionSnapshot)}::jsonb
16284
- ) as id`,
16285
- );
16286
- if (created) {
16287
- [inserted] = await scopedDb
16288
- .select()
16289
- .from(schema.scheduledTaskRuns)
16290
- .where(eq(schema.scheduledTaskRuns.id, created.id))
16291
- .limit(1);
16856
+ let inserted = replayedProducerRow;
16857
+ if (!inserted) {
16858
+ if (actionKind === "agent_turn" && input.producerKey && acceptedExecutionSnapshot) {
16859
+ const [created] = await rawRows<{ id: string }>(
16860
+ scopedDb,
16861
+ sql`select create_scheduled_agent_run_with_admission(
16862
+ ${input.runId ?? randomUUID()}::uuid,
16863
+ ${taskRow.accountId}::uuid,
16864
+ ${taskRow.workspaceId}::uuid,
16865
+ ${input.taskId}::uuid,
16866
+ ${input.taskAuthorityRevision}::bigint,
16867
+ ${input.taskExecutionDigest}::text,
16868
+ ${input.triggerType}::text,
16869
+ ${input.producerKey}::text,
16870
+ ${input.scheduledAt?.toISOString() ?? null}::timestamptz,
16871
+ ${(input.firedAt ?? new Date()).toISOString()}::timestamptz,
16872
+ ${JSON.stringify(acceptedExecutionSnapshot)}::jsonb
16873
+ ) as id`,
16874
+ );
16875
+ if (created) {
16876
+ [inserted] = await scopedDb
16877
+ .select()
16878
+ .from(schema.scheduledTaskRuns)
16879
+ .where(eq(schema.scheduledTaskRuns.id, created.id))
16880
+ .limit(1);
16881
+ }
16882
+ } else {
16883
+ [inserted] = input.producerKey
16884
+ ? await scopedDb
16885
+ .insert(schema.scheduledTaskRuns)
16886
+ .values(values)
16887
+ .onConflictDoNothing({
16888
+ target: [
16889
+ schema.scheduledTaskRuns.workspaceId,
16890
+ schema.scheduledTaskRuns.producerKey,
16891
+ ],
16892
+ where: sql`${schema.scheduledTaskRuns.producerKey} is not null`,
16893
+ })
16894
+ .returning()
16895
+ : await scopedDb.insert(schema.scheduledTaskRuns).values(values).returning();
16292
16896
  }
16293
- } else {
16294
- [inserted] = input.producerKey
16295
- ? await scopedDb
16296
- .insert(schema.scheduledTaskRuns)
16297
- .values(values)
16298
- .onConflictDoNothing({
16299
- target: [schema.scheduledTaskRuns.workspaceId, schema.scheduledTaskRuns.producerKey],
16300
- where: sql`${schema.scheduledTaskRuns.producerKey} is not null`,
16301
- })
16302
- .returning()
16303
- : await scopedDb.insert(schema.scheduledTaskRuns).values(values).returning();
16304
16897
  }
16305
16898
  const [row] = inserted
16306
16899
  ? [inserted]
@@ -16948,17 +17541,10 @@ async function validateScheduledTargetExecutionAtClaim(
16948
17541
  runId: string;
16949
17542
  },
16950
17543
  ): Promise<string | null> {
16951
- const [liveAuthority] = await rawRows<{ denial: string | null }>(
16952
- tx,
16953
- sql`select validate_scheduled_agent_run_live_authority(
16954
- ${input.accountId}::uuid,
16955
- ${input.workspaceId}::uuid,
16956
- ${input.runId}::uuid
16957
- ) as denial`,
16958
- );
16959
- if (liveAuthority?.denial) return liveAuthority.denial;
16960
17544
  const [run] = await tx
16961
17545
  .select({
17546
+ status: schema.scheduledTaskRuns.status,
17547
+ error: schema.scheduledTaskRuns.error,
16962
17548
  acceptedExecutionSnapshot: schema.scheduledTaskRuns.acceptedExecutionSnapshot,
16963
17549
  })
16964
17550
  .from(schema.scheduledTaskRuns)
@@ -16973,6 +17559,22 @@ async function validateScheduledTargetExecutionAtClaim(
16973
17559
  .limit(1)
16974
17560
  .for("update");
16975
17561
  if (!run?.acceptedExecutionSnapshot) return "scheduled_authority_snapshot_missing";
17562
+ if (run.status !== "dispatched") {
17563
+ return run.status === "skipped" &&
17564
+ (run.error === "scheduled_task_paused_before_claim" ||
17565
+ run.error === "scheduled_task_deleted_before_claim")
17566
+ ? "scheduled_task_inactive_before_claim"
17567
+ : "scheduled_run_terminal_before_claim";
17568
+ }
17569
+ const [liveAuthority] = await rawRows<{ denial: string | null }>(
17570
+ tx,
17571
+ sql`select validate_scheduled_agent_run_live_authority(
17572
+ ${input.accountId}::uuid,
17573
+ ${input.workspaceId}::uuid,
17574
+ ${input.runId}::uuid
17575
+ ) as denial`,
17576
+ );
17577
+ if (liveAuthority?.denial) return liveAuthority.denial;
16976
17578
  const accepted = ScheduledTaskRunAcceptedExecution.parse(run.acceptedExecutionSnapshot);
16977
17579
  const target = accepted.targetSessionExecution;
16978
17580
  if (!target) return null;
@@ -21304,6 +21906,8 @@ export async function setWorkspaceCodexSubscriptionModeInTransaction(
21304
21906
  workspaceId: string;
21305
21907
  subjectId: string | null;
21306
21908
  mode: WorkspaceCodexSubscriptionMode;
21909
+ /** Effective source captured under the source lock before caller-owned credential writes. */
21910
+ effectiveSourceBeforeMutation?: EffectiveCodexSubscriptionSource;
21307
21911
  },
21308
21912
  ): Promise<WorkspaceCodexSubscriptionSource> {
21309
21913
  await lockWorkspaceCodexSubscriptionSource(scopedDb, input.workspaceId);
@@ -21314,48 +21918,55 @@ export async function setWorkspaceCodexSubscriptionModeInTransaction(
21314
21918
  if (current.workspaceKind === "personal" && input.mode !== "automatic") {
21315
21919
  throw new Error("personal workspaces always use workspace Codex subscriptions");
21316
21920
  }
21317
- if (current.mode === input.mode) return current;
21318
- const [activeCodexTurn] = await scopedDb
21319
- .select({ id: schema.sessionTurns.id })
21320
- .from(schema.sessionTurns)
21321
- .where(
21322
- and(
21323
- eq(schema.sessionTurns.workspaceId, input.workspaceId),
21324
- inArray(schema.sessionTurns.status, ["running", "requires_action", "recovering"]),
21325
- sql`${schema.sessionTurns.model} like 'codex/%'`,
21326
- ),
21327
- )
21328
- .limit(1);
21329
- const [liveLease] = await scopedDb
21330
- .select({ id: schema.codexCredentialLeases.id })
21331
- .from(schema.codexCredentialLeases)
21332
- .where(
21333
- and(
21334
- eq(schema.codexCredentialLeases.workspaceId, input.workspaceId),
21335
- gt(schema.codexCredentialLeases.leasedUntil, new Date()),
21336
- ),
21337
- )
21338
- .limit(1);
21339
- if (activeCodexTurn || liveLease) {
21340
- throw new Error("Codex subscription source cannot change while active turns are using it");
21341
- }
21342
- await scopedDb
21343
- .insert(schema.workspaceCodexSubscriptionPreferences)
21344
- .values({
21345
- accountId: input.accountId,
21346
- workspaceId: input.workspaceId,
21347
- mode: input.mode,
21348
- updatedBySubjectId: input.subjectId,
21349
- })
21350
- .onConflictDoUpdate({
21351
- target: schema.workspaceCodexSubscriptionPreferences.workspaceId,
21352
- set: {
21921
+ const effectiveSourceBeforeMutation =
21922
+ input.effectiveSourceBeforeMutation ?? current.effectiveSource;
21923
+ let next = current;
21924
+ if (current.mode !== input.mode) {
21925
+ await scopedDb
21926
+ .insert(schema.workspaceCodexSubscriptionPreferences)
21927
+ .values({
21928
+ accountId: input.accountId,
21929
+ workspaceId: input.workspaceId,
21353
21930
  mode: input.mode,
21354
21931
  updatedBySubjectId: input.subjectId,
21355
- updatedAt: new Date(),
21356
- },
21357
- });
21358
- return await getWorkspaceCodexSubscriptionSourceScoped(scopedDb, input.workspaceId);
21932
+ })
21933
+ .onConflictDoUpdate({
21934
+ target: schema.workspaceCodexSubscriptionPreferences.workspaceId,
21935
+ set: {
21936
+ mode: input.mode,
21937
+ updatedBySubjectId: input.subjectId,
21938
+ updatedAt: new Date(),
21939
+ },
21940
+ });
21941
+ next = await getWorkspaceCodexSubscriptionSourceScoped(scopedDb, input.workspaceId);
21942
+ }
21943
+ if (next.effectiveSource !== effectiveSourceBeforeMutation) {
21944
+ const [activeCodexTurn] = await scopedDb
21945
+ .select({ id: schema.sessionTurns.id })
21946
+ .from(schema.sessionTurns)
21947
+ .where(
21948
+ and(
21949
+ eq(schema.sessionTurns.workspaceId, input.workspaceId),
21950
+ inArray(schema.sessionTurns.status, ["running", "requires_action", "recovering"]),
21951
+ sql`${schema.sessionTurns.model} like 'codex/%'`,
21952
+ ),
21953
+ )
21954
+ .limit(1);
21955
+ const [liveLease] = await scopedDb
21956
+ .select({ id: schema.codexCredentialLeases.id })
21957
+ .from(schema.codexCredentialLeases)
21958
+ .where(
21959
+ and(
21960
+ eq(schema.codexCredentialLeases.workspaceId, input.workspaceId),
21961
+ gt(schema.codexCredentialLeases.leasedUntil, new Date()),
21962
+ ),
21963
+ )
21964
+ .limit(1);
21965
+ if (activeCodexTurn || liveLease) {
21966
+ throw new Error("Codex subscription source cannot change while active turns are using it");
21967
+ }
21968
+ }
21969
+ return next;
21359
21970
  }
21360
21971
 
21361
21972
  export async function setWorkspaceCodexSubscriptionMode(
@@ -29375,6 +29986,8 @@ export type SessionCreateInput = {
29375
29986
  policyRole?: string | null;
29376
29987
  parentSessionId?: string | null;
29377
29988
  createIdempotencyKey?: string | null;
29989
+ /** Exact explicit installed-Skill selection used for keyed-create replay. */
29990
+ selectedInstalledSkillIds?: string[];
29378
29991
  sandboxGroupId?: string | null;
29379
29992
  sandboxOs?: SandboxOs;
29380
29993
  /** Exact accepted generated-session compaction policy; internal lifecycle callers only. */
@@ -29394,7 +30007,11 @@ export type SessionCreateInput = {
29394
30007
  /** Trusted, transaction-local linkage run after the exact session row and
29395
30008
  * MCP metadata exist but before either can commit. It must perform database
29396
30009
  * work only; throwing rolls the complete create/replay transaction back. */
29397
- beforeCreateCommit?: (tx: Database, sessionId: string) => Promise<void>;
30010
+ beforeCreateCommit?: (
30011
+ tx: Database,
30012
+ sessionId: string,
30013
+ context?: { created: boolean },
30014
+ ) => Promise<void>;
29398
30015
  };
29399
30016
 
29400
30017
  type SessionDepthDecision =
@@ -29658,6 +30275,68 @@ async function existingSessionForCreateKey(
29658
30275
  return existing;
29659
30276
  }
29660
30277
 
30278
+ type SessionCreateReplayIdentity = {
30279
+ requestedSessionId?: string;
30280
+ visibility?: "user_private" | "workspace_shared";
30281
+ variableSetIds: string[];
30282
+ selectedInstalledSkillIds: string[];
30283
+ initialPersonalResourceAttachmentIntent?: PersonalResourceAttachmentIntent | null;
30284
+ };
30285
+
30286
+ // Session metadata is immutable after creation and already participates in
30287
+ // every historical schema fixture. Keep this reserved request-identity fact
30288
+ // there instead of making current code require a post-fixture sessions column.
30289
+ // Create admission always replaces a caller-supplied value for this key.
30290
+ const SESSION_CREATE_SELECTED_INSTALLED_SKILL_IDS_METADATA_KEY =
30291
+ "_opengeni_session_create_selected_installed_skill_ids_v1";
30292
+
30293
+ function metadataWithSelectedInstalledSkillCreateIdentity(
30294
+ metadata: Record<string, unknown>,
30295
+ selectedInstalledSkillIds: string[],
30296
+ ): Record<string, unknown> {
30297
+ const next = { ...metadata };
30298
+ delete next[SESSION_CREATE_SELECTED_INSTALLED_SKILL_IDS_METADATA_KEY];
30299
+ if (selectedInstalledSkillIds.length > 0) {
30300
+ next[SESSION_CREATE_SELECTED_INSTALLED_SKILL_IDS_METADATA_KEY] = [...selectedInstalledSkillIds];
30301
+ }
30302
+ return next;
30303
+ }
30304
+
30305
+ function selectedInstalledSkillCreateIdentity(metadata: unknown): string[] {
30306
+ if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) return [];
30307
+ const value = (metadata as Record<string, unknown>)[
30308
+ SESSION_CREATE_SELECTED_INSTALLED_SKILL_IDS_METADATA_KEY
30309
+ ];
30310
+ return Array.isArray(value) && value.every((entry) => typeof entry === "string") ? value : [];
30311
+ }
30312
+
30313
+ function assertSessionCreateReplayIdentity(
30314
+ existing: typeof schema.sessions.$inferSelect,
30315
+ input: SessionCreateReplayIdentity,
30316
+ ): void {
30317
+ if (input.requestedSessionId && existing.id !== input.requestedSessionId) {
30318
+ throw new SessionIdConflictError(input.requestedSessionId);
30319
+ }
30320
+ if (
30321
+ stableJson(existing.initialPersonalResourceAttachmentIntent ?? null) !==
30322
+ stableJson(input.initialPersonalResourceAttachmentIntent ?? null)
30323
+ ) {
30324
+ throw new SessionCreateIdempotencyConflictError();
30325
+ }
30326
+ if (stableJson(existing.variableSetIds ?? []) !== stableJson(input.variableSetIds)) {
30327
+ throw new SessionCreateIdempotencyConflictError();
30328
+ }
30329
+ if (
30330
+ stableJson(selectedInstalledSkillCreateIdentity(existing.metadata)) !==
30331
+ stableJson(input.selectedInstalledSkillIds)
30332
+ ) {
30333
+ throw new SessionCreateIdempotencyConflictError();
30334
+ }
30335
+ if (existing.createRequestedVisibility !== (input.visibility ?? "workspace_shared")) {
30336
+ throw new SessionCreateIdempotencyConflictError();
30337
+ }
30338
+ }
30339
+
29661
30340
  async function lockSessionCreateIdempotencyKey(
29662
30341
  tx: Database,
29663
30342
  workspaceId: string,
@@ -29747,6 +30426,11 @@ async function createSessionInTransaction(
29747
30426
  ): Promise<SessionCreateResult> {
29748
30427
  const variableSetIds = input.variableSetIds ?? (input.variableSetId ? [input.variableSetId] : []);
29749
30428
  const variableSetId = variableSetIds.at(-1) ?? null;
30429
+ const selectedInstalledSkillIds = input.selectedInstalledSkillIds ?? [];
30430
+ const sessionMetadata = metadataWithSelectedInstalledSkillCreateIdentity(
30431
+ input.metadata,
30432
+ selectedInstalledSkillIds,
30433
+ );
29750
30434
  const createIdempotencyKey = input.createIdempotencyKey ?? null;
29751
30435
  const createRequestedVisibility = input.visibility ?? "workspace_shared";
29752
30436
  if (createRequestedVisibility === "user_private") {
@@ -29793,21 +30477,14 @@ async function createSessionInTransaction(
29793
30477
  // caller-provided identity agrees with the winning request. Check this
29794
30478
  // before any first-turn repair so a retry cannot hide a requested-ID
29795
30479
  // conflict behind an apparently successful replay.
29796
- if (input.requestedSessionId && existing.id !== input.requestedSessionId) {
29797
- throw new SessionIdConflictError(input.requestedSessionId);
29798
- }
29799
- if (
29800
- stableJson(existing.initialPersonalResourceAttachmentIntent ?? null) !==
29801
- stableJson(input.initialPersonalResourceAttachmentIntent ?? null)
29802
- ) {
29803
- throw new SessionCreateIdempotencyConflictError();
29804
- }
29805
- if (stableJson(existing.variableSetIds ?? []) !== stableJson(variableSetIds)) {
29806
- throw new SessionCreateIdempotencyConflictError();
29807
- }
29808
- if (existing.createRequestedVisibility !== createRequestedVisibility) {
29809
- throw new SessionCreateIdempotencyConflictError();
29810
- }
30480
+ assertSessionCreateReplayIdentity(existing, {
30481
+ ...(input.requestedSessionId ? { requestedSessionId: input.requestedSessionId } : {}),
30482
+ visibility: createRequestedVisibility,
30483
+ variableSetIds,
30484
+ selectedInstalledSkillIds: input.selectedInstalledSkillIds ?? [],
30485
+ initialPersonalResourceAttachmentIntent:
30486
+ input.initialPersonalResourceAttachmentIntent ?? null,
30487
+ });
29811
30488
  const grouped = await sessionMcpServerMetadataForSessions(tx, input.workspaceId, [
29812
30489
  existing.id,
29813
30490
  ]);
@@ -29819,7 +30496,7 @@ async function createSessionInTransaction(
29819
30496
  if (!canonicalExisting) {
29820
30497
  throw new Error(`Session event cursor missing for session ${existing.id}`);
29821
30498
  }
29822
- await input.beforeCreateCommit?.(tx, existing.id);
30499
+ await input.beforeCreateCommit?.(tx, existing.id, { created: false });
29823
30500
  return {
29824
30501
  session: await mapSessionWithControl(tx, canonicalExisting, grouped.get(existing.id) ?? []),
29825
30502
  created: false,
@@ -29932,7 +30609,7 @@ async function createSessionInTransaction(
29932
30609
  mode: "explicit",
29933
30610
  inheritedFromSessionId: input.parentSessionId ?? null,
29934
30611
  },
29935
- metadata: input.metadata,
30612
+ metadata: sessionMetadata,
29936
30613
  ...creatorColumns(frozenCreator),
29937
30614
  ...(privateCreateOwnerMembershipId
29938
30615
  ? {
@@ -30006,21 +30683,14 @@ async function createSessionInTransaction(
30006
30683
  createIdempotencyKey,
30007
30684
  );
30008
30685
  if (existing) {
30009
- if (input.requestedSessionId && existing.id !== input.requestedSessionId) {
30010
- throw new SessionIdConflictError(input.requestedSessionId);
30011
- }
30012
- if (
30013
- stableJson(existing.initialPersonalResourceAttachmentIntent ?? null) !==
30014
- stableJson(input.initialPersonalResourceAttachmentIntent ?? null)
30015
- ) {
30016
- throw new SessionCreateIdempotencyConflictError();
30017
- }
30018
- if (stableJson(existing.variableSetIds ?? []) !== stableJson(variableSetIds)) {
30019
- throw new SessionCreateIdempotencyConflictError();
30020
- }
30021
- if (existing.createRequestedVisibility !== createRequestedVisibility) {
30022
- throw new SessionCreateIdempotencyConflictError();
30023
- }
30686
+ assertSessionCreateReplayIdentity(existing, {
30687
+ ...(input.requestedSessionId ? { requestedSessionId: input.requestedSessionId } : {}),
30688
+ visibility: createRequestedVisibility,
30689
+ variableSetIds,
30690
+ selectedInstalledSkillIds: input.selectedInstalledSkillIds ?? [],
30691
+ initialPersonalResourceAttachmentIntent:
30692
+ input.initialPersonalResourceAttachmentIntent ?? null,
30693
+ });
30024
30694
  const grouped = await sessionMcpServerMetadataForSessions(tx, input.workspaceId, [
30025
30695
  existing.id,
30026
30696
  ]);
@@ -30032,7 +30702,7 @@ async function createSessionInTransaction(
30032
30702
  if (!canonicalExisting) {
30033
30703
  throw new Error(`Session event cursor missing for session ${existing.id}`);
30034
30704
  }
30035
- await input.beforeCreateCommit?.(tx, existing.id);
30705
+ await input.beforeCreateCommit?.(tx, existing.id, { created: false });
30036
30706
  return {
30037
30707
  session: await mapSessionWithControl(
30038
30708
  tx,
@@ -30069,7 +30739,7 @@ async function createSessionInTransaction(
30069
30739
  sessionId: inserted.id,
30070
30740
  servers: input.mcpServers ?? [],
30071
30741
  });
30072
- await input.beforeCreateCommit?.(tx, inserted.id);
30742
+ await input.beforeCreateCommit?.(tx, inserted.id, { created: true });
30073
30743
  return {
30074
30744
  session: await mapSessionWithControl(tx, inserted, mcpServers),
30075
30745
  created: true,
@@ -30174,6 +30844,168 @@ export async function getSessionByCreateIdempotencyKey(
30174
30844
  });
30175
30845
  }
30176
30846
 
30847
+ /**
30848
+ * Recover a keyed create before mutable create dependencies (notably the
30849
+ * current model catalog) are consulted. A fully initialized queued turn gets a
30850
+ * fresh durable wake revision from persisted state only; an uninitialized shell
30851
+ * returns its persisted model so the caller can reopen it strictly as retained
30852
+ * while the canonical keyed transaction repairs initialization. The same
30853
+ * request-identity checks as transactional create replay run before any session
30854
+ * or committed denial is returned.
30855
+ */
30856
+ export type InitializedSessionCreateReplay =
30857
+ | {
30858
+ outcome: "session";
30859
+ session: Session & { initialTurnId: string | null };
30860
+ temporalWorkflowId: string;
30861
+ workflowWakeRevision: number | null;
30862
+ changed: boolean;
30863
+ }
30864
+ | {
30865
+ outcome: "pending";
30866
+ session: Session;
30867
+ }
30868
+ | { outcome: "denied"; denial: SessionSpawnDenial };
30869
+
30870
+ export async function getInitializedSessionCreateReplay(
30871
+ db: Database,
30872
+ input: {
30873
+ accountId: string;
30874
+ workspaceId: string;
30875
+ subjectId: string;
30876
+ activeManagedHumanSubjectId?: string;
30877
+ createIdempotencyKey: string;
30878
+ requestedSessionId?: string;
30879
+ visibility?: "user_private" | "workspace_shared";
30880
+ variableSetIds: string[];
30881
+ selectedInstalledSkillIds: string[];
30882
+ initialPersonalResourceAttachmentIntent?: PersonalResourceAttachmentIntent | null;
30883
+ deferInitialTurn?: boolean;
30884
+ },
30885
+ ): Promise<InitializedSessionCreateReplay | null> {
30886
+ return await withWorkspaceSubjectSessionActivityRls(
30887
+ db,
30888
+ input.workspaceId,
30889
+ input.subjectId,
30890
+ async (scopedDb) => {
30891
+ if (input.visibility === "user_private") {
30892
+ await scopedDb.execute(
30893
+ sql`select pg_advisory_xact_lock(hashtextextended(${`organization-membership:${input.accountId}`}, 0))`,
30894
+ );
30895
+ }
30896
+ await lockWorkspaceForSessionCreate(scopedDb, input.workspaceId, input.accountId);
30897
+ if (input.activeManagedHumanSubjectId) {
30898
+ await setSubjectRlsContext(scopedDb, input.activeManagedHumanSubjectId);
30899
+ await assertActiveManagedHumanOrganizationMembership(scopedDb, {
30900
+ accountId: input.accountId,
30901
+ subjectId: input.activeManagedHumanSubjectId,
30902
+ });
30903
+ }
30904
+ await lockSessionCreateIdempotencyKey(
30905
+ scopedDb,
30906
+ input.workspaceId,
30907
+ input.createIdempotencyKey,
30908
+ );
30909
+
30910
+ const existing = await existingSessionForCreateKey(
30911
+ scopedDb,
30912
+ input.workspaceId,
30913
+ input.createIdempotencyKey,
30914
+ );
30915
+ if (!existing) {
30916
+ const denial = await existingSpawnDenialForKey(
30917
+ scopedDb,
30918
+ input.workspaceId,
30919
+ input.createIdempotencyKey,
30920
+ );
30921
+ return denial ? { outcome: "denied", denial } : null;
30922
+ }
30923
+ assertSessionCreateReplayIdentity(existing, input);
30924
+
30925
+ const startEvents = await scopedDb
30926
+ .select({ id: schema.sessionEvents.id, type: schema.sessionEvents.type })
30927
+ .from(schema.sessionEvents)
30928
+ .where(
30929
+ and(
30930
+ eq(schema.sessionEvents.workspaceId, input.workspaceId),
30931
+ eq(schema.sessionEvents.sessionId, existing.id),
30932
+ inArray(schema.sessionEvents.type, ["session.created", "user.message"]),
30933
+ ),
30934
+ )
30935
+ .orderBy(asc(schema.sessionEvents.sequence))
30936
+ .limit(2);
30937
+ const createdEvent = startEvents.find((event) => event.type === "session.created");
30938
+ const initialUserEvent = startEvents.find((event) => event.type === "user.message");
30939
+
30940
+ const grouped = await sessionMcpServerMetadataForSessions(scopedDb, input.workspaceId, [
30941
+ existing.id,
30942
+ ]);
30943
+ const session = await mapSessionWithControl(
30944
+ scopedDb,
30945
+ existing,
30946
+ grouped.get(existing.id) ?? [],
30947
+ );
30948
+ if (!createdEvent) {
30949
+ return { outcome: "pending", session };
30950
+ }
30951
+ const [initialTurn] = await scopedDb
30952
+ .select({
30953
+ id: schema.sessionTurns.id,
30954
+ status: schema.sessionTurns.status,
30955
+ })
30956
+ .from(schema.sessionTurns)
30957
+ .where(
30958
+ and(
30959
+ eq(schema.sessionTurns.workspaceId, input.workspaceId),
30960
+ eq(schema.sessionTurns.sessionId, existing.id),
30961
+ initialUserEvent
30962
+ ? eq(schema.sessionTurns.triggerEventId, initialUserEvent.id)
30963
+ : sql`false`,
30964
+ ),
30965
+ )
30966
+ .limit(1);
30967
+
30968
+ const temporalWorkflowId = existing.temporalWorkflowId ?? `session-${existing.id}`;
30969
+ if (existing.status === "cancelled") {
30970
+ return {
30971
+ outcome: "session",
30972
+ session: { ...session, initialTurnId: initialTurn?.id ?? null },
30973
+ temporalWorkflowId,
30974
+ workflowWakeRevision: null,
30975
+ changed: false,
30976
+ };
30977
+ }
30978
+ if (input.deferInitialTurn === true) {
30979
+ return {
30980
+ outcome: "session",
30981
+ session: { ...session, initialTurnId: initialTurn?.id ?? null },
30982
+ temporalWorkflowId,
30983
+ workflowWakeRevision: null,
30984
+ changed: false,
30985
+ };
30986
+ }
30987
+ if (!initialTurn) return null;
30988
+ const workflowWakeRevision =
30989
+ initialTurn.status === "queued" && session.effectiveControl.state === "active"
30990
+ ? await registerSessionWorkflowWakeInTransaction(scopedDb, {
30991
+ accountId: input.accountId,
30992
+ workspaceId: input.workspaceId,
30993
+ sessionId: existing.id,
30994
+ temporalWorkflowId,
30995
+ reason: "initial_session",
30996
+ })
30997
+ : null;
30998
+ return {
30999
+ outcome: "session",
31000
+ session: { ...session, initialTurnId: initialTurn.id },
31001
+ temporalWorkflowId,
31002
+ workflowWakeRevision,
31003
+ changed: workflowWakeRevision !== null,
31004
+ };
31005
+ },
31006
+ );
31007
+ }
31008
+
30177
31009
  export async function getSession(
30178
31010
  db: Database,
30179
31011
  workspaceId: string,
@@ -33691,7 +34523,10 @@ async function sessionDiscoveryClaimsForSessions(
33691
34523
  );
33692
34524
  const grouped = new Map<string, { claims: WorkClaimDiscoverySummary[]; truncated: boolean }>();
33693
34525
  for (const row of rows) {
33694
- const entry = grouped.get(row.sessionId) ?? { claims: [], truncated: false };
34526
+ const entry = grouped.get(row.sessionId) ?? {
34527
+ claims: [],
34528
+ truncated: false,
34529
+ };
33695
34530
  entry.claims.push(
33696
34531
  WorkClaimDiscoverySummarySchema.parse({
33697
34532
  id: row.id,
@@ -34013,7 +34848,10 @@ export async function listSessionDiscoverySummaries(
34013
34848
  const relatedAccess = rootRelatedIds.has(sessionId) ? "root" : "target";
34014
34849
  const goal = goalsBySession.get(sessionId);
34015
34850
  const latest = latestBySession.get(sessionId);
34016
- const claims = workClaims.get(sessionId) ?? { claims: [], truncated: false };
34851
+ const claims = workClaims.get(sessionId) ?? {
34852
+ claims: [],
34853
+ truncated: false,
34854
+ };
34017
34855
  const match =
34018
34856
  !includeWorkDiscovery ||
34019
34857
  row.matchClass === null ||
@@ -34525,6 +35363,8 @@ export type SessionEventPage = {
34525
35363
  events: SessionEvent[];
34526
35364
  hasMore: boolean;
34527
35365
  bytes: number;
35366
+ /** True only when every full-mode event is the exact retained database row. */
35367
+ fullPayloadsExact: boolean;
34528
35368
  direction: SessionEventReadDirection;
34529
35369
  coveredSequence: { first: number; last: number } | null;
34530
35370
  nextAfter: number | null;
@@ -34554,12 +35394,21 @@ type SessionEventProjectionRow = {
34554
35394
  duplicateReason: string | null;
34555
35395
  };
34556
35396
 
35397
+ type SessionEventProjectionMetadataRow = {
35398
+ id: string;
35399
+ sequence: number;
35400
+ /** Conservative server-side size including columns omitted from the public event. */
35401
+ transferBytes: number;
35402
+ };
35403
+
34557
35404
  /**
34558
35405
  * Read one direction-aware session-event page. Full mode selects the canonical
34559
- * row exactly; summary/none modes derive bounded monitoring projections in SQL
34560
- * without rewriting the retained source. `bytes` is the exact UTF-8 size of
34561
- * `JSON.stringify(events)`; `hasMore` is true whenever count or byte selection
34562
- * stopped before the durable range ended.
35406
+ * row exactly when it fits the page; an oversized historical row that predates
35407
+ * the durable write guard is projected at the bounded database-read boundary
35408
+ * when it would otherwise strand its cursor forever. Summary/none modes derive
35409
+ * bounded monitoring projections in SQL without rewriting the retained source.
35410
+ * `bytes` is the exact UTF-8 size of `JSON.stringify(events)`; `hasMore` is true
35411
+ * whenever count or byte selection stopped before the durable range ended.
34563
35412
  */
34564
35413
  export async function listSessionEventPage(
34565
35414
  db: Database,
@@ -34594,6 +35443,7 @@ export async function listSessionEventPage(
34594
35443
  let bytes = 2; // []
34595
35444
  let cursor = direction === "before" ? before : after;
34596
35445
  let hasMore = false;
35446
+ let fullPayloadsExact = payloadMode === "full";
34597
35447
  let truncatedBy: SessionEventPage["truncatedBy"] = null;
34598
35448
 
34599
35449
  for (;;) {
@@ -34638,19 +35488,89 @@ export async function listSessionEventPage(
34638
35488
  filters.push(lt(schema.sessionEvents.sequence, before));
34639
35489
  }
34640
35490
 
34641
- const rows = await scopedDb
34642
- .select(sessionEventProjectionSelect(payloadMode))
34643
- .from(schema.sessionEvents)
34644
- .where(and(...filters))
34645
- .orderBy(
34646
- options.authoritativeLatest
34647
- ? desc(schema.sessionEvents.sequence)
34648
- : direction === "before"
34649
- ? desc(schema.sessionEvents.sequence)
34650
- : asc(schema.sessionEvents.sequence),
34651
- )
34652
- .limit(queryLimit);
34653
- if (rows.length === 0) break;
35491
+ const ordering =
35492
+ options.authoritativeLatest || direction === "before"
35493
+ ? desc(schema.sessionEvents.sequence)
35494
+ : asc(schema.sessionEvents.sequence);
35495
+ let rows: SessionEventProjectionRow[];
35496
+ let sourceRowCount: number;
35497
+ let sourceRowsFullyConsumed = true;
35498
+ let databaseReadProjected = false;
35499
+ if (payloadMode === "full") {
35500
+ // Plan the page from bounded metadata before selecting any canonical
35501
+ // payload. row_to_json includes private storage columns omitted by the
35502
+ // public event, so this conservatively bounds payload transfer without
35503
+ // moving an oversized legacy value through postgres.js.
35504
+ const metadataRows = await scopedDb
35505
+ .select(sessionEventProjectionMetadataSelect())
35506
+ .from(schema.sessionEvents)
35507
+ .where(and(...filters))
35508
+ .orderBy(ordering)
35509
+ .limit(queryLimit);
35510
+ sourceRowCount = metadataRows.length;
35511
+ if (metadataRows.length === 0) break;
35512
+
35513
+ const exactIds: string[] = [];
35514
+ let projectId: string | null = null;
35515
+ let estimatedBytes = bytes;
35516
+ for (const metadata of metadataRows) {
35517
+ if (events.length + exactIds.length >= requestedLimit) {
35518
+ hasMore = true;
35519
+ truncatedBy = "count";
35520
+ break;
35521
+ }
35522
+ const separatorBytes = events.length + exactIds.length === 0 ? 0 : 1;
35523
+ const transferBytes = Number(metadata.transferBytes);
35524
+ if (estimatedBytes + separatorBytes + transferBytes > maxBytes) {
35525
+ if (events.length === 0 && exactIds.length === 0) {
35526
+ projectId = metadata.id;
35527
+ } else {
35528
+ hasMore = true;
35529
+ truncatedBy = "bytes";
35530
+ }
35531
+ break;
35532
+ }
35533
+ exactIds.push(metadata.id);
35534
+ estimatedBytes += separatorBytes + transferBytes;
35535
+ }
35536
+ sourceRowsFullyConsumed =
35537
+ exactIds.length + (projectId === null ? 0 : 1) >= metadataRows.length;
35538
+
35539
+ if (projectId !== null) {
35540
+ rows = await scopedDb
35541
+ .select(sessionEventProjectionSelect("full", { databaseReadProjection: true }))
35542
+ .from(schema.sessionEvents)
35543
+ .where(and(...filters, eq(schema.sessionEvents.id, projectId)))
35544
+ .orderBy(ordering)
35545
+ .limit(1);
35546
+ fullPayloadsExact = false;
35547
+ databaseReadProjected = true;
35548
+ } else if (exactIds.length > 0) {
35549
+ rows = await scopedDb
35550
+ .select(sessionEventProjectionSelect("full"))
35551
+ .from(schema.sessionEvents)
35552
+ .where(and(...filters, inArray(schema.sessionEvents.id, exactIds)))
35553
+ .orderBy(ordering)
35554
+ .limit(exactIds.length);
35555
+ } else {
35556
+ rows = [];
35557
+ }
35558
+ } else {
35559
+ rows = await scopedDb
35560
+ .select(sessionEventProjectionSelect(payloadMode))
35561
+ .from(schema.sessionEvents)
35562
+ .where(and(...filters))
35563
+ .orderBy(ordering)
35564
+ .limit(queryLimit);
35565
+ sourceRowCount = rows.length;
35566
+ if (rows.length === 0) break;
35567
+ }
35568
+
35569
+ if (databaseReadProjected) {
35570
+ for (const row of rows) {
35571
+ settleDatabaseReadProjectionPayload(row.payload);
35572
+ }
35573
+ }
34654
35574
 
34655
35575
  for (const row of rows) {
34656
35576
  if (events.length >= requestedLimit) {
@@ -34658,25 +35578,33 @@ export async function listSessionEventPage(
34658
35578
  truncatedBy = "count";
34659
35579
  break;
34660
35580
  }
34661
- const event = mapProjectedEvent(row);
34662
- const eventBytes = utf8JsonBytes(event);
35581
+ let event = mapProjectedEvent(row);
35582
+ let eventBytes = utf8JsonBytes(event);
34663
35583
  const separatorBytes = events.length === 0 ? 0 : 1;
34664
35584
  if (bytes + separatorBytes + eventBytes > maxBytes) {
34665
35585
  if (events.length === 0) {
34666
- throw new RangeError(
34667
- `A projected session event cannot fit in the database page envelope (${eventBytes + 2} > ${maxBytes} bytes)`,
34668
- );
35586
+ if (payloadMode === "full") {
35587
+ event = boundSessionEvent(event, { surface: "database_read_projection" });
35588
+ eventBytes = utf8JsonBytes(event);
35589
+ fullPayloadsExact = false;
35590
+ }
35591
+ if (eventBytes + 2 > maxBytes) {
35592
+ throw new RangeError(
35593
+ `A projected session event cannot fit in the database page envelope (${eventBytes + 2} > ${maxBytes} bytes)`,
35594
+ );
35595
+ }
35596
+ } else {
35597
+ hasMore = true;
35598
+ truncatedBy = "bytes";
35599
+ break;
34669
35600
  }
34670
- hasMore = true;
34671
- truncatedBy = "bytes";
34672
- break;
34673
35601
  }
34674
35602
  events.push(event);
34675
35603
  bytes += separatorBytes + eventBytes;
34676
35604
  cursor = event.sequence;
34677
35605
  }
34678
35606
  if (hasMore) break;
34679
- if (rows.length < queryLimit) break;
35607
+ if (sourceRowsFullyConsumed && sourceRowCount < queryLimit) break;
34680
35608
  }
34681
35609
 
34682
35610
  if (direction === "before") events.reverse();
@@ -34686,6 +35614,7 @@ export async function listSessionEventPage(
34686
35614
  events,
34687
35615
  hasMore,
34688
35616
  bytes,
35617
+ fullPayloadsExact,
34689
35618
  direction,
34690
35619
  coveredSequence: first === null || last === null ? null : { first, last },
34691
35620
  nextAfter: direction === "after" ? (last ?? after) : null,
@@ -34695,7 +35624,18 @@ export async function listSessionEventPage(
34695
35624
  });
34696
35625
  }
34697
35626
 
34698
- function sessionEventProjectionSelect(payloadMode: SessionEventPayloadMode = "full") {
35627
+ function sessionEventProjectionMetadataSelect() {
35628
+ return {
35629
+ id: schema.sessionEvents.id,
35630
+ sequence: schema.sessionEvents.sequence,
35631
+ transferBytes: sql<number>`octet_length(row_to_json(${schema.sessionEvents})::text)::int`,
35632
+ } satisfies Record<keyof SessionEventProjectionMetadataRow, unknown>;
35633
+ }
35634
+
35635
+ function sessionEventProjectionSelect(
35636
+ payloadMode: SessionEventPayloadMode = "full",
35637
+ options: { databaseReadProjection?: boolean } = {},
35638
+ ) {
34699
35639
  const typeInvalid = sql`(
34700
35640
  octet_length(${schema.sessionEvents.type}) > ${SESSION_EVENT_TYPE_MAX_BYTES}
34701
35641
  or position(E'\\n' in ${schema.sessionEvents.type}) > 0
@@ -34774,10 +35714,40 @@ function sessionEventProjectionSelect(payloadMode: SessionEventPayloadMode = "fu
34774
35714
  )
34775
35715
  else opengeni_private.project_session_event_payload(${schema.sessionEvents.payload})
34776
35716
  end`;
35717
+ const databaseReadProjectedPayload = sql<unknown>`case
35718
+ when ${envelopeInvalid} then ${projectedPayload}
35719
+ else jsonb_build_object(
35720
+ 'preview', jsonb_build_object(
35721
+ 'head', left(${schema.sessionEvents.payload}::text, 2048),
35722
+ 'omission', '[middle payload bytes omitted at bounded database read boundary]',
35723
+ 'tail', right(${schema.sessionEvents.payload}::text, 2048)
35724
+ ),
35725
+ 'truncation', jsonb_build_object(
35726
+ 'truncated', true,
35727
+ 'surface', 'database_read_projection',
35728
+ 'reason', 'payload_bytes_exceeded',
35729
+ 'originalBytes', octet_length(${schema.sessionEvents.payload}::text),
35730
+ 'deliveredBytes', 0,
35731
+ 'omittedBytes', octet_length(${schema.sessionEvents.payload}::text),
35732
+ 'estimatedOriginalTokens', ceil(
35733
+ octet_length(${schema.sessionEvents.payload}::text) / 4.0
35734
+ )::integer,
35735
+ 'estimatedDeliveredTokens', 0,
35736
+ 'fullEvidence', jsonb_build_object('available', false, 'reason', 'not_retained'),
35737
+ 'details', jsonb_build_array(jsonb_build_object(
35738
+ 'path', '$',
35739
+ 'kind', 'object',
35740
+ 'originalBytes', octet_length(${schema.sessionEvents.payload}::text)
35741
+ ))
35742
+ )
35743
+ )
35744
+ end`;
34777
35745
  const projectedPayloadBytes = sql<number>`octet_length((${projectedPayload})::text)`;
34778
35746
  const selectedPayload =
34779
35747
  payloadMode === "full"
34780
- ? schema.sessionEvents.payload
35748
+ ? options.databaseReadProjection
35749
+ ? databaseReadProjectedPayload
35750
+ : schema.sessionEvents.payload
34781
35751
  : payloadMode === "none"
34782
35752
  ? sql<unknown>`jsonb_build_object(
34783
35753
  '_monitoring', jsonb_build_object(
@@ -34804,24 +35774,59 @@ function sessionEventProjectionSelect(payloadMode: SessionEventPayloadMode = "fu
34804
35774
  workspaceId: schema.sessionEvents.workspaceId,
34805
35775
  sessionId: schema.sessionEvents.sessionId,
34806
35776
  sequence: schema.sessionEvents.sequence,
34807
- type: payloadMode === "full" ? schema.sessionEvents.type : projectedType,
35777
+ type:
35778
+ payloadMode === "full" && !options.databaseReadProjection
35779
+ ? schema.sessionEvents.type
35780
+ : projectedType,
34808
35781
  payload: selectedPayload,
34809
35782
  payloadCodecVersion:
34810
- payloadMode === "full" ? schema.sessionEvents.payloadCodecVersion : sql<number | null>`null`,
35783
+ payloadMode === "full" && !options.databaseReadProjection
35784
+ ? schema.sessionEvents.payloadCodecVersion
35785
+ : sql<number | null>`null`,
34811
35786
  occurredAt: schema.sessionEvents.occurredAt,
34812
35787
  clientEventId:
34813
- payloadMode === "full" ? schema.sessionEvents.clientEventId : projectedClientEventId,
35788
+ payloadMode === "full" && !options.databaseReadProjection
35789
+ ? schema.sessionEvents.clientEventId
35790
+ : projectedClientEventId,
34814
35791
  turnId: schema.sessionEvents.turnId,
34815
35792
  turnGeneration: schema.sessionEvents.turnGeneration,
34816
35793
  turnAttemptId: schema.sessionEvents.turnAttemptId,
34817
35794
  turnAssociation:
34818
- payloadMode === "full" ? schema.sessionEvents.turnAssociation : projectedTurnAssociation,
35795
+ payloadMode === "full" && !options.databaseReadProjection
35796
+ ? schema.sessionEvents.turnAssociation
35797
+ : projectedTurnAssociation,
34819
35798
  duplicateOfEventId: schema.sessionEvents.duplicateOfEventId,
34820
35799
  duplicateReason:
34821
- payloadMode === "full" ? schema.sessionEvents.duplicateReason : projectedDuplicateReason,
35800
+ payloadMode === "full" && !options.databaseReadProjection
35801
+ ? schema.sessionEvents.duplicateReason
35802
+ : projectedDuplicateReason,
34822
35803
  };
34823
35804
  }
34824
35805
 
35806
+ function settleDatabaseReadProjectionPayload(payload: unknown): void {
35807
+ const truncation = sessionEventPayloadTruncation(payload);
35808
+ if (truncation?.surface !== "database_read_projection") return;
35809
+ for (let attempt = 0; attempt < 16; attempt += 1) {
35810
+ const deliveredBytes = sessionEventJsonBytes(payload);
35811
+ const omittedBytes =
35812
+ truncation.originalBytes === null
35813
+ ? null
35814
+ : Math.max(0, truncation.originalBytes - deliveredBytes);
35815
+ const deliveredTokens = approximateSessionEventTokens(deliveredBytes);
35816
+ if (
35817
+ truncation.deliveredBytes === deliveredBytes &&
35818
+ truncation.omittedBytes === omittedBytes &&
35819
+ truncation.estimatedDeliveredTokens === deliveredTokens
35820
+ ) {
35821
+ return;
35822
+ }
35823
+ truncation.deliveredBytes = deliveredBytes;
35824
+ truncation.omittedBytes = omittedBytes;
35825
+ truncation.estimatedDeliveredTokens = deliveredTokens;
35826
+ }
35827
+ throw new RangeError("Database-read session event payload byte accounting did not converge");
35828
+ }
35829
+
34825
35830
  export async function listSessionEvents(
34826
35831
  db: Database,
34827
35832
  workspaceId: string,
@@ -35804,7 +36809,10 @@ export async function acceptSessionHumanInputResponse(
35804
36809
  turnAssociation: "current",
35805
36810
  sequence: session.lastSequence + 1,
35806
36811
  type: "user.humanInputResponse",
35807
- payload: { requestId: request.id, response: { outcome: "cancelled" } },
36812
+ payload: {
36813
+ requestId: request.id,
36814
+ response: { outcome: "cancelled" },
36815
+ },
35808
36816
  clientEventId: null,
35809
36817
  occurredAt,
35810
36818
  },
@@ -35840,7 +36848,10 @@ export async function acceptSessionHumanInputResponse(
35840
36848
  });
35841
36849
  await tx
35842
36850
  .update(schema.sessions)
35843
- .set({ lastSequence: session.lastSequence + 1, updatedAt: new Date() })
36851
+ .set({
36852
+ lastSequence: session.lastSequence + 1,
36853
+ updatedAt: new Date(),
36854
+ })
35844
36855
  .where(eq(schema.sessions.id, session.id));
35845
36856
  event = mapEvent(inserted);
35846
36857
  repairedEvents = [event];
@@ -38960,6 +39971,8 @@ type LeaseRow = {
38960
39971
  archive_capture_generation: number | string | null;
38961
39972
  archive_capture_started_at: Date | string | null;
38962
39973
  archive_capture_deadline_at: Date | string | null;
39974
+ /** Transaction-local PostgreSQL-clock projection used only by bounded waiters. */
39975
+ archive_capture_remaining_ms?: number | string | null;
38963
39976
  archive_capture_published_at: Date | string | null;
38964
39977
  reaper_hold_id: string | null;
38965
39978
  reaper_hold_until: Date | string | null;
@@ -39149,6 +40162,9 @@ export type AcquireLeaseResult =
39149
40162
  | "rotation_in_progress"
39150
40163
  | "provider_recovery_in_progress";
39151
40164
  lease: LeaseSnapshot;
40165
+ /** Remaining durable capture budget from PostgreSQL's clock. Present only
40166
+ * for an exact active capture; callers cap it at the lifecycle ceiling. */
40167
+ captureRemainingMs?: number;
39152
40168
  };
39153
40169
 
39154
40170
  export type SandboxSharedPreparationSnapshot = {
@@ -39179,7 +40195,10 @@ export type ClaimSandboxSharedPreparationInput = {
39179
40195
  };
39180
40196
 
39181
40197
  export type ClaimSandboxSharedPreparationResult =
39182
- | { role: "owner" | "joined" | "reused"; preparation: SandboxSharedPreparationSnapshot }
40198
+ | {
40199
+ role: "owner" | "joined" | "reused";
40200
+ preparation: SandboxSharedPreparationSnapshot;
40201
+ }
39183
40202
  | { role: "fenced"; lease: LeaseSnapshot | null };
39184
40203
 
39185
40204
  function validateSandboxSharedPreparationIdentity(input: ClaimSandboxSharedPreparationInput): void {
@@ -39304,7 +40323,10 @@ export async function claimSandboxSharedPreparation(
39304
40323
  Number(row.lease_epoch) !== input.expectedLeaseEpoch ||
39305
40324
  row.instance_id !== input.expectedInstanceId
39306
40325
  ) {
39307
- return { role: "fenced" as const, lease: row ? mapLeaseRow(row) : null };
40326
+ return {
40327
+ role: "fenced" as const,
40328
+ lease: row ? mapLeaseRow(row) : null,
40329
+ };
39308
40330
  }
39309
40331
  const holderRows = await tx.execute<{ present: boolean }>(sql`
39310
40332
  select exists (
@@ -39701,6 +40723,33 @@ function leaseBackendRequiresResumableInstance(row: LeaseRow): boolean {
39701
40723
  return fields === undefined || fields.length > 0;
39702
40724
  }
39703
40725
 
40726
+ function archiveCaptureRemainingMs(
40727
+ row: Pick<LeaseRow, "archive_capture_remaining_ms">,
40728
+ ): number | null {
40729
+ if (row.archive_capture_remaining_ms === null || row.archive_capture_remaining_ms === undefined) {
40730
+ return null;
40731
+ }
40732
+ const remainingMs = Number(row.archive_capture_remaining_ms);
40733
+ return Number.isSafeInteger(remainingMs) &&
40734
+ remainingMs >= 0 &&
40735
+ remainingMs <= SANDBOX_LIFECYCLE_TRANSITION_MAX_WAIT_MS
40736
+ ? remainingMs
40737
+ : null;
40738
+ }
40739
+
40740
+ function extendSandboxTransitionDeadline(
40741
+ currentDeadline: number,
40742
+ hardDeadline: number,
40743
+ remainingCaptureMs: number | null | undefined,
40744
+ now: number,
40745
+ ): number {
40746
+ if (remainingCaptureMs === null || remainingCaptureMs === undefined) return currentDeadline;
40747
+ return Math.min(
40748
+ hardDeadline,
40749
+ Math.max(currentDeadline, now + remainingCaptureMs + SANDBOX_LIFECYCLE_RETRY_HANDOFF_GRACE_MS),
40750
+ );
40751
+ }
40752
+
39704
40753
  function providerlessLeaseBackendPredicateSql() {
39705
40754
  const providerlessBackends = Object.entries(SANDBOX_PROVIDER_INSTANCE_ID_FIELDS_BY_BACKEND)
39706
40755
  .filter(([, fields]) => fields.length === 0)
@@ -40259,9 +41308,19 @@ async function acquireLeaseOnce(
40259
41308
  // UPDATE (block, do NOT skip) — unlike concurrent enrollment scans,
40260
41309
  // because we WANT the loser to block then attach, not skip and lose.
40261
41310
  const rows = await tx.execute<LeaseRow>(sql`
40262
- select *
40263
- from sandbox_leases
40264
- where workspace_id = ${workspaceId} and sandbox_group_id = ${sandboxGroupId}
41311
+ select lease.*,
41312
+ case
41313
+ when lease.archive_capture_deadline_at is null then null
41314
+ else greatest(
41315
+ 0,
41316
+ ceil(extract(epoch from (
41317
+ lease.archive_capture_deadline_at - clock_timestamp()
41318
+ )) * 1000)
41319
+ )::bigint
41320
+ end as archive_capture_remaining_ms
41321
+ from sandbox_leases as lease
41322
+ where lease.workspace_id = ${workspaceId}
41323
+ and lease.sandbox_group_id = ${sandboxGroupId}
40265
41324
  for update
40266
41325
  `);
40267
41326
  const row = rows[0];
@@ -40293,10 +41352,12 @@ async function acquireLeaseOnce(
40293
41352
  // another holder while that operation owns the exact lease: the caller
40294
41353
  // retries after the claim clears, before it can reach the provider.
40295
41354
  if (row.archive_capture_id !== null) {
41355
+ const captureRemainingMs = archiveCaptureRemainingMs(row);
40296
41356
  return {
40297
41357
  role: "fenced" as const,
40298
41358
  reason: "capture_in_progress" as const,
40299
41359
  lease: mapLeaseRow(row),
41360
+ ...(captureRemainingMs === null ? {} : { captureRemainingMs }),
40300
41361
  };
40301
41362
  }
40302
41363
 
@@ -40520,20 +41581,30 @@ export async function acquireLease(
40520
41581
  }
40521
41582
  // A bounded in-process wait must use a monotonic clock. Wall-clock steps from
40522
41583
  // NTP/VM resume may change timestamp interpretation, but must never stretch
40523
- // or prematurely consume the caller's resource budget.
40524
- const deadline = performance.now() + captureWaitMs;
41584
+ // or prematurely consume the caller's resource budget. PostgreSQL projects
41585
+ // an active claim's remaining time on its own authoritative clock; that may
41586
+ // extend a positive caller budget after a rolling config change, but never
41587
+ // beyond the same one-hour lifecycle ceiling.
41588
+ const startedAt = performance.now();
41589
+ const hardDeadline = startedAt + SANDBOX_LIFECYCLE_TRANSITION_MAX_WAIT_MS;
41590
+ let deadline = Math.min(hardDeadline, startedAt + captureWaitMs);
40525
41591
  let delayMs = 25;
40526
41592
  for (;;) {
40527
41593
  const result = await acquireLeaseOnce(db, input);
40528
- if (
40529
- result.role !== "fenced" ||
40530
- result.reason === "superseded" ||
40531
- performance.now() >= deadline
40532
- ) {
41594
+ const now = performance.now();
41595
+ if (captureWaitMs > 0 && result.role === "fenced" && result.reason === "capture_in_progress") {
41596
+ deadline = extendSandboxTransitionDeadline(
41597
+ deadline,
41598
+ hardDeadline,
41599
+ result.captureRemainingMs,
41600
+ now,
41601
+ );
41602
+ }
41603
+ if (result.role !== "fenced" || result.reason === "superseded" || now >= deadline) {
40533
41604
  return result;
40534
41605
  }
40535
41606
  await waitForSandboxTransition(
40536
- Math.min(delayMs, Math.max(1, deadline - performance.now())),
41607
+ Math.min(delayMs, Math.max(1, deadline - now)),
40537
41608
  input.waitSignal,
40538
41609
  "Sandbox lease transition wait cancelled",
40539
41610
  );
@@ -44618,7 +45689,11 @@ export async function reapStaleLeaseHoldersGlobal(
44618
45689
  // A separate lifecycle-aware function settles only stale starting/restoring/
44619
45690
  // suspending/ending transitions. API controller mutations pulse their holder
44620
45691
  // while in flight, so the transition TTL detects an abandoned caller rather
44621
- // than imposing a maximum duration on profile capture or restore.
45692
+ // than imposing a maximum duration on profile capture or restore. The same
45693
+ // lifecycle function is also the hard provider-deadline override: once a
45694
+ // requested finite-lifetime Modal lease is due, its controller resources are
45695
+ // lost and their holders are released without turning timestamp age into a
45696
+ // general active-session expiry policy.
44622
45697
  const rows = await db.transaction(async (txRaw) => {
44623
45698
  const tx = txRaw as unknown as Database;
44624
45699
  await tx.execute(sql`select set_config('opengeni.sandbox_recovery_protocol_v2', '1', true)`);
@@ -44723,7 +45798,10 @@ export async function countSessionRecoveryBacklog(
44723
45798
  quiescence_missing: 0,
44724
45799
  projection_stale: 0,
44725
45800
  };
44726
- const rows = await rawRows<{ state: SessionRecoveryBacklogState; count: number | string }>(
45801
+ const rows = await rawRows<{
45802
+ state: SessionRecoveryBacklogState;
45803
+ count: number | string;
45804
+ }>(
44727
45805
  db,
44728
45806
  sql`
44729
45807
  select state, count
@@ -44736,6 +45814,36 @@ export async function countSessionRecoveryBacklog(
44736
45814
  return counts;
44737
45815
  }
44738
45816
 
45817
+ export type ContextCompactionPendingSummary = {
45818
+ pendingCount: number;
45819
+ oldestStartedAt: Date | null;
45820
+ };
45821
+
45822
+ /**
45823
+ * Content-free cross-workspace projection of exact active attempts whose latest
45824
+ * automatic compaction landmark is still `started`. The trigger-maintained
45825
+ * private ledger survives turn-worker loss without exposing tenant identities.
45826
+ */
45827
+ export async function getContextCompactionPendingSummary(
45828
+ db: Database,
45829
+ ): Promise<ContextCompactionPendingSummary> {
45830
+ const rows = await rawRows<{
45831
+ pending_count: number | string;
45832
+ oldest_started_at: Date | string | null;
45833
+ }>(
45834
+ db,
45835
+ sql`
45836
+ select pending_count, oldest_started_at
45837
+ from opengeni_private.context_compaction_pending_summary()
45838
+ `,
45839
+ );
45840
+ const row = rows[0];
45841
+ return {
45842
+ pendingCount: Number(row?.pending_count ?? 0),
45843
+ oldestStartedAt: row?.oldest_started_at == null ? null : new Date(row.oldest_started_at),
45844
+ };
45845
+ }
45846
+
44739
45847
  export async function countSandboxLeasesByLiveness(
44740
45848
  db: Database,
44741
45849
  ): Promise<Record<SandboxLeaseLiveness, number>> {
@@ -45141,6 +46249,9 @@ export class SandboxWorkspaceMutationFencedError extends Error {
45141
46249
  // The exact grant identity behind this writer is no longer active.
45142
46250
  | "authority_revoked",
45143
46251
  message: string,
46252
+ /** Remaining exact archive-capture budget projected by PostgreSQL. This is
46253
+ * observational wait input only; it never licenses capture takeover. */
46254
+ public readonly captureRemainingMs: number | null = null,
45144
46255
  ) {
45145
46256
  super(message);
45146
46257
  }
@@ -46156,6 +47267,7 @@ async function advanceWorkspaceGenerationForAuthorityOnce(
46156
47267
  rotation_pending: boolean;
46157
47268
  holder_current: boolean;
46158
47269
  capture_in_progress: boolean;
47270
+ archive_capture_remaining_ms: number | string | null;
46159
47271
  }>(sql`
46160
47272
  select lease.workspace_generation,
46161
47273
  (
@@ -46177,6 +47289,15 @@ async function advanceWorkspaceGenerationForAuthorityOnce(
46177
47289
  and (${locked.expectedBackend}::text is null or lease.backend = ${locked.expectedBackend}::text)
46178
47290
  ) as rotation_pending,
46179
47291
  (lease.archive_capture_id is not null) as capture_in_progress,
47292
+ case
47293
+ when lease.archive_capture_deadline_at is null then null
47294
+ else greatest(
47295
+ 0,
47296
+ ceil(extract(epoch from (
47297
+ lease.archive_capture_deadline_at - clock_timestamp()
47298
+ )) * 1000)
47299
+ )::bigint
47300
+ end as archive_capture_remaining_ms,
46180
47301
  exists (
46181
47302
  select 1 from sandbox_lease_holders as holder
46182
47303
  where holder.lease_id = lease.id
@@ -46205,9 +47326,13 @@ async function advanceWorkspaceGenerationForAuthorityOnce(
46205
47326
  );
46206
47327
  }
46207
47328
  if (current[0]?.lease_current && current[0].capture_in_progress) {
47329
+ const captureRemainingMs = archiveCaptureRemainingMs({
47330
+ archive_capture_remaining_ms: current[0].archive_capture_remaining_ms,
47331
+ });
46208
47332
  throw new SandboxWorkspaceMutationFencedError(
46209
47333
  "capture_in_progress",
46210
47334
  "Workspace mutation is waiting for the exact provider archive capture to settle",
47335
+ captureRemainingMs,
46211
47336
  );
46212
47337
  }
46213
47338
  if (current[0]?.rotation_pending) {
@@ -46240,7 +47365,9 @@ async function advanceWorkspaceGenerationForAuthority(
46240
47365
  if (!Number.isSafeInteger(captureWaitMs) || captureWaitMs < 0 || captureWaitMs > 60 * 60_000) {
46241
47366
  throw new Error("Workspace archive capture wait is invalid");
46242
47367
  }
46243
- const deadline = performance.now() + captureWaitMs;
47368
+ const startedAt = performance.now();
47369
+ const hardDeadline = startedAt + SANDBOX_LIFECYCLE_TRANSITION_MAX_WAIT_MS;
47370
+ let deadline = Math.min(hardDeadline, startedAt + captureWaitMs);
46244
47371
  let delayMs = 25;
46245
47372
  let captureWaitStartedAt: number | undefined;
46246
47373
  let captureWaitOutcome: "completed" | "failed" = "failed";
@@ -46255,16 +47382,29 @@ async function advanceWorkspaceGenerationForAuthority(
46255
47382
  captureWaitOutcome = "completed";
46256
47383
  return admission;
46257
47384
  } catch (error) {
47385
+ const now = performance.now();
47386
+ if (
47387
+ captureWaitMs > 0 &&
47388
+ error instanceof SandboxWorkspaceMutationFencedError &&
47389
+ error.code === "capture_in_progress"
47390
+ ) {
47391
+ deadline = extendSandboxTransitionDeadline(
47392
+ deadline,
47393
+ hardDeadline,
47394
+ error.captureRemainingMs,
47395
+ now,
47396
+ );
47397
+ }
46258
47398
  if (
46259
47399
  !(error instanceof SandboxWorkspaceMutationFencedError) ||
46260
47400
  error.code !== "capture_in_progress" ||
46261
- performance.now() >= deadline
47401
+ now >= deadline
46262
47402
  ) {
46263
47403
  throw error;
46264
47404
  }
46265
47405
  captureWaitStartedAt ??= performance.now();
46266
47406
  await waitForSandboxTransition(
46267
- Math.min(delayMs, Math.max(1, deadline - performance.now())),
47407
+ Math.min(delayMs, Math.max(1, deadline - now)),
46268
47408
  waitSignal,
46269
47409
  "Sandbox workspace mutation wait cancelled",
46270
47410
  );
@@ -49146,6 +50286,7 @@ export type SandboxRotationBacklog = {
49146
50286
  turnBlocked: number;
49147
50287
  directBlocked: number;
49148
50288
  processBlocked: number;
50289
+ interactionBlocked: number;
49149
50290
  };
49150
50291
 
49151
50292
  export async function readSandboxRotationBacklog(db: Database): Promise<SandboxRotationBacklog> {
@@ -49155,7 +50296,13 @@ export async function readSandboxRotationBacklog(db: Database): Promise<SandboxR
49155
50296
  turn_blocked: number | string;
49156
50297
  direct_blocked: number | string;
49157
50298
  process_blocked: number | string;
49158
- }>(db, sql`select * from opengeni_private.sandbox_rotation_backlog()`);
50299
+ interaction_blocked: number | string;
50300
+ }>(
50301
+ db,
50302
+ sql`select backlog.*,
50303
+ opengeni_private.sandbox_rotation_interaction_blocked() as interaction_blocked
50304
+ from opengeni_private.sandbox_rotation_backlog() backlog`,
50305
+ );
49159
50306
  const row = rows[0];
49160
50307
  return {
49161
50308
  requested: Number(row?.requested ?? 0),
@@ -49163,6 +50310,7 @@ export async function readSandboxRotationBacklog(db: Database): Promise<SandboxR
49163
50310
  turnBlocked: Number(row?.turn_blocked ?? 0),
49164
50311
  directBlocked: Number(row?.direct_blocked ?? 0),
49165
50312
  processBlocked: Number(row?.process_blocked ?? 0),
50313
+ interactionBlocked: Number(row?.interaction_blocked ?? 0),
49166
50314
  };
49167
50315
  }
49168
50316
 
@@ -57695,6 +58843,7 @@ export async function materializeGoalContinuation(
57695
58843
  model: string;
57696
58844
  reasoningEffort: ReasoningEffort;
57697
58845
  latencyMode: LatencyMode;
58846
+ turnExecutionPolicy?: TurnExecutionPolicyV1;
57698
58847
  tools: ToolRef[];
57699
58848
  sandboxBackend: SandboxBackend;
57700
58849
  };
@@ -59959,6 +61108,7 @@ export async function claimSessionWorkForAttempt(
59959
61108
  }
59960
61109
  const validUpdates: typeof updates = [];
59961
61110
  const cancelledUpdateIds: string[] = [];
61111
+ const scheduledTaskInactiveUpdateIds: string[] = [];
59962
61112
  const authorityRejectedUpdateIds: string[] = [];
59963
61113
  const unrecognizedUpdateIds: string[] = [];
59964
61114
  // Retained so later consumers of the delivered batch (the child
@@ -59994,17 +61144,28 @@ export async function claimSessionWorkForAttempt(
59994
61144
  })
59995
61145
  : null;
59996
61146
  if (update.scheduledTaskRunId && scheduledAuthorityDenial) {
59997
- await markScheduledTaskRunAuthorityRejectedInTransaction(tx as unknown as Database, {
59998
- workspaceId,
59999
- sessionId,
60000
- runId: update.scheduledTaskRunId,
60001
- error: scheduledAuthorityDenial,
60002
- });
61147
+ const scheduledTaskInactive =
61148
+ scheduledAuthorityDenial === "scheduled_task_inactive_before_claim";
61149
+ if (!scheduledTaskInactive) {
61150
+ await markScheduledTaskRunAuthorityRejectedInTransaction(
61151
+ tx as unknown as Database,
61152
+ {
61153
+ workspaceId,
61154
+ sessionId,
61155
+ runId: update.scheduledTaskRunId,
61156
+ error: scheduledAuthorityDenial,
61157
+ },
61158
+ );
61159
+ }
60003
61160
  await tx
60004
61161
  .update(schema.sessionSystemUpdates)
60005
- .set({ state: "failed" })
61162
+ .set({ state: scheduledTaskInactive ? "cancelled" : "failed" })
60006
61163
  .where(eq(schema.sessionSystemUpdates.id, update.id));
60007
- authorityRejectedUpdateIds.push(update.id);
61164
+ if (scheduledTaskInactive) {
61165
+ scheduledTaskInactiveUpdateIds.push(update.id);
61166
+ } else {
61167
+ authorityRejectedUpdateIds.push(update.id);
61168
+ }
60008
61169
  continue;
60009
61170
  }
60010
61171
  if (input.validatePendingSystemUpdateAuthority) {
@@ -60114,6 +61275,25 @@ export async function claimSessionWorkForAttempt(
60114
61275
  occurredAt,
60115
61276
  });
60116
61277
  }
61278
+ if (scheduledTaskInactiveUpdateIds.length > 0) {
61279
+ cancellationEvents.push({
61280
+ accountId,
61281
+ workspaceId,
61282
+ sessionId,
61283
+ turnId: null,
61284
+ turnGeneration: null,
61285
+ turnAttemptId: null,
61286
+ turnAssociation: null,
61287
+ sequence: ++sequence,
61288
+ type: "system.update.cancelled" as const,
61289
+ payload: {
61290
+ updateIds: scheduledTaskInactiveUpdateIds,
61291
+ count: scheduledTaskInactiveUpdateIds.length,
61292
+ reason: "scheduled_task_inactive",
61293
+ },
61294
+ occurredAt,
61295
+ });
61296
+ }
60117
61297
  if (authorityRejectedUpdateIds.length > 0) {
60118
61298
  cancellationEvents.push({
60119
61299
  accountId,
@@ -60252,6 +61432,25 @@ export async function claimSessionWorkForAttempt(
60252
61432
  occurredAt,
60253
61433
  });
60254
61434
  }
61435
+ if (scheduledTaskInactiveUpdateIds.length > 0) {
61436
+ events.push({
61437
+ accountId,
61438
+ workspaceId,
61439
+ sessionId,
61440
+ turnId,
61441
+ turnGeneration,
61442
+ turnAttemptId: input.attemptId,
61443
+ turnAssociation: "current",
61444
+ sequence: ++sequence,
61445
+ type: "system.update.cancelled",
61446
+ payload: {
61447
+ updateIds: scheduledTaskInactiveUpdateIds,
61448
+ count: scheduledTaskInactiveUpdateIds.length,
61449
+ reason: "scheduled_task_inactive",
61450
+ },
61451
+ occurredAt,
61452
+ });
61453
+ }
60255
61454
  if (authorityRejectedUpdateIds.length > 0) {
60256
61455
  events.push({
60257
61456
  accountId,
@@ -61406,6 +62605,9 @@ export async function claimSessionWorkForAttempt(
61406
62605
  typeof routingGoalUpdate.payload.policy === "object"
61407
62606
  ? (routingGoalUpdate.payload.policy as Record<string, unknown>)
61408
62607
  : null;
62608
+ let frozenTurnExecutionPolicy = goalPolicy?.turnExecutionPolicy
62609
+ ? TurnExecutionPolicyV1.parse(goalPolicy.turnExecutionPolicy)
62610
+ : null;
61409
62611
  const [latestStarted] = await tx
61410
62612
  .select({
61411
62613
  model: schema.sessionTurns.model,
@@ -61475,6 +62677,9 @@ export async function claimSessionWorkForAttempt(
61475
62677
  const accepted = ScheduledTaskRunAcceptedExecution.parse(
61476
62678
  scheduledRun.acceptedExecutionSnapshot,
61477
62679
  );
62680
+ frozenTurnExecutionPolicy = accepted.turnExecutionPolicy
62681
+ ? TurnExecutionPolicyV1.parse(accepted.turnExecutionPolicy)
62682
+ : null;
61478
62683
  const targetPolicy = accepted.targetSessionExecution;
61479
62684
  if (targetPolicy) {
61480
62685
  scheduledEffectiveMcpServerIds = targetPolicy.effectiveMcpServerIds;
@@ -61696,11 +62901,28 @@ export async function claimSessionWorkForAttempt(
61696
62901
  sandboxBackend,
61697
62902
  sandboxOs,
61698
62903
  metadata: metadataWithTurnDispatchAttempt(
61699
- {
61700
- internalUpdateCount: delivered.count,
61701
- ...(routingGoalUpdate ? { goalId: routingGoalUpdate.payload.goalId } : {}),
61702
- ...(scheduledEffectiveMcpServerIds ? { scheduledEffectiveMcpServerIds } : {}),
61703
- },
62904
+ frozenTurnExecutionPolicy
62905
+ ? metadataWithTurnExecutionPolicyV1(
62906
+ {
62907
+ internalUpdateCount: delivered.count,
62908
+ ...(routingGoalUpdate
62909
+ ? { goalId: routingGoalUpdate.payload.goalId }
62910
+ : {}),
62911
+ ...(scheduledEffectiveMcpServerIds
62912
+ ? { scheduledEffectiveMcpServerIds }
62913
+ : {}),
62914
+ },
62915
+ frozenTurnExecutionPolicy,
62916
+ )
62917
+ : {
62918
+ internalUpdateCount: delivered.count,
62919
+ ...(routingGoalUpdate
62920
+ ? { goalId: routingGoalUpdate.payload.goalId }
62921
+ : {}),
62922
+ ...(scheduledEffectiveMcpServerIds
62923
+ ? { scheduledEffectiveMcpServerIds }
62924
+ : {}),
62925
+ },
61704
62926
  { id: input.dispatchId, generation: 1, triggerEventId },
61705
62927
  ),
61706
62928
  ...initiatorColumns(internalInitiator),
@@ -64000,7 +65222,10 @@ export async function failSessionWorkBeforeAttemptClaim(
64000
65222
  sessionId: input.sessionId,
64001
65223
  sequence: ++sequence,
64002
65224
  type: "user.humanInputResponse" as const,
64003
- payload: { requestId: request.id, response: { outcome: "cancelled" as const } },
65225
+ payload: {
65226
+ requestId: request.id,
65227
+ response: { outcome: "cancelled" as const },
65228
+ },
64004
65229
  turnId: turn.id,
64005
65230
  turnGeneration: request.turnGeneration,
64006
65231
  turnAttemptId: null,
@@ -68648,8 +69873,9 @@ export type AddSessionSystemUpdateInput = {
68648
69873
  scheduledTaskRunId?: string | null;
68649
69874
  } & SessionSystemUpdateInputVariant;
68650
69875
 
68651
- export type AddSessionSystemUpdateResult =
69876
+ export type AddSessionSystemUpdateResult<RequireIdleSession extends boolean = boolean> =
68652
69877
  | { added: false; reason: "session_cancelled" }
69878
+ | (RequireIdleSession extends true ? { added: false; reason: "session_not_idle" } : never)
68653
69879
  | {
68654
69880
  added: boolean;
68655
69881
  reason: "added" | "duplicate";
@@ -68664,12 +69890,14 @@ export type AddSessionSystemUpdateResult =
68664
69890
  export type PrepareSessionSystemUpdateSourceResult = {
68665
69891
  /** Private, bounded, content-free source authority appended before insert. */
68666
69892
  lineage?: Record<string, unknown>;
69893
+ /** Source-owned events appended before the pending-update event. */
69894
+ events?: SessionEvent[];
68667
69895
  };
68668
69896
 
68669
69897
  export async function addSessionSystemUpdate(
68670
69898
  db: Database,
68671
69899
  input: AddSessionSystemUpdateInput,
68672
- ): Promise<AddSessionSystemUpdateResult> {
69900
+ ): Promise<AddSessionSystemUpdateResult<false>> {
68673
69901
  if (input.payload.type !== input.kind) {
68674
69902
  throw new Error(`Internal update payload discriminator must equal kind ${input.kind}`);
68675
69903
  }
@@ -68680,29 +69908,34 @@ export async function addSessionSystemUpdate(
68680
69908
  * Persist one internal update without fabricating a user prompt or queue row.
68681
69909
  * Dedupe and any producer/outbox mutation commit in the same transaction.
68682
69910
  */
68683
- export async function addSessionSystemUpdateWithSourceMutation(
69911
+ export async function addSessionSystemUpdateWithSourceMutation<
69912
+ RequireIdleSession extends boolean = false,
69913
+ >(
68684
69914
  db: Database,
68685
69915
  input: AddSessionSystemUpdateInput,
68686
69916
  mutateSource: (
68687
69917
  tx: Database,
68688
69918
  wakeEventId: string | null,
68689
69919
  updateId: string | null,
69920
+ rejectionReason: "session_cancelled" | "session_not_idle" | null,
68690
69921
  ) => Promise<void>,
68691
69922
  options: {
69923
+ /** Admit only when the already-locked session is at its idle boundary. */
69924
+ requireIdleSession?: RequireIdleSession;
68692
69925
  /** Runs under the locked session/source transaction before any update/event insert. */
68693
69926
  prepareSource?: (
68694
69927
  tx: Database,
68695
69928
  sessionId: string,
68696
69929
  ) => Promise<PrepareSessionSystemUpdateSourceResult | void>;
68697
69930
  } = {},
68698
- ): Promise<AddSessionSystemUpdateResult> {
69931
+ ): Promise<AddSessionSystemUpdateResult<RequireIdleSession>> {
68699
69932
  if (Buffer.byteLength(JSON.stringify(input.payload)) > MAX_INTERNAL_UPDATE_BYTES) {
68700
69933
  throw new Error(`Internal update payload exceeds ${MAX_INTERNAL_UPDATE_BYTES} bytes`);
68701
69934
  }
68702
69935
  if (Buffer.byteLength(input.summary) > MAX_INTERNAL_UPDATE_BYTES) {
68703
69936
  throw new Error(`Internal update summary exceeds ${MAX_INTERNAL_UPDATE_BYTES} bytes`);
68704
69937
  }
68705
- return await withSessionActivityRlsContext(
69938
+ return (await withSessionActivityRlsContext(
68706
69939
  db,
68707
69940
  { accountId: input.accountId, workspaceId: input.workspaceId },
68708
69941
  async (scopedDb) =>
@@ -68712,8 +69945,9 @@ export async function addSessionSystemUpdateWithSourceMutation(
68712
69945
  controlLock: "share",
68713
69946
  sessionIds: [input.sessionId],
68714
69947
  });
68715
- const session = locks.sessions[0];
69948
+ let session = locks.sessions[0];
68716
69949
  if (!session) throw new Error(`Session not found: ${input.sessionId}`);
69950
+ const temporalWorkflowId = session.temporalWorkflowId;
68717
69951
  const effectiveControl = await evaluateSessionControl(
68718
69952
  tx as unknown as Database,
68719
69953
  input.workspaceId,
@@ -68721,10 +69955,71 @@ export async function addSessionSystemUpdateWithSourceMutation(
68721
69955
  { workspaceControl: locks.control ?? undefined },
68722
69956
  );
68723
69957
  if (session.status === "cancelled") {
68724
- await mutateSource(tx as unknown as Database, null, null);
69958
+ await mutateSource(tx as unknown as Database, null, null, "session_cancelled");
68725
69959
  return { added: false, reason: "session_cancelled" } as const;
68726
69960
  }
69961
+ const replayExistingUpdate = async (events: SessionEvent[]) => {
69962
+ const [existing] = await tx
69963
+ .select()
69964
+ .from(schema.sessionSystemUpdates)
69965
+ .where(
69966
+ and(
69967
+ eq(schema.sessionSystemUpdates.workspaceId, input.workspaceId),
69968
+ eq(schema.sessionSystemUpdates.sessionId, input.sessionId),
69969
+ eq(schema.sessionSystemUpdates.dedupeKey, input.dedupeKey),
69970
+ ),
69971
+ )
69972
+ .limit(1);
69973
+ if (!existing) return null;
69974
+ const [pendingEvent] = await tx
69975
+ .select({ id: schema.sessionEvents.id })
69976
+ .from(schema.sessionEvents)
69977
+ .where(
69978
+ and(
69979
+ eq(schema.sessionEvents.workspaceId, input.workspaceId),
69980
+ eq(schema.sessionEvents.sessionId, input.sessionId),
69981
+ eq(schema.sessionEvents.type, "system.update.pending"),
69982
+ sql`${schema.sessionEvents.payload} ->> 'updateId' = ${existing.id}`,
69983
+ ),
69984
+ )
69985
+ .limit(1);
69986
+ if (!pendingEvent) throw new Error("System-update pending event disappeared");
69987
+ await mutateSource(tx as unknown as Database, pendingEvent.id, existing.id, null);
69988
+ return {
69989
+ added: false,
69990
+ reason: "duplicate",
69991
+ update: mapSessionSystemUpdate(existing),
69992
+ shouldWake: false,
69993
+ workflowWakeRevision: null,
69994
+ wakeEventId: pendingEvent.id,
69995
+ temporalWorkflowId,
69996
+ events,
69997
+ } as const;
69998
+ };
69999
+
70000
+ if (options.requireIdleSession && session.status !== "idle") {
70001
+ const replayed = await replayExistingUpdate([]);
70002
+ if (replayed) {
70003
+ return replayed;
70004
+ }
70005
+ await mutateSource(tx as unknown as Database, null, null, "session_not_idle");
70006
+ return { added: false, reason: "session_not_idle" } as const;
70007
+ }
68727
70008
  const prepared = await options.prepareSource?.(tx as unknown as Database, input.sessionId);
70009
+ if ((prepared?.events?.length ?? 0) > 0) {
70010
+ const [refreshedSession] = await tx
70011
+ .select()
70012
+ .from(schema.sessions)
70013
+ .where(
70014
+ and(
70015
+ eq(schema.sessions.workspaceId, input.workspaceId),
70016
+ eq(schema.sessions.id, input.sessionId),
70017
+ ),
70018
+ )
70019
+ .limit(1);
70020
+ if (!refreshedSession) throw new Error(`Session not found: ${input.sessionId}`);
70021
+ session = refreshedSession;
70022
+ }
68728
70023
  const lineage = {
68729
70024
  ...(input.lineage ?? {}),
68730
70025
  ...(prepared?.lineage ?? {}),
@@ -68769,42 +70064,9 @@ export async function addSessionSystemUpdateWithSourceMutation(
68769
70064
  })
68770
70065
  .returning();
68771
70066
  if (!inserted) {
68772
- const [existing] = await tx
68773
- .select()
68774
- .from(schema.sessionSystemUpdates)
68775
- .where(
68776
- and(
68777
- eq(schema.sessionSystemUpdates.workspaceId, input.workspaceId),
68778
- eq(schema.sessionSystemUpdates.sessionId, input.sessionId),
68779
- eq(schema.sessionSystemUpdates.dedupeKey, input.dedupeKey),
68780
- ),
68781
- )
68782
- .limit(1);
68783
- if (!existing) throw new Error("System-update dedupe row disappeared");
68784
- const [pendingEvent] = await tx
68785
- .select({ id: schema.sessionEvents.id })
68786
- .from(schema.sessionEvents)
68787
- .where(
68788
- and(
68789
- eq(schema.sessionEvents.workspaceId, input.workspaceId),
68790
- eq(schema.sessionEvents.sessionId, input.sessionId),
68791
- eq(schema.sessionEvents.type, "system.update.pending"),
68792
- sql`${schema.sessionEvents.payload} ->> 'updateId' = ${existing.id}`,
68793
- ),
68794
- )
68795
- .limit(1);
68796
- if (!pendingEvent) throw new Error("System-update pending event disappeared");
68797
- await mutateSource(tx as unknown as Database, pendingEvent.id, existing.id);
68798
- return {
68799
- added: false,
68800
- reason: "duplicate",
68801
- update: mapSessionSystemUpdate(existing),
68802
- shouldWake: false,
68803
- workflowWakeRevision: null,
68804
- wakeEventId: pendingEvent.id,
68805
- temporalWorkflowId: session.temporalWorkflowId,
68806
- events: [],
68807
- };
70067
+ const replayed = await replayExistingUpdate(prepared?.events ?? []);
70068
+ if (!replayed) throw new Error("System-update dedupe row disappeared");
70069
+ return replayed;
68808
70070
  }
68809
70071
 
68810
70072
  const now = new Date();
@@ -68836,7 +70098,7 @@ export async function addSessionSystemUpdateWithSourceMutation(
68836
70098
  )
68837
70099
  .returning();
68838
70100
  if (!event) throw new Error("Failed to create system-update pending event");
68839
- await mutateSource(tx as unknown as Database, event.id, inserted.id);
70101
+ await mutateSource(tx as unknown as Database, event.id, inserted.id, null);
68840
70102
  // Producer-side supersession of this child's now-stale pending notices
68841
70103
  // on the parent: a newer child_progress replaces an older one, and a
68842
70104
  // resolution supersedes the child_requires_action of the same exact
@@ -68980,11 +70242,16 @@ export async function addSessionSystemUpdateWithSourceMutation(
68980
70242
  temporalWorkflowId: session.temporalWorkflowId ?? `session-${session.id}`,
68981
70243
  })
68982
70244
  : null;
70245
+ // An idle-gated admission consumes that boundary even when control or
70246
+ // realtime ownership withholds the workflow wake. Persisting `queued`
70247
+ // reserves the accepted work so a later distinct skip occurrence
70248
+ // cannot cross the same idle boundary while this update is pending.
70249
+ const shouldQueue = shouldWake || options.requireIdleSession === true;
68983
70250
  await tx
68984
70251
  .update(schema.sessions)
68985
70252
  .set({
68986
70253
  lastSequence: session.lastSequence + appendedSequences,
68987
- ...(shouldWake ? { status: "queued" as const } : {}),
70254
+ ...(shouldQueue ? { status: "queued" as const } : {}),
68988
70255
  updatedAt: now,
68989
70256
  })
68990
70257
  .where(eq(schema.sessions.id, session.id));
@@ -68997,13 +70264,14 @@ export async function addSessionSystemUpdateWithSourceMutation(
68997
70264
  wakeEventId: event.id,
68998
70265
  temporalWorkflowId: session.temporalWorkflowId,
68999
70266
  events: [
70267
+ ...(prepared?.events ?? []),
69000
70268
  mapEvent(event),
69001
70269
  ...(supersededEvent ? [mapEvent(supersededEvent)] : []),
69002
70270
  ...(resumedEvent ? [mapEvent(resumedEvent)] : []),
69003
70271
  ],
69004
70272
  };
69005
70273
  }),
69006
- );
70274
+ )) as AddSessionSystemUpdateResult<RequireIdleSession>;
69007
70275
  }
69008
70276
 
69009
70277
  /**
@@ -71497,6 +72765,13 @@ function mapConnectionMetadata(row: {
71497
72765
  createdAt: Date;
71498
72766
  updatedAt: Date;
71499
72767
  }): ConnectionMetadataWithVerification {
72768
+ const {
72769
+ [OPENROUTER_CREDENTIAL_OPERATION_ID_METADATA_KEY]: _openRouterOperationId,
72770
+ [OPENROUTER_CREDENTIAL_OPERATION_DIGEST_METADATA_KEY]: _openRouterOperationDigest,
72771
+ [VERCEL_AI_GATEWAY_CREDENTIAL_OPERATION_ID_METADATA_KEY]: _operationId,
72772
+ [VERCEL_AI_GATEWAY_CREDENTIAL_OPERATION_DIGEST_METADATA_KEY]: _operationDigest,
72773
+ ...publicMetadata
72774
+ } = row.metadata;
71500
72775
  return {
71501
72776
  id: row.id,
71502
72777
  ...(row.subjectId !== null && row.authorityId ? { authorityId: row.authorityId } : {}),
@@ -71514,7 +72789,7 @@ function mapConnectionMetadata(row: {
71514
72789
  version: row.version,
71515
72790
  verifiedInstallAt: row.verifiedInstallAt?.toISOString() ?? null,
71516
72791
  verifiedInstallVersion: row.verifiedInstallVersion,
71517
- metadata: row.metadata,
72792
+ metadata: publicMetadata,
71518
72793
  createdBySubjectId: row.createdBySubjectId,
71519
72794
  updatedBySubjectId: row.updatedBySubjectId,
71520
72795
  createdAt: row.createdAt.toISOString(),
@@ -71852,6 +73127,9 @@ function connectionRefConfig(value: unknown): McpServerConnectionRef | undefined
71852
73127
  return undefined;
71853
73128
  }
71854
73129
  const ref: McpServerConnectionRef = { providerDomain: record.providerDomain };
73130
+ if (record.authoritySource === "host") {
73131
+ ref.authoritySource = "host";
73132
+ }
71855
73133
  if (typeof record.connectionId === "string" && record.connectionId.length > 0) {
71856
73134
  ref.connectionId = record.connectionId;
71857
73135
  }
@@ -72096,3 +73374,4 @@ export * from "./organization-recovery";
72096
73374
  export * from "./session-tenancy";
72097
73375
  export * from "./governed-learning-activation";
72098
73376
  export * from "./automations";
73377
+ export * from "./organization-model-providers";