@opengeni/db 0.10.7 → 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 (35) hide show
  1. package/dist/{chunk-P6PKXY5W.js → chunk-VUKRIBO5.js} +485 -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 +3 -2
  6. package/dist/index.js +5114 -2085
  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 +720 -63
  11. package/dist/{schema-CqkzrBRS.d.ts → schema-CnpD6BcX.d.ts} +4684 -2913
  12. package/dist/schema.d.ts +1 -1
  13. package/dist/schema.js +17 -1
  14. package/drizzle/0109_nested_agent_depth_expand.sql +42 -0
  15. package/drizzle/0110_nested_agent_depth_boundary.sql +480 -0
  16. package/drizzle/0111_nested_agent_depth_backfill.sql +49 -0
  17. package/drizzle/0112_nested_agent_depth_contract.sql +38 -0
  18. package/drizzle/0113_nested_agent_depth_validate.sql +13 -0
  19. package/drizzle/0114_nested_agent_depth_contract.sql +49 -0
  20. package/drizzle/0115_nested_agent_depth_validate.sql +11 -0
  21. package/drizzle/0116_nested_agent_depth_index.sql +4 -0
  22. package/drizzle/0117_sandbox_recovery_generations.sql +699 -0
  23. package/drizzle/0118_new_session_drafts.sql +59 -0
  24. package/drizzle/0119_pending_tool_output_policy.sql +5 -0
  25. package/drizzle/0120_durable_goal_wake.sql +360 -0
  26. package/drizzle/0121_goal_update_idempotency.sql +11 -0
  27. package/package.json +3 -3
  28. package/src/index.ts +5961 -1240
  29. package/src/migrate.ts +131 -2
  30. package/src/new-session-drafts.ts +144 -0
  31. package/src/schema.ts +519 -15
  32. package/src/session-control.ts +42 -18
  33. package/src/session-tool-call-settlement.ts +6 -1
  34. package/dist/chunk-KW526IJA.js.map +0 -1
  35. package/dist/chunk-P6PKXY5W.js.map +0 -1
package/src/schema.ts CHANGED
@@ -81,6 +81,34 @@ export const workspaces = pgTable(
81
81
  }),
82
82
  );
83
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
+
84
112
  // One mandatory workspace-wide admission barrier. Every inference-admitting
85
113
  // transaction locks this row before it touches a session; Pause/Resume and
86
114
  // foreground Send/Steer advance its monotonic revision under FOR UPDATE.
@@ -741,6 +769,15 @@ export const sessions = pgTable(
741
769
  // workspace to a single session row — the dedup that closes the
742
770
  // double-submit/double-dispatch stuck-queued bug.
743
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"),
744
781
  temporalWorkflowId: text("temporal_workflow_id"),
745
782
  activeTurnId: uuid("active_turn_id"),
746
783
  // Actual input tokens reported for the last model call of the most recent
@@ -823,12 +860,64 @@ export const sessions = pgTable(
823
860
  // point and enumerate all sessions in a group for attribution/disclosure.
824
861
  sandboxGroup: index("sessions_sandbox_group_idx").on(table.workspaceId, table.sandboxGroupId),
825
862
  // Partial unique index: one session per (workspace, create_idempotency_key)
826
- // when a key is present. Concurrent creates racing on the same key see a
827
- // unique violation on all but one; the domain layer catches it and returns
828
- // 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.
829
866
  createIdempotency: uniqueIndex("sessions_workspace_create_idempotency_idx")
830
867
  .on(table.workspaceId, table.createIdempotencyKey)
831
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"),
832
921
  }),
833
922
  );
834
923
 
@@ -1464,6 +1553,9 @@ export const sessionCommandReceipts = pgTable(
1464
1553
  table.targetSessionId,
1465
1554
  table.createdAt,
1466
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'`),
1467
1559
  actorValid: check(
1468
1560
  "session_command_receipts_actor_check",
1469
1561
  sql`(
@@ -1640,6 +1732,43 @@ export const composerDrafts = pgTable(
1640
1732
  }),
1641
1733
  );
1642
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
+
1643
1772
  export const sessionSystemUpdates = pgTable(
1644
1773
  "session_system_updates",
1645
1774
  {
@@ -1779,6 +1908,10 @@ export const sessionWorkflowWakeOutbox = pgTable(
1779
1908
  "session_workflow_wake_outbox_revision_check",
1780
1909
  sql`${table.wakeRevision} > 0 and ${table.deliveredRevision} >= 0 and ${table.deliveredRevision} <= ${table.wakeRevision}`,
1781
1910
  ),
1911
+ revisionSafe: check(
1912
+ "session_workflow_wake_outbox_revision_safe_check",
1913
+ sql`${table.wakeRevision} <= 9007199254740991 and ${table.deliveredRevision} <= 9007199254740991`,
1914
+ ),
1782
1915
  workspaceAccount: foreignKey({
1783
1916
  name: "session_workflow_wake_outbox_workspace_account_fk",
1784
1917
  columns: [table.workspaceId, table.accountId],
@@ -1825,6 +1958,18 @@ export const sessionGoals = pgTable(
1825
1958
  maxAutoContinuations: integer("max_auto_continuations"), // per-goal override; a configured settings cap (if any) remains the hard ceiling
1826
1959
  lastContinuationTurnId: uuid("last_continuation_turn_id"),
1827
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),
1828
1973
  metadata: jsonb("metadata").$type<Record<string, unknown>>().notNull().default({}),
1829
1974
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
1830
1975
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
@@ -1839,6 +1984,10 @@ export const sessionGoals = pgTable(
1839
1984
  table.sessionId,
1840
1985
  ),
1841
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
+ ),
1842
1991
  }),
1843
1992
  );
1844
1993
 
@@ -2198,6 +2347,7 @@ export const sessionPendingToolCalls = pgTable(
2198
2347
  callId: text("call_id").notNull(),
2199
2348
  callType: text("call_type").notNull(),
2200
2349
  callItem: jsonb("call_item").$type<Record<string, unknown>>().notNull(),
2350
+ modelToolOutputTruncationTokens: integer("model_tool_output_truncation_tokens"),
2201
2351
  resultItem: jsonb("result_item").$type<Record<string, unknown>>(),
2202
2352
  resultRecordedAt: timestamp("result_recorded_at", { withTimezone: true }),
2203
2353
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
@@ -2310,6 +2460,14 @@ export const sandboxLeases = pgTable(
2310
2460
  // Epochs never approach 2^31, so the narrower type loses nothing.
2311
2461
  leaseEpoch: integer("lease_epoch").notNull().default(0),
2312
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
+
2313
2471
  // The group box-envelope (the "envelope split" Critical): the small recovery
2314
2472
  // descriptor to resume()-by-id the group's box without a per-session join.
2315
2473
  resumeBackendId: text("resume_backend_id"),
@@ -2327,9 +2485,30 @@ export const sandboxLeases = pgTable(
2327
2485
  },
2328
2486
  (table) => ({
2329
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
+ ),
2330
2499
  reaperIdx: index("sandbox_leases_reaper_idx")
2331
2500
  .on(table.expiresAt)
2332
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
+ ),
2333
2512
  }),
2334
2513
  );
2335
2514
 
@@ -2348,7 +2527,7 @@ export const sandboxLeaseHolders = pgTable(
2348
2527
  leaseId: uuid("lease_id")
2349
2528
  .notNull()
2350
2529
  .references(() => sandboxLeases.id, { onDelete: "cascade" }),
2351
- kind: text("kind", { enum: ["turn", "viewer"] }).notNull(),
2530
+ kind: text("kind", { enum: ["turn", "viewer", "direct", "process"] }).notNull(),
2352
2531
  holderId: text("holder_id").notNull(),
2353
2532
  // The attributing session within the (possibly shared) group.
2354
2533
  subjectId: uuid("subject_id"),
@@ -2356,6 +2535,11 @@ export const sandboxLeaseHolders = pgTable(
2356
2535
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
2357
2536
  },
2358
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"),
2359
2543
  holderIdx: uniqueIndex("sandbox_lease_holders_holder_idx").on(
2360
2544
  table.leaseId,
2361
2545
  table.kind,
@@ -2366,6 +2550,283 @@ export const sandboxLeaseHolders = pgTable(
2366
2550
  }),
2367
2551
  );
2368
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
+
2369
2830
  // The recording lifecycle states (P4.3). Exported so the activity + the query
2370
2831
  // layer share one source of truth for the §3.1 state machine.
2371
2832
  export const sessionRecordingStateValues = [
@@ -2486,14 +2947,12 @@ export const workspaceCaptures = pgTable(
2486
2947
  }),
2487
2948
  );
2488
2949
 
2489
- // Interactive PTY sessions. This is the persistent state needed for a live
2490
- // terminal; file and Git reads remain stateless point
2491
- // queries; an interactive PTY is a live in-box process keyed by the SDK's numeric
2492
- // exec-session id (writeStdin({sessionId})). We map our UUID ptyId <-> that id,
2493
- // the owning workspace/session, the lease_epoch that fences it to the box it was
2494
- // opened on (a box re-key strands the PTY -> reaped with reason owner_gone), and
2495
- // a last_input_at heartbeat so the reaper can kill idle/orphaned PTYs. Mirrors
2496
- // 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.
2497
2956
  export const sandboxPtySessions = pgTable(
2498
2957
  "sandbox_pty_sessions",
2499
2958
  {
@@ -2507,10 +2966,18 @@ export const sandboxPtySessions = pgTable(
2507
2966
  sessionId: uuid("session_id")
2508
2967
  .notNull()
2509
2968
  .references(() => sessions.id, { onDelete: "cascade" }),
2510
- // The SDK numeric exec-session id used by writeStdin({ sessionId }). Null until
2511
- // 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.
2512
2974
  execSessionId: integer("exec_session_id"),
2513
- 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"),
2514
2981
  cols: integer("cols").notNull(),
2515
2982
  rows: integer("rows").notNull(),
2516
2983
  shell: text("shell").notNull(),
@@ -2524,9 +2991,46 @@ export const sandboxPtySessions = pgTable(
2524
2991
  closedAt: timestamp("closed_at", { withTimezone: true }),
2525
2992
  },
2526
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"),
2527
3011
  openIdx: index("sandbox_pty_sessions_session_idx")
2528
3012
  .on(table.workspaceId, table.sessionId)
2529
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
+ ),
2530
3034
  }),
2531
3035
  );
2532
3036