@opengeni/db 0.9.3 → 0.12.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 (54) hide show
  1. package/dist/{chunk-4LG5NBTC.js → chunk-VUKRIBO5.js} +577 -12
  2. package/dist/chunk-VUKRIBO5.js.map +1 -0
  3. package/dist/{chunk-KW526IJA.js → chunk-Y5WZZVQK.js} +80 -4
  4. package/dist/chunk-Y5WZZVQK.js.map +1 -0
  5. package/dist/index.d.ts +4 -2
  6. package/dist/index.js +6372 -2189
  7. package/dist/index.js.map +1 -1
  8. package/dist/migrate.d.ts +6 -3
  9. package/dist/migrate.js +1 -1
  10. package/dist/provision-roles.d.ts +1122 -91
  11. package/dist/{schema-CdPGTHlD.d.ts → schema-CnpD6BcX.d.ts} +5908 -3626
  12. package/dist/schema.d.ts +1 -1
  13. package/dist/schema.js +19 -1
  14. package/drizzle/0053_codex_credential_leases.sql +2 -2
  15. package/drizzle/0057_durable_queue_control.sql +1 -1
  16. package/drizzle/0061_session_workflow_wake_outbox.sql +1 -1
  17. package/drizzle/0062_session_list_snapshot_reaper.sql +1 -1
  18. package/drizzle/0063_session_control_mega_foundation.sql +1 -1
  19. package/drizzle/0064_rotation_strategy_sharded_backfill.sql +1 -1
  20. package/drizzle/0065_codex_subscription_overview.sql +168 -0
  21. package/drizzle/0065_session_tool_policy.sql +38 -0
  22. package/drizzle/0067_session_event_payload_bounds.sql +2 -2
  23. package/drizzle/0068_workspace_control_event_bounds.sql +2 -2
  24. package/drizzle/0069_session_event_history_backfill.sql +2 -2
  25. package/drizzle/0074_session_activity_revisions.sql +2 -2
  26. package/drizzle/0106_session_attempt_mcp_approval_policies.sql +29 -0
  27. package/drizzle/0107_host_export_lineage_contract.sql +381 -0
  28. package/drizzle/0108_fence_invalidated_warming_epochs.sql +76 -0
  29. package/drizzle/0109_nested_agent_depth_expand.sql +42 -0
  30. package/drizzle/0110_nested_agent_depth_boundary.sql +480 -0
  31. package/drizzle/0111_nested_agent_depth_backfill.sql +49 -0
  32. package/drizzle/0112_nested_agent_depth_contract.sql +38 -0
  33. package/drizzle/0113_nested_agent_depth_validate.sql +13 -0
  34. package/drizzle/0114_nested_agent_depth_contract.sql +49 -0
  35. package/drizzle/0115_nested_agent_depth_validate.sql +11 -0
  36. package/drizzle/0116_nested_agent_depth_index.sql +4 -0
  37. package/drizzle/0117_sandbox_recovery_generations.sql +699 -0
  38. package/drizzle/0118_new_session_drafts.sql +59 -0
  39. package/drizzle/0119_pending_tool_output_policy.sql +5 -0
  40. package/drizzle/0120_durable_goal_wake.sql +360 -0
  41. package/drizzle/0121_goal_update_idempotency.sql +11 -0
  42. package/package.json +5 -4
  43. package/src/codex-token-resolver.ts +175 -14
  44. package/src/connection-token-resolver.ts +143 -120
  45. package/src/event-payload-sanitizer.ts +32 -2
  46. package/src/index.ts +7734 -1330
  47. package/src/migrate.ts +131 -2
  48. package/src/new-session-drafts.ts +144 -0
  49. package/src/schema.ts +626 -16
  50. package/src/session-control.ts +44 -18
  51. package/src/session-queue-commands.ts +94 -21
  52. package/src/session-tool-call-settlement.ts +6 -1
  53. package/dist/chunk-4LG5NBTC.js.map +0 -1
  54. package/dist/chunk-KW526IJA.js.map +0 -1
package/src/schema.ts CHANGED
@@ -1,5 +1,6 @@
1
- import type { McpServerConnectionRef } from "@opengeni/contracts";
1
+ import type { McpServerConnectionRef, SessionMcpApprovalPolicy } from "@opengeni/contracts";
2
2
  import { sql } from "drizzle-orm";
3
+ import type { SessionToolPolicy } from "@opengeni/contracts";
3
4
  import type { HumanInputQuestion, HumanInputResponse } from "@opengeni/contracts";
4
5
  import {
5
6
  bigint,
@@ -80,6 +81,34 @@ export const workspaces = pgTable(
80
81
  }),
81
82
  );
82
83
 
84
+ // One target-schema-local deployment fallback. The migration runner reconciles
85
+ // this singleton from OPENGENI_MAX_NESTED_AGENT_DEPTH; session admission locks
86
+ // and reads it through the SECURITY DEFINER capability installed by the
87
+ // boundary migration so the application role cannot mutate policy authority.
88
+ export const nestedAgentDepthConfiguration = pgTable(
89
+ "nested_agent_depth_configuration",
90
+ {
91
+ singleton: boolean("singleton").primaryKey().notNull().default(true),
92
+ maxNestedAgentDepth: integer("max_nested_agent_depth").notNull(),
93
+ policySource: text("policy_source").$type<"deployment" | "default">().notNull(),
94
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
95
+ },
96
+ (table) => ({
97
+ singletonOnly: check(
98
+ "nested_agent_depth_configuration_singleton_check",
99
+ sql`${table.singleton}`,
100
+ ),
101
+ maxValid: check(
102
+ "nested_agent_depth_configuration_max_check",
103
+ sql`${table.maxNestedAgentDepth} >= 0`,
104
+ ),
105
+ sourceValid: check(
106
+ "nested_agent_depth_configuration_source_check",
107
+ sql`${table.policySource} in ('deployment', 'default')`,
108
+ ),
109
+ }),
110
+ );
111
+
83
112
  // One mandatory workspace-wide admission barrier. Every inference-admitting
84
113
  // transaction locks this row before it touches a session; Pause/Resume and
85
114
  // foreground Send/Steer advance its monotonic revision under FOR UPDATE.
@@ -320,6 +349,19 @@ export const codexSubscriptionCredentials = pgTable(
320
349
  // refresh, encrypted material, and already-frozen/in-flight turns are
321
350
  // intentionally independent. account eligibility policy owns toggle OCC/audit and product UI.
322
351
  allocatorEnabled: boolean("allocator_enabled").notNull().default(true),
352
+ // Independent OCC/audit sequence for the allocator toggle. Token refresh
353
+ // continues to own `version`; quota/cache writes own neither counter.
354
+ allocatorVersion: integer("allocator_version").notNull().default(1),
355
+ allocatorUpdatedBySubjectId: text("allocator_updated_by_subject_id"),
356
+ allocatorUpdatedAt: timestamp("allocator_updated_at", { withTimezone: true }),
357
+ // Authoritative count-only summary cached from /wham/usage. Detailed rows
358
+ // are never persisted as redemption authority; every first POST preflights
359
+ // the provider's fresh detail endpoint.
360
+ resetCreditAvailableCount: integer("reset_credit_available_count"),
361
+ resetCreditsCheckedAt: timestamp("reset_credits_checked_at", { withTimezone: true }),
362
+ // Set only by a direct Better Auth cookie connection/reconnection. Legacy,
363
+ // configured, delegated, API-key, and agent-created rows remain view-only.
364
+ connectedBySubjectId: text("connected_by_subject_id"),
323
365
  selectionCount: integer("selection_count").notNull().default(0),
324
366
  lastSelectedAt: timestamp("last_selected_at", { withTimezone: true }),
325
367
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
@@ -343,6 +385,82 @@ export const codexSubscriptionCredentials = pgTable(
343
385
  }),
344
386
  );
345
387
 
388
+ // One durable logical human redemption. `processing` means the fresh provider
389
+ // detail preflight is still owed; `provider_started` means the POST may have
390
+ // reached upstream and every retry must reuse upstreamIdempotencyKey without
391
+ // requiring the credit to remain visible as available.
392
+ export const codexResetRedemptionAttempts = pgTable(
393
+ "codex_reset_redemption_attempts",
394
+ {
395
+ id: uuid("id").primaryKey(),
396
+ accountId: uuid("account_id")
397
+ .notNull()
398
+ .references(() => managedAccounts.id, { onDelete: "cascade" }),
399
+ workspaceId: uuid("workspace_id")
400
+ .notNull()
401
+ .references(() => workspaces.id, { onDelete: "cascade" }),
402
+ credentialId: uuid("credential_id").notNull(),
403
+ subjectId: text("subject_id").notNull(),
404
+ browserSessionHash: text("browser_session_hash").notNull(),
405
+ creditId: text("credit_id").notNull(),
406
+ upstreamIdempotencyKey: uuid("upstream_idempotency_key").notNull().defaultRandom(),
407
+ status: text("status").notNull().default("processing"),
408
+ outcome: text("outcome"),
409
+ claimHolderId: uuid("claim_holder_id"),
410
+ claimExpiresAt: timestamp("claim_expires_at", { withTimezone: true }),
411
+ confirmationExpiresAt: timestamp("confirmation_expires_at", { withTimezone: true }).notNull(),
412
+ providerStartedAt: timestamp("provider_started_at", { withTimezone: true }),
413
+ completedAt: timestamp("completed_at", { withTimezone: true }),
414
+ lastFailureKind: text("last_failure_kind"),
415
+ retryCount: integer("retry_count").notNull().default(0),
416
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
417
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
418
+ },
419
+ (table) => ({
420
+ workspaceAccount: foreignKey({
421
+ name: "codex_reset_redemption_workspace_account_fk",
422
+ columns: [table.workspaceId, table.accountId],
423
+ foreignColumns: [workspaces.id, workspaces.accountId],
424
+ }).onDelete("cascade"),
425
+ upstreamKey: uniqueIndex("codex_reset_redemption_upstream_key_idx").on(
426
+ table.upstreamIdempotencyKey,
427
+ ),
428
+ credentialCredit: uniqueIndex("codex_reset_redemption_credential_credit_idx")
429
+ .on(table.workspaceId, table.credentialId, table.creditId)
430
+ .where(
431
+ sql`${table.status} <> 'completed' or ${table.outcome} in ('reset', 'alreadyRedeemed')`,
432
+ ),
433
+ workspaceCredential: index("codex_reset_redemption_workspace_credential_idx").on(
434
+ table.workspaceId,
435
+ table.credentialId,
436
+ table.createdAt,
437
+ ),
438
+ claimExpiry: index("codex_reset_redemption_claim_expiry_idx")
439
+ .on(table.claimExpiresAt)
440
+ .where(sql`${table.status} <> 'completed'`),
441
+ statusValid: check(
442
+ "codex_reset_redemption_status_check",
443
+ sql`${table.status} in ('processing', 'provider_started', 'completed')`,
444
+ ),
445
+ outcomeValid: check(
446
+ "codex_reset_redemption_outcome_check",
447
+ sql`${table.outcome} is null or ${table.outcome} in ('reset', 'nothingToReset', 'noCredit', 'alreadyRedeemed')`,
448
+ ),
449
+ completionConsistent: check(
450
+ "codex_reset_redemption_completed_check",
451
+ sql`(${table.status} = 'completed') = (${table.outcome} is not null and ${table.completedAt} is not null)`,
452
+ ),
453
+ retryCountValid: check(
454
+ "codex_reset_redemption_retry_count_check",
455
+ sql`${table.retryCount} >= 0`,
456
+ ),
457
+ humanSubjectValid: check(
458
+ "codex_reset_redemption_human_subject_check",
459
+ sql`${table.subjectId} like 'user:_%'`,
460
+ ),
461
+ }),
462
+ );
463
+
346
464
  // Generic external-service credential spine. credential_encrypted is the ONLY
347
465
  // secret-bearing column; normal API reads use metadata-only helpers below the DB
348
466
  // layer. Runtime token material is decrypted only by the broker accessor.
@@ -634,6 +752,10 @@ export const sessions = pgTable(
634
752
  // Non-default first-party MCP token permissions (manager-style sessions);
635
753
  // null means the fixed worker default set in @opengeni/runtime.
636
754
  firstPartyMcpPermissions: jsonb("first_party_mcp_permissions").$type<string[]>(),
755
+ // Durable tool-policy origin. NULL is retained for pre-migration rows;
756
+ // mapSession exposes those rows as `legacy` instead of guessing omitted vs
757
+ // explicit [].
758
+ toolPolicy: jsonb("tool_policy").$type<SessionToolPolicy>(),
637
759
  // The manager session that spawned this one via session_create. Set only
638
760
  // when the creating grant carried a worker-signed sessionId claim (a session
639
761
  // spawning a worker); null for direct API creates and scheduled-task runs.
@@ -647,6 +769,15 @@ export const sessions = pgTable(
647
769
  // workspace to a single session row — the dedup that closes the
648
770
  // double-submit/double-dispatch stuck-queued bug.
649
771
  createIdempotencyKey: text("create_idempotency_key"),
772
+ // Immutable creation-time hierarchy and policy snapshot. These values are
773
+ // populated by the database admission boundary and never re-derived from
774
+ // a live workspace setting for an existing session.
775
+ rootSessionId: uuid("root_session_id").notNull(),
776
+ nestedAgentDepth: integer("nested_agent_depth").notNull(),
777
+ maxNestedAgentDepthOverride: integer("max_nested_agent_depth_override"),
778
+ effectiveMaxNestedAgentDepth: integer("effective_max_nested_agent_depth").notNull(),
779
+ nestedAgentDepthPolicySource: text("nested_agent_depth_policy_source").notNull(),
780
+ nestedAgentDepthPolicySessionId: uuid("nested_agent_depth_policy_session_id"),
650
781
  temporalWorkflowId: text("temporal_workflow_id"),
651
782
  activeTurnId: uuid("active_turn_id"),
652
783
  // Actual input tokens reported for the last model call of the most recent
@@ -729,12 +860,64 @@ export const sessions = pgTable(
729
860
  // point and enumerate all sessions in a group for attribution/disclosure.
730
861
  sandboxGroup: index("sessions_sandbox_group_idx").on(table.workspaceId, table.sandboxGroupId),
731
862
  // Partial unique index: one session per (workspace, create_idempotency_key)
732
- // when a key is present. Concurrent creates racing on the same key see a
733
- // unique violation on all but one; the domain layer catches it and returns
734
- // the winning row instead of erroring.
863
+ // when a key is present. The boundary trigger reserves the cross-outcome
864
+ // winner before this source row commits; a losing source insert is
865
+ // suppressed and the domain layer replays the durable winner.
735
866
  createIdempotency: uniqueIndex("sessions_workspace_create_idempotency_idx")
736
867
  .on(table.workspaceId, table.createIdempotencyKey)
737
868
  .where(sql`${table.createIdempotencyKey} is not null`),
869
+ rootDepth: index("sessions_workspace_root_depth_idx").on(
870
+ table.workspaceId,
871
+ table.rootSessionId,
872
+ table.nestedAgentDepth,
873
+ ),
874
+ }),
875
+ );
876
+
877
+ // A denied session create is durable evidence, not a mutable session/resource
878
+ // artifact. It has its own workspace-scoped idempotency key so retries replay
879
+ // the same denial without creating a session or billing/run rows.
880
+ export const sessionSpawnDenials = pgTable(
881
+ "session_spawn_denials",
882
+ {
883
+ id: uuid("id").primaryKey().defaultRandom(),
884
+ accountId: uuid("account_id").notNull(),
885
+ workspaceId: uuid("workspace_id").notNull(),
886
+ parentSessionId: uuid("parent_session_id"),
887
+ rootSessionId: uuid("root_session_id"),
888
+ currentDepth: integer("current_depth").notNull(),
889
+ attemptedDepth: bigint("attempted_depth", { mode: "number" }).notNull(),
890
+ effectiveMaxNestedAgentDepth: integer("effective_max_nested_agent_depth").notNull(),
891
+ requestedMaxNestedAgentDepthOverride: integer("requested_max_nested_agent_depth_override"),
892
+ policySource: text("policy_source").notNull(),
893
+ policySessionId: uuid("policy_session_id"),
894
+ subjectId: text("subject_id"),
895
+ code: text("code").notNull(),
896
+ idempotencyKey: text("idempotency_key"),
897
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
898
+ },
899
+ (table) => ({
900
+ workspaceIdentity: uniqueIndex("session_spawn_denials_workspace_id_uq").on(
901
+ table.workspaceId,
902
+ table.id,
903
+ ),
904
+ workspaceCreated: index("session_spawn_denials_workspace_created_idx").on(
905
+ table.workspaceId,
906
+ table.createdAt,
907
+ ),
908
+ parent: index("session_spawn_denials_parent_idx").on(
909
+ table.workspaceId,
910
+ table.parentSessionId,
911
+ table.createdAt,
912
+ ),
913
+ idempotency: uniqueIndex("session_spawn_denials_workspace_idempotency_idx")
914
+ .on(table.workspaceId, table.idempotencyKey)
915
+ .where(sql`${table.idempotencyKey} is not null`),
916
+ workspaceAccount: foreignKey({
917
+ name: "session_spawn_denials_workspace_account_fk",
918
+ columns: [table.workspaceId, table.accountId],
919
+ foreignColumns: [workspaces.id, workspaces.accountId],
920
+ }).onDelete("cascade"),
738
921
  }),
739
922
  );
740
923
 
@@ -1156,6 +1339,9 @@ export const sessionTurns = pgTable(
1156
1339
  turnInstructions: text("turn_instructions"),
1157
1340
  resources: jsonb("resources").$type<unknown[]>().notNull().default([]),
1158
1341
  tools: jsonb("tools").$type<unknown[]>().notNull().default([]),
1342
+ // false = inherit the durable session policy; true = this turn explicitly
1343
+ // replaces it with `tools` after the core subset fence.
1344
+ toolsProvided: boolean("tools_provided").notNull().default(false),
1159
1345
  model: text("model").notNull(),
1160
1346
  reasoningEffort: text("reasoning_effort").notNull(),
1161
1347
  sandboxBackend: text("sandbox_backend").notNull(),
@@ -1230,6 +1416,10 @@ export const sessionTurnAttempts = pgTable(
1230
1416
  verifiedControlRevision: bigint("verified_control_revision", {
1231
1417
  mode: "number",
1232
1418
  }).notNull(),
1419
+ // Immutable policy snapshot captured under the session lock at claim.
1420
+ mcpApprovalPolicies: jsonb("mcp_approval_policies")
1421
+ .$type<Record<string, SessionMcpApprovalPolicy>>()
1422
+ .notNull(),
1233
1423
  startedAt: timestamp("started_at", { withTimezone: true }).notNull().defaultNow(),
1234
1424
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
1235
1425
  closedAt: timestamp("closed_at", { withTimezone: true }),
@@ -1363,6 +1553,9 @@ export const sessionCommandReceipts = pgTable(
1363
1553
  table.targetSessionId,
1364
1554
  table.createdAt,
1365
1555
  ),
1556
+ goalUpdateOperation: uniqueIndex("session_command_receipts_goal_update_operation_uq")
1557
+ .on(table.workspaceId, table.action, table.targetSessionId, table.operationKey)
1558
+ .where(sql`${table.action} = 'goal.update'`),
1366
1559
  actorValid: check(
1367
1560
  "session_command_receipts_actor_check",
1368
1561
  sql`(
@@ -1502,6 +1695,7 @@ export const composerDrafts = pgTable(
1502
1695
  text: text("text").notNull().default(""),
1503
1696
  resources: jsonb("resources").$type<unknown[]>().notNull().default([]),
1504
1697
  tools: jsonb("tools").$type<unknown[]>().notNull().default([]),
1698
+ toolsProvided: boolean("tools_provided").notNull().default(false),
1505
1699
  model: text("model").notNull(),
1506
1700
  reasoningEffort: text("reasoning_effort").notNull(),
1507
1701
  sourceTurnId: uuid("source_turn_id"),
@@ -1538,6 +1732,43 @@ export const composerDrafts = pgTable(
1538
1732
  }),
1539
1733
  );
1540
1734
 
1735
+ // Private pre-session composer truth. It is separate from composerDrafts so
1736
+ // the established-session table keeps its mandatory session foreign key.
1737
+ export const newSessionDrafts = pgTable(
1738
+ "new_session_drafts",
1739
+ {
1740
+ id: uuid("id").primaryKey().defaultRandom(),
1741
+ accountId: uuid("account_id").notNull(),
1742
+ workspaceId: uuid("workspace_id").notNull(),
1743
+ subjectId: text("subject_id").notNull(),
1744
+ revision: bigint("revision", { mode: "number" }).notNull().default(1),
1745
+ text: text("text").notNull().default(""),
1746
+ resources: jsonb("resources").$type<unknown[]>().notNull().default([]),
1747
+ tools: jsonb("tools").$type<unknown[]>().notNull().default([]),
1748
+ model: text("model").notNull(),
1749
+ reasoningEffort: text("reasoning_effort").notNull(),
1750
+ sessionOptions: jsonb("session_options").$type<Record<string, unknown>>().notNull().default({}),
1751
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
1752
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
1753
+ },
1754
+ (table) => ({
1755
+ workspaceAccount: foreignKey({
1756
+ name: "new_session_drafts_workspace_account_fk",
1757
+ columns: [table.workspaceId, table.accountId],
1758
+ foreignColumns: [workspaces.id, workspaces.accountId],
1759
+ }).onDelete("cascade"),
1760
+ subjectWorkspace: uniqueIndex("new_session_drafts_subject_workspace_uq").on(
1761
+ table.workspaceId,
1762
+ table.subjectId,
1763
+ ),
1764
+ subjectValid: check(
1765
+ "new_session_drafts_subject_check",
1766
+ sql`length(btrim(${table.subjectId})) > 0`,
1767
+ ),
1768
+ revisionValid: check("new_session_drafts_revision_check", sql`${table.revision} >= 1`),
1769
+ }),
1770
+ );
1771
+
1541
1772
  export const sessionSystemUpdates = pgTable(
1542
1773
  "session_system_updates",
1543
1774
  {
@@ -1677,6 +1908,10 @@ export const sessionWorkflowWakeOutbox = pgTable(
1677
1908
  "session_workflow_wake_outbox_revision_check",
1678
1909
  sql`${table.wakeRevision} > 0 and ${table.deliveredRevision} >= 0 and ${table.deliveredRevision} <= ${table.wakeRevision}`,
1679
1910
  ),
1911
+ revisionSafe: check(
1912
+ "session_workflow_wake_outbox_revision_safe_check",
1913
+ sql`${table.wakeRevision} <= 9007199254740991 and ${table.deliveredRevision} <= 9007199254740991`,
1914
+ ),
1680
1915
  workspaceAccount: foreignKey({
1681
1916
  name: "session_workflow_wake_outbox_workspace_account_fk",
1682
1917
  columns: [table.workspaceId, table.accountId],
@@ -1723,6 +1958,18 @@ export const sessionGoals = pgTable(
1723
1958
  maxAutoContinuations: integer("max_auto_continuations"), // per-goal override; a configured settings cap (if any) remains the hard ceiling
1724
1959
  lastContinuationTurnId: uuid("last_continuation_turn_id"),
1725
1960
  versionAtLastContinuation: integer("version_at_last_continuation"),
1961
+ // Postgres owns the continuation obligation. Terminal settlement advances
1962
+ // wakeRevision in the same transaction that makes the session idle;
1963
+ // materialization advances observedRevision only alongside the one typed
1964
+ // update, timeline events, usage row, and workflow-wake outbox row.
1965
+ // Temporal signals and workflow history are replaceable nudges over these
1966
+ // monotonic revisions.
1967
+ continuationWakeRevision: bigint("continuation_wake_revision", { mode: "number" })
1968
+ .notNull()
1969
+ .default(0),
1970
+ continuationObservedRevision: bigint("continuation_observed_revision", { mode: "number" })
1971
+ .notNull()
1972
+ .default(0),
1726
1973
  metadata: jsonb("metadata").$type<Record<string, unknown>>().notNull().default({}),
1727
1974
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
1728
1975
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
@@ -1737,6 +1984,10 @@ export const sessionGoals = pgTable(
1737
1984
  table.sessionId,
1738
1985
  ),
1739
1986
  status: index("session_goals_workspace_status_idx").on(table.workspaceId, table.status),
1987
+ continuationRevisionValid: check(
1988
+ "session_goals_continuation_revision_check",
1989
+ sql`${table.continuationWakeRevision} >= 0 and ${table.continuationObservedRevision} >= 0 and ${table.continuationObservedRevision} <= ${table.continuationWakeRevision} and ${table.continuationWakeRevision} <= 9007199254740991 and ${table.continuationObservedRevision} <= 9007199254740991`,
1990
+ ),
1740
1991
  }),
1741
1992
  );
1742
1993
 
@@ -2096,6 +2347,7 @@ export const sessionPendingToolCalls = pgTable(
2096
2347
  callId: text("call_id").notNull(),
2097
2348
  callType: text("call_type").notNull(),
2098
2349
  callItem: jsonb("call_item").$type<Record<string, unknown>>().notNull(),
2350
+ modelToolOutputTruncationTokens: integer("model_tool_output_truncation_tokens"),
2099
2351
  resultItem: jsonb("result_item").$type<Record<string, unknown>>(),
2100
2352
  resultRecordedAt: timestamp("result_recorded_at", { withTimezone: true }),
2101
2353
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
@@ -2208,6 +2460,14 @@ export const sandboxLeases = pgTable(
2208
2460
  // Epochs never approach 2^31, so the narrower type loses nothing.
2209
2461
  leaseEpoch: integer("lease_epoch").notNull().default(0),
2210
2462
 
2463
+ // Monotonic mutation intent for the live workspace. Every acknowledged
2464
+ // filesystem-writing operation advances this under the exact lease epoch +
2465
+ // provider-instance fence BEFORE it reaches the provider. A verified
2466
+ // archive fold copies the exact captured value into archive_generation in
2467
+ // the same row update; equality is the only durable completeness proof.
2468
+ workspaceGeneration: integer("workspace_generation").notNull().default(0),
2469
+ archiveGeneration: integer("archive_generation"),
2470
+
2211
2471
  // The group box-envelope (the "envelope split" Critical): the small recovery
2212
2472
  // descriptor to resume()-by-id the group's box without a per-session join.
2213
2473
  resumeBackendId: text("resume_backend_id"),
@@ -2225,9 +2485,30 @@ export const sandboxLeases = pgTable(
2225
2485
  },
2226
2486
  (table) => ({
2227
2487
  groupIdx: uniqueIndex("sandbox_leases_group_idx").on(table.workspaceId, table.sandboxGroupId),
2488
+ scopedId: uniqueIndex("sandbox_leases_scoped_id_uq").on(
2489
+ table.accountId,
2490
+ table.workspaceId,
2491
+ table.sandboxGroupId,
2492
+ table.id,
2493
+ ),
2494
+ accountWorkspaceId: uniqueIndex("sandbox_leases_account_workspace_id_uq").on(
2495
+ table.accountId,
2496
+ table.workspaceId,
2497
+ table.id,
2498
+ ),
2228
2499
  reaperIdx: index("sandbox_leases_reaper_idx")
2229
2500
  .on(table.expiresAt)
2230
2501
  .where(sql`${table.liveness} in ('warming','warm','draining')`),
2502
+ workspaceGenerationValid: check(
2503
+ "sandbox_leases_workspace_generation_check",
2504
+ sql`${table.workspaceGeneration} >= 0`,
2505
+ ),
2506
+ archiveGenerationValid: check(
2507
+ "sandbox_leases_archive_generation_check",
2508
+ sql`${table.archiveGeneration} is null
2509
+ or (${table.archiveGeneration} >= 0
2510
+ and ${table.archiveGeneration} <= ${table.workspaceGeneration})`,
2511
+ ),
2231
2512
  }),
2232
2513
  );
2233
2514
 
@@ -2246,7 +2527,7 @@ export const sandboxLeaseHolders = pgTable(
2246
2527
  leaseId: uuid("lease_id")
2247
2528
  .notNull()
2248
2529
  .references(() => sandboxLeases.id, { onDelete: "cascade" }),
2249
- kind: text("kind", { enum: ["turn", "viewer"] }).notNull(),
2530
+ kind: text("kind", { enum: ["turn", "viewer", "direct", "process"] }).notNull(),
2250
2531
  holderId: text("holder_id").notNull(),
2251
2532
  // The attributing session within the (possibly shared) group.
2252
2533
  subjectId: uuid("subject_id"),
@@ -2254,6 +2535,11 @@ export const sandboxLeaseHolders = pgTable(
2254
2535
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
2255
2536
  },
2256
2537
  (table) => ({
2538
+ leaseScope: foreignKey({
2539
+ name: "sandbox_lease_holders_lease_scope_fk",
2540
+ columns: [table.accountId, table.workspaceId, table.leaseId],
2541
+ foreignColumns: [sandboxLeases.accountId, sandboxLeases.workspaceId, sandboxLeases.id],
2542
+ }).onDelete("cascade"),
2257
2543
  holderIdx: uniqueIndex("sandbox_lease_holders_holder_idx").on(
2258
2544
  table.leaseId,
2259
2545
  table.kind,
@@ -2264,6 +2550,283 @@ export const sandboxLeaseHolders = pgTable(
2264
2550
  }),
2265
2551
  );
2266
2552
 
2553
+ export const sandboxWorkspaceMutationActorKindValues = ["turn", "direct", "process"] as const;
2554
+ export const sandboxWorkspaceMutationHolderKindValues = ["turn", "direct", "process"] as const;
2555
+
2556
+ // Durable admission ledger for every provider operation that may mutate a
2557
+ // persistable /workspace. The row is inserted atomically with the lease's
2558
+ // workspace_generation increment before the provider is invoked, then marked
2559
+ // physically settled after the provider promise resolves OR rejects. Capture
2560
+ // remains blocked by an unsettled direct/process row. A turn row may cease
2561
+ // blocking only after its exact attempt carries the authoritative quiesced_at
2562
+ // receipt. Yielded provider processes remain retained/unsettled until exact
2563
+ // exit or loss proof settles their parent admission.
2564
+ export const sandboxWorkspaceMutationAdmissions = pgTable(
2565
+ "sandbox_workspace_mutation_admissions",
2566
+ {
2567
+ id: uuid("id").primaryKey().defaultRandom(),
2568
+ accountId: uuid("account_id").notNull(),
2569
+ workspaceId: uuid("workspace_id").notNull(),
2570
+ leaseId: uuid("lease_id")
2571
+ .notNull()
2572
+ .references(() => sandboxLeases.id, { onDelete: "cascade" }),
2573
+ sandboxGroupId: uuid("sandbox_group_id").notNull(),
2574
+ sessionId: uuid("session_id").notNull(),
2575
+ actorKind: text("actor_kind", { enum: sandboxWorkspaceMutationActorKindValues }).notNull(),
2576
+ actorId: uuid("actor_id").notNull(),
2577
+ // Exact turn authority is present only for actor_kind='turn'. Direct HTTP
2578
+ // requests and retained processes never invent a turn or quiescence owner.
2579
+ turnId: uuid("turn_id"),
2580
+ attemptId: uuid("attempt_id"),
2581
+ executionGeneration: integer("execution_generation"),
2582
+ holderKind: text("holder_kind", {
2583
+ enum: sandboxWorkspaceMutationHolderKindValues,
2584
+ }).notNull(),
2585
+ holderId: text("holder_id").notNull(),
2586
+ leaseEpoch: integer("lease_epoch").notNull(),
2587
+ providerBackend: text("provider_backend").notNull(),
2588
+ providerInstanceId: text("provider_instance_id").notNull(),
2589
+ routeKind: text("route_kind", { enum: ["home", "active"] }).notNull(),
2590
+ // null route target means the persistable home/group provider. A non-null
2591
+ // target is pinned together with the active pointer epoch observed when the
2592
+ // operation was admitted.
2593
+ routeTargetId: uuid("route_target_id"),
2594
+ routeEpoch: integer("route_epoch").notNull(),
2595
+ workspaceGeneration: integer("workspace_generation").notNull(),
2596
+ operation: text("operation").notNull(),
2597
+ providerOutcome: text("provider_outcome", {
2598
+ enum: ["resolved", "rejected", "retained"],
2599
+ }),
2600
+ admittedAt: timestamp("admitted_at", { withTimezone: true }).notNull().defaultNow(),
2601
+ settledAt: timestamp("settled_at", { withTimezone: true }),
2602
+ },
2603
+ (table) => ({
2604
+ workspaceAccount: foreignKey({
2605
+ name: "sandbox_workspace_mutation_admissions_workspace_account_fk",
2606
+ columns: [table.workspaceId, table.accountId],
2607
+ foreignColumns: [workspaces.id, workspaces.accountId],
2608
+ }).onDelete("cascade"),
2609
+ workspaceSession: foreignKey({
2610
+ name: "sandbox_workspace_mutation_admissions_workspace_session_fk",
2611
+ columns: [table.workspaceId, table.sessionId],
2612
+ foreignColumns: [sessions.workspaceId, sessions.id],
2613
+ }).onDelete("restrict"),
2614
+ workspaceTurn: foreignKey({
2615
+ name: "sandbox_workspace_mutation_admissions_workspace_turn_fk",
2616
+ columns: [table.workspaceId, table.turnId],
2617
+ foreignColumns: [sessionTurns.workspaceId, sessionTurns.id],
2618
+ }).onDelete("restrict"),
2619
+ workspaceAttempt: foreignKey({
2620
+ name: "sandbox_workspace_mutation_admissions_workspace_attempt_fk",
2621
+ columns: [table.workspaceId, table.attemptId],
2622
+ foreignColumns: [sessionTurnAttempts.workspaceId, sessionTurnAttempts.id],
2623
+ }).onDelete("restrict"),
2624
+ leaseGeneration: uniqueIndex("sandbox_workspace_mutation_admissions_lease_generation_uq").on(
2625
+ table.leaseId,
2626
+ table.workspaceGeneration,
2627
+ ),
2628
+ scopedId: uniqueIndex("sandbox_workspace_mutation_admissions_scoped_id_uq").on(
2629
+ table.accountId,
2630
+ table.workspaceId,
2631
+ table.sessionId,
2632
+ table.leaseId,
2633
+ table.id,
2634
+ ),
2635
+ blocking: index("sandbox_workspace_mutation_admissions_blocking_idx")
2636
+ .on(table.leaseId, table.workspaceGeneration)
2637
+ .where(sql`${table.settledAt} is null`),
2638
+ attempt: index("sandbox_workspace_mutation_admissions_attempt_idx").on(
2639
+ table.workspaceId,
2640
+ table.attemptId,
2641
+ ),
2642
+ actor: index("sandbox_workspace_mutation_admissions_actor_idx").on(
2643
+ table.workspaceId,
2644
+ table.actorKind,
2645
+ table.actorId,
2646
+ ),
2647
+ generationValid: check(
2648
+ "sandbox_workspace_mutation_admissions_generation_check",
2649
+ sql`${table.workspaceGeneration} > 0
2650
+ and ${table.leaseEpoch} >= 0
2651
+ and ${table.routeEpoch} >= 0
2652
+ and (${table.executionGeneration} is null or ${table.executionGeneration} > 0)`,
2653
+ ),
2654
+ actorValid: check(
2655
+ "sandbox_workspace_mutation_admissions_actor_check",
2656
+ sql`(
2657
+ ${table.actorKind} = 'turn'
2658
+ and ${table.actorId} = ${table.attemptId}
2659
+ and ${table.turnId} is not null
2660
+ and ${table.attemptId} is not null
2661
+ and ${table.executionGeneration} is not null
2662
+ and ${table.holderKind} = 'turn'
2663
+ ) or (
2664
+ ${table.actorKind} = 'direct'
2665
+ and ${table.turnId} is null
2666
+ and ${table.attemptId} is null
2667
+ and ${table.executionGeneration} is null
2668
+ and ${table.holderKind} = 'direct'
2669
+ ) or (
2670
+ ${table.actorKind} = 'process'
2671
+ and ${table.turnId} is null
2672
+ and ${table.attemptId} is null
2673
+ and ${table.executionGeneration} is null
2674
+ and ${table.holderKind} = 'process'
2675
+ )`,
2676
+ ),
2677
+ routeValid: check(
2678
+ "sandbox_workspace_mutation_admissions_route_check",
2679
+ sql`${table.actorKind} in ('turn', 'direct', 'process')
2680
+ and ${table.holderKind} in ('turn', 'direct', 'process')
2681
+ and octet_length(${table.holderId}) between 1 and 256
2682
+ and octet_length(${table.providerBackend}) between 1 and 64
2683
+ and octet_length(${table.providerInstanceId}) between 1 and 512
2684
+ and ${table.routeKind} in ('home', 'active')
2685
+ and (${table.routeKind} = 'active' or ${table.routeTargetId} is null)`,
2686
+ ),
2687
+ operationValid: check(
2688
+ "sandbox_workspace_mutation_admissions_operation_check",
2689
+ sql`octet_length(${table.operation}) between 1 and 128`,
2690
+ ),
2691
+ outcomeValid: check(
2692
+ "sandbox_workspace_mutation_admissions_outcome_check",
2693
+ sql`${table.providerOutcome} is null or ${table.providerOutcome} in ('resolved', 'rejected', 'retained')`,
2694
+ ),
2695
+ settlementConsistent: check(
2696
+ "sandbox_workspace_mutation_admissions_settlement_check",
2697
+ sql`(${table.providerOutcome} is null and ${table.settledAt} is null)
2698
+ or (${table.providerOutcome} = 'retained' and ${table.settledAt} is null)
2699
+ or (${table.providerOutcome} in ('resolved', 'rejected') and ${table.settledAt} is not null)`,
2700
+ ),
2701
+ }),
2702
+ );
2703
+
2704
+ export const sandboxRetainedProcessStateValues = ["active", "exited", "lost"] as const;
2705
+
2706
+ // A yielded exec is not merely a numeric provider session id: it is a durable
2707
+ // continuation of the exact admitted mutation and owns a non-TTL process lease
2708
+ // holder until exit/loss is proven. Every later model-facing stdin write gets a
2709
+ // distinct actor_kind='process' admission tied back to this identity. Control
2710
+ // polling may use the pinned provider route without creating a new generation.
2711
+ export const sandboxRetainedProcesses = pgTable(
2712
+ "sandbox_retained_processes",
2713
+ {
2714
+ id: uuid("id").primaryKey().defaultRandom(),
2715
+ accountId: uuid("account_id").notNull(),
2716
+ workspaceId: uuid("workspace_id").notNull(),
2717
+ sessionId: uuid("session_id").notNull(),
2718
+ leaseId: uuid("lease_id").notNull(),
2719
+ sandboxGroupId: uuid("sandbox_group_id").notNull(),
2720
+ parentAdmissionId: uuid("parent_admission_id").notNull(),
2721
+ holderId: text("holder_id").notNull(),
2722
+ ownerActorKind: text("owner_actor_kind", { enum: ["turn", "direct"] }).notNull(),
2723
+ ownerActorId: uuid("owner_actor_id").notNull(),
2724
+ ownerTurnId: uuid("owner_turn_id"),
2725
+ ownerAttemptId: uuid("owner_attempt_id"),
2726
+ ownerExecutionGeneration: integer("owner_execution_generation"),
2727
+ leaseEpoch: integer("lease_epoch").notNull(),
2728
+ providerBackend: text("provider_backend").notNull(),
2729
+ providerInstanceId: text("provider_instance_id").notNull(),
2730
+ routeKind: text("route_kind", { enum: ["home", "active"] }).notNull(),
2731
+ routeTargetId: uuid("route_target_id"),
2732
+ routeEpoch: integer("route_epoch").notNull(),
2733
+ providerSessionId: integer("provider_session_id").notNull(),
2734
+ state: text("state", { enum: sandboxRetainedProcessStateValues }).notNull().default("active"),
2735
+ exitCode: integer("exit_code"),
2736
+ settlementReason: text("settlement_reason"),
2737
+ startedAt: timestamp("started_at", { withTimezone: true }).notNull().defaultNow(),
2738
+ settledAt: timestamp("settled_at", { withTimezone: true }),
2739
+ },
2740
+ (table) => ({
2741
+ workspaceAccount: foreignKey({
2742
+ name: "sandbox_retained_processes_workspace_account_fk",
2743
+ columns: [table.workspaceId, table.accountId],
2744
+ foreignColumns: [workspaces.id, workspaces.accountId],
2745
+ }).onDelete("cascade"),
2746
+ workspaceSession: foreignKey({
2747
+ name: "sandbox_retained_processes_workspace_session_fk",
2748
+ columns: [table.workspaceId, table.sessionId],
2749
+ foreignColumns: [sessions.workspaceId, sessions.id],
2750
+ }).onDelete("restrict"),
2751
+ parentAdmissionScope: foreignKey({
2752
+ name: "sandbox_retained_processes_parent_admission_scope_fk",
2753
+ columns: [
2754
+ table.accountId,
2755
+ table.workspaceId,
2756
+ table.sessionId,
2757
+ table.leaseId,
2758
+ table.parentAdmissionId,
2759
+ ],
2760
+ foreignColumns: [
2761
+ sandboxWorkspaceMutationAdmissions.accountId,
2762
+ sandboxWorkspaceMutationAdmissions.workspaceId,
2763
+ sandboxWorkspaceMutationAdmissions.sessionId,
2764
+ sandboxWorkspaceMutationAdmissions.leaseId,
2765
+ sandboxWorkspaceMutationAdmissions.id,
2766
+ ],
2767
+ }).onDelete("restrict"),
2768
+ scopedId: uniqueIndex("sandbox_retained_processes_scoped_id_uq").on(
2769
+ table.accountId,
2770
+ table.workspaceId,
2771
+ table.sessionId,
2772
+ table.leaseId,
2773
+ table.id,
2774
+ ),
2775
+ parentAdmission: uniqueIndex("sandbox_retained_processes_parent_admission_uq").on(
2776
+ table.parentAdmissionId,
2777
+ ),
2778
+ liveProviderSession: uniqueIndex("sandbox_retained_processes_live_provider_session_uq")
2779
+ .on(
2780
+ table.leaseId,
2781
+ table.leaseEpoch,
2782
+ table.providerInstanceId,
2783
+ table.routeEpoch,
2784
+ table.providerSessionId,
2785
+ )
2786
+ .where(sql`${table.state} = 'active'`),
2787
+ holder: uniqueIndex("sandbox_retained_processes_holder_uq").on(table.leaseId, table.holderId),
2788
+ active: index("sandbox_retained_processes_active_idx")
2789
+ .on(table.workspaceId, table.sessionId, table.startedAt)
2790
+ .where(sql`${table.state} = 'active'`),
2791
+ identityValid: check(
2792
+ "sandbox_retained_processes_identity_check",
2793
+ sql`${table.leaseEpoch} >= 0
2794
+ and ${table.routeEpoch} >= 0
2795
+ and ${table.providerSessionId} > 0
2796
+ and octet_length(${table.holderId}) between 1 and 256
2797
+ and octet_length(${table.providerBackend}) between 1 and 64
2798
+ and octet_length(${table.providerInstanceId}) between 1 and 512
2799
+ and (${table.routeKind} = 'active' or ${table.routeTargetId} is null)`,
2800
+ ),
2801
+ ownerValid: check(
2802
+ "sandbox_retained_processes_owner_check",
2803
+ sql`(
2804
+ ${table.ownerActorKind} = 'turn'
2805
+ and ${table.ownerActorId} = ${table.ownerAttemptId}
2806
+ and ${table.ownerTurnId} is not null
2807
+ and ${table.ownerAttemptId} is not null
2808
+ and ${table.ownerExecutionGeneration} > 0
2809
+ ) or (
2810
+ ${table.ownerActorKind} = 'direct'
2811
+ and ${table.ownerTurnId} is null
2812
+ and ${table.ownerAttemptId} is null
2813
+ and ${table.ownerExecutionGeneration} is null
2814
+ )`,
2815
+ ),
2816
+ settlementValid: check(
2817
+ "sandbox_retained_processes_settlement_check",
2818
+ sql`(${table.state} = 'active' and ${table.settledAt} is null and ${table.exitCode} is null)
2819
+ or (${table.state} = 'exited' and ${table.settledAt} is not null)
2820
+ or (${table.state} = 'lost' and ${table.settledAt} is not null and ${table.exitCode} is null)`,
2821
+ ),
2822
+ reasonValid: check(
2823
+ "sandbox_retained_processes_reason_check",
2824
+ sql`${table.settlementReason} is null
2825
+ or octet_length(${table.settlementReason}) between 1 and 512`,
2826
+ ),
2827
+ }),
2828
+ );
2829
+
2267
2830
  // The recording lifecycle states (P4.3). Exported so the activity + the query
2268
2831
  // layer share one source of truth for the §3.1 state machine.
2269
2832
  export const sessionRecordingStateValues = [
@@ -2384,14 +2947,12 @@ export const workspaceCaptures = pgTable(
2384
2947
  }),
2385
2948
  );
2386
2949
 
2387
- // Interactive PTY sessions. This is the persistent state needed for a live
2388
- // terminal; file and Git reads remain stateless point
2389
- // queries; an interactive PTY is a live in-box process keyed by the SDK's numeric
2390
- // exec-session id (writeStdin({sessionId})). We map our UUID ptyId <-> that id,
2391
- // the owning workspace/session, the lease_epoch that fences it to the box it was
2392
- // opened on (a box re-key strands the PTY -> reaped with reason owner_gone), and
2393
- // a last_input_at heartbeat so the reaper can kill idle/orphaned PTYs. Mirrors
2394
- // the account/workspace/session FK chain of sandboxSessionEnvelopes.
2950
+ // Interactive PTY sessions. An OPEN PTY adopts one exact retained process; the
2951
+ // provider's numeric exec-session id is only a copied locator and never authority
2952
+ // on its own. Provider/lease/route/admission identity is copied onto the row so a
2953
+ // stale pointer or box epoch cannot redirect control to a rival process. Legacy
2954
+ // numeric-only rows are closed by the maintenance cutover and may retain null
2955
+ // identity columns only in that terminal state.
2395
2956
  export const sandboxPtySessions = pgTable(
2396
2957
  "sandbox_pty_sessions",
2397
2958
  {
@@ -2405,10 +2966,18 @@ export const sandboxPtySessions = pgTable(
2405
2966
  sessionId: uuid("session_id")
2406
2967
  .notNull()
2407
2968
  .references(() => sessions.id, { onDelete: "cascade" }),
2408
- // The SDK numeric exec-session id used by writeStdin({ sessionId }). Null until
2409
- // the open exec yields a still-running process (a fast-exiting shell has none).
2969
+ leaseId: uuid("lease_id"),
2970
+ sandboxGroupId: uuid("sandbox_group_id"),
2971
+ retainedProcessId: uuid("retained_process_id"),
2972
+ openAdmissionId: uuid("open_admission_id"),
2973
+ // Copied provider locator for the adopted retained process.
2410
2974
  execSessionId: integer("exec_session_id"),
2411
- leaseEpoch: integer("lease_epoch").notNull(), // fenced to the box that opened it
2975
+ leaseEpoch: integer("lease_epoch").notNull(),
2976
+ providerBackend: text("provider_backend"),
2977
+ providerInstanceId: text("provider_instance_id"),
2978
+ routeKind: text("route_kind", { enum: ["home", "active"] }),
2979
+ routeTargetId: uuid("route_target_id"),
2980
+ routeEpoch: integer("route_epoch"),
2412
2981
  cols: integer("cols").notNull(),
2413
2982
  rows: integer("rows").notNull(),
2414
2983
  shell: text("shell").notNull(),
@@ -2422,9 +2991,46 @@ export const sandboxPtySessions = pgTable(
2422
2991
  closedAt: timestamp("closed_at", { withTimezone: true }),
2423
2992
  },
2424
2993
  (table) => ({
2994
+ retainedProcessScope: foreignKey({
2995
+ name: "sandbox_pty_sessions_retained_process_scope_fk",
2996
+ columns: [
2997
+ table.accountId,
2998
+ table.workspaceId,
2999
+ table.sessionId,
3000
+ table.leaseId,
3001
+ table.retainedProcessId,
3002
+ ],
3003
+ foreignColumns: [
3004
+ sandboxRetainedProcesses.accountId,
3005
+ sandboxRetainedProcesses.workspaceId,
3006
+ sandboxRetainedProcesses.sessionId,
3007
+ sandboxRetainedProcesses.leaseId,
3008
+ sandboxRetainedProcesses.id,
3009
+ ],
3010
+ }).onDelete("restrict"),
2425
3011
  openIdx: index("sandbox_pty_sessions_session_idx")
2426
3012
  .on(table.workspaceId, table.sessionId)
2427
3013
  .where(sql`${table.status} = 'open'`),
3014
+ processIdx: uniqueIndex("sandbox_pty_sessions_open_process_uq")
3015
+ .on(table.retainedProcessId)
3016
+ .where(sql`${table.status} = 'open'`),
3017
+ openIdentityValid: check(
3018
+ "sandbox_pty_sessions_open_identity_check",
3019
+ sql`${table.status} <> 'open' or (
3020
+ ${table.leaseId} is not null
3021
+ and ${table.sandboxGroupId} is not null
3022
+ and ${table.retainedProcessId} is not null
3023
+ and ${table.openAdmissionId} is not null
3024
+ and ${table.execSessionId} > 0
3025
+ and octet_length(${table.providerBackend}) between 1 and 64
3026
+ and octet_length(${table.providerInstanceId}) between 1 and 512
3027
+ and ${table.routeKind} in ('home', 'active')
3028
+ and (${table.routeKind} = 'active' or ${table.routeTargetId} is null)
3029
+ and ${table.routeEpoch} is not null
3030
+ and ${table.leaseEpoch} >= 0
3031
+ and ${table.routeEpoch} >= 0
3032
+ )`,
3033
+ ),
2428
3034
  }),
2429
3035
  );
2430
3036
 
@@ -2999,6 +3605,10 @@ export const hostExportOutbox = pgTable(
2999
3605
  "host_export_outbox_kind_check",
3000
3606
  sql`${table.exportKind} in ('session_event', 'usage_event')`,
3001
3607
  ),
3608
+ rootSessionCaptured: check(
3609
+ "host_export_outbox_root_session_check",
3610
+ sql`${table.sessionId} is null or ${table.rootSessionId} is not null`,
3611
+ ),
3002
3612
  sourceUnique: uniqueIndex("host_export_outbox_source_uq").on(table.exportKind, table.sourceId),
3003
3613
  cursorUnique: uniqueIndex("host_export_outbox_cursor_uq")
3004
3614
  .on(table.exportKind, table.exportCursor)