@opengeni/db 0.14.0 → 0.16.2

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 (63) hide show
  1. package/dist/{chunk-ELMUCYZF.js → chunk-4RUK5CON.js} +183 -42
  2. package/dist/chunk-4RUK5CON.js.map +1 -0
  3. package/dist/chunk-HZD7KVAR.js +4648 -0
  4. package/dist/chunk-HZD7KVAR.js.map +1 -0
  5. package/dist/{chunk-Y5WZZVQK.js → chunk-SK7YYHBI.js} +5 -2
  6. package/dist/chunk-SK7YYHBI.js.map +1 -0
  7. package/dist/codex-token-resolver.d.ts +64 -0
  8. package/dist/connection-token-resolver.d.ts +90 -0
  9. package/dist/environment-crypto.d.ts +11 -0
  10. package/dist/event-payload-sanitizer.d.ts +32 -0
  11. package/dist/index.d.ts +6217 -10
  12. package/dist/index.js +6992 -2728
  13. package/dist/index.js.map +1 -1
  14. package/dist/insights.d.ts +157 -0
  15. package/dist/memory-domain.d.ts +44 -0
  16. package/dist/migrate.d.ts +5 -7
  17. package/dist/migrate.js +1 -1
  18. package/dist/new-session-drafts.d.ts +49 -0
  19. package/dist/persistence-errors.d.ts +69 -0
  20. package/dist/preference-registry-schema.d.ts +1147 -0
  21. package/dist/preference-registry.d.ts +878 -0
  22. package/dist/provision-roles.d.ts +4 -6478
  23. package/dist/provision-roles.js +1 -1
  24. package/dist/role-relationships.d.ts +35 -0
  25. package/dist/runtime-posture-cli.d.ts +1 -0
  26. package/dist/runtime-posture.d.ts +103 -0
  27. package/dist/schema.d.ts +23533 -3
  28. package/dist/schema.js +17 -1
  29. package/dist/session-control.d.ts +331 -0
  30. package/dist/session-queue-commands.d.ts +186 -0
  31. package/dist/session-tool-call-settlement.d.ts +32 -0
  32. package/dist/turn-initiator.d.ts +28 -0
  33. package/dist/workspace-instruction-policies-schema.d.ts +735 -0
  34. package/dist/workspace-instruction-policies.d.ts +64 -0
  35. package/drizzle/0136_unified_session_tool_policy.sql +109 -0
  36. package/drizzle/0137_preference_registry.sql +1347 -0
  37. package/drizzle/0138_sandbox_checkpoint_artifacts_and_deadlines.sql +1074 -0
  38. package/drizzle/0139_codex_provider_artifact_invalidations.sql +51 -0
  39. package/drizzle/0140_sandbox_restore_and_reaper_fences.sql +276 -0
  40. package/drizzle/0142_sandbox_archive_capture_gate.sql +80 -0
  41. package/drizzle/0143_session_codex_compaction_mode.sql +20 -0
  42. package/drizzle/0144_sandbox_viewer_force_drain_gate.sql +95 -0
  43. package/drizzle/0145_model_call_facts.sql +96 -0
  44. package/drizzle/0146_slack_bot_delete_idempotency.sql +105 -0
  45. package/drizzle/0147_draft_latency_mode.sql +12 -0
  46. package/drizzle/0148_session_turn_latency_mode.sql +12 -0
  47. package/package.json +6 -6
  48. package/src/index.ts +5095 -430
  49. package/src/insights.ts +837 -0
  50. package/src/migrate.ts +9 -1
  51. package/src/new-session-drafts.ts +4 -0
  52. package/src/preference-registry-schema.ts +170 -0
  53. package/src/preference-registry.ts +1220 -0
  54. package/src/provision-roles.ts +95 -22
  55. package/src/role-relationships.ts +132 -0
  56. package/src/runtime-posture.ts +28 -26
  57. package/src/schema.ts +484 -12
  58. package/src/session-queue-commands.ts +47 -46
  59. package/dist/chunk-ELMUCYZF.js.map +0 -1
  60. package/dist/chunk-PQKVUWQW.js +0 -4047
  61. package/dist/chunk-PQKVUWQW.js.map +0 -1
  62. package/dist/chunk-Y5WZZVQK.js.map +0 -1
  63. package/dist/schema-FoPub9yt.d.ts +0 -22666
package/src/schema.ts CHANGED
@@ -73,6 +73,17 @@ export const workspaces = pgTable(
73
73
  // .references(), because `rigs` is declared later in this file (same
74
74
  // forward-reference pattern as sessions.activeSandboxId). Consumed in M3.
75
75
  defaultRigId: uuid("default_rig_id"),
76
+ // Workspace-wide viewer admission intent set by the warm-meter reaper when
77
+ // managed credits or the monthly warm allowance are exhausted. It is
78
+ // deliberately independent of a lease so a dashboard cannot re-arm a
79
+ // draining box or spawn a cold successor before a fresh limit evaluation
80
+ // clears the gate.
81
+ sandboxViewerForceDrainReason: text("sandbox_viewer_force_drain_reason", {
82
+ enum: ["balance", "warm_cap"],
83
+ }),
84
+ sandboxViewerForceDrainRequestedAt: timestamp("sandbox_viewer_force_drain_requested_at", {
85
+ withTimezone: true,
86
+ }),
76
87
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
77
88
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
78
89
  },
@@ -82,6 +93,19 @@ export const workspaces = pgTable(
82
93
  .on(table.accountId, table.slug)
83
94
  .where(sql`${table.slug} is not null`),
84
95
  external: uniqueIndex("workspaces_external_idx").on(table.externalSource, table.externalId),
96
+ sandboxViewerForceDrain: index("workspaces_sandbox_viewer_force_drain_idx")
97
+ .on(table.id)
98
+ .where(sql`${table.sandboxViewerForceDrainReason} is not null`),
99
+ sandboxViewerForceDrainValid: check(
100
+ "workspaces_sandbox_viewer_force_drain_check",
101
+ sql`(
102
+ ${table.sandboxViewerForceDrainReason} is null
103
+ and ${table.sandboxViewerForceDrainRequestedAt} is null
104
+ ) or (
105
+ ${table.sandboxViewerForceDrainReason} in ('balance', 'warm_cap')
106
+ and ${table.sandboxViewerForceDrainRequestedAt} is not null
107
+ )`,
108
+ ),
85
109
  }),
86
110
  );
87
111
 
@@ -599,6 +623,90 @@ export const slackBotPostOperations = pgTable(
599
623
  }),
600
624
  );
601
625
 
626
+ // Durable provider-operation identity for OpenGeni Slack bot deletions. Slack
627
+ // has no client-supplied idempotency key for chat.delete, so an expired
628
+ // provider_started claim becomes outcome_unknown and must be reconciled before
629
+ // another mutation is admitted.
630
+ export const slackBotDeleteOperations = pgTable(
631
+ "slack_bot_delete_operations",
632
+ {
633
+ id: uuid("id").primaryKey().defaultRandom(),
634
+ accountId: uuid("account_id")
635
+ .notNull()
636
+ .references(() => managedAccounts.id, { onDelete: "cascade" }),
637
+ workspaceId: uuid("workspace_id")
638
+ .notNull()
639
+ .references(() => workspaces.id, { onDelete: "cascade" }),
640
+ connectionId: uuid("connection_id")
641
+ .notNull()
642
+ .references(() => connections.id, { onDelete: "cascade" }),
643
+ operationId: uuid("operation_id").notNull(),
644
+ principalType: text("principal_type").$type<"subject" | "service">().notNull(),
645
+ principalId: text("principal_id").notNull(),
646
+ toolName: text("tool_name").notNull(),
647
+ channelId: text("channel_id").notNull(),
648
+ messageTimestamp: text("message_timestamp").notNull(),
649
+ requestDigest: text("request_digest").notNull(),
650
+ status: text("status")
651
+ .$type<"pending" | "provider_started" | "outcome_unknown" | "completed">()
652
+ .notNull(),
653
+ claimHolderId: uuid("claim_holder_id"),
654
+ claimExpiresAt: timestamp("claim_expires_at", { withTimezone: true }),
655
+ attemptCount: integer("attempt_count").notNull().default(0),
656
+ lastFailureCode: text("last_failure_code"),
657
+ slackChannelId: text("slack_channel_id"),
658
+ slackMessageTimestamp: text("slack_message_timestamp"),
659
+ completedAt: timestamp("completed_at", { withTimezone: true }),
660
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
661
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
662
+ },
663
+ (table) => ({
664
+ workspaceOperation: uniqueIndex("slack_bot_delete_operations_workspace_operation_uq").on(
665
+ table.workspaceId,
666
+ table.operationId,
667
+ ),
668
+ workspaceStatus: index("slack_bot_delete_operations_workspace_status_idx").on(
669
+ table.workspaceId,
670
+ table.status,
671
+ table.updatedAt,
672
+ ),
673
+ principalValid: check(
674
+ "slack_bot_delete_operations_principal_check",
675
+ sql`${table.principalType} in ('subject', 'service')
676
+ and length(${table.principalId}) between 1 and 512`,
677
+ ),
678
+ identityValid: check(
679
+ "slack_bot_delete_operations_identity_check",
680
+ sql`${table.toolName} = 'slack_bot_delete_message'
681
+ and length(${table.channelId}) between 1 and 64
682
+ and length(${table.messageTimestamp}) between 1 and 64
683
+ and ${table.requestDigest} ~ '^[0-9a-f]{64}$'
684
+ and ${table.attemptCount} > 0
685
+ and ((${table.claimHolderId} is null) = (${table.claimExpiresAt} is null))`,
686
+ ),
687
+ statusValid: check(
688
+ "slack_bot_delete_operations_status_check",
689
+ sql`${table.status} in ('pending', 'provider_started', 'outcome_unknown', 'completed')`,
690
+ ),
691
+ completionValid: check(
692
+ "slack_bot_delete_operations_completion_check",
693
+ sql`(
694
+ ${table.status} <> 'completed'
695
+ and ${table.slackChannelId} is null
696
+ and ${table.slackMessageTimestamp} is null
697
+ and ${table.completedAt} is null
698
+ ) or (
699
+ ${table.status} = 'completed'
700
+ and ${table.claimHolderId} is null
701
+ and ${table.claimExpiresAt} is null
702
+ and ${table.slackChannelId} is not null
703
+ and ${table.slackMessageTimestamp} is not null
704
+ and ${table.completedAt} is not null
705
+ )`,
706
+ ),
707
+ }),
708
+ );
709
+
602
710
  // OAuth client registrations minted through MCP DCR, keyed by authorization
603
711
  // server issuer. This is deployment-wide client identity, not a workspace
604
712
  // credential; per-user/provider tokens still live only in connections.
@@ -842,13 +950,12 @@ export const sessions = pgTable(
842
950
  // Non-default first-party MCP token permissions (manager-style sessions);
843
951
  // null means the fixed worker default set in @opengeni/runtime.
844
952
  firstPartyMcpPermissions: jsonb("first_party_mcp_permissions").$type<string[]>(),
845
- // Exact model-visible first-party tool selection. NULL resolves to the
846
- // fixed minimal default; [] intentionally selects no broad-server tools.
847
- firstPartyMcpTools: jsonb("first_party_mcp_tools").$type<FirstPartyMcpToolName[]>(),
848
- // Durable tool-policy origin. NULL is retained for pre-migration rows;
849
- // mapSession exposes those rows as `legacy` instead of guessing omitted vs
850
- // explicit [].
851
- toolPolicy: jsonb("tool_policy").$type<SessionToolPolicy>(),
953
+ // Exact model-visible first-party tool selection. All catalogued tools are
954
+ // selected by default; [] intentionally selects no broad-server tools.
955
+ firstPartyMcpTools: jsonb("first_party_mcp_tools").$type<FirstPartyMcpToolName[]>().notNull(),
956
+ // Durable tool-policy origin. Migration 0136 removes the old null/legacy
957
+ // representation so every session has one explicit policy mode.
958
+ toolPolicy: jsonb("tool_policy").$type<SessionToolPolicy>().notNull(),
852
959
  // Optimistic-concurrency fence for durable session tool-policy writes.
853
960
  toolPolicyVersion: integer("tool_policy_version").notNull().default(1),
854
961
  // The manager session that spawned this one via session_create. Set only
@@ -917,6 +1024,10 @@ export const sessions = pgTable(
917
1024
  // (manual|policy) lives in the migration; no FK (it describes the pin, not an
918
1025
  // account).
919
1026
  codexPinSource: text("codex_pin_source"),
1027
+ // Frozen at create: remote_v2 (Codex remote compaction + Codex-only models)
1028
+ // or portable (plaintext compaction + free provider switching). Existing
1029
+ // rows backfill to portable. CHECK lives in the migration.
1030
+ codexCompactionMode: text("codex_compaction_mode").notNull().default("portable"),
920
1031
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
921
1032
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
922
1033
  // Assigned by the database trigger whenever canonical updated_at activity
@@ -1474,6 +1585,8 @@ export const sessionTurns = pgTable(
1474
1585
  toolsProvided: boolean("tools_provided").notNull().default(false),
1475
1586
  model: text("model").notNull(),
1476
1587
  reasoningEffort: text("reasoning_effort").notNull(),
1588
+ // Peer of model + reasoning_effort for Fast/priority inheritance.
1589
+ latencyMode: text("latency_mode").notNull().default("standard"),
1477
1590
  sandboxBackend: text("sandbox_backend").notNull(),
1478
1591
  // Per-turn OS override. NULL = inherit the session's sandbox_os. CHECK-
1479
1592
  // constrained to the SandboxOs enum (or NULL) in migration 0018.
@@ -1520,6 +1633,10 @@ export const sessionTurns = pgTable(
1520
1633
  oneCurrentInference: uniqueIndex("session_turns_one_current_inference_uq")
1521
1634
  .on(table.workspaceId, table.sessionId)
1522
1635
  .where(sql`${table.status} in ('running','requires_action','recovering','waiting_capacity')`),
1636
+ latencyModeValid: check(
1637
+ "session_turns_latency_mode_check",
1638
+ sql`${table.latencyMode} in ('standard', 'priority', 'fast')`,
1639
+ ),
1523
1640
  }),
1524
1641
  );
1525
1642
 
@@ -1829,6 +1946,7 @@ export const composerDrafts = pgTable(
1829
1946
  toolsProvided: boolean("tools_provided").notNull().default(false),
1830
1947
  model: text("model").notNull(),
1831
1948
  reasoningEffort: text("reasoning_effort").notNull(),
1949
+ latencyMode: text("latency_mode").notNull().default("standard"),
1832
1950
  sourceTurnId: uuid("source_turn_id"),
1833
1951
  sourceTurnVersion: integer("source_turn_version"),
1834
1952
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
@@ -1860,6 +1978,10 @@ export const composerDrafts = pgTable(
1860
1978
  sql`length(btrim(${table.subjectId})) > 0`,
1861
1979
  ),
1862
1980
  revisionValid: check("composer_drafts_revision_check", sql`${table.revision} >= 1`),
1981
+ latencyModeValid: check(
1982
+ "composer_drafts_latency_mode_check",
1983
+ sql`${table.latencyMode} in ('standard', 'priority', 'fast')`,
1984
+ ),
1863
1985
  }),
1864
1986
  );
1865
1987
 
@@ -1878,6 +2000,7 @@ export const newSessionDrafts = pgTable(
1878
2000
  tools: jsonb("tools").$type<unknown[]>().notNull().default([]),
1879
2001
  model: text("model").notNull(),
1880
2002
  reasoningEffort: text("reasoning_effort").notNull(),
2003
+ latencyMode: text("latency_mode").notNull().default("standard"),
1881
2004
  sessionOptions: jsonb("session_options").$type<Record<string, unknown>>().notNull().default({}),
1882
2005
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
1883
2006
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
@@ -1897,6 +2020,10 @@ export const newSessionDrafts = pgTable(
1897
2020
  sql`length(btrim(${table.subjectId})) > 0`,
1898
2021
  ),
1899
2022
  revisionValid: check("new_session_drafts_revision_check", sql`${table.revision} >= 1`),
2023
+ latencyModeValid: check(
2024
+ "new_session_drafts_latency_mode_check",
2025
+ sql`${table.latencyMode} in ('standard', 'priority', 'fast')`,
2026
+ ),
1900
2027
  }),
1901
2028
  );
1902
2029
 
@@ -2323,6 +2450,14 @@ export const agentRunStates = pgTable("agent_run_states", {
2323
2450
  // correct either way). NULL on both sides (non-codex freeze + non-codex
2324
2451
  // resume) is a no-op, so single-account and non-codex sessions are unchanged.
2325
2452
  frozenCodexCredentialId: uuid("frozen_codex_credential_id"),
2453
+ // Exact provider rejection invalidates only the opaque reasoning identity
2454
+ // inside this frozen state. The serialized receipt remains durable and
2455
+ // auditable; the resume path neutralizes its provider-bound identity.
2456
+ providerArtifactInvalidatedAt: timestamp("provider_artifact_invalidated_at", {
2457
+ withTimezone: true,
2458
+ }),
2459
+ providerArtifactInvalidationReason: text("provider_artifact_invalidation_reason"),
2460
+ providerArtifactInvalidatedByAttemptId: uuid("provider_artifact_invalidated_by_attempt_id"),
2326
2461
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
2327
2462
  });
2328
2463
 
@@ -2452,6 +2587,16 @@ export const sessionHistoryItems = pgTable(
2452
2587
  // (an ON DELETE SET NULL would erase the tag, and a stale-but-null tag still
2453
2588
  // mismatches a live codex id so the strip stays correct either way).
2454
2589
  producerCodexCredentialId: uuid("producer_codex_credential_id"),
2590
+ // An exact provider 400 can prove that an otherwise same-credential opaque
2591
+ // reasoning artifact is no longer decryptable. Keep the original item and
2592
+ // producer provenance immutable, but record the attempt-fenced rejection so
2593
+ // later model reads omit only the provider-bound identity. No FK: the receipt
2594
+ // must outlive operational attempt retention just like producer provenance.
2595
+ providerArtifactInvalidatedAt: timestamp("provider_artifact_invalidated_at", {
2596
+ withTimezone: true,
2597
+ }),
2598
+ providerArtifactInvalidationReason: text("provider_artifact_invalidation_reason"),
2599
+ providerArtifactInvalidatedByAttemptId: uuid("provider_artifact_invalidated_by_attempt_id"),
2455
2600
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
2456
2601
  },
2457
2602
  (table) => ({
@@ -2544,6 +2689,134 @@ export const sandboxSessionEnvelopes = pgTable(
2544
2689
  }),
2545
2690
  );
2546
2691
 
2692
+ export const sandboxCheckpointArtifactStateValues = [
2693
+ "candidate",
2694
+ "current",
2695
+ "previous",
2696
+ "delete_pending",
2697
+ "deleting",
2698
+ "delete_failed",
2699
+ "deleted",
2700
+ ] as const;
2701
+
2702
+ /**
2703
+ * Durable ownership ledger for provider-native workspace checkpoints. These
2704
+ * rows intentionally have no cascading parent FK: a workspace/lease can vanish
2705
+ * before the provider object is deleted, and global GC still needs the exact
2706
+ * non-secret provider binding and object id after that deletion.
2707
+ */
2708
+ export const sandboxCheckpointArtifacts = pgTable(
2709
+ "sandbox_checkpoint_artifacts",
2710
+ {
2711
+ id: uuid("id").primaryKey().defaultRandom(),
2712
+ accountId: uuid("account_id").notNull(),
2713
+ workspaceId: uuid("workspace_id").notNull(),
2714
+ sandboxGroupId: uuid("sandbox_group_id").notNull(),
2715
+ sourceLeaseId: uuid("source_lease_id").notNull(),
2716
+ sourceLeaseEpoch: integer("source_lease_epoch").notNull(),
2717
+ sourceInstanceId: text("source_instance_id"),
2718
+ sourceWorkspaceGeneration: integer("source_workspace_generation"),
2719
+ provenance: text("provenance", {
2720
+ enum: ["native_capture", "legacy_provider_adopted"],
2721
+ }).notNull(),
2722
+ providerBackend: text("provider_backend").notNull(),
2723
+ providerBindingKey: text("provider_binding_key").notNull(),
2724
+ providerBinding: jsonb("provider_binding").$type<Record<string, unknown>>().notNull(),
2725
+ objectKind: text("object_kind", {
2726
+ enum: ["modal_filesystem_snapshot", "modal_directory_snapshot"],
2727
+ }).notNull(),
2728
+ objectId: text("object_id").notNull(),
2729
+ archiveBase64: text("archive_base64").notNull(),
2730
+ archiveSha256: text("archive_sha256").notNull(),
2731
+ archiveBytes: integer("archive_bytes").notNull(),
2732
+ descriptor: jsonb("descriptor").$type<Record<string, unknown>>().notNull(),
2733
+ descriptorRevision: text("descriptor_revision").notNull(),
2734
+ state: text("state", { enum: sandboxCheckpointArtifactStateValues })
2735
+ .notNull()
2736
+ .default("candidate"),
2737
+ deleteAfter: timestamp("delete_after", { withTimezone: true }),
2738
+ deleteAttempts: integer("delete_attempts").notNull().default(0),
2739
+ deleteClaimId: uuid("delete_claim_id"),
2740
+ deleteClaimedAt: timestamp("delete_claimed_at", { withTimezone: true }),
2741
+ lastDeleteError: text("last_delete_error"),
2742
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
2743
+ publishedAt: timestamp("published_at", { withTimezone: true }),
2744
+ deletedAt: timestamp("deleted_at", { withTimezone: true }),
2745
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
2746
+ },
2747
+ (table) => ({
2748
+ providerObject: uniqueIndex("sandbox_checkpoint_artifacts_provider_object_uq").on(
2749
+ table.providerBackend,
2750
+ table.providerBindingKey,
2751
+ table.objectId,
2752
+ ),
2753
+ gc: index("sandbox_checkpoint_artifacts_gc_idx")
2754
+ .on(table.deleteAfter, table.createdAt, table.id)
2755
+ .where(
2756
+ sql`${table.state} in (
2757
+ 'candidate', 'current', 'previous',
2758
+ 'delete_pending', 'delete_failed', 'deleting'
2759
+ )`,
2760
+ ),
2761
+ source: index("sandbox_checkpoint_artifacts_source_idx").on(
2762
+ table.workspaceId,
2763
+ table.sandboxGroupId,
2764
+ table.sourceLeaseId,
2765
+ ),
2766
+ sourceValid: check(
2767
+ "sandbox_checkpoint_artifacts_source_check",
2768
+ sql`${table.sourceLeaseEpoch} >= 0
2769
+ and (
2770
+ (
2771
+ ${table.provenance} = 'native_capture'
2772
+ and ${table.sourceWorkspaceGeneration} is not null
2773
+ and ${table.sourceWorkspaceGeneration} >= 0
2774
+ and ${table.sourceInstanceId} is not null
2775
+ and octet_length(${table.sourceInstanceId}) between 1 and 512
2776
+ )
2777
+ or (
2778
+ ${table.provenance} = 'legacy_provider_adopted'
2779
+ and ${table.sourceWorkspaceGeneration} is null
2780
+ and ${table.sourceInstanceId} is null
2781
+ )
2782
+ )`,
2783
+ ),
2784
+ providerValid: check(
2785
+ "sandbox_checkpoint_artifacts_provider_check",
2786
+ sql`${table.providerBackend} = 'modal'
2787
+ and octet_length(${table.providerBindingKey}) between 1 and 1024
2788
+ and jsonb_typeof(${table.providerBinding}) = 'object'
2789
+ and ${table.providerBindingKey}::jsonb = ${table.providerBinding}
2790
+ and ${table.objectKind} in ('modal_filesystem_snapshot', 'modal_directory_snapshot')
2791
+ and octet_length(${table.objectId}) between 1 and 1024`,
2792
+ ),
2793
+ archiveValid: check(
2794
+ "sandbox_checkpoint_artifacts_archive_check",
2795
+ sql`${table.archiveBytes} > 0
2796
+ and octet_length(${table.archiveBase64}) > 0
2797
+ and ${table.archiveSha256} ~ '^[0-9a-f]{64}$'
2798
+ and jsonb_typeof(${table.descriptor}) = 'object'
2799
+ and octet_length(${table.descriptorRevision}) between 1 and 256`,
2800
+ ),
2801
+ stateValid: check(
2802
+ "sandbox_checkpoint_artifacts_state_check",
2803
+ sql`${table.state} in (
2804
+ 'candidate', 'current', 'previous', 'delete_pending',
2805
+ 'deleting', 'delete_failed', 'deleted'
2806
+ )`,
2807
+ ),
2808
+ deleteClaimValid: check(
2809
+ "sandbox_checkpoint_artifacts_delete_claim_check",
2810
+ sql`(${table.state} = 'deleting'
2811
+ and ${table.deleteClaimId} is not null
2812
+ and ${table.deleteClaimedAt} is not null)
2813
+ or (${table.state} <> 'deleting'
2814
+ and ${table.deleteClaimId} is null
2815
+ and ${table.deleteClaimedAt} is null)`,
2816
+ ),
2817
+ }),
2818
+ );
2819
+
2547
2820
  // The 4 liveness states of the singleton lease. Exported so the query layer and
2548
2821
  // the stateless resume-by-id path share one source of truth for the domain.
2549
2822
  export const sandboxLeaseLivenessValues = ["cold", "warming", "warm", "draining"] as const;
@@ -2579,14 +2852,14 @@ export const sandboxLeases = pgTable(
2579
2852
  // The container IMAGE the group box runs (Modal image ref / docker image). A shared
2580
2853
  // box is SHARED STATE: all its sessions run the SAME filesystem, so they must run the
2581
2854
  // same image. This column stamps the image the live box was created with; a resume
2582
- // whose resolved image DIFFERS is a conflict (B3): a solo holder recreates the box on
2583
- // the new image, N-holders are rejected (SandboxImageConflictError). Nullable — a
2855
+ // whose resolved image DIFFERS is a conflict (B3): a solo holder requests a
2856
+ // capture-and-drain rotation; N-holders are rejected (SandboxImageConflictError). Nullable — a
2584
2857
  // legacy/cold row reads NULL = "image unknown", which never conflicts.
2585
2858
  image: text("image"),
2586
2859
  // The frozen rig version the live box was created under (M3). Like `image`,
2587
2860
  // this is SHARED STATE: all the box's sessions run the same rig-baked setup,
2588
- // so a resume resolving a DIFFERENT rig_version_id conflicts (solo holder
2589
- // recreates cold on the new rig; N-holders throw SandboxRigConflictError).
2861
+ // so a resume resolving a DIFFERENT rig_version_id conflicts (a solo holder
2862
+ // requests capture-and-drain rotation; N-holders throw SandboxRigConflictError).
2590
2863
  // Nullable — a legacy/cold row or a rig-less session reads NULL = "rig
2591
2864
  // unknown", which never conflicts. No FK (symmetric with sandbox_group_id's
2592
2865
  // bare-uuid rationale: this lease outlives no single rig_versions row's RLS).
@@ -2612,6 +2885,19 @@ export const sandboxLeases = pgTable(
2612
2885
  // the same row update; equality is the only durable completeness proof.
2613
2886
  workspaceGeneration: integer("workspace_generation").notNull().default(0),
2614
2887
  archiveGeneration: integer("archive_generation"),
2888
+ // Provider-native capture may pause the live box. The exact claim is
2889
+ // acquired before provider I/O and cleared only after that capture settles;
2890
+ // new holders and workspace mutations fence on it. The SQL constraint also
2891
+ // requires the claimed generation to remain current, so an unaware writer
2892
+ // cannot advance generation through an active capture.
2893
+ archiveCaptureId: uuid("archive_capture_id"),
2894
+ archiveCaptureGeneration: integer("archive_capture_generation"),
2895
+ archiveCaptureStartedAt: timestamp("archive_capture_started_at", {
2896
+ withTimezone: true,
2897
+ }),
2898
+ archiveCaptureDeadlineAt: timestamp("archive_capture_deadline_at", {
2899
+ withTimezone: true,
2900
+ }),
2615
2901
 
2616
2902
  // The group box-envelope (the "envelope split" Critical): the small recovery
2617
2903
  // descriptor to resume()-by-id the group's box without a per-session join.
@@ -2624,6 +2910,18 @@ export const sandboxLeases = pgTable(
2624
2910
  lastMeterAt: timestamp("last_meter_at", { withTimezone: true }),
2625
2911
  lastMeterTick: integer("last_meter_tick").notNull().default(0),
2626
2912
 
2913
+ providerCreatedAt: timestamp("provider_created_at", { withTimezone: true }),
2914
+ providerDeadlineAt: timestamp("provider_deadline_at", { withTimezone: true }),
2915
+ rotationRequestedAt: timestamp("rotation_requested_at", { withTimezone: true }),
2916
+ rotationReason: text("rotation_reason", {
2917
+ enum: ["provider_deadline", "operator"],
2918
+ }),
2919
+ // The SQL migration owns the two DEFERRABLE foreign keys and deferred
2920
+ // scope/state constraint triggers. Drizzle does not model deferrability, so
2921
+ // these remain bare UUIDs here rather than generating a stricter wrong FK.
2922
+ currentCheckpointArtifactId: uuid("current_checkpoint_artifact_id"),
2923
+ previousCheckpointArtifactId: uuid("previous_checkpoint_artifact_id"),
2924
+
2627
2925
  expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
2628
2926
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
2629
2927
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
@@ -2647,6 +2945,13 @@ export const sandboxLeases = pgTable(
2647
2945
  expiredDrainingInventory: index("sandbox_leases_expired_draining_inventory_idx")
2648
2946
  .on(table.expiresAt, table.backend)
2649
2947
  .where(sql`${table.liveness} = 'draining'`),
2948
+ providerDeadline: index("sandbox_leases_provider_deadline_idx")
2949
+ .on(table.providerDeadlineAt, table.id)
2950
+ .where(
2951
+ sql`${table.backend} = 'modal'
2952
+ and ${table.liveness} in ('warming', 'warm')
2953
+ and ${table.rotationRequestedAt} is null`,
2954
+ ),
2650
2955
  workspaceGenerationValid: check(
2651
2956
  "sandbox_leases_workspace_generation_check",
2652
2957
  sql`${table.workspaceGeneration} >= 0`,
@@ -2657,6 +2962,43 @@ export const sandboxLeases = pgTable(
2657
2962
  or (${table.archiveGeneration} >= 0
2658
2963
  and ${table.archiveGeneration} <= ${table.workspaceGeneration})`,
2659
2964
  ),
2965
+ archiveCaptureValid: check(
2966
+ "sandbox_leases_archive_capture_check",
2967
+ sql`(
2968
+ ${table.archiveCaptureId} is null
2969
+ and ${table.archiveCaptureGeneration} is null
2970
+ and ${table.archiveCaptureStartedAt} is null
2971
+ and ${table.archiveCaptureDeadlineAt} is null
2972
+ ) or (
2973
+ ${table.archiveCaptureId} is not null
2974
+ and ${table.archiveCaptureGeneration} is not null
2975
+ and ${table.archiveCaptureGeneration} = ${table.workspaceGeneration}
2976
+ and ${table.archiveCaptureStartedAt} is not null
2977
+ and ${table.archiveCaptureDeadlineAt} > ${table.archiveCaptureStartedAt}
2978
+ )`,
2979
+ ),
2980
+ archiveCaptureDeadline: index("sandbox_leases_archive_capture_deadline_idx")
2981
+ .on(table.archiveCaptureDeadlineAt, table.id)
2982
+ .where(sql`${table.archiveCaptureId} is not null`),
2983
+ providerDeadlineValid: check(
2984
+ "sandbox_leases_provider_deadline_check",
2985
+ sql`(${table.providerCreatedAt} is null and ${table.providerDeadlineAt} is null)
2986
+ or (${table.providerCreatedAt} is not null
2987
+ and ${table.providerDeadlineAt} is not null
2988
+ and ${table.providerDeadlineAt} > ${table.providerCreatedAt})`,
2989
+ ),
2990
+ rotationValid: check(
2991
+ "sandbox_leases_rotation_check",
2992
+ sql`(${table.rotationRequestedAt} is null and ${table.rotationReason} is null)
2993
+ or (${table.rotationRequestedAt} is not null
2994
+ and ${table.rotationReason} in ('provider_deadline', 'operator'))`,
2995
+ ),
2996
+ checkpointDistinct: check(
2997
+ "sandbox_leases_checkpoint_distinct_check",
2998
+ sql`${table.currentCheckpointArtifactId} is null
2999
+ or ${table.previousCheckpointArtifactId} is null
3000
+ or ${table.currentCheckpointArtifactId} <> ${table.previousCheckpointArtifactId}`,
3001
+ ),
2660
3002
  }),
2661
3003
  );
2662
3004
 
@@ -2875,6 +3217,11 @@ export const sandboxRetainedProcesses = pgTable(
2875
3217
  leaseEpoch: integer("lease_epoch").notNull(),
2876
3218
  providerBackend: text("provider_backend").notNull(),
2877
3219
  providerInstanceId: text("provider_instance_id").notNull(),
3220
+ // Provider object ids are namespace-scoped. New Modal processes bind the
3221
+ // exact authenticated workspace before execution; legacy rows remain null
3222
+ // until a positive lookup proves which namespace owns the historical box.
3223
+ providerBindingKey: text("provider_binding_key"),
3224
+ providerBinding: jsonb("provider_binding").$type<Record<string, unknown>>(),
2878
3225
  routeKind: text("route_kind", { enum: ["home", "active"] }).notNull(),
2879
3226
  routeTargetId: uuid("route_target_id"),
2880
3227
  routeEpoch: integer("route_epoch").notNull(),
@@ -2991,6 +3338,33 @@ export const sandboxRetainedProcesses = pgTable(
2991
3338
  sql`${table.settlementReason} is null
2992
3339
  or octet_length(${table.settlementReason}) between 1 and 512`,
2993
3340
  ),
3341
+ providerBindingValid: check(
3342
+ "sandbox_retained_processes_provider_binding_check",
3343
+ sql`(
3344
+ ${table.providerBindingKey} is null
3345
+ and ${table.providerBinding} is null
3346
+ ) or (
3347
+ ${table.providerBackend} = 'modal'
3348
+ and octet_length(${table.providerBindingKey}) between 1 and 1024
3349
+ and jsonb_typeof(${table.providerBinding}) = 'object'
3350
+ and ${table.providerBindingKey}::jsonb = ${table.providerBinding}
3351
+ and ${table.providerBindingKey} = format(
3352
+ '{"version":1,"serverUrl":%s,"workspaceName":%s,"environment":%s}',
3353
+ to_jsonb(${table.providerBinding} ->> 'serverUrl')::text,
3354
+ to_jsonb(${table.providerBinding} ->> 'workspaceName')::text,
3355
+ to_jsonb(${table.providerBinding} ->> 'environment')::text
3356
+ )
3357
+ and ${table.providerBinding} = jsonb_build_object(
3358
+ 'version', 1,
3359
+ 'serverUrl', ${table.providerBinding} ->> 'serverUrl',
3360
+ 'workspaceName', ${table.providerBinding} ->> 'workspaceName',
3361
+ 'environment', ${table.providerBinding} ->> 'environment'
3362
+ )
3363
+ and coalesce(octet_length(${table.providerBinding} ->> 'serverUrl'), 0) > 0
3364
+ and coalesce(octet_length(${table.providerBinding} ->> 'workspaceName'), 0) > 0
3365
+ and ${table.providerBinding} ->> 'environment' is not null
3366
+ )`,
3367
+ ),
2994
3368
  reconcileClaimValid: check(
2995
3369
  "sandbox_retained_processes_reconcile_claim_check",
2996
3370
  sql`(${table.reconcileClaimId} is null and ${table.reconcileClaimedAt} is null)
@@ -3021,7 +3395,8 @@ export const sandboxRetainedProcesses = pgTable(
3021
3395
  ${table.reconcileProofOutcome} = 'lost'
3022
3396
  and ${table.reconcileProofExitCode} is null
3023
3397
  and ${table.reconcileProofReason} in (
3024
- 'provider_session_lost_banner', 'provider_instance_not_found'
3398
+ 'provider_session_lost_banner', 'provider_instance_not_found',
3399
+ 'provider_instance_terminated'
3025
3400
  )
3026
3401
  and ${table.reconcileProofObservedAt} is not null
3027
3402
  )`,
@@ -3806,6 +4181,102 @@ export const usageEvents = pgTable(
3806
4181
  }),
3807
4182
  );
3808
4183
 
4184
+ /**
4185
+ * Per model-call Insights facts. Additive observability only — never the billing
4186
+ * ledger. Written after an authoritative `agent.model.usage` emit.
4187
+ */
4188
+ export const modelCallFacts = pgTable(
4189
+ "model_call_facts",
4190
+ {
4191
+ id: uuid("id").primaryKey().defaultRandom(),
4192
+ accountId: uuid("account_id")
4193
+ .notNull()
4194
+ .references(() => managedAccounts.id, { onDelete: "cascade" }),
4195
+ workspaceId: uuid("workspace_id")
4196
+ .notNull()
4197
+ .references(() => workspaces.id, { onDelete: "cascade" }),
4198
+ sessionId: uuid("session_id").notNull(),
4199
+ turnId: uuid("turn_id").notNull(),
4200
+ turnAttemptId: uuid("turn_attempt_id"),
4201
+ sourceKey: text("source_key").notNull(),
4202
+ provider: text("provider").notNull(),
4203
+ providerApi: text("provider_api").notNull(),
4204
+ model: text("model").notNull(),
4205
+ billingPath: text("billing_path").notNull(),
4206
+ turnSource: text("turn_source"),
4207
+ initiatorKind: text("initiator_kind"),
4208
+ initiatorSubjectId: text("initiator_subject_id"),
4209
+ scheduledTaskId: uuid("scheduled_task_id"),
4210
+ inputTokens: bigint("input_tokens", { mode: "number" }),
4211
+ outputTokens: bigint("output_tokens", { mode: "number" }),
4212
+ cachedTokens: bigint("cached_tokens", { mode: "number" }),
4213
+ cacheWriteTokens: bigint("cache_write_tokens", { mode: "number" }),
4214
+ reasoningTokens: bigint("reasoning_tokens", { mode: "number" }),
4215
+ totalTokens: bigint("total_tokens", { mode: "number" }),
4216
+ pricedCostMicros: bigint("priced_cost_micros", { mode: "number" }).notNull().default(0),
4217
+ occurredAt: timestamp("occurred_at", { withTimezone: true }).notNull(),
4218
+ recordedAt: timestamp("recorded_at", { withTimezone: true }).notNull().defaultNow(),
4219
+ },
4220
+ (table) => ({
4221
+ workspaceTurnSource: uniqueIndex("model_call_facts_workspace_turn_source_uq").on(
4222
+ table.workspaceId,
4223
+ table.turnId,
4224
+ table.sourceKey,
4225
+ ),
4226
+ workspaceOccurred: index("model_call_facts_workspace_occurred_idx").on(
4227
+ table.workspaceId,
4228
+ table.occurredAt,
4229
+ ),
4230
+ workspaceProviderModelOccurred: index(
4231
+ "model_call_facts_workspace_provider_model_occurred_idx",
4232
+ ).on(table.workspaceId, table.provider, table.model, table.occurredAt),
4233
+ workspaceSessionOccurred: index("model_call_facts_workspace_session_occurred_idx").on(
4234
+ table.workspaceId,
4235
+ table.sessionId,
4236
+ table.occurredAt,
4237
+ ),
4238
+ workspaceScheduledTaskOccurred: index("model_call_facts_workspace_scheduled_task_occurred_idx")
4239
+ .on(table.workspaceId, table.scheduledTaskId, table.occurredAt)
4240
+ .where(sql`${table.scheduledTaskId} is not null`),
4241
+ workspaceAccount: foreignKey({
4242
+ name: "model_call_facts_workspace_account_fk",
4243
+ columns: [table.workspaceId, table.accountId],
4244
+ foreignColumns: [workspaces.id, workspaces.accountId],
4245
+ }).onDelete("cascade"),
4246
+ billingPathValid: check(
4247
+ "model_call_facts_billing_path_check",
4248
+ sql`${table.billingPath} in ('opengeni_credits', 'external')`,
4249
+ ),
4250
+ pricedCostNonNegative: check(
4251
+ "model_call_facts_priced_cost_check",
4252
+ sql`${table.pricedCostMicros} >= 0`,
4253
+ ),
4254
+ initiatorConsistent: check(
4255
+ "model_call_facts_initiator_check",
4256
+ sql`(${table.initiatorKind} is null and ${table.initiatorSubjectId} is null)
4257
+ or (${table.initiatorKind} in ('subject', 'service')
4258
+ and ${table.initiatorSubjectId} is not null
4259
+ and octet_length(${table.initiatorSubjectId}) between 1 and 1024)`,
4260
+ ),
4261
+ sourceKeyBytes: check(
4262
+ "model_call_facts_source_key_bytes_check",
4263
+ sql`octet_length(${table.sourceKey}) between 1 and 1024`,
4264
+ ),
4265
+ providerBytes: check(
4266
+ "model_call_facts_provider_bytes_check",
4267
+ sql`octet_length(${table.provider}) between 1 and 256`,
4268
+ ),
4269
+ providerApiBytes: check(
4270
+ "model_call_facts_provider_api_bytes_check",
4271
+ sql`octet_length(${table.providerApi}) between 1 and 256`,
4272
+ ),
4273
+ modelBytes: check(
4274
+ "model_call_facts_model_bytes_check",
4275
+ sql`octet_length(${table.model}) between 1 and 512`,
4276
+ ),
4277
+ }),
4278
+ );
4279
+
3809
4280
  /** Singleton, migration-installed gate. Standalone defaults keep both off. */
3810
4281
  export const hostExportConfig = pgTable(
3811
4282
  "host_export_config",
@@ -4440,3 +4911,4 @@ export const rigChanges = pgTable(
4440
4911
  );
4441
4912
 
4442
4913
  export * from "./workspace-instruction-policies-schema";
4914
+ export * from "./preference-registry-schema";