@opengeni/db 0.7.3 → 0.9.3

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 (59) hide show
  1. package/dist/{chunk-B22X3IEZ.js → chunk-4LG5NBTC.js} +512 -32
  2. package/dist/chunk-4LG5NBTC.js.map +1 -0
  3. package/dist/{chunk-YFQ7SGE4.js → chunk-BMFDXFPA.js} +23 -1
  4. package/dist/chunk-BMFDXFPA.js.map +1 -0
  5. package/dist/chunk-KW526IJA.js +127 -0
  6. package/dist/chunk-KW526IJA.js.map +1 -0
  7. package/dist/index.d.ts +3 -3
  8. package/dist/index.js +6068 -2090
  9. package/dist/index.js.map +1 -1
  10. package/dist/migrate.d.ts +29 -4
  11. package/dist/migrate.js +3 -1
  12. package/dist/provision-roles.d.ts +721 -46
  13. package/dist/provision-roles.js +1 -1
  14. package/dist/{schema-BN5mB9xZ.d.ts → schema-CdPGTHlD.d.ts} +2118 -119
  15. package/dist/schema.d.ts +1 -1
  16. package/dist/schema.js +17 -1
  17. package/drizzle/0064_rotation_strategy_sharded_backfill.sql +15 -0
  18. package/drizzle/0065_session_attempt_quiescence.sql +26 -0
  19. package/drizzle/0066_session_interruption_attempt_lookup.sql +4 -0
  20. package/drizzle/0067_session_event_payload_bounds.sql +209 -0
  21. package/drizzle/0068_workspace_control_event_bounds.sql +134 -0
  22. package/drizzle/0069_session_event_history_backfill.sql +32 -0
  23. package/drizzle/0070_session_event_type_sequence_lookup.sql +4 -0
  24. package/drizzle/0071_session_event_monitoring_tail.sql +10 -0
  25. package/drizzle/0072_sessions_workspace_created_id_idx.sql +4 -0
  26. package/drizzle/0073_sessions_workspace_updated_id_idx.sql +4 -0
  27. package/drizzle/0074_session_activity_revisions.sql +72 -0
  28. package/drizzle/0075_sessions_workspace_activity_revision_idx.sql +4 -0
  29. package/drizzle/0076_session_workflow_wake_acl.sql +13 -0
  30. package/drizzle/0077_session_attempt_latest_lookup.sql +4 -0
  31. package/drizzle/0094_quarantine_credential_bearing_catalog_urls.sql +51 -0
  32. package/drizzle/0095_github_existing_installations.sql +84 -0
  33. package/drizzle/0096_session_turn_initiators.sql +97 -0
  34. package/drizzle/0097_host_export_outbox.sql +1220 -0
  35. package/drizzle/0098_usage_events_workspace_session_idx.sql +4 -0
  36. package/drizzle/0099_session_human_input_attempt_owner_index.sql +6 -0
  37. package/drizzle/0100_session_human_input_requests.sql +89 -0
  38. package/drizzle/0101_session_mcp_connection_refs.sql +30 -0
  39. package/drizzle/0102_session_command_receipt_service_actor.sql +16 -0
  40. package/drizzle/0103_host_export_root_session.sql +166 -0
  41. package/drizzle/0104_host_export_root_session_backfill.sql +27 -0
  42. package/drizzle/0105_session_turn_instructions.sql +9 -0
  43. package/package.json +4 -4
  44. package/src/connection-token-resolver.ts +287 -1
  45. package/src/event-payload-sanitizer.ts +57 -19
  46. package/src/index.ts +5377 -1125
  47. package/src/memory-domain.ts +1 -1
  48. package/src/migrate.ts +86 -23
  49. package/src/persistence-errors.ts +252 -0
  50. package/src/provision-roles.ts +42 -0
  51. package/src/schema.ts +552 -34
  52. package/src/session-control.ts +518 -38
  53. package/src/session-queue-commands.ts +308 -57
  54. package/src/session-tool-call-settlement.ts +58 -7
  55. package/src/turn-initiator.ts +155 -0
  56. package/dist/chunk-7LDU7F5P.js +0 -80
  57. package/dist/chunk-7LDU7F5P.js.map +0 -1
  58. package/dist/chunk-B22X3IEZ.js.map +0 -1
  59. package/dist/chunk-YFQ7SGE4.js.map +0 -1
package/src/schema.ts CHANGED
@@ -1,4 +1,6 @@
1
+ import type { McpServerConnectionRef } from "@opengeni/contracts";
1
2
  import { sql } from "drizzle-orm";
3
+ import type { HumanInputQuestion, HumanInputResponse } from "@opengeni/contracts";
2
4
  import {
3
5
  bigint,
4
6
  boolean,
@@ -88,7 +90,9 @@ export const workspaceInferenceControls = pgTable(
88
90
  accountId: uuid("account_id").notNull(),
89
91
  revision: bigint("revision", { mode: "number" }).notNull().default(0),
90
92
  workspaceState: text("workspace_state").notNull().default("active"),
91
- workspacePauseRevision: bigint("workspace_pause_revision", { mode: "number" }),
93
+ workspacePauseRevision: bigint("workspace_pause_revision", {
94
+ mode: "number",
95
+ }),
92
96
  reason: text("reason"),
93
97
  changedBy: text("changed_by"),
94
98
  changedAt: timestamp("changed_at", { withTimezone: true }),
@@ -121,6 +125,30 @@ export const workspaceInferenceControls = pgTable(
121
125
  }),
122
126
  );
123
127
 
128
+ // One transactionally allocated activity clock per workspace. An updated-order
129
+ // first page takes a SHARE lock on this row after the workspace-control lock;
130
+ // semantic writers allocate after UUID-ordered session locks. Plain MVCC page
131
+ // reads never lock session rows, so that ordering cannot form a cycle.
132
+ export const workspaceSessionActivityRevisions = pgTable(
133
+ "workspace_session_activity_revisions",
134
+ {
135
+ workspaceId: uuid("workspace_id").primaryKey(),
136
+ accountId: uuid("account_id").notNull(),
137
+ revision: bigint("revision", { mode: "number" }).notNull().default(0),
138
+ },
139
+ (table) => ({
140
+ workspaceAccount: foreignKey({
141
+ name: "workspace_session_activity_revisions_workspace_account_fk",
142
+ columns: [table.workspaceId, table.accountId],
143
+ foreignColumns: [workspaces.id, workspaces.accountId],
144
+ }).onDelete("cascade"),
145
+ revisionValid: check(
146
+ "workspace_session_activity_revisions_revision_check",
147
+ sql`${table.revision} >= 0`,
148
+ ),
149
+ }),
150
+ );
151
+
124
152
  export const workspaceMemberships = pgTable(
125
153
  "workspace_memberships",
126
154
  {
@@ -155,7 +183,9 @@ export const apiKeys = pgTable(
155
183
  accountId: uuid("account_id")
156
184
  .notNull()
157
185
  .references(() => managedAccounts.id, { onDelete: "cascade" }),
158
- workspaceId: uuid("workspace_id").references(() => workspaces.id, { onDelete: "cascade" }),
186
+ workspaceId: uuid("workspace_id").references(() => workspaces.id, {
187
+ onDelete: "cascade",
188
+ }),
159
189
  name: text("name").notNull(),
160
190
  prefix: text("prefix").notNull(),
161
191
  keyHash: text("key_hash").notNull(),
@@ -280,13 +310,15 @@ export const codexSubscriptionCredentials = pgTable(
280
310
  // excluded). The writer only ever sets a NON-empty set, so a flaky empty turn
281
311
  // can't false-drop coverage. connectorsCheckedAt is the freshness clock.
282
312
  connectorNamespaces: text("connector_namespaces").array(),
283
- connectorsCheckedAt: timestamp("connectors_checked_at", { withTimezone: true }),
313
+ connectorsCheckedAt: timestamp("connectors_checked_at", {
314
+ withTimezone: true,
315
+ }),
284
316
  // Workspace-local, server-held fairness cursor. Provider usage headers are
285
317
  // capacity hints, never the sole allocator: live lease count is ranked first
286
318
  // and this cursor deterministically breaks equal-load/equal-capacity ties.
287
319
  // This flag controls NEW automatic allocations only. Credential health,
288
320
  // refresh, encrypted material, and already-frozen/in-flight turns are
289
- // intentionally independent. OPE-24 owns toggle OCC/audit and product UI.
321
+ // intentionally independent. account eligibility policy owns toggle OCC/audit and product UI.
290
322
  allocatorEnabled: boolean("allocator_enabled").notNull().default(true),
291
323
  selectionCount: integer("selection_count").notNull().default(0),
292
324
  lastSelectedAt: timestamp("last_selected_at", { withTimezone: true }),
@@ -432,7 +464,7 @@ export const codexRotationSettings = pgTable(
432
464
  // code reads this bit; old binaries safely ignore it and keep the legacy
433
465
  // pin/rotation policy.
434
466
  leaseRotationEnabled: boolean("lease_rotation_enabled").notNull().default(false),
435
- rotationStrategy: text("rotation_strategy").notNull().default("most_remaining"), // P3, inert
467
+ rotationStrategy: text("rotation_strategy").notNull().default("sharded"), // sharded-rotation policy: legacy residue; behavior is always sharded
436
468
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
437
469
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
438
470
  },
@@ -527,6 +559,10 @@ export const sessions = pgTable(
527
559
  .references(() => workspaces.id, { onDelete: "cascade" }),
528
560
  status: text("status").notNull().default("queued"),
529
561
  initialMessage: text("initial_message").notNull(),
562
+ // Invisible host context frozen with the winning session create. The
563
+ // initial turn copies this value so an idempotent repair can never adopt a
564
+ // retrying caller's different instructions.
565
+ initialTurnInstructions: text("initial_turn_instructions"),
530
566
  title: text("title"),
531
567
  titleSource: text("title_source"),
532
568
  // Per-session agent persona/system instructions supplied at create (the
@@ -539,6 +575,14 @@ export const sessions = pgTable(
539
575
  resources: jsonb("resources").$type<unknown[]>().notNull().default([]),
540
576
  tools: jsonb("tools").$type<unknown[]>().notNull().default([]),
541
577
  metadata: jsonb("metadata").$type<Record<string, unknown>>().notNull().default({}),
578
+ // Frozen creator fact. This is used for creation attribution and for the
579
+ // idempotent first-turn repair only; later turns capture their own actor.
580
+ createdByKind: text("created_by_kind").notNull().default("service"),
581
+ createdBySubjectId: text("created_by_subject_id").notNull().default("unattributed-legacy"),
582
+ createdByContext: jsonb("created_by_context")
583
+ .$type<Record<string, unknown>>()
584
+ .notNull()
585
+ .default({ backfill: true }),
542
586
  model: text("model").notNull(),
543
587
  sandboxBackend: text("sandbox_backend").notNull(),
544
588
  // The OS this session's box runs. Defaults to 'linux' (today's only OS, so
@@ -554,8 +598,8 @@ export const sessions = pgTable(
554
598
  // The app generates the uuid and uses it for both id and sandbox_group_id in
555
599
  // one insert — it cannot SQL-default to id (id is defaultRandom()).
556
600
  sandboxGroupId: uuid("sandbox_group_id").notNull(),
557
- // The first-class swappable-sandbox POINTER (bring-your-own-compute M2,
558
- // dossier §10.3). NULL == "use the session's own group sandbox" (the
601
+ // The first-class swappable-sandbox POINTER (bring-your-own-compute M2).
602
+ // NULL == "use the session's own group sandbox" (the
559
603
  // backward-compat default — every existing/new row is a behavior-preserving
560
604
  // no-op). The routing proxy re-reads (active_sandbox_id, active_epoch) PER
561
605
  // TOOL CALL to make a Modal<->selfhosted hot-swap seamless. The FK
@@ -620,11 +664,15 @@ export const sessions = pgTable(
620
664
  queueTailPosition: bigint("queue_tail_position", { mode: "number" }).notNull().default(0),
621
665
  directControlState: text("direct_control_state").notNull().default("active"),
622
666
  directPauseRevision: bigint("direct_pause_revision", { mode: "number" }),
623
- subtreeRunOverrideRevision: bigint("subtree_run_override_revision", { mode: "number" }),
667
+ subtreeRunOverrideRevision: bigint("subtree_run_override_revision", {
668
+ mode: "number",
669
+ }),
624
670
  controlVersion: bigint("control_version", { mode: "number" }).notNull().default(0),
625
671
  directControlReason: text("direct_control_reason"),
626
672
  directControlChangedBy: text("direct_control_changed_by"),
627
- directControlChangedAt: timestamp("direct_control_changed_at", { withTimezone: true }),
673
+ directControlChangedAt: timestamp("direct_control_changed_at", {
674
+ withTimezone: true,
675
+ }),
628
676
  lastSequence: integer("last_sequence").notNull().default(0),
629
677
  // The session's PINNED Codex account (manual override from the in-session
630
678
  // switcher). NULL ⇒ follow the workspace active pointer. FK declared in the
@@ -645,6 +693,10 @@ export const sessions = pgTable(
645
693
  codexPinSource: text("codex_pin_source"),
646
694
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
647
695
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
696
+ // Assigned by the database trigger whenever canonical updated_at activity
697
+ // advances. Legacy rows remain zero until touched; raw delta-only writers
698
+ // intentionally omit updated_at and therefore do not allocate revisions.
699
+ activityRevision: bigint("activity_revision", { mode: "number" }).notNull().default(0),
648
700
  },
649
701
  (table) => ({
650
702
  workspaceIdentity: uniqueIndex("sessions_workspace_id_idx").on(table.workspaceId, table.id),
@@ -652,6 +704,25 @@ export const sessions = pgTable(
652
704
  table.workspaceId,
653
705
  table.createdAt,
654
706
  ),
707
+ // Model-facing monitoring pages use exact (timestamp,id) keysets. Keep the
708
+ // older prefix index during rolling deploys; these composites serve both
709
+ // deterministic traversal and updatedAfter change scans.
710
+ workspaceCreatedId: index("sessions_workspace_created_id_idx").on(
711
+ table.workspaceId,
712
+ table.createdAt.desc(),
713
+ table.id.desc(),
714
+ ),
715
+ workspaceUpdatedId: index("sessions_workspace_updated_id_idx").on(
716
+ table.workspaceId,
717
+ table.updatedAt.desc(),
718
+ table.id.desc(),
719
+ ),
720
+ workspaceActivityRevision: index("sessions_workspace_activity_revision_idx").on(
721
+ table.workspaceId,
722
+ table.activityRevision.desc(),
723
+ table.updatedAt.desc(),
724
+ table.id.desc(),
725
+ ),
655
726
  variableSet: index("sessions_variable_set_idx").on(table.workspaceId, table.variableSetId),
656
727
  parent: index("sessions_parent_idx").on(table.workspaceId, table.parentSessionId),
657
728
  // Routing index: resolve session_id -> sandbox_group_id at every lease entry
@@ -801,6 +872,9 @@ export const sessionMcpServers = pgTable(
801
872
  // Human-approval policy: `true` = every tool requires approval, a string[] of
802
873
  // UNPREFIXED tool names = only those require it, null/absent = auto-run.
803
874
  requireApproval: jsonb("require_approval").$type<boolean | string[]>(),
875
+ // Non-secret pointer resolved at request time by the standalone broker or
876
+ // an embedding host. Unlike headersEncrypted, this is safe to project.
877
+ connectionRef: jsonb("connection_ref").$type<McpServerConnectionRef>(),
804
878
  // Map of header name -> AES-GCM ciphertext. Values are decrypted only by the
805
879
  // worker's run-preparation path and never returned by API helpers.
806
880
  headersEncrypted: jsonb("headers_encrypted")
@@ -1077,6 +1151,9 @@ export const sessionTurns = pgTable(
1077
1151
  source: text("source").notNull().default("user"),
1078
1152
  position: bigint("position", { mode: "number" }).notNull(),
1079
1153
  prompt: text("prompt").notNull(),
1154
+ // Host context for this exact turn. System-level at runtime and deliberately
1155
+ // separate from the visible prompt/event payload.
1156
+ turnInstructions: text("turn_instructions"),
1080
1157
  resources: jsonb("resources").$type<unknown[]>().notNull().default([]),
1081
1158
  tools: jsonb("tools").$type<unknown[]>().notNull().default([]),
1082
1159
  model: text("model").notNull(),
@@ -1094,6 +1171,14 @@ export const sessionTurns = pgTable(
1094
1171
  // the SQL constraint is therefore DEFERRABLE INITIALLY DEFERRED.
1095
1172
  activeAttemptId: uuid("active_attempt_id"),
1096
1173
  lineage: jsonb("lineage").$type<Record<string, unknown>>().notNull().default({}),
1174
+ // Required immutable authority captured when the turn is accepted. These
1175
+ // columns are deliberately outside mutable metadata/lineage.
1176
+ initiatorKind: text("initiator_kind").notNull().default("service"),
1177
+ initiatorSubjectId: text("initiator_subject_id").notNull().default("unattributed-legacy"),
1178
+ initiatorContext: jsonb("initiator_context")
1179
+ .$type<Record<string, unknown>>()
1180
+ .notNull()
1181
+ .default({ backfill: true }),
1097
1182
  cancelledBy: text("cancelled_by"),
1098
1183
  cancelReason: text("cancel_reason"),
1099
1184
  // Atomic per-turn toolspace call budget counter (migration 0043). Incremented
@@ -1142,10 +1227,18 @@ export const sessionTurnAttempts = pgTable(
1142
1227
  workerId: text("worker_id"),
1143
1228
  leaseId: text("lease_id"),
1144
1229
  leaseExpiresAt: timestamp("lease_expires_at", { withTimezone: true }),
1145
- verifiedControlRevision: bigint("verified_control_revision", { mode: "number" }).notNull(),
1230
+ verifiedControlRevision: bigint("verified_control_revision", {
1231
+ mode: "number",
1232
+ }).notNull(),
1146
1233
  startedAt: timestamp("started_at", { withTimezone: true }).notNull().defaultNow(),
1147
1234
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
1148
1235
  closedAt: timestamp("closed_at", { withTimezone: true }),
1236
+ // The cancelled activity writes this after losing inference, user-visible
1237
+ // output, and workspace-persistence authority. Fenced/idempotent cleanup
1238
+ // and telemetry may still finish. Temporal activity cancellation or
1239
+ // terminalization is transport state only; queue admission and clients use
1240
+ // this durable receipt as the physical-quiescence authority.
1241
+ quiescedAt: timestamp("quiesced_at", { withTimezone: true }),
1149
1242
  },
1150
1243
  (table) => ({
1151
1244
  workspaceAccount: foreignKey({
@@ -1167,12 +1260,25 @@ export const sessionTurnAttempts = pgTable(
1167
1260
  table.workspaceId,
1168
1261
  table.id,
1169
1262
  ),
1263
+ ownershipIdentity: uniqueIndex("session_turn_attempts_human_input_owner_uq").on(
1264
+ table.accountId,
1265
+ table.workspaceId,
1266
+ table.sessionId,
1267
+ table.turnId,
1268
+ table.id,
1269
+ ),
1170
1270
  liveTurn: uniqueIndex("session_turn_attempts_live_turn_uq")
1171
1271
  .on(table.workspaceId, table.turnId)
1172
1272
  .where(sql`${table.state} in ('claimed', 'running')`),
1173
1273
  liveSession: uniqueIndex("session_turn_attempts_live_session_uq")
1174
1274
  .on(table.workspaceId, table.sessionId)
1175
1275
  .where(sql`${table.state} in ('claimed', 'running')`),
1276
+ latestSessionAttempt: index("session_turn_attempts_latest_session_idx").on(
1277
+ table.workspaceId,
1278
+ table.sessionId,
1279
+ table.startedAt.desc(),
1280
+ table.id.desc(),
1281
+ ),
1176
1282
  dispatch: uniqueIndex("session_turn_attempts_dispatch_uq").on(
1177
1283
  table.workspaceId,
1178
1284
  table.temporalWorkflowRunId,
@@ -1217,7 +1323,9 @@ export const sessionCommandReceipts = pgTable(
1217
1323
  targetTurnId: uuid("target_turn_id"),
1218
1324
  operationKey: text("operation_key").notNull(),
1219
1325
  canonicalRequestHash: text("canonical_request_hash").notNull(),
1220
- appliedControlRevision: bigint("applied_control_revision", { mode: "number" }),
1326
+ appliedControlRevision: bigint("applied_control_revision", {
1327
+ mode: "number",
1328
+ }),
1221
1329
  appliedQueueVersion: integer("applied_queue_version"),
1222
1330
  appliedTurnVersion: integer("applied_turn_version"),
1223
1331
  appliedDraftRevision: bigint("applied_draft_revision", { mode: "number" }),
@@ -1262,7 +1370,7 @@ export const sessionCommandReceipts = pgTable(
1262
1370
  and ${table.actorAttemptId} is not null
1263
1371
  and ${table.actorSubjectId} is null
1264
1372
  ) or (
1265
- ${table.actorType} in ('human', 'operator')
1373
+ ${table.actorType} in ('human', 'operator', 'service')
1266
1374
  and ${table.actorSubjectId} is not null
1267
1375
  and ${table.actorAttemptId} is null
1268
1376
  )`,
@@ -1286,7 +1394,11 @@ export const workspaceControlEvents = pgTable(
1286
1394
  action: text("action").notNull(),
1287
1395
  automatic: boolean("automatic").notNull().default(false),
1288
1396
  reason: text("reason"),
1397
+ reasonOriginalBytes: integer("reason_original_bytes"),
1289
1398
  actor: text("actor").notNull(),
1399
+ // Null is the rolling-upgrade shape for untouched, already-bounded legacy
1400
+ // rows. New writes and rewritten poison rows carry exact source byte facts.
1401
+ actorOriginalBytes: integer("actor_original_bytes"),
1290
1402
  occurredAt: timestamp("occurred_at", { withTimezone: true }).notNull().defaultNow(),
1291
1403
  },
1292
1404
  (table) => ({
@@ -1628,7 +1740,7 @@ export const sessionGoals = pgTable(
1628
1740
  }),
1629
1741
  );
1630
1742
 
1631
- // OPE-21: one durable, coalescing capacity waiter per session. The row is both
1743
+ // credential allocator: one durable, coalescing capacity waiter per session. The row is both
1632
1744
  // the wait state and the commit->signal outbox: capacity mutations increment
1633
1745
  // wakeRevision in the SAME transaction as the mutation, while the session
1634
1746
  // workflow advances observedWakeRevision only after it has re-evaluated the
@@ -1638,7 +1750,7 @@ export const sessionGoals = pgTable(
1638
1750
  // The session/goal/turn foreign keys are declared in migration 0053 so the
1639
1751
  // table keeps the same composite workspace-integrity posture as credential
1640
1752
  // leases. Control is evaluated independently at admission and never changes a
1641
- // capacity waiter's identity. OPE-32 supplies policyHash when accepted-turn
1753
+ // capacity waiter's identity. policy filter supplies policyHash when accepted-turn
1642
1754
  // pool routing lands.
1643
1755
  export const codexCapacityWaiters = pgTable(
1644
1756
  "codex_capacity_waiters",
@@ -1744,6 +1856,41 @@ export const sessionEvents = pgTable(
1744
1856
  table.sessionId,
1745
1857
  table.createdAt,
1746
1858
  ),
1859
+ sessionTypeSequence: index("session_events_workspace_session_type_sequence_idx").on(
1860
+ table.workspaceId,
1861
+ table.sessionId,
1862
+ table.type,
1863
+ table.sequence,
1864
+ ),
1865
+ monitoringTail: index("session_events_workspace_session_monitoring_tail_idx")
1866
+ .on(table.workspaceId, table.sessionId, table.sequence)
1867
+ .where(
1868
+ sql`${table.type} not in ('agent.message.delta', 'agent.reasoning.delta', 'sandbox.command.output.delta', 'terminal.pty.output.delta')`,
1869
+ ),
1870
+ payloadBytes: check(
1871
+ "session_events_payload_bytes_check",
1872
+ sql`octet_length(${table.payload}::text) <= 65536`,
1873
+ ),
1874
+ typeBytes: check(
1875
+ "session_events_type_bytes_check",
1876
+ sql`octet_length(${table.type}) <= 256 and position(E'\n' in ${table.type}) = 0 and position(E'\r' in ${table.type}) = 0`,
1877
+ ),
1878
+ clientEventIdBytes: check(
1879
+ "session_events_client_event_id_bytes_check",
1880
+ sql`${table.clientEventId} is null or octet_length(${table.clientEventId}) <= 1024`,
1881
+ ),
1882
+ producerIdBytes: check(
1883
+ "session_events_producer_id_bytes_check",
1884
+ sql`${table.producerId} is null or octet_length(${table.producerId}) <= 1024`,
1885
+ ),
1886
+ turnAssociationBytes: check(
1887
+ "session_events_turn_association_bytes_check",
1888
+ sql`${table.turnAssociation} is null or octet_length(${table.turnAssociation}) <= 64`,
1889
+ ),
1890
+ duplicateReasonBytes: check(
1891
+ "session_events_duplicate_reason_bytes_check",
1892
+ sql`${table.duplicateReason} is null or octet_length(${table.duplicateReason}) <= 4096`,
1893
+ ),
1747
1894
  }),
1748
1895
  );
1749
1896
 
@@ -1758,7 +1905,9 @@ export const agentRunStates = pgTable("agent_run_states", {
1758
1905
  sessionId: uuid("session_id")
1759
1906
  .notNull()
1760
1907
  .references(() => sessions.id, { onDelete: "cascade" }),
1761
- turnId: uuid("turn_id").references(() => sessionTurns.id, { onDelete: "set null" }),
1908
+ turnId: uuid("turn_id").references(() => sessionTurns.id, {
1909
+ onDelete: "set null",
1910
+ }),
1762
1911
  stateVersion: integer("state_version").notNull(),
1763
1912
  serializedRunState: text("serialized_run_state").notNull(),
1764
1913
  pendingApprovals: jsonb("pending_approvals").$type<unknown[]>().notNull().default([]),
@@ -1781,6 +1930,88 @@ export const agentRunStates = pgTable("agent_run_states", {
1781
1930
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
1782
1931
  });
1783
1932
 
1933
+ // Durable structured human-input requests. The creation attempt is immutable
1934
+ // provenance; legitimate resume attempts are newer owners of the SAME logical
1935
+ // turn. Settlement therefore fences on the active session/turn plus the
1936
+ // request's turn generation, never on creationAttemptId.
1937
+ export const sessionHumanInputRequests = pgTable(
1938
+ "session_human_input_requests",
1939
+ {
1940
+ id: uuid("id").primaryKey().defaultRandom(),
1941
+ accountId: uuid("account_id").notNull(),
1942
+ workspaceId: uuid("workspace_id").notNull(),
1943
+ sessionId: uuid("session_id").notNull(),
1944
+ turnId: uuid("turn_id").notNull(),
1945
+ turnGeneration: integer("turn_generation").notNull(),
1946
+ creationAttemptId: uuid("creation_attempt_id").notNull(),
1947
+ toolCallId: text("tool_call_id").notNull(),
1948
+ status: text("status").notNull().default("pending"),
1949
+ questions: jsonb("questions").$type<HumanInputQuestion[]>().notNull(),
1950
+ allowSkip: boolean("allow_skip").notNull().default(false),
1951
+ response: jsonb("response").$type<HumanInputResponse>(),
1952
+ respondedBy: text("responded_by"),
1953
+ respondedAt: timestamp("responded_at", { withTimezone: true }),
1954
+ expiresAt: timestamp("expires_at", { withTimezone: true }),
1955
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
1956
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
1957
+ },
1958
+ (table) => ({
1959
+ creationAttemptOwner: foreignKey({
1960
+ name: "session_human_input_requests_creation_attempt_fk",
1961
+ columns: [
1962
+ table.accountId,
1963
+ table.workspaceId,
1964
+ table.sessionId,
1965
+ table.turnId,
1966
+ table.creationAttemptId,
1967
+ ],
1968
+ foreignColumns: [
1969
+ sessionTurnAttempts.accountId,
1970
+ sessionTurnAttempts.workspaceId,
1971
+ sessionTurnAttempts.sessionId,
1972
+ sessionTurnAttempts.turnId,
1973
+ sessionTurnAttempts.id,
1974
+ ],
1975
+ }).onDelete("cascade"),
1976
+ toolCall: uniqueIndex("session_human_input_requests_tool_call_uq").on(
1977
+ table.workspaceId,
1978
+ table.sessionId,
1979
+ table.turnId,
1980
+ table.toolCallId,
1981
+ ),
1982
+ pendingSession: index("session_human_input_requests_pending_session_idx")
1983
+ .on(table.workspaceId, table.sessionId, table.createdAt, table.id)
1984
+ .where(sql`${table.status} = 'pending'`),
1985
+ pendingExpiry: index("session_human_input_requests_pending_expiry_idx")
1986
+ .on(table.expiresAt, table.id)
1987
+ .where(sql`${table.status} = 'pending' and ${table.expiresAt} is not null`),
1988
+ status: check(
1989
+ "session_human_input_requests_status_check",
1990
+ sql`${table.status} in ('pending','answered','skipped','expired','cancelled')`,
1991
+ ),
1992
+ generation: check(
1993
+ "session_human_input_requests_generation_check",
1994
+ sql`${table.turnGeneration} > 0`,
1995
+ ),
1996
+ toolCallBytes: check(
1997
+ "session_human_input_requests_tool_call_bytes_check",
1998
+ sql`octet_length(${table.toolCallId}) between 1 and 1024`,
1999
+ ),
2000
+ questionsBytes: check(
2001
+ "session_human_input_requests_questions_bytes_check",
2002
+ sql`octet_length(${table.questions}::text) <= 49152`,
2003
+ ),
2004
+ responseBytes: check(
2005
+ "session_human_input_requests_response_bytes_check",
2006
+ sql`${table.response} is null or octet_length(${table.response}::text) <= 49152`,
2007
+ ),
2008
+ actorBytes: check(
2009
+ "session_human_input_requests_actor_bytes_check",
2010
+ sql`${table.respondedBy} is null or octet_length(${table.respondedBy}) <= 1024`,
2011
+ ),
2012
+ }),
2013
+ );
2014
+
1784
2015
  // Conversation truth: ordered, verbatim SDK input items (issue #35). The
1785
2016
  // model-facing memory store — unredacted and replay-ready. session_events
1786
2017
  // remains the redacted human/audit timeline.
@@ -1797,7 +2028,9 @@ export const sessionHistoryItems = pgTable(
1797
2028
  sessionId: uuid("session_id")
1798
2029
  .notNull()
1799
2030
  .references(() => sessions.id, { onDelete: "cascade" }),
1800
- turnId: uuid("turn_id").references(() => sessionTurns.id, { onDelete: "set null" }),
2031
+ turnId: uuid("turn_id").references(() => sessionTurns.id, {
2032
+ onDelete: "set null",
2033
+ }),
1801
2034
  // Numeric (not integer) so the synthetic compaction-summary row can be
1802
2035
  // inserted at a FRACTIONAL position (boundaryPosition - 0.5) that sorts ahead
1803
2036
  // of the kept tail without colliding with — and thus overwriting — the real
@@ -2061,7 +2294,9 @@ export const sessionRecordings = pgTable(
2061
2294
  sessionId: uuid("session_id")
2062
2295
  .notNull()
2063
2296
  .references(() => sessions.id, { onDelete: "cascade" }),
2064
- turnId: uuid("turn_id").references(() => sessionTurns.id, { onDelete: "set null" }),
2297
+ turnId: uuid("turn_id").references(() => sessionTurns.id, {
2298
+ onDelete: "set null",
2299
+ }),
2065
2300
 
2066
2301
  state: text("state", { enum: sessionRecordingStateValues }).notNull(),
2067
2302
  mode: text("mode", { enum: sessionRecordingModeValues }).notNull(),
@@ -2088,7 +2323,7 @@ export const sessionRecordings = pgTable(
2088
2323
  }),
2089
2324
  );
2090
2325
 
2091
- // Workbench v2 turn-end workspace capture (dossier §10.2; model: sessionRecordings).
2326
+ // Workbench v2 turn-end workspace capture (model: sessionRecordings).
2092
2327
  // One row per capture revision — a point-in-time snapshot of a session's changed
2093
2328
  // files, probed off the live box at turn end. The manifest (tree index + per-repo
2094
2329
  // status/diff + file index) and each after-image blob live in @opengeni/storage;
@@ -2111,7 +2346,9 @@ export const workspaceCaptures = pgTable(
2111
2346
  sessionId: uuid("session_id")
2112
2347
  .notNull()
2113
2348
  .references(() => sessions.id, { onDelete: "cascade" }),
2114
- turnId: uuid("turn_id").references(() => sessionTurns.id, { onDelete: "set null" }),
2349
+ turnId: uuid("turn_id").references(() => sessionTurns.id, {
2350
+ onDelete: "set null",
2351
+ }),
2115
2352
 
2116
2353
  revision: bigint("revision", { mode: "number" }).notNull(),
2117
2354
  leaseEpoch: integer("lease_epoch").notNull(),
@@ -2147,8 +2384,8 @@ export const workspaceCaptures = pgTable(
2147
2384
  }),
2148
2385
  );
2149
2386
 
2150
- // Channel-A interactive PTY sessions (P4.4 / modules/08-channel-a.md §3.1). The
2151
- // ONLY new persistent state Channel A needs — FS/Git reads are stateless point
2387
+ // Interactive PTY sessions. This is the persistent state needed for a live
2388
+ // terminal; file and Git reads remain stateless point
2152
2389
  // queries; an interactive PTY is a live in-box process keyed by the SDK's numeric
2153
2390
  // exec-session id (writeStdin({sessionId})). We map our UUID ptyId <-> that id,
2154
2391
  // the owning workspace/session, the lease_epoch that fences it to the box it was
@@ -2193,7 +2430,7 @@ export const sandboxPtySessions = pgTable(
2193
2430
 
2194
2431
  // ============================================================================
2195
2432
  // Bring-your-own-compute (M2): first-class swappable sandboxes + enrollment +
2196
- // metrics (migration 0024 / dossier §10.3 + §10.7 + §23). The session→box
2433
+ // metrics (migration 0024). The session→box
2197
2434
  // binding becomes a per-session mutable, epoch-fenced active_sandbox_id pointer
2198
2435
  // (declared on sessions above) that the routing proxy resolves PER TOOL CALL.
2199
2436
 
@@ -2267,7 +2504,7 @@ export const enrollments = pgTable(
2267
2504
  );
2268
2505
 
2269
2506
  // The OAuth 2.0 device-authorization (RFC 8628) PENDING request (M5, migration
2270
- // 0025 / dossier §10.2 enrollment + §18 LOUD consent). An agent's `enroll` starts
2507
+ // 0025 / enrollment + §18 LOUD consent). An agent's `enroll` starts
2271
2508
  // a flow (POST /enrollments/device/start) → one short-TTL, single-use row keyed by
2272
2509
  // an opaque `device_code` (the agent polls with) + a short `user_code` (the user
2273
2510
  // types at the approve page). The user (workspace-membership / workspace:admin
@@ -2308,7 +2545,9 @@ export const deviceEnrollmentRequests = pgTable(
2308
2545
  machineName: text("machine_name"),
2309
2546
  // The exposure the agent REQUESTED (whole-machine in v1; loudly consented at
2310
2547
  // approve). Mirrors the enrollment column domain.
2311
- requestedExposure: text("requested_exposure", { enum: enrollmentExposureValues })
2548
+ requestedExposure: text("requested_exposure", {
2549
+ enum: enrollmentExposureValues,
2550
+ })
2312
2551
  .notNull()
2313
2552
  .default("whole-machine"),
2314
2553
  // The agent CAN offer a display (a real screen / Xvfb is available) — gates
@@ -2328,8 +2567,12 @@ export const deviceEnrollmentRequests = pgTable(
2328
2567
  approvedAt: timestamp("approved_at", { withTimezone: true }),
2329
2568
  // The enrollment + sandbox the approve produced (acceptance #2: an enrollment
2330
2569
  // row AND a sandbox row appear). Null until approved.
2331
- enrollmentId: uuid("enrollment_id").references(() => enrollments.id, { onDelete: "set null" }),
2332
- sandboxId: uuid("sandbox_id").references(() => sandboxes.id, { onDelete: "set null" }),
2570
+ enrollmentId: uuid("enrollment_id").references(() => enrollments.id, {
2571
+ onDelete: "set null",
2572
+ }),
2573
+ sandboxId: uuid("sandbox_id").references(() => sandboxes.id, {
2574
+ onDelete: "set null",
2575
+ }),
2333
2576
  // The short-TTL expiry; a pending row past this is EXPIRED on poll.
2334
2577
  expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
2335
2578
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
@@ -2370,7 +2613,9 @@ export const sandboxes = pgTable(
2370
2613
  .references(() => workspaces.id, { onDelete: "cascade" }),
2371
2614
  kind: text("kind", { enum: sandboxKindValues }).notNull(),
2372
2615
  name: text("name").notNull(),
2373
- enrollmentId: uuid("enrollment_id").references(() => enrollments.id, { onDelete: "set null" }),
2616
+ enrollmentId: uuid("enrollment_id").references(() => enrollments.id, {
2617
+ onDelete: "set null",
2618
+ }),
2374
2619
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
2375
2620
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
2376
2621
  },
@@ -2521,7 +2766,9 @@ export const scheduledTaskRuns = pgTable(
2521
2766
  triggerType: text("trigger_type").notNull(),
2522
2767
  scheduledAt: timestamp("scheduled_at", { withTimezone: true }),
2523
2768
  firedAt: timestamp("fired_at", { withTimezone: true }).notNull().defaultNow(),
2524
- sessionId: uuid("session_id").references(() => sessions.id, { onDelete: "set null" }),
2769
+ sessionId: uuid("session_id").references(() => sessions.id, {
2770
+ onDelete: "set null",
2771
+ }),
2525
2772
  triggerEventId: uuid("trigger_event_id"),
2526
2773
  // Stable Temporal producer identity. Activity replay/re-dispatch returns
2527
2774
  // the exact run instead of allocating a second schedule source row.
@@ -2559,6 +2806,8 @@ export const githubInstallations = pgTable(
2559
2806
  installationId: integer("installation_id").notNull(),
2560
2807
  accountLogin: text("account_login"),
2561
2808
  accountType: text("account_type"),
2809
+ repositoryScope: text("repository_scope").notNull().default("all"),
2810
+ linkedBySubjectId: text("linked_by_subject_id"),
2562
2811
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
2563
2812
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
2564
2813
  },
@@ -2569,6 +2818,47 @@ export const githubInstallations = pgTable(
2569
2818
  ),
2570
2819
  installation: index("github_installations_installation_idx").on(table.installationId),
2571
2820
  workspace: index("github_installations_workspace_idx").on(table.workspaceId),
2821
+ repositoryScopeCheck: check(
2822
+ "github_installations_repository_scope_check",
2823
+ sql`${table.repositoryScope} in ('all', 'selected')`,
2824
+ ),
2825
+ }),
2826
+ );
2827
+
2828
+ export const githubInstallationRepositories = pgTable(
2829
+ "github_installation_repositories",
2830
+ {
2831
+ id: uuid("id").primaryKey().defaultRandom(),
2832
+ accountId: uuid("account_id")
2833
+ .notNull()
2834
+ .references(() => managedAccounts.id, { onDelete: "cascade" }),
2835
+ workspaceId: uuid("workspace_id")
2836
+ .notNull()
2837
+ .references(() => workspaces.id, { onDelete: "cascade" }),
2838
+ installationId: integer("installation_id").notNull(),
2839
+ repositoryId: bigint("repository_id", { mode: "number" }).notNull(),
2840
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
2841
+ },
2842
+ (table) => ({
2843
+ installationRepository: uniqueIndex("github_install_repo_workspace_installation_repo_idx").on(
2844
+ table.workspaceId,
2845
+ table.installationId,
2846
+ table.repositoryId,
2847
+ ),
2848
+ workspaceInstallation: index("github_install_repo_workspace_installation_idx").on(
2849
+ table.workspaceId,
2850
+ table.installationId,
2851
+ ),
2852
+ workspaceAccount: foreignKey({
2853
+ columns: [table.workspaceId, table.accountId],
2854
+ foreignColumns: [workspaces.id, workspaces.accountId],
2855
+ name: "github_installation_repositories_workspace_account_fk",
2856
+ }).onDelete("cascade"),
2857
+ installationBinding: foreignKey({
2858
+ columns: [table.workspaceId, table.installationId],
2859
+ foreignColumns: [githubInstallations.workspaceId, githubInstallations.installationId],
2860
+ name: "github_installation_repositories_installation_fk",
2861
+ }).onDelete("cascade"),
2572
2862
  }),
2573
2863
  );
2574
2864
 
@@ -2588,10 +2878,26 @@ export const usageEvents = pgTable(
2588
2878
  unit: text("unit").notNull(),
2589
2879
  sourceResourceType: text("source_resource_type"),
2590
2880
  sourceResourceId: text("source_resource_id"),
2881
+ // Exact execution source for host usage export. These are deliberately
2882
+ // validated soft references rather than cascading foreign keys: usage is
2883
+ // an immutable billing/audit fact and must retain its source identity after
2884
+ // a session or turn is deleted.
2885
+ sessionId: uuid("session_id"),
2886
+ turnId: uuid("turn_id"),
2887
+ turnAttemptId: uuid("turn_attempt_id"),
2888
+ initiatorKind: text("initiator_kind"),
2889
+ initiatorSubjectId: text("initiator_subject_id"),
2890
+ initiatorContext: jsonb("initiator_context")
2891
+ .$type<Record<string, unknown>>()
2892
+ .notNull()
2893
+ .default({}),
2894
+ origin: text("origin"),
2591
2895
  idempotencyKey: text("idempotency_key").notNull(),
2592
2896
  occurredAt: timestamp("occurred_at", { withTimezone: true }).notNull(),
2593
2897
  recordedAt: timestamp("recorded_at", { withTimezone: true }).notNull().defaultNow(),
2594
- exportedToBillingAt: timestamp("exported_to_billing_at", { withTimezone: true }),
2898
+ exportedToBillingAt: timestamp("exported_to_billing_at", {
2899
+ withTimezone: true,
2900
+ }),
2595
2901
  billingProviderEventId: text("billing_provider_event_id"),
2596
2902
  },
2597
2903
  (table) => ({
@@ -2606,6 +2912,208 @@ export const usageEvents = pgTable(
2606
2912
  table.eventType,
2607
2913
  table.occurredAt,
2608
2914
  ),
2915
+ workspaceSession: index("usage_events_workspace_session_idx").on(
2916
+ table.workspaceId,
2917
+ table.sessionId,
2918
+ table.occurredAt,
2919
+ ),
2920
+ contextHierarchy: check(
2921
+ "usage_events_context_hierarchy_check",
2922
+ sql`(${table.turnId} is null or ${table.sessionId} is not null)
2923
+ and (${table.turnAttemptId} is null or ${table.turnId} is not null)`,
2924
+ ),
2925
+ initiatorConsistent: check(
2926
+ "usage_events_initiator_check",
2927
+ sql`(${table.initiatorKind} is null and ${table.initiatorSubjectId} is null)
2928
+ or (${table.initiatorKind} in ('subject', 'service')
2929
+ and ${table.initiatorSubjectId} is not null
2930
+ and octet_length(${table.initiatorSubjectId}) between 1 and 1024)`,
2931
+ ),
2932
+ attributionContextBytes: check(
2933
+ "usage_events_initiator_context_bytes_check",
2934
+ sql`octet_length(${table.initiatorContext}::text) <= 4096`,
2935
+ ),
2936
+ originValid: check(
2937
+ "usage_events_origin_check",
2938
+ sql`${table.origin} is null or ${table.origin} in (
2939
+ 'user', 'scheduled_task', 'api', 'goal', 'system', 'compaction'
2940
+ )`,
2941
+ ),
2942
+ }),
2943
+ );
2944
+
2945
+ /** Singleton, migration-installed gate. Standalone defaults keep both off. */
2946
+ export const hostExportConfig = pgTable(
2947
+ "host_export_config",
2948
+ {
2949
+ id: integer("id").primaryKey().default(1),
2950
+ sessionEventsEnabled: boolean("session_events_enabled").notNull().default(false),
2951
+ usageEventsEnabled: boolean("usage_events_enabled").notNull().default(false),
2952
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
2953
+ },
2954
+ (table) => ({
2955
+ singleton: check("host_export_config_singleton_check", sql`${table.id} = 1`),
2956
+ }),
2957
+ );
2958
+
2959
+ /**
2960
+ * Transactional delivery buffer. It intentionally has no tenant/source FKs:
2961
+ * a workspace deletion must not erase an already-committed, unacknowledged
2962
+ * host fact. Payloads are sanitized/bounded before they reach this table.
2963
+ */
2964
+ export const hostExportOutbox = pgTable(
2965
+ "host_export_outbox",
2966
+ {
2967
+ id: uuid("id").primaryKey().defaultRandom(),
2968
+ exportKind: text("export_kind").notNull(),
2969
+ exportCursor: bigint("export_cursor", { mode: "bigint" }),
2970
+ sourceId: uuid("source_id").notNull(),
2971
+ accountId: uuid("account_id").notNull(),
2972
+ workspaceId: uuid("workspace_id").notNull(),
2973
+ sessionId: uuid("session_id"),
2974
+ rootSessionId: uuid("root_session_id"),
2975
+ turnId: uuid("turn_id"),
2976
+ turnGeneration: integer("turn_generation"),
2977
+ turnAttemptId: uuid("turn_attempt_id"),
2978
+ sessionSequence: integer("session_sequence"),
2979
+ clientEventId: text("client_event_id"),
2980
+ turnAssociation: text("turn_association"),
2981
+ duplicateOfEventId: uuid("duplicate_of_event_id"),
2982
+ duplicateReason: text("duplicate_reason"),
2983
+ eventType: text("event_type").notNull(),
2984
+ idempotencyKey: text("idempotency_key").notNull(),
2985
+ initiator: jsonb("initiator").$type<Record<string, unknown> | null>(),
2986
+ initiatorContext: jsonb("initiator_context")
2987
+ .$type<Record<string, unknown>>()
2988
+ .notNull()
2989
+ .default({}),
2990
+ origin: text("origin"),
2991
+ payload: jsonb("payload").$type<unknown>().notNull(),
2992
+ envelopeBytes: integer("envelope_bytes").notNull(),
2993
+ occurredAt: timestamp("occurred_at", { withTimezone: true }).notNull(),
2994
+ sourceRecordedAt: timestamp("source_recorded_at", { withTimezone: true }).notNull(),
2995
+ enqueuedAt: timestamp("enqueued_at", { withTimezone: true }).notNull(),
2996
+ },
2997
+ (table) => ({
2998
+ kindValid: check(
2999
+ "host_export_outbox_kind_check",
3000
+ sql`${table.exportKind} in ('session_event', 'usage_event')`,
3001
+ ),
3002
+ sourceUnique: uniqueIndex("host_export_outbox_source_uq").on(table.exportKind, table.sourceId),
3003
+ cursorUnique: uniqueIndex("host_export_outbox_cursor_uq")
3004
+ .on(table.exportKind, table.exportCursor)
3005
+ .where(sql`${table.exportCursor} is not null`),
3006
+ unassigned: index("host_export_outbox_unassigned_idx")
3007
+ .on(table.exportKind, table.enqueuedAt, table.id)
3008
+ .where(sql`${table.exportCursor} is null`),
3009
+ unassignedSession: index("host_export_outbox_unassigned_session_idx")
3010
+ .on(table.exportKind, table.sessionId, table.sessionSequence)
3011
+ .where(sql`${table.exportCursor} is null and ${table.sessionId} is not null`),
3012
+ contextBytes: check(
3013
+ "host_export_outbox_context_bytes_check",
3014
+ sql`octet_length(${table.initiatorContext}::text) <= 4096`,
3015
+ ),
3016
+ originValid: check(
3017
+ "host_export_outbox_origin_check",
3018
+ sql`${table.origin} is null or ${table.origin} in (
3019
+ 'user', 'scheduled_task', 'api', 'goal', 'system', 'compaction'
3020
+ )`,
3021
+ ),
3022
+ }),
3023
+ );
3024
+
3025
+ export const hostExportCursorState = pgTable(
3026
+ "host_export_cursor_state",
3027
+ {
3028
+ exportKind: text("export_kind").primaryKey(),
3029
+ nextCursor: bigint("next_cursor", { mode: "bigint" }).notNull().default(1n),
3030
+ prunedThrough: bigint("pruned_through", { mode: "bigint" }).notNull().default(0n),
3031
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
3032
+ },
3033
+ (table) => ({
3034
+ kindValid: check(
3035
+ "host_export_cursor_state_kind_check",
3036
+ sql`${table.exportKind} in ('session_event', 'usage_event')`,
3037
+ ),
3038
+ cursorValid: check(
3039
+ "host_export_cursor_state_next_check",
3040
+ sql`${table.nextCursor} > 0 and ${table.prunedThrough} >= 0
3041
+ and ${table.prunedThrough} < ${table.nextCursor}`,
3042
+ ),
3043
+ }),
3044
+ );
3045
+
3046
+ /** Named at-least-once consumer checkpoint and one-batch lease. */
3047
+ export const hostExportConsumers = pgTable(
3048
+ "host_export_consumers",
3049
+ {
3050
+ id: uuid("id").primaryKey().defaultRandom(),
3051
+ consumerId: text("consumer_id").notNull(),
3052
+ exportKind: text("export_kind").notNull(),
3053
+ checkpoint: bigint("checkpoint", { mode: "bigint" }).notNull().default(0n),
3054
+ enabled: boolean("enabled").notNull().default(true),
3055
+ leaseToken: uuid("lease_token"),
3056
+ leaseHolderId: text("lease_holder_id"),
3057
+ leaseExpiresAt: timestamp("lease_expires_at", { withTimezone: true }),
3058
+ leaseFrom: bigint("lease_from", { mode: "bigint" }),
3059
+ leaseThrough: bigint("lease_through", { mode: "bigint" }),
3060
+ consecutiveFailures: integer("consecutive_failures").notNull().default(0),
3061
+ nextAttemptAt: timestamp("next_attempt_at", { withTimezone: true }).notNull().defaultNow(),
3062
+ lastError: text("last_error"),
3063
+ lastErrorAt: timestamp("last_error_at", { withTimezone: true }),
3064
+ blockedAt: timestamp("blocked_at", { withTimezone: true }),
3065
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
3066
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
3067
+ },
3068
+ (table) => ({
3069
+ consumerKind: uniqueIndex("host_export_consumers_kind_id_uq").on(
3070
+ table.exportKind,
3071
+ table.consumerId,
3072
+ ),
3073
+ kindValid: check(
3074
+ "host_export_consumers_kind_check",
3075
+ sql`${table.exportKind} in ('session_event', 'usage_event')`,
3076
+ ),
3077
+ checkpointValid: check("host_export_consumers_checkpoint_check", sql`${table.checkpoint} >= 0`),
3078
+ due: index("host_export_consumers_due_idx").on(
3079
+ table.enabled,
3080
+ table.blockedAt,
3081
+ table.nextAttemptAt,
3082
+ ),
3083
+ }),
3084
+ );
3085
+
3086
+ /** Explicit poison-row disposition; transient failures never write here. */
3087
+ export const hostExportDeadLetters = pgTable(
3088
+ "host_export_dead_letters",
3089
+ {
3090
+ id: uuid("id").primaryKey().defaultRandom(),
3091
+ consumerId: text("consumer_id").notNull(),
3092
+ exportKind: text("export_kind").notNull(),
3093
+ exportCursor: bigint("export_cursor", { mode: "bigint" }).notNull(),
3094
+ sourceId: uuid("source_id").notNull(),
3095
+ reason: text("reason").notNull(),
3096
+ envelope: jsonb("envelope").$type<Record<string, unknown>>().notNull(),
3097
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
3098
+ },
3099
+ (table) => ({
3100
+ consumerCursor: uniqueIndex("host_export_dead_letters_consumer_cursor_uq").on(
3101
+ table.exportKind,
3102
+ table.consumerId,
3103
+ table.exportCursor,
3104
+ ),
3105
+ kindValid: check(
3106
+ "host_export_dead_letters_kind_check",
3107
+ sql`${table.exportKind} in ('session_event', 'usage_event')`,
3108
+ ),
3109
+ reasonValid: check(
3110
+ "host_export_dead_letters_reason_check",
3111
+ sql`length(${table.reason}) between 1 and 500`,
3112
+ ),
3113
+ envelopeBytes: check(
3114
+ "host_export_dead_letters_envelope_bytes_check",
3115
+ sql`pg_column_size(${table.envelope}) <= 114688`,
3116
+ ),
2609
3117
  }),
2610
3118
  );
2611
3119
 
@@ -2616,7 +3124,9 @@ export const creditLedgerEntries = pgTable(
2616
3124
  accountId: uuid("account_id")
2617
3125
  .notNull()
2618
3126
  .references(() => managedAccounts.id, { onDelete: "cascade" }),
2619
- workspaceId: uuid("workspace_id").references(() => workspaces.id, { onDelete: "set null" }),
3127
+ workspaceId: uuid("workspace_id").references(() => workspaces.id, {
3128
+ onDelete: "set null",
3129
+ }),
2620
3130
  type: text("type").notNull(),
2621
3131
  amountMicros: bigint("amount_micros", { mode: "number" }).notNull(),
2622
3132
  currency: text("currency").notNull().default("usd"),
@@ -2674,8 +3184,12 @@ export const auditEvents = pgTable(
2674
3184
  "audit_events",
2675
3185
  {
2676
3186
  id: uuid("id").primaryKey().defaultRandom(),
2677
- accountId: uuid("account_id").references(() => managedAccounts.id, { onDelete: "set null" }),
2678
- workspaceId: uuid("workspace_id").references(() => workspaces.id, { onDelete: "set null" }),
3187
+ accountId: uuid("account_id").references(() => managedAccounts.id, {
3188
+ onDelete: "set null",
3189
+ }),
3190
+ workspaceId: uuid("workspace_id").references(() => workspaces.id, {
3191
+ onDelete: "set null",
3192
+ }),
2679
3193
  subjectId: text("subject_id"),
2680
3194
  action: text("action").notNull(),
2681
3195
  targetType: text("target_type"),
@@ -2770,8 +3284,12 @@ export const capabilityCatalogItems = pgTable(
2770
3284
  "capability_catalog_items",
2771
3285
  {
2772
3286
  id: text("id").notNull(),
2773
- accountId: uuid("account_id").references(() => managedAccounts.id, { onDelete: "cascade" }),
2774
- workspaceId: uuid("workspace_id").references(() => workspaces.id, { onDelete: "cascade" }),
3287
+ accountId: uuid("account_id").references(() => managedAccounts.id, {
3288
+ onDelete: "cascade",
3289
+ }),
3290
+ workspaceId: uuid("workspace_id").references(() => workspaces.id, {
3291
+ onDelete: "cascade",
3292
+ }),
2775
3293
  kind: text("kind").notNull(),
2776
3294
  source: text("source").notNull().default("manual"),
2777
3295
  name: text("name").notNull(),