@opengeni/db 0.36.1 → 3.1.0-canary.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (274) hide show
  1. package/dist/attached-browser-devices.d.ts +7 -0
  2. package/dist/automations.d.ts +154 -0
  3. package/dist/canonical-human-identities.js +3 -3
  4. package/dist/capability-integrations.d.ts +13 -0
  5. package/dist/child-lifecycle-notices.d.ts +68 -0
  6. package/dist/{chunk-GYO7ED2Q.js → chunk-EULAN2NP.js} +2 -2
  7. package/dist/{chunk-VTPRIXKG.js → chunk-NJVZPWXY.js} +3 -3
  8. package/dist/{chunk-HSOWSYOX.js → chunk-NMUBV7Z5.js} +19 -11
  9. package/dist/chunk-NMUBV7Z5.js.map +1 -0
  10. package/dist/chunk-OF5OBB76.js +4457 -0
  11. package/dist/chunk-OF5OBB76.js.map +1 -0
  12. package/dist/{chunk-5KTIEDX7.js → chunk-PR2WMLOQ.js} +7714 -4443
  13. package/dist/chunk-PR2WMLOQ.js.map +1 -0
  14. package/dist/{chunk-Z7VYMNPZ.js → chunk-SCPUJS53.js} +41 -4
  15. package/dist/chunk-SCPUJS53.js.map +1 -0
  16. package/dist/{chunk-SK7YYHBI.js → chunk-SIBYBA2D.js} +46 -1
  17. package/dist/chunk-SIBYBA2D.js.map +1 -0
  18. package/dist/chunk-TIKF3KTI.js +383 -0
  19. package/dist/chunk-TIKF3KTI.js.map +1 -0
  20. package/dist/chunk-ZZ5LJEFX.js +516 -0
  21. package/dist/chunk-ZZ5LJEFX.js.map +1 -0
  22. package/dist/codemode-operations.d.ts +1 -6
  23. package/dist/company-brain-context-selection-schema.d.ts +705 -0
  24. package/dist/company-brain-context-selection.d.ts +94 -0
  25. package/dist/company-brain-governed-writes.d.ts +24 -0
  26. package/dist/company-brain.d.ts +55 -0
  27. package/dist/company-profile-agent-admin.d.ts +15 -0
  28. package/dist/company-profile-schema.d.ts +619 -2
  29. package/dist/company-profile.d.ts +9 -4
  30. package/dist/connection-authority.d.ts +95 -0
  31. package/dist/connection-scopes.d.ts +8 -0
  32. package/dist/connection-tenancy-backfill.d.ts +41 -0
  33. package/dist/connection-token-resolver.d.ts +30 -1
  34. package/dist/database.d.ts +19 -0
  35. package/dist/editable-artifact-durable-export.d.ts +10 -0
  36. package/dist/editable-artifact-durable-export.js +18 -3
  37. package/dist/editable-artifact-durable-export.js.map +1 -1
  38. package/dist/editable-artifacts.d.ts +2 -2
  39. package/dist/editable-artifacts.js +3 -3
  40. package/dist/governed-learning-activation-schema.d.ts +984 -0
  41. package/dist/governed-learning-activation.d.ts +98 -0
  42. package/dist/governed-learning-evaluator-schema.d.ts +633 -0
  43. package/dist/governed-learning-evaluator.d.ts +28 -0
  44. package/dist/index.d.ts +1633 -87
  45. package/dist/index.js +22004 -8109
  46. package/dist/index.js.map +1 -1
  47. package/dist/insights.d.ts +18 -0
  48. package/dist/integration-bindings.d.ts +1 -0
  49. package/dist/integration-facets.d.ts +30 -0
  50. package/dist/interaction-placement-loss.d.ts +22 -0
  51. package/dist/knowledge-source-sync-schema.d.ts +730 -0
  52. package/dist/knowledge-source-sync.d.ts +60 -0
  53. package/dist/managed-human-provisioning.d.ts +25 -0
  54. package/dist/memory-domain.d.ts +3 -3
  55. package/dist/migrate.d.ts +11 -1
  56. package/dist/migrate.js +1 -1
  57. package/dist/new-session-drafts.d.ts +43 -5
  58. package/dist/organization-membership-backfill.d.ts +174 -0
  59. package/dist/organization-membership-lifecycle.d.ts +162 -0
  60. package/dist/pr-review.d.ts +107 -0
  61. package/dist/preference-registry-schema.d.ts +2 -0
  62. package/dist/preference-registry.d.ts +2 -0
  63. package/dist/provision-roles.js +1 -1
  64. package/dist/runtime-posture.d.ts +23 -9
  65. package/dist/schema.d.ts +24925 -13697
  66. package/dist/schema.js +103 -1
  67. package/dist/scoped-knowledge-schema.d.ts +52 -1
  68. package/dist/session-background-commands.d.ts +125 -0
  69. package/dist/session-background-commands.js +34 -0
  70. package/dist/session-background-commands.js.map +1 -0
  71. package/dist/session-control.d.ts +156 -3
  72. package/dist/session-goal-pacing.d.ts +68 -0
  73. package/dist/session-queue-commands.d.ts +42 -5
  74. package/dist/session-realtime-ledger.d.ts +3 -0
  75. package/dist/session-tenancy.d.ts +71 -1
  76. package/dist/session-tenancy.js +25 -3
  77. package/dist/session-tool-call-settlement.d.ts +9 -4
  78. package/dist/slack-routing-personal-workspace.d.ts +23 -0
  79. package/dist/slack-routing.d.ts +153 -0
  80. package/dist/slack-task-policy-schema.d.ts +34 -0
  81. package/dist/slack-task-policy.d.ts +8 -0
  82. package/dist/slack-user-link-access.d.ts +1 -0
  83. package/dist/task-notes-schema.d.ts +594 -0
  84. package/dist/task-notes.d.ts +39 -0
  85. package/dist/turn-initiator.d.ts +2 -0
  86. package/dist/user-resource-authority.d.ts +109 -0
  87. package/dist/video-generation.js +3 -3
  88. package/dist/workspace-authority.d.ts +81 -0
  89. package/dist/workspace-instruction-policies-schema.d.ts +279 -0
  90. package/dist/workspace-instruction-policies.d.ts +62 -0
  91. package/dist/workspace-learning-policy.d.ts +1 -0
  92. package/dist/workspace-membership-permissions.d.ts +2 -0
  93. package/drizzle/0240_enrollment_connection_authority.sql +39 -0
  94. package/drizzle/0240_model_context_user_messages.sql +204 -0
  95. package/drizzle/0241_atomic_personal_resource_delegation.sql +1000 -0
  96. package/drizzle/0241_enrollment_agent_runtime.sql +70 -0
  97. package/drizzle/0243_google_drive_object_acl_authority.sql +554 -0
  98. package/drizzle/0244_slack_app_home_refresh_queue.sql +120 -0
  99. package/drizzle/0245_model_context_contribution_facts.sql +88 -0
  100. package/drizzle/0246_integration_personal_instance_authority.sql +70 -0
  101. package/drizzle/0247_terraform_stacks_provenance_repair.sql +428 -0
  102. package/drizzle/0248_terraform_stacks_component_resolution_fence.sql +83 -0
  103. package/drizzle/0249_personal_resource_delegation_authority_correction.sql +662 -0
  104. package/drizzle/0250_direct_retained_process_owner_liveness.sql +199 -0
  105. package/drizzle/0251_connected_machine_operation_policy.sql +45 -0
  106. package/drizzle/0252_scheduled_personal_resource_delegation.sql +1867 -0
  107. package/drizzle/0253_common_user_resource_authority_lifecycle.sql +1555 -0
  108. package/drizzle/0254_scoped_variable_set_authority.sql +1255 -0
  109. package/drizzle/0255_company_brain_governed_write_proposals.sql +524 -0
  110. package/drizzle/0256_connection_authority_delegation.sql +703 -0
  111. package/drizzle/0257_goal_revision_decisions_and_root_constraints.sql +436 -0
  112. package/drizzle/0258_three_scope_document_knowledge_authority.sql +1273 -0
  113. package/drizzle/0259_company_brain_context_selection_receipts.sql +869 -0
  114. package/drizzle/0260_task_note_knowledge_promotion.sql +820 -0
  115. package/drizzle/0261_preference_knowledge_proposal_actor_binding.sql +471 -0
  116. package/drizzle/0262_scoped_connected_machines_and_rigs.sql +1700 -0
  117. package/drizzle/0263_organization_membership_lifecycle.sql +3284 -0
  118. package/drizzle/0264_connection_authority_runtime_activation.sql +1084 -0
  119. package/drizzle/0266_company_brain_context_receipt_inspection.sql +156 -0
  120. package/drizzle/0268_governed_learning_decision_receipts.sql +825 -0
  121. package/drizzle/0269_governed_learning_activation_controller.sql +1874 -0
  122. package/drizzle/0270_governed_learning_history_inspection.sql +170 -0
  123. package/drizzle/0271_company_brain_retrieval_only_default.sql +524 -0
  124. package/drizzle/0272_human_confirmed_learning_activation.sql +591 -0
  125. package/drizzle/0273_scheduled_variable_set_materialization.sql +268 -0
  126. package/drizzle/0274_human_confirmed_knowledge_review.sql +310 -0
  127. package/drizzle/0275_scheduled_connection_authority.sql +5527 -0
  128. package/drizzle/0276_onboarding_proposal_initiating_human_guc.sql +198 -0
  129. package/drizzle/0277_workspace_writer_authority_attribution.sql +393 -0
  130. package/drizzle/0278_workspace_membership_removal_fencing.sql +1174 -0
  131. package/drizzle/0279_workspace_connection_use_lane.sql +343 -0
  132. package/drizzle/0280_connection_and_variable_set_audit_attribution.sql +773 -0
  133. package/drizzle/0281_viewer_holder_authority_claims.sql +52 -0
  134. package/drizzle/0282_variable_set_session_attach_attribution.sql +178 -0
  135. package/drizzle/0283_editable_spreadsheet_authored_state.sql +82 -0
  136. package/drizzle/0284_truthful_human_confirmed_review_reason.sql +930 -0
  137. package/drizzle/0285_organization_tenancy_inventory.sql +329 -0
  138. package/drizzle/0286_widen_task_note_expiry_ceiling.sql +391 -0
  139. package/drizzle/0287_open_suffix_pending_tool_calls.sql +18 -0
  140. package/drizzle/0288_attached_browser_reenrollment.sql +64 -0
  141. package/drizzle/0289_session_composer_policy_authority.sql +597 -0
  142. package/drizzle/0290_organization_membership_backfill.sql +268 -0
  143. package/drizzle/0291_resource_authority_classification_assertion.sql +371 -0
  144. package/drizzle/0292_truthful_tenancy_inventory_counters.sql +291 -0
  145. package/drizzle/0293_confirm_time_rule_rebaseline.sql +615 -0
  146. package/drizzle/0294_preference_activation_authority.sql +237 -0
  147. package/drizzle/0295_retire_legacy_standing_memory_mode.sql +476 -0
  148. package/drizzle/0296_force_rls_backfill_noop_repair.sql +215 -0
  149. package/drizzle/0297_session_ownership_classification_and_backfill.sql +752 -0
  150. package/drizzle/0298_organization_tenancy_parity.sql +609 -0
  151. package/drizzle/0299_organization_membership_lock_order.sql +291 -0
  152. package/drizzle/0300_tenancy_backfill_ledger.sql +459 -0
  153. package/drizzle/0301_session_snapshot_and_pin_visibility.sql +181 -0
  154. package/drizzle/0302_personal_workspace_session_ownership.sql +303 -0
  155. package/drizzle/0303_session_tenancy_product_activation.sql +919 -0
  156. package/drizzle/0304_personal_workspace_private_session_reads.sql +97 -0
  157. package/drizzle/0305_personal_resource_grant_management.sql +584 -0
  158. package/drizzle/0306_atomic_personal_resource_attachments.sql +1374 -0
  159. package/drizzle/0307_session_attention_state.sql +51 -0
  160. package/drizzle/0308_session_archives.sql +35 -0
  161. package/drizzle/0309_channel_project_pins.sql +6 -0
  162. package/drizzle/0310_channel_project_order.sql +27 -0
  163. package/drizzle/0311_company_scope_and_private_session_create.sql +656 -0
  164. package/drizzle/0312_quiescent_session_tree_deletion.sql +39 -0
  165. package/drizzle/0313_private_child_session_authority.sql +365 -0
  166. package/drizzle/0314_unregistered_organization_invitations.sql +606 -0
  167. package/drizzle/0315_personal_github_repository_selection.sql +624 -0
  168. package/drizzle/0316_human_confirmed_activation_resumed_generation.sql +869 -0
  169. package/drizzle/0317_session_goal_continuation_hold.sql +64 -0
  170. package/drizzle/0318_session_turn_startup_milestones.sql +115 -0
  171. package/drizzle/0319_event_triggered_automations.sql +254 -0
  172. package/drizzle/0320_pr_review_pack.sql +113 -0
  173. package/drizzle/0321_slack_bot_environment_display_name.sql +15 -0
  174. package/drizzle/0322_session_turns_unclaimed_prompt_trigger_index.sql +5 -0
  175. package/drizzle/0323_organization_private_session_enablement.sql +489 -0
  176. package/drizzle/0324_human_confirmed_company_profile_agent_admin.sql +856 -0
  177. package/drizzle/0325_child_lifecycle_notices.sql +44 -0
  178. package/drizzle/0326_interaction_operation_error_codes.sql +62 -0
  179. package/drizzle/0327_slack_first_task_hint.sql +29 -0
  180. package/drizzle/0328_session_background_commands.sql +590 -0
  181. package/drizzle/0329_slack_orchestration_delivery_events.sql +91 -0
  182. package/drizzle/0330_api_key_descriptions.sql +20 -0
  183. package/drizzle/0331_managed_organization_creation.sql +163 -0
  184. package/drizzle/0332_organization_shared_workspace_control_plane.sql +539 -0
  185. package/drizzle/0333_session_turn_prompt_routing.sql +45 -0
  186. package/drizzle/0334_connected_machine_workspace_root.sql +99 -0
  187. package/drizzle/0335_slack_workspace_routing.sql +460 -0
  188. package/drizzle/0336_atomic_session_fork_visibility.sql +667 -0
  189. package/drizzle/0337_slack_routed_action_handles.sql +189 -0
  190. package/drizzle/0338_atomic_connected_machine_attachments.sql +410 -0
  191. package/drizzle/0339_document_authority_reclassification.sql +1153 -0
  192. package/drizzle/0340_tenancy_backfill_activation_evidence.sql +1347 -0
  193. package/drizzle/0341_slack_routing_probe_organization_fence.sql +109 -0
  194. package/package.json +10 -6
  195. package/src/attached-browser-devices.ts +233 -5
  196. package/src/automations.ts +911 -0
  197. package/src/browser-sessions.ts +10 -0
  198. package/src/capability-integrations.ts +175 -1
  199. package/src/child-lifecycle-notices.ts +241 -0
  200. package/src/codemode-operations.ts +1 -35
  201. package/src/company-brain-context-selection-schema.ts +100 -0
  202. package/src/company-brain-context-selection.ts +359 -0
  203. package/src/company-brain-governed-writes.ts +1170 -0
  204. package/src/company-brain.ts +309 -0
  205. package/src/company-profile-agent-admin.ts +225 -0
  206. package/src/company-profile-schema.ts +121 -1
  207. package/src/company-profile.ts +21 -0
  208. package/src/computer-sessions.ts +10 -1
  209. package/src/connection-authority.ts +290 -0
  210. package/src/connection-scopes.ts +20 -0
  211. package/src/connection-tenancy-backfill.ts +81 -0
  212. package/src/connection-token-resolver.ts +263 -31
  213. package/src/database.ts +75 -2
  214. package/src/editable-artifact-durable-export.ts +27 -0
  215. package/src/editable-artifacts-schema.ts +10 -5
  216. package/src/editable-artifacts.ts +21 -11
  217. package/src/governed-learning-activation-schema.ts +121 -0
  218. package/src/governed-learning-activation.ts +588 -0
  219. package/src/governed-learning-evaluator-schema.ts +72 -0
  220. package/src/governed-learning-evaluator.ts +217 -0
  221. package/src/index.ts +12001 -1699
  222. package/src/insights.ts +107 -0
  223. package/src/integration-bindings.ts +88 -6
  224. package/src/integration-facets.ts +287 -84
  225. package/src/interaction-placement-loss.ts +234 -0
  226. package/src/knowledge-source-sync-schema.ts +83 -0
  227. package/src/knowledge-source-sync.ts +530 -0
  228. package/src/managed-human-provisioning.ts +116 -0
  229. package/src/memory-domain.ts +27 -32
  230. package/src/migrate.ts +77 -1
  231. package/src/new-session-drafts.ts +229 -35
  232. package/src/organization-membership-backfill.ts +735 -0
  233. package/src/organization-membership-lifecycle.ts +985 -0
  234. package/src/pr-review.ts +890 -0
  235. package/src/preference-registry.ts +31 -1
  236. package/src/provision-roles.ts +931 -24
  237. package/src/runtime-posture-cli.ts +2 -0
  238. package/src/runtime-posture.ts +1315 -52
  239. package/src/schema.ts +3336 -438
  240. package/src/scoped-knowledge-schema.ts +16 -8
  241. package/src/scoped-knowledge.ts +1 -1
  242. package/src/session-background-commands.ts +759 -0
  243. package/src/session-control.ts +640 -74
  244. package/src/session-goal-pacing.ts +126 -0
  245. package/src/session-queue-commands.ts +450 -98
  246. package/src/session-realtime-context.ts +19 -3
  247. package/src/session-realtime-ledger.ts +75 -12
  248. package/src/session-realtime-mirror.ts +1 -1
  249. package/src/session-tenancy.ts +403 -49
  250. package/src/session-tool-call-settlement.ts +38 -29
  251. package/src/slack-routing-personal-workspace.ts +67 -0
  252. package/src/slack-routing.ts +469 -0
  253. package/src/slack-task-policy-schema.ts +5 -0
  254. package/src/slack-task-policy.ts +18 -0
  255. package/src/slack-user-link-access.ts +17 -1
  256. package/src/task-notes-schema.ts +74 -0
  257. package/src/task-notes.ts +124 -2
  258. package/src/turn-initiator.ts +8 -0
  259. package/src/user-resource-authority.ts +377 -0
  260. package/src/workspace-authority.ts +160 -0
  261. package/src/workspace-instruction-policies-schema.ts +53 -1
  262. package/src/workspace-instruction-policies.ts +528 -77
  263. package/src/workspace-learning-policy.ts +21 -5
  264. package/src/workspace-membership-permissions.ts +9 -0
  265. package/dist/chunk-5KTIEDX7.js.map +0 -1
  266. package/dist/chunk-FPVAD4CO.js +0 -117
  267. package/dist/chunk-FPVAD4CO.js.map +0 -1
  268. package/dist/chunk-HSOWSYOX.js.map +0 -1
  269. package/dist/chunk-SK7YYHBI.js.map +0 -1
  270. package/dist/chunk-VKLB2ZGI.js +0 -2389
  271. package/dist/chunk-VKLB2ZGI.js.map +0 -1
  272. package/dist/chunk-Z7VYMNPZ.js.map +0 -1
  273. /package/dist/{chunk-GYO7ED2Q.js.map → chunk-EULAN2NP.js.map} +0 -0
  274. /package/dist/{chunk-VTPRIXKG.js.map → chunk-NJVZPWXY.js.map} +0 -0
package/src/schema.ts CHANGED
@@ -1,10 +1,15 @@
1
1
  import type {
2
+ AutomationAcceptedExecution,
3
+ AutomationSessionTemplate,
2
4
  AttemptToolCatalog,
3
5
  AttemptToolResult,
4
6
  DraftTimelineAnnotation,
5
7
  FirstPartyMcpToolName,
6
8
  McpPersonalConnectionDelegation,
9
+ PersonalResourceAttachmentIntent,
10
+ PersonalResourceAttachmentSummary,
7
11
  McpServerConnectionRef,
12
+ ModelContextContributionSummary,
8
13
  RigProviderImages,
9
14
  SessionMcpApprovalPolicy,
10
15
  SessionGoalChangeKind,
@@ -12,6 +17,7 @@ import type {
12
17
  SessionGoalSnapshot,
13
18
  SlackUserLinkAccessRequest,
14
19
  TimelineAnnotation,
20
+ UserResourceDelegation,
15
21
  XaiProviderAccountAuthoritySnapshotV1,
16
22
  } from "@opengeni/contracts";
17
23
  import { WORKSPACE_XAI_PROVIDER_ACCOUNT_AUTHORITY_SNAPSHOT_V1 } from "@opengeni/contracts";
@@ -49,6 +55,12 @@ const vector = customType<{ data: number[]; driverData: string }>({
49
55
  },
50
56
  });
51
57
 
58
+ const bytea = customType<{ data: Uint8Array; driverData: Uint8Array }>({
59
+ dataType() {
60
+ return "bytea";
61
+ },
62
+ });
63
+
52
64
  export type ConnectorActionPolicyDecision = "allow" | "ask" | "block";
53
65
 
54
66
  export type ConnectorActionPolicySnapshotEntry = {
@@ -436,6 +448,7 @@ export const organizationMemberships = pgTable(
436
448
  .notNull()
437
449
  .references(() => managedAccounts.id, { onDelete: "cascade" }),
438
450
  subjectId: text("subject_id").notNull(),
451
+ role: text("role").notNull().default("member"),
439
452
  status: text("status").notNull().default("provisioning"),
440
453
  personalWorkspaceId: uuid("personal_workspace_id"),
441
454
  authorizationRevision: bigint("authorization_revision", { mode: "number" })
@@ -460,6 +473,9 @@ export const organizationMemberships = pgTable(
460
473
  personalWorkspace: uniqueIndex("organization_memberships_personal_workspace_idx")
461
474
  .on(table.accountId, table.personalWorkspaceId)
462
475
  .where(sql`${table.personalWorkspaceId} is not null`),
476
+ retentionDue: index("organization_memberships_retention_due_idx")
477
+ .on(table.accountId, table.personalRetentionUntil, table.id)
478
+ .where(sql`${table.status} = 'revoked' and ${table.personalRetentionUntil} is not null`),
463
479
  personalWorkspaceAccount: foreignKey({
464
480
  name: "organization_memberships_personal_workspace_account_fk",
465
481
  columns: [table.personalWorkspaceId, table.accountId],
@@ -469,6 +485,10 @@ export const organizationMemberships = pgTable(
469
485
  "organization_memberships_status_check",
470
486
  sql`${table.status} in ('provisioning', 'active', 'suspended', 'revoked')`,
471
487
  ),
488
+ roleValid: check(
489
+ "organization_memberships_role_check",
490
+ sql`${table.role} in ('owner', 'admin', 'member')`,
491
+ ),
472
492
  activePersonalWorkspace: check(
473
493
  "organization_memberships_active_personal_workspace_check",
474
494
  sql`${table.status} <> 'active' or ${table.personalWorkspaceId} is not null`,
@@ -489,6 +509,122 @@ export const organizationMemberships = pgTable(
489
509
  }),
490
510
  );
491
511
 
512
+ export const organizationProfileEvents = pgTable(
513
+ "organization_profile_events",
514
+ {
515
+ id: uuid("id").primaryKey(),
516
+ accountId: uuid("account_id")
517
+ .notNull()
518
+ .references(() => managedAccounts.id, { onDelete: "cascade" }),
519
+ actorMembershipId: uuid("actor_membership_id").notNull(),
520
+ previousName: text("previous_name").notNull(),
521
+ requestedName: text("requested_name").notNull(),
522
+ expectedUpdatedAt: timestamp("expected_updated_at", { withTimezone: true }).notNull(),
523
+ resultUpdatedAt: timestamp("result_updated_at", { withTimezone: true }).notNull(),
524
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
525
+ },
526
+ (table) => ({
527
+ actorMembership: foreignKey({
528
+ name: "organization_profile_events_actor_fk",
529
+ columns: [table.actorMembershipId, table.accountId],
530
+ foreignColumns: [organizationMemberships.id, organizationMemberships.accountId],
531
+ }).onDelete("restrict"),
532
+ }),
533
+ );
534
+
535
+ // Forward-only organization-scoped activation receipt for the product session-
536
+ // tenancy surface. Runtime may observe this marker, but only the drained
537
+ // migration-owner activation seam may insert it.
538
+ export const sessionTenancyActivations = pgTable(
539
+ "session_tenancy_activations",
540
+ {
541
+ accountId: uuid("account_id")
542
+ .primaryKey()
543
+ .references(() => managedAccounts.id, { onDelete: "restrict" }),
544
+ activationVersion: integer("activation_version").notNull(),
545
+ inventoryDigest: text("inventory_digest").notNull(),
546
+ parityDigest: text("parity_digest").notNull(),
547
+ activatedBy: text("activated_by").notNull(),
548
+ backfillReceiptIds: uuid("backfill_receipt_ids")
549
+ .array()
550
+ .notNull()
551
+ .default(sql`'{}'::uuid[]`),
552
+ activatedAt: timestamp("activated_at", { withTimezone: true }).notNull().defaultNow(),
553
+ },
554
+ (table) => ({
555
+ versionValid: check(
556
+ "session_tenancy_activations_version_check",
557
+ sql`${table.activationVersion} = 1`,
558
+ ),
559
+ digestsValid: check(
560
+ "session_tenancy_activations_digests_check",
561
+ sql`${table.inventoryDigest} ~ '^[0-9a-f]{64}$'
562
+ and ${table.parityDigest} ~ '^[0-9a-f]{64}$'`,
563
+ ),
564
+ actorValid: check(
565
+ "session_tenancy_activations_actor_check",
566
+ sql`octet_length(${table.activatedBy}) between 1 and 256`,
567
+ ),
568
+ backfillReceiptsValid: check(
569
+ "session_tenancy_activation_backfill_receipts_check",
570
+ sql`cardinality(${table.backfillReceiptIds}) in (0, 5, 6)`,
571
+ ),
572
+ }),
573
+ );
574
+
575
+ export const organizationPrivateSessionSettings = pgTable(
576
+ "organization_private_session_settings",
577
+ {
578
+ accountId: uuid("account_id")
579
+ .primaryKey()
580
+ .references(() => managedAccounts.id, { onDelete: "cascade" }),
581
+ enabled: boolean("enabled").notNull().default(false),
582
+ version: bigint("version", { mode: "number" }).notNull().default(1),
583
+ updatedByMembershipId: uuid("updated_by_membership_id"),
584
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
585
+ },
586
+ (table) => ({
587
+ updater: foreignKey({
588
+ name: "organization_private_session_settings_updater_fk",
589
+ columns: [table.updatedByMembershipId, table.accountId],
590
+ foreignColumns: [organizationMemberships.id, organizationMemberships.accountId],
591
+ }).onDelete("restrict"),
592
+ versionValid: check(
593
+ "organization_private_session_settings_version_check",
594
+ sql`${table.version} > 0`,
595
+ ),
596
+ }),
597
+ );
598
+
599
+ export const organizationPrivateSessionSettingEvents = pgTable(
600
+ "organization_private_session_setting_events",
601
+ {
602
+ id: uuid("id").primaryKey(),
603
+ accountId: uuid("account_id")
604
+ .notNull()
605
+ .references(() => managedAccounts.id, { onDelete: "cascade" }),
606
+ actorMembershipId: uuid("actor_membership_id").notNull(),
607
+ requestedEnabled: boolean("requested_enabled").notNull(),
608
+ expectedVersion: bigint("expected_version", { mode: "number" }).notNull(),
609
+ resultEnabled: boolean("result_enabled").notNull(),
610
+ resultVersion: bigint("result_version", { mode: "number" }).notNull(),
611
+ resultUpdatedAt: timestamp("result_updated_at", { withTimezone: true }).notNull(),
612
+ changed: boolean("changed").notNull(),
613
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
614
+ },
615
+ (table) => ({
616
+ actor: foreignKey({
617
+ name: "organization_private_session_setting_events_actor_fk",
618
+ columns: [table.actorMembershipId, table.accountId],
619
+ foreignColumns: [organizationMemberships.id, organizationMemberships.accountId],
620
+ }).onDelete("restrict"),
621
+ versionsValid: check(
622
+ "organization_private_session_setting_events_versions_check",
623
+ sql`${table.expectedVersion} >= 0 and ${table.resultVersion} > 0`,
624
+ ),
625
+ }),
626
+ );
627
+
492
628
  export const organizationUserRetentionPolicies = pgTable(
493
629
  "organization_user_retention_policies",
494
630
  {
@@ -517,7 +653,7 @@ export const organizationUserRetentionPolicies = pgTable(
517
653
  or (
518
654
  ${table.mode} = 'delete_after'
519
655
  and ${table.retentionDays} is not null
520
- and ${table.retentionDays} between 1 and 3650
656
+ and ${table.retentionDays} between 30 and 90
521
657
  )`,
522
658
  ),
523
659
  versionValid: check(
@@ -527,280 +663,1193 @@ export const organizationUserRetentionPolicies = pgTable(
527
663
  }),
528
664
  );
529
665
 
530
- export const organizationUserResourceAuthorities = pgTable(
531
- "organization_user_resource_authorities",
666
+ export const organizationMembershipInvitations = pgTable(
667
+ "organization_membership_invitations",
532
668
  {
533
669
  id: uuid("id").primaryKey().defaultRandom(),
534
670
  accountId: uuid("account_id")
535
671
  .notNull()
536
672
  .references(() => managedAccounts.id, { onDelete: "cascade" }),
537
- organizationMembershipId: uuid("organization_membership_id").notNull(),
538
- resourceKind: text("resource_kind").notNull(),
539
- resourceId: uuid("resource_id").notNull(),
540
- // Provenance only. The migration owns a composite same-organization FK
541
- // whose delete action clears only this column, never account_id.
542
- originWorkspaceId: uuid("origin_workspace_id"),
543
- generation: bigint("generation", { mode: "number" }).notNull().default(1),
544
- status: text("status").notNull().default("active"),
545
- revokedAt: timestamp("revoked_at", { withTimezone: true }),
673
+ targetSubjectId: text("target_subject_id"),
674
+ targetEmail: text("target_email").notNull(),
675
+ targetName: text("target_name"),
676
+ initialWorkspaceIds: uuid("initial_workspace_ids")
677
+ .array()
678
+ .notNull()
679
+ .default(sql`'{}'::uuid[]`),
680
+ role: text("role").notNull().default("member"),
681
+ status: text("status").notNull().default("pending"),
682
+ revision: bigint("revision", { mode: "number" }).notNull().default(1),
683
+ createdByMembershipId: uuid("created_by_membership_id").notNull(),
684
+ acceptedMembershipId: uuid("accepted_membership_id"),
685
+ expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
546
686
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
547
687
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
548
688
  },
549
689
  (table) => ({
550
- accountIdentity: uniqueIndex("organization_user_resource_authorities_id_account_idx").on(
690
+ accountIdentity: uniqueIndex("organization_membership_invitations_id_account_idx").on(
551
691
  table.id,
552
692
  table.accountId,
553
693
  ),
554
- accountMembershipIdentity: uniqueIndex(
555
- "organization_user_resource_authorities_id_account_membership_idx",
556
- ).on(table.id, table.accountId, table.organizationMembershipId),
557
- resourceIdentity: uniqueIndex(
558
- "organization_user_resource_authorities_resource_identity_idx",
559
- ).on(table.accountId, table.organizationMembershipId, table.resourceKind, table.resourceId),
560
- membership: foreignKey({
561
- name: "organization_user_resource_authorities_membership_fk",
562
- columns: [table.organizationMembershipId, table.accountId],
694
+ pendingTarget: uniqueIndex("organization_membership_invitations_pending_target_uq")
695
+ .on(table.accountId, table.targetSubjectId)
696
+ .where(sql`${table.status} = 'pending' AND ${table.targetSubjectId} IS NOT NULL`),
697
+ pendingEmail: uniqueIndex("organization_membership_invitations_pending_email_uq")
698
+ .on(table.accountId, table.targetEmail)
699
+ .where(sql`${table.status} = 'pending'`),
700
+ accountCreated: index("organization_membership_invitations_account_created_idx").on(
701
+ table.accountId,
702
+ table.createdAt.desc(),
703
+ table.id.desc(),
704
+ ),
705
+ subjectCreated: index("organization_membership_invitations_subject_created_idx").on(
706
+ table.targetSubjectId,
707
+ table.createdAt.desc(),
708
+ table.id.desc(),
709
+ ),
710
+ creator: foreignKey({
711
+ name: "organization_membership_invitations_creator_fk",
712
+ columns: [table.createdByMembershipId, table.accountId],
563
713
  foreignColumns: [organizationMemberships.id, organizationMemberships.accountId],
564
714
  }).onDelete("restrict"),
565
- resourceKindValid: check(
566
- "organization_user_resource_authorities_kind_check",
567
- sql`${table.resourceKind} = lower(btrim(${table.resourceKind}))
568
- and length(${table.resourceKind}) between 1 and 64
569
- and ${table.resourceKind} ~ '^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$'`,
570
- ),
571
- generationValid: check(
572
- "organization_user_resource_authorities_generation_check",
573
- sql`${table.generation} > 0`,
715
+ acceptedMembership: foreignKey({
716
+ name: "organization_membership_invitations_accepted_membership_fk",
717
+ columns: [table.acceptedMembershipId, table.accountId],
718
+ foreignColumns: [organizationMemberships.id, organizationMemberships.accountId],
719
+ }).onDelete("restrict"),
720
+ roleValid: check(
721
+ "organization_membership_invitations_role_check",
722
+ sql`${table.role} in ('owner', 'admin', 'member')`,
574
723
  ),
575
724
  statusValid: check(
576
- "organization_user_resource_authorities_status_check",
577
- sql`${table.status} in ('active', 'retained', 'revoked')`,
725
+ "organization_membership_invitations_status_check",
726
+ sql`${table.status} in ('pending', 'accepted', 'revoked', 'expired')`,
578
727
  ),
579
- revocationValid: check(
580
- "organization_user_resource_authorities_revocation_check",
581
- sql`(${table.status} = 'revoked' and ${table.revokedAt} is not null)
582
- or (${table.status} <> 'revoked' and ${table.revokedAt} is null)`,
728
+ revisionValid: check(
729
+ "organization_membership_invitations_revision_check",
730
+ sql`${table.revision} > 0`,
731
+ ),
732
+ acceptanceValid: check(
733
+ "organization_membership_invitations_acceptance_check",
734
+ sql`(${table.status} = 'accepted' and ${table.acceptedMembershipId} is not null)
735
+ or (${table.status} <> 'accepted' and ${table.acceptedMembershipId} is null)`,
583
736
  ),
584
737
  }),
585
738
  );
586
739
 
587
- export const apiKeys = pgTable(
588
- "api_keys",
740
+ export const organizationInvitationBindingEvents = pgTable(
741
+ "organization_invitation_binding_events",
589
742
  {
590
743
  id: uuid("id").primaryKey().defaultRandom(),
591
744
  accountId: uuid("account_id")
592
745
  .notNull()
593
746
  .references(() => managedAccounts.id, { onDelete: "cascade" }),
594
- workspaceId: uuid("workspace_id").references(() => workspaces.id, {
595
- onDelete: "cascade",
596
- }),
597
- name: text("name").notNull(),
598
- prefix: text("prefix").notNull(),
599
- keyHash: text("key_hash").notNull(),
600
- permissions: jsonb("permissions").$type<string[]>().notNull().default([]),
601
- expiresAt: timestamp("expires_at", { withTimezone: true }),
602
- revokedAt: timestamp("revoked_at", { withTimezone: true }),
603
- lastUsedAt: timestamp("last_used_at", { withTimezone: true }),
604
- createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
605
- updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
747
+ invitationId: uuid("invitation_id").notNull(),
748
+ targetSubjectId: text("target_subject_id").notNull(),
749
+ resultingRevision: bigint("resulting_revision", { mode: "number" }).notNull(),
750
+ boundAt: timestamp("bound_at", { withTimezone: true }).notNull().defaultNow(),
606
751
  },
607
752
  (table) => ({
608
- prefix: index("api_keys_prefix_idx").on(table.prefix),
609
- hash: uniqueIndex("api_keys_key_hash_idx").on(table.keyHash),
610
- account: index("api_keys_account_idx").on(table.accountId),
611
- workspace: index("api_keys_workspace_idx").on(table.workspaceId),
753
+ invitation: foreignKey({
754
+ name: "organization_invitation_binding_events_invitation_fk",
755
+ columns: [table.invitationId, table.accountId],
756
+ foreignColumns: [
757
+ organizationMembershipInvitations.id,
758
+ organizationMembershipInvitations.accountId,
759
+ ],
760
+ }).onDelete("restrict"),
761
+ invitationIdentity: uniqueIndex("organization_invitation_binding_events_invitation_uq").on(
762
+ table.accountId,
763
+ table.invitationId,
764
+ ),
765
+ subjectValid: check(
766
+ "organization_invitation_binding_events_subject_check",
767
+ sql`${table.targetSubjectId} = btrim(${table.targetSubjectId})
768
+ and ${table.targetSubjectId} like 'user:%'
769
+ and octet_length(convert_to(${table.targetSubjectId}, 'UTF8')) between 6 and 1024`,
770
+ ),
771
+ revisionValid: check(
772
+ "organization_invitation_binding_events_revision_check",
773
+ sql`${table.resultingRevision} > 1`,
774
+ ),
612
775
  }),
613
776
  );
614
777
 
615
- export const workspaceVariableSets = pgTable(
616
- "workspace_variable_sets",
778
+ export const organizationMembershipOperationReceipts = pgTable(
779
+ "organization_membership_operation_receipts",
617
780
  {
618
- id: uuid("id").primaryKey().defaultRandom(),
619
781
  accountId: uuid("account_id")
620
782
  .notNull()
621
783
  .references(() => managedAccounts.id, { onDelete: "cascade" }),
622
- workspaceId: uuid("workspace_id")
623
- .notNull()
624
- .references(() => workspaces.id, { onDelete: "cascade" }),
625
- name: text("name").notNull(),
626
- description: text("description"),
627
- // Migration 0230 is schema-only: all shipped CRUD continues to omit these
628
- // fields and therefore remains workspace-scoped. A later activation slice
629
- // must provide the complete owner authority and runtime protocol.
630
- authorityScope: text("authority_scope").notNull().default("workspace"),
631
- authorityId: uuid("authority_id"),
632
- ownerOrganizationMembershipId: uuid("owner_organization_membership_id"),
633
- originWorkspaceId: uuid("origin_workspace_id"),
784
+ operationId: uuid("operation_id").notNull(),
785
+ action: text("action").notNull(),
786
+ inputHash: text("input_hash").notNull(),
787
+ result: jsonb("result").$type<Record<string, unknown>>().notNull(),
634
788
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
635
- updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
636
789
  },
637
790
  (table) => ({
638
- workspaceName: uniqueIndex("workspace_variable_sets_workspace_name_idx").on(
639
- table.workspaceId,
640
- table.name,
641
- ),
642
- workspaceCreated: index("workspace_variable_sets_workspace_created_idx").on(
643
- table.workspaceId,
644
- table.createdAt,
645
- ),
646
- authorityShape: check(
647
- "workspace_variable_sets_authority_shape_check",
648
- sql`(
649
- ${table.authorityScope} = 'workspace'
650
- and ${table.authorityId} is null
651
- and ${table.ownerOrganizationMembershipId} is null
652
- ) or (
653
- ${table.authorityScope} = 'user'
654
- and ${table.authorityId} is not null
655
- and ${table.ownerOrganizationMembershipId} is not null
656
- )`,
791
+ identity: primaryKey({ columns: [table.accountId, table.operationId] }),
792
+ actionValid: check(
793
+ "organization_membership_operation_receipts_action_check",
794
+ sql`${table.action} in ('invite', 'accept', 'revoke_invitation', 'change_role', 'suspend', 'reactivate', 'offboard', 'retention', 'create_workspace')`,
657
795
  ),
658
- authorityScopeValid: check(
659
- "workspace_variable_sets_authority_scope_check",
660
- sql`${table.authorityScope} in ('workspace', 'user')`,
796
+ inputHashValid: check(
797
+ "organization_membership_operation_receipts_input_hash_check",
798
+ sql`${table.inputHash} ~ '^[0-9a-f]{64}$'`,
661
799
  ),
662
- authority: foreignKey({
663
- name: "workspace_variable_sets_authority_fk",
664
- columns: [table.authorityId, table.accountId, table.ownerOrganizationMembershipId],
665
- foreignColumns: [
666
- organizationUserResourceAuthorities.id,
667
- organizationUserResourceAuthorities.accountId,
668
- organizationUserResourceAuthorities.organizationMembershipId,
669
- ],
670
- }).onDelete("restrict"),
671
- // PostgreSQL's column-subset SET NULL action preserves account_id; Drizzle
672
- // cannot represent that action, so migration 0230 owns the origin FK.
673
800
  }),
674
801
  );
675
802
 
676
- export const workspaceVariableSetVariables = pgTable(
677
- "workspace_variable_set_variables",
803
+ export const organizationMembershipLifecycleEvents = pgTable(
804
+ "organization_membership_lifecycle_events",
678
805
  {
679
806
  id: uuid("id").primaryKey().defaultRandom(),
680
807
  accountId: uuid("account_id")
681
808
  .notNull()
682
809
  .references(() => managedAccounts.id, { onDelete: "cascade" }),
683
- workspaceId: uuid("workspace_id")
684
- .notNull()
685
- .references(() => workspaces.id, { onDelete: "cascade" }),
686
- variableSetId: uuid("variable_set_id")
687
- .notNull()
688
- .references(() => workspaceVariableSets.id, { onDelete: "cascade" }),
689
- name: text("name").notNull(),
690
- // Format: v1:<base64 iv>:<base64 ciphertext||gcm-tag>. Never returned by any API.
691
- valueEncrypted: text("value_encrypted").notNull(),
692
- version: integer("version").notNull().default(1),
810
+ operationId: uuid("operation_id").notNull(),
811
+ actorMembershipId: uuid("actor_membership_id").notNull(),
812
+ targetMembershipId: uuid("target_membership_id"),
813
+ kind: text("kind").notNull(),
814
+ priorAuthorizationRevision: bigint("prior_authorization_revision", {
815
+ mode: "number",
816
+ }),
817
+ resultingAuthorizationRevision: bigint("resulting_authorization_revision", {
818
+ mode: "number",
819
+ }),
820
+ reason: text("reason"),
693
821
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
694
- updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
695
822
  },
696
823
  (table) => ({
697
- variableSetName: uniqueIndex("workspace_variable_set_variables_env_name_idx").on(
698
- table.workspaceId,
699
- table.variableSetId,
700
- table.name,
824
+ operation: uniqueIndex("organization_membership_lifecycle_events_operation_uq").on(
825
+ table.accountId,
826
+ table.operationId,
701
827
  ),
702
- variableSet: index("workspace_variable_set_variables_workspace_env_idx").on(
703
- table.workspaceId,
704
- table.variableSetId,
828
+ actor: foreignKey({
829
+ name: "organization_membership_lifecycle_events_actor_fk",
830
+ columns: [table.actorMembershipId, table.accountId],
831
+ foreignColumns: [organizationMemberships.id, organizationMemberships.accountId],
832
+ }).onDelete("restrict"),
833
+ target: foreignKey({
834
+ name: "organization_membership_lifecycle_events_target_fk",
835
+ columns: [table.targetMembershipId, table.accountId],
836
+ foreignColumns: [organizationMemberships.id, organizationMemberships.accountId],
837
+ }).onDelete("restrict"),
838
+ kindValid: check(
839
+ "organization_membership_lifecycle_events_kind_check",
840
+ sql`${table.kind} in ('invite', 'accept', 'revoke_invitation', 'change_role', 'suspend', 'reactivate', 'offboard', 'retention')`,
705
841
  ),
706
842
  }),
707
843
  );
708
844
 
709
- // Per-workspace ChatGPT/Codex subscription credential. One row per connected
710
- // ChatGPT account within a workspace.
711
- // access/refresh/id tokens live INSIDE credential_encrypted (v1 AES-256-GCM,
712
- // same envelope as workspace_variable_set_variables); the other columns are
713
- // plaintext metadata (header value + UI). RLS-isolated per workspace.
714
- export const codexSubscriptionCredentials = pgTable(
715
- "codex_subscription_credentials",
845
+ export const organizationUserRetentionDeletions = pgTable(
846
+ "organization_user_retention_deletions",
716
847
  {
717
- id: uuid("id").primaryKey().defaultRandom(),
718
848
  accountId: uuid("account_id")
719
849
  .notNull()
720
850
  .references(() => managedAccounts.id, { onDelete: "cascade" }),
721
- workspaceId: uuid("workspace_id")
722
- .notNull()
723
- .references(() => workspaces.id, { onDelete: "cascade" }),
724
- // Format: v1:<base64 iv>:<base64 ciphertext||gcm-tag>. JSON {access_token, refresh_token, id_token}. Never returned by any API.
725
- credentialEncrypted: text("credential_encrypted").notNull(),
726
- chatgptAccountId: text("chatgpt_account_id"), // plaintext ChatGPT-Account-ID header value (non-secret)
727
- scopes: text("scopes"), // space-delimited, as granted
728
- planType: text("plan_type"),
729
- isFedramp: boolean("is_fedramp").notNull().default(false),
730
- expiresAt: timestamp("expires_at", { withTimezone: true }), // derived from access-token JWT exp
731
- lastRefreshAt: timestamp("last_refresh_at", { withTimezone: true }),
732
- status: text("status").notNull().default("active"), // active | needs_relogin | error
733
- lastError: text("last_error"),
734
- version: integer("version").notNull().default(1),
735
- label: text("label"), // user-chosen nickname; null ⇒ derive from email/plan/account
736
- accountEmail: text("account_email"), // email from the id_token (user's own email; non-secret)
737
- // P2 usage cache (plaintext metadata; NEVER a token). Snapshotted from
738
- // GET /wham/usage; drives the quota bars + the cache TTL. primary = 5h window
739
- // (limit_window_seconds 18000), secondary = weekly (604800).
740
- primaryUsedPercent: integer("primary_used_percent"),
741
- primaryResetAt: timestamp("primary_reset_at", { withTimezone: true }),
742
- secondaryUsedPercent: integer("secondary_used_percent"),
743
- secondaryResetAt: timestamp("secondary_reset_at", { withTimezone: true }),
744
- usageCheckedAt: timestamp("usage_checked_at", { withTimezone: true }), // snapshot freshness → cache TTL clock
745
- // P3 rotation cooldown (plaintext metadata; NEVER a token). Set when this account hit its
746
- // usage cap on a rotation turn; the rotation engine treats `exhausted_until > now()` as
747
- // capped/skip so it isn't immediately re-picked. Self-clears via the now() comparison.
748
- exhaustedUntil: timestamp("exhausted_until", { withTimezone: true }),
749
- // Workspace-local, server-held fairness cursor. Provider usage headers are
750
- // capacity hints, never the sole allocator: live lease count is ranked first
751
- // and this cursor deterministically breaks equal-load/equal-capacity ties.
752
- // This flag controls NEW automatic allocations only. Credential health,
753
- // refresh, encrypted material, and an already-leased in-flight turn are
754
- // intentionally independent. account eligibility policy owns toggle OCC/audit and product UI.
755
- allocatorEnabled: boolean("allocator_enabled").notNull().default(true),
756
- // Independent OCC/audit sequence for the allocator toggle. Token refresh
757
- // continues to own `version`; quota/cache writes own neither counter.
758
- allocatorVersion: integer("allocator_version").notNull().default(1),
759
- allocatorUpdatedBySubjectId: text("allocator_updated_by_subject_id"),
760
- allocatorUpdatedAt: timestamp("allocator_updated_at", {
761
- withTimezone: true,
851
+ membershipId: uuid("membership_id").notNull(),
852
+ retentionUntil: timestamp("retention_until", { withTimezone: true }).notNull(),
853
+ state: text("state").notNull().default("claimed"),
854
+ claimOperationId: uuid("claim_operation_id").notNull(),
855
+ claimExpiresAt: timestamp("claim_expires_at", { withTimezone: true }).notNull(),
856
+ attemptCount: integer("attempt_count").notNull().default(1),
857
+ databaseResult: jsonb("database_result").$type<Record<string, unknown>>(),
858
+ databaseFinalizedAt: timestamp("database_finalized_at", { withTimezone: true }),
859
+ result: jsonb("result").$type<Record<string, unknown>>(),
860
+ completedAt: timestamp("completed_at", { withTimezone: true }),
861
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
862
+ },
863
+ (table) => ({
864
+ identity: primaryKey({ columns: [table.accountId, table.membershipId] }),
865
+ operation: uniqueIndex("organization_user_retention_deletions_operation_uq").on(
866
+ table.claimOperationId,
867
+ ),
868
+ claim: index("organization_user_retention_deletions_claim_idx").on(
869
+ table.accountId,
870
+ table.state,
871
+ table.claimExpiresAt,
872
+ ),
873
+ membership: foreignKey({
874
+ name: "organization_user_retention_deletions_membership_fk",
875
+ columns: [table.membershipId, table.accountId],
876
+ foreignColumns: [organizationMemberships.id, organizationMemberships.accountId],
877
+ }).onDelete("restrict"),
878
+ stateValid: check(
879
+ "organization_user_retention_deletions_state_check",
880
+ sql`${table.state} in ('claimed', 'failed', 'completed')`,
881
+ ),
882
+ attemptValid: check(
883
+ "organization_user_retention_deletions_attempt_check",
884
+ sql`${table.attemptCount} > 0`,
885
+ ),
886
+ completionValid: check(
887
+ "organization_user_retention_deletions_completion_check",
888
+ sql`(${table.state} = 'completed' and ${table.completedAt} is not null and ${table.result} is not null)
889
+ or (${table.state} <> 'completed' and ${table.completedAt} is null and ${table.result} is null)`,
890
+ ),
891
+ databaseFinalizationValid: check(
892
+ "organization_retention_deletions_db_finalized_chk",
893
+ sql`(${table.databaseFinalizedAt} is null and ${table.databaseResult} is null)
894
+ or (${table.databaseFinalizedAt} is not null and ${table.databaseResult} is not null)`,
895
+ ),
896
+ }),
897
+ );
898
+
899
+ export const organizationUserRetentionObjectObligations = pgTable(
900
+ "organization_user_retention_object_obligations",
901
+ {
902
+ accountId: uuid("account_id").notNull(),
903
+ membershipId: uuid("membership_id").notNull(),
904
+ objectKind: text("object_kind").notNull(),
905
+ sourceId: text("source_id").notNull(),
906
+ objectBucket: text("object_bucket").notNull(),
907
+ objectKey: text("object_key").notNull(),
908
+ preparedOperationId: uuid("prepared_operation_id").notNull(),
909
+ objectKeyHash: text("object_key_hash").notNull(),
910
+ preparedAt: timestamp("prepared_at", { withTimezone: true }).notNull().defaultNow(),
911
+ },
912
+ (table) => ({
913
+ identity: primaryKey({
914
+ columns: [
915
+ table.accountId,
916
+ table.membershipId,
917
+ table.objectKind,
918
+ table.sourceId,
919
+ table.objectBucket,
920
+ ],
762
921
  }),
763
- // Authoritative count-only summary cached from /wham/usage. Detailed rows
764
- // are never persisted as redemption authority; every first POST preflights
765
- // the provider's fresh detail endpoint.
766
- resetCreditAvailableCount: integer("reset_credit_available_count"),
767
- resetCreditsCheckedAt: timestamp("reset_credits_checked_at", {
768
- withTimezone: true,
922
+ deletion: foreignKey({
923
+ name: "organization_retention_object_obligations_deletion_fk",
924
+ columns: [table.accountId, table.membershipId],
925
+ foreignColumns: [
926
+ organizationUserRetentionDeletions.accountId,
927
+ organizationUserRetentionDeletions.membershipId,
928
+ ],
929
+ }).onDelete("restrict"),
930
+ hashValid: check(
931
+ "organization_user_retention_object_obligations_hash_check",
932
+ sql`${table.objectKeyHash} ~ '^[0-9a-f]{64}$'`,
933
+ ),
934
+ shapeValid: check(
935
+ "organization_user_retention_object_obligations_shape_check",
936
+ sql`${table.objectKind} in (
937
+ 'file', 'session_recording', 'browser_state_artifact', 'browser_state_upload',
938
+ 'transcription_recording_object', 'video_staging_reference',
939
+ 'workspace_artifact_version', 'editable_artifact_blob',
940
+ 'workspace_capture_manifest', 'workspace_capture_tree_index',
941
+ 'workspace_capture_blob'
942
+ )
943
+ and octet_length(${table.sourceId}) between 1 and 2048
944
+ and octet_length(${table.objectBucket}) between 1 and 1024
945
+ and octet_length(${table.objectKey}) between 1 and 4096`,
946
+ ),
947
+ }),
948
+ );
949
+
950
+ export const organizationUserRetentionObjectDeletionReceipts = pgTable(
951
+ "organization_user_retention_object_deletion_receipts",
952
+ {
953
+ accountId: uuid("account_id").notNull(),
954
+ membershipId: uuid("membership_id").notNull(),
955
+ objectKind: text("object_kind").notNull(),
956
+ sourceId: text("source_id").notNull(),
957
+ objectBucket: text("object_bucket").notNull(),
958
+ operationId: uuid("operation_id").notNull(),
959
+ deletedAt: timestamp("deleted_at", { withTimezone: true }).notNull().defaultNow(),
960
+ },
961
+ (table) => ({
962
+ identity: primaryKey({
963
+ columns: [
964
+ table.accountId,
965
+ table.membershipId,
966
+ table.objectKind,
967
+ table.sourceId,
968
+ table.objectBucket,
969
+ ],
769
970
  }),
770
- // Set only by a direct Better Auth cookie connection/reconnection. Legacy,
771
- // configured, delegated, API-key, and agent-created rows remain view-only.
772
- connectedBySubjectId: text("connected_by_subject_id"),
773
- selectionCount: integer("selection_count").notNull().default(0),
774
- lastSelectedAt: timestamp("last_selected_at", { withTimezone: true }),
971
+ obligation: foreignKey({
972
+ name: "organization_retention_object_deletions_obligation_fk",
973
+ columns: [
974
+ table.accountId,
975
+ table.membershipId,
976
+ table.objectKind,
977
+ table.sourceId,
978
+ table.objectBucket,
979
+ ],
980
+ foreignColumns: [
981
+ organizationUserRetentionObjectObligations.accountId,
982
+ organizationUserRetentionObjectObligations.membershipId,
983
+ organizationUserRetentionObjectObligations.objectKind,
984
+ organizationUserRetentionObjectObligations.sourceId,
985
+ organizationUserRetentionObjectObligations.objectBucket,
986
+ ],
987
+ }).onDelete("restrict"),
988
+ }),
989
+ );
990
+
991
+ export const organizationUserRetentionDeletionEvents = pgTable(
992
+ "organization_user_retention_deletion_events",
993
+ {
994
+ id: uuid("id").primaryKey().defaultRandom(),
995
+ accountId: uuid("account_id").notNull(),
996
+ membershipId: uuid("membership_id").notNull(),
997
+ operationId: uuid("operation_id").notNull(),
998
+ kind: text("kind").notNull(),
999
+ reasonCode: text("reason_code"),
1000
+ result: jsonb("result").$type<Record<string, unknown>>().notNull(),
1001
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
1002
+ },
1003
+ (table) => ({
1004
+ operationKind: uniqueIndex("organization_user_retention_deletion_events_operation_kind_uq").on(
1005
+ table.accountId,
1006
+ table.operationId,
1007
+ table.kind,
1008
+ ),
1009
+ membershipCreated: index("organization_retention_events_member_created_idx").on(
1010
+ table.accountId,
1011
+ table.membershipId,
1012
+ table.createdAt,
1013
+ table.id,
1014
+ ),
1015
+ membership: foreignKey({
1016
+ name: "organization_user_retention_deletion_events_membership_fk",
1017
+ columns: [table.membershipId, table.accountId],
1018
+ foreignColumns: [organizationMemberships.id, organizationMemberships.accountId],
1019
+ }).onDelete("restrict"),
1020
+ kindValid: check(
1021
+ "organization_user_retention_deletion_events_kind_check",
1022
+ sql`${table.kind} in ('claimed', 'failed', 'completed')`,
1023
+ ),
1024
+ reasonValid: check(
1025
+ "organization_user_retention_deletion_events_reason_check",
1026
+ sql`${table.reasonCode} is null or ${table.reasonCode} ~ '^[a-z0-9_]{1,64}$'`,
1027
+ ),
1028
+ }),
1029
+ );
1030
+
1031
+ export const organizationUserResourceAuthorities = pgTable(
1032
+ "organization_user_resource_authorities",
1033
+ {
1034
+ id: uuid("id").primaryKey().defaultRandom(),
1035
+ accountId: uuid("account_id")
1036
+ .notNull()
1037
+ .references(() => managedAccounts.id, { onDelete: "cascade" }),
1038
+ organizationMembershipId: uuid("organization_membership_id").notNull(),
1039
+ resourceKind: text("resource_kind").notNull(),
1040
+ resourceId: uuid("resource_id").notNull(),
1041
+ // Provenance only. The migration owns a composite same-organization FK
1042
+ // whose delete action clears only this column, never account_id.
1043
+ originWorkspaceId: uuid("origin_workspace_id"),
1044
+ generation: bigint("generation", { mode: "number" }).notNull().default(1),
1045
+ status: text("status").notNull().default("active"),
1046
+ revokedAt: timestamp("revoked_at", { withTimezone: true }),
775
1047
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
776
1048
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
777
1049
  },
778
1050
  (table) => ({
779
- // REPLACES codex_subscription_credentials_workspace_idx (the one-per-workspace cap).
780
- // One row per (workspace, ChatGPT account). Partial WHERE chatgpt_account_id IS NOT NULL
781
- // so degenerate null-account rows can't collide; the device-grant connect path always
782
- // populates chatgpt_account_id.
783
- wsAccount: uniqueIndex("codex_subscription_credentials_ws_account_idx")
784
- .on(table.workspaceId, table.chatgptAccountId)
785
- .where(sql`${table.chatgptAccountId} is not null`),
786
- workspace: index("codex_subscription_credentials_workspace_lookup_idx").on(table.workspaceId),
787
- // Composite identity is the defense-in-depth FK target for workspace-local
788
- // lease references in migration 0053.
789
- workspaceIdentity: uniqueIndex("codex_subscription_credentials_workspace_id_idx").on(
790
- table.workspaceId,
1051
+ accountIdentity: uniqueIndex("organization_user_resource_authorities_id_account_idx").on(
791
1052
  table.id,
1053
+ table.accountId,
1054
+ ),
1055
+ accountMembershipIdentity: uniqueIndex(
1056
+ "organization_user_resource_authorities_id_account_membership_idx",
1057
+ ).on(table.id, table.accountId, table.organizationMembershipId),
1058
+ resourceIdentity: uniqueIndex(
1059
+ "organization_user_resource_authorities_resource_identity_idx",
1060
+ ).on(table.accountId, table.organizationMembershipId, table.resourceKind, table.resourceId),
1061
+ membership: foreignKey({
1062
+ name: "organization_user_resource_authorities_membership_fk",
1063
+ columns: [table.organizationMembershipId, table.accountId],
1064
+ foreignColumns: [organizationMemberships.id, organizationMemberships.accountId],
1065
+ }).onDelete("restrict"),
1066
+ resourceKindValid: check(
1067
+ "organization_user_resource_authorities_kind_check",
1068
+ sql`${table.resourceKind} = lower(btrim(${table.resourceKind}))
1069
+ and length(${table.resourceKind}) between 1 and 64
1070
+ and ${table.resourceKind} ~ '^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$'`,
1071
+ ),
1072
+ generationValid: check(
1073
+ "organization_user_resource_authorities_generation_check",
1074
+ sql`${table.generation} > 0`,
1075
+ ),
1076
+ statusValid: check(
1077
+ "organization_user_resource_authorities_status_check",
1078
+ sql`${table.status} in ('active', 'retained', 'revoked')`,
1079
+ ),
1080
+ revocationValid: check(
1081
+ "organization_user_resource_authorities_revocation_check",
1082
+ sql`(${table.status} = 'revoked' and ${table.revokedAt} is not null)
1083
+ or (${table.status} <> 'revoked' and ${table.revokedAt} is null)`,
1084
+ ),
1085
+ }),
1086
+ );
1087
+
1088
+ export const apiKeys = pgTable(
1089
+ "api_keys",
1090
+ {
1091
+ id: uuid("id").primaryKey().defaultRandom(),
1092
+ accountId: uuid("account_id")
1093
+ .notNull()
1094
+ .references(() => managedAccounts.id, { onDelete: "cascade" }),
1095
+ workspaceId: uuid("workspace_id").references(() => workspaces.id, {
1096
+ onDelete: "cascade",
1097
+ }),
1098
+ name: text("name").notNull(),
1099
+ description: text("description"),
1100
+ prefix: text("prefix").notNull(),
1101
+ keyHash: text("key_hash").notNull(),
1102
+ permissions: jsonb("permissions").$type<string[]>().notNull().default([]),
1103
+ expiresAt: timestamp("expires_at", { withTimezone: true }),
1104
+ revokedAt: timestamp("revoked_at", { withTimezone: true }),
1105
+ lastUsedAt: timestamp("last_used_at", { withTimezone: true }),
1106
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
1107
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
1108
+ },
1109
+ (table) => ({
1110
+ prefix: index("api_keys_prefix_idx").on(table.prefix),
1111
+ hash: uniqueIndex("api_keys_key_hash_idx").on(table.keyHash),
1112
+ account: index("api_keys_account_idx").on(table.accountId),
1113
+ workspace: index("api_keys_workspace_idx").on(table.workspaceId),
1114
+ descriptionValid: check(
1115
+ "api_keys_description_check",
1116
+ sql`${table.description} is null or length(${table.description}) between 1 and 500`,
1117
+ ),
1118
+ }),
1119
+ );
1120
+
1121
+ export const workspaceVariableSets = pgTable(
1122
+ "workspace_variable_sets",
1123
+ {
1124
+ id: uuid("id").primaryKey().defaultRandom(),
1125
+ accountId: uuid("account_id")
1126
+ .notNull()
1127
+ .references(() => managedAccounts.id, { onDelete: "cascade" }),
1128
+ workspaceId: uuid("workspace_id")
1129
+ .notNull()
1130
+ .references(() => workspaces.id, { onDelete: "cascade" }),
1131
+ name: text("name").notNull(),
1132
+ description: text("description"),
1133
+ // Omitted creation remains workspace-owned. User ownership is anchored to
1134
+ // the common organization-user resource authority; organization ownership
1135
+ // has no member owner and is administered through account authority.
1136
+ authorityScope: text("authority_scope").notNull().default("workspace"),
1137
+ authorityId: uuid("authority_id"),
1138
+ ownerOrganizationMembershipId: uuid("owner_organization_membership_id"),
1139
+ originWorkspaceId: uuid("origin_workspace_id"),
1140
+ generation: bigint("generation", { mode: "number" }).notNull().default(1),
1141
+ status: text("status").notNull().default("active"),
1142
+ revokedAt: timestamp("revoked_at", { withTimezone: true }),
1143
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
1144
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
1145
+ },
1146
+ (table) => ({
1147
+ workspaceName: uniqueIndex("workspace_variable_sets_workspace_name_active_idx")
1148
+ .on(table.workspaceId, table.name)
1149
+ .where(sql`${table.authorityScope} = 'workspace' and ${table.status} = 'active'`),
1150
+ workspaceCreated: index("workspace_variable_sets_workspace_created_idx").on(
1151
+ table.workspaceId,
1152
+ table.createdAt,
1153
+ ),
1154
+ authorityShape: check(
1155
+ "workspace_variable_sets_authority_shape_check",
1156
+ sql`(
1157
+ ${table.authorityScope} in ('organization', 'workspace')
1158
+ and ${table.authorityId} is null
1159
+ and ${table.ownerOrganizationMembershipId} is null
1160
+ ) or (
1161
+ ${table.authorityScope} = 'user'
1162
+ and ${table.authorityId} is not null
1163
+ and ${table.ownerOrganizationMembershipId} is not null
1164
+ )`,
1165
+ ),
1166
+ authorityScopeValid: check(
1167
+ "workspace_variable_sets_authority_scope_check",
1168
+ sql`${table.authorityScope} in ('organization', 'workspace', 'user')`,
1169
+ ),
1170
+ generationValid: check(
1171
+ "workspace_variable_sets_generation_check",
1172
+ sql`${table.generation} > 0`,
1173
+ ),
1174
+ statusValid: check(
1175
+ "workspace_variable_sets_status_check",
1176
+ sql`${table.status} in ('active', 'revoked')`,
1177
+ ),
1178
+ revocationValid: check(
1179
+ "workspace_variable_sets_revocation_check",
1180
+ sql`(${table.status} = 'revoked' and ${table.revokedAt} is not null)
1181
+ or (${table.status} = 'active' and ${table.revokedAt} is null)`,
1182
+ ),
1183
+ authority: foreignKey({
1184
+ name: "workspace_variable_sets_authority_fk",
1185
+ columns: [table.authorityId, table.accountId, table.ownerOrganizationMembershipId],
1186
+ foreignColumns: [
1187
+ organizationUserResourceAuthorities.id,
1188
+ organizationUserResourceAuthorities.accountId,
1189
+ organizationUserResourceAuthorities.organizationMembershipId,
1190
+ ],
1191
+ }).onDelete("restrict"),
1192
+ // PostgreSQL's column-subset SET NULL action preserves account_id; Drizzle
1193
+ // cannot represent that action, so migration 0230 owns the origin FK.
1194
+ }),
1195
+ );
1196
+
1197
+ export const workspaceVariableSetVariables = pgTable(
1198
+ "workspace_variable_set_variables",
1199
+ {
1200
+ id: uuid("id").primaryKey().defaultRandom(),
1201
+ accountId: uuid("account_id")
1202
+ .notNull()
1203
+ .references(() => managedAccounts.id, { onDelete: "cascade" }),
1204
+ workspaceId: uuid("workspace_id")
1205
+ .notNull()
1206
+ .references(() => workspaces.id, { onDelete: "cascade" }),
1207
+ variableSetId: uuid("variable_set_id")
1208
+ .notNull()
1209
+ .references(() => workspaceVariableSets.id, { onDelete: "cascade" }),
1210
+ name: text("name").notNull(),
1211
+ // Format: v1:<base64 iv>:<base64 ciphertext||gcm-tag>. Never returned by any API.
1212
+ valueEncrypted: text("value_encrypted").notNull(),
1213
+ version: integer("version").notNull().default(1),
1214
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
1215
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
1216
+ },
1217
+ (table) => ({
1218
+ variableSetName: uniqueIndex("workspace_variable_set_variables_env_name_idx").on(
1219
+ table.workspaceId,
1220
+ table.variableSetId,
1221
+ table.name,
1222
+ ),
1223
+ variableSet: index("workspace_variable_set_variables_workspace_env_idx").on(
1224
+ table.workspaceId,
1225
+ table.variableSetId,
1226
+ ),
1227
+ }),
1228
+ );
1229
+
1230
+ // Per-workspace ChatGPT/Codex subscription credential. One row per connected
1231
+ // ChatGPT account within a workspace.
1232
+ // access/refresh/id tokens live INSIDE credential_encrypted (v1 AES-256-GCM,
1233
+ // same envelope as workspace_variable_set_variables); the other columns are
1234
+ // plaintext metadata (header value + UI). RLS-isolated per workspace.
1235
+ export const codexSubscriptionCredentials = pgTable(
1236
+ "codex_subscription_credentials",
1237
+ {
1238
+ id: uuid("id").primaryKey().defaultRandom(),
1239
+ accountId: uuid("account_id")
1240
+ .notNull()
1241
+ .references(() => managedAccounts.id, { onDelete: "cascade" }),
1242
+ workspaceId: uuid("workspace_id")
1243
+ .notNull()
1244
+ .references(() => workspaces.id, { onDelete: "cascade" }),
1245
+ // Format: v1:<base64 iv>:<base64 ciphertext||gcm-tag>. JSON {access_token, refresh_token, id_token}. Never returned by any API.
1246
+ credentialEncrypted: text("credential_encrypted").notNull(),
1247
+ chatgptAccountId: text("chatgpt_account_id"), // plaintext ChatGPT-Account-ID header value (non-secret)
1248
+ scopes: text("scopes"), // space-delimited, as granted
1249
+ planType: text("plan_type"),
1250
+ isFedramp: boolean("is_fedramp").notNull().default(false),
1251
+ expiresAt: timestamp("expires_at", { withTimezone: true }), // derived from access-token JWT exp
1252
+ lastRefreshAt: timestamp("last_refresh_at", { withTimezone: true }),
1253
+ status: text("status").notNull().default("active"), // active | needs_relogin | error
1254
+ lastError: text("last_error"),
1255
+ version: integer("version").notNull().default(1),
1256
+ label: text("label"), // user-chosen nickname; null ⇒ derive from email/plan/account
1257
+ accountEmail: text("account_email"), // email from the id_token (user's own email; non-secret)
1258
+ // P2 usage cache (plaintext metadata; NEVER a token). Snapshotted from
1259
+ // GET /wham/usage; drives the quota bars + the cache TTL. primary = 5h window
1260
+ // (limit_window_seconds 18000), secondary = weekly (604800).
1261
+ primaryUsedPercent: integer("primary_used_percent"),
1262
+ primaryResetAt: timestamp("primary_reset_at", { withTimezone: true }),
1263
+ secondaryUsedPercent: integer("secondary_used_percent"),
1264
+ secondaryResetAt: timestamp("secondary_reset_at", { withTimezone: true }),
1265
+ usageCheckedAt: timestamp("usage_checked_at", { withTimezone: true }), // snapshot freshness → cache TTL clock
1266
+ // P3 rotation cooldown (plaintext metadata; NEVER a token). Set when this account hit its
1267
+ // usage cap on a rotation turn; the rotation engine treats `exhausted_until > now()` as
1268
+ // capped/skip so it isn't immediately re-picked. Self-clears via the now() comparison.
1269
+ exhaustedUntil: timestamp("exhausted_until", { withTimezone: true }),
1270
+ // Workspace-local, server-held fairness cursor. Provider usage headers are
1271
+ // capacity hints, never the sole allocator: live lease count is ranked first
1272
+ // and this cursor deterministically breaks equal-load/equal-capacity ties.
1273
+ // This flag controls NEW automatic allocations only. Credential health,
1274
+ // refresh, encrypted material, and an already-leased in-flight turn are
1275
+ // intentionally independent. account eligibility policy owns toggle OCC/audit and product UI.
1276
+ allocatorEnabled: boolean("allocator_enabled").notNull().default(true),
1277
+ // Independent OCC/audit sequence for the allocator toggle. Token refresh
1278
+ // continues to own `version`; quota/cache writes own neither counter.
1279
+ allocatorVersion: integer("allocator_version").notNull().default(1),
1280
+ allocatorUpdatedBySubjectId: text("allocator_updated_by_subject_id"),
1281
+ allocatorUpdatedAt: timestamp("allocator_updated_at", {
1282
+ withTimezone: true,
1283
+ }),
1284
+ // Authoritative count-only summary cached from /wham/usage. Detailed rows
1285
+ // are never persisted as redemption authority; every first POST preflights
1286
+ // the provider's fresh detail endpoint.
1287
+ resetCreditAvailableCount: integer("reset_credit_available_count"),
1288
+ resetCreditsCheckedAt: timestamp("reset_credits_checked_at", {
1289
+ withTimezone: true,
1290
+ }),
1291
+ // Set only by a direct Better Auth cookie connection/reconnection. Legacy,
1292
+ // configured, delegated, API-key, and agent-created rows remain view-only.
1293
+ connectedBySubjectId: text("connected_by_subject_id"),
1294
+ selectionCount: integer("selection_count").notNull().default(0),
1295
+ lastSelectedAt: timestamp("last_selected_at", { withTimezone: true }),
1296
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
1297
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
1298
+ },
1299
+ (table) => ({
1300
+ // REPLACES codex_subscription_credentials_workspace_idx (the one-per-workspace cap).
1301
+ // One row per (workspace, ChatGPT account). Partial WHERE chatgpt_account_id IS NOT NULL
1302
+ // so degenerate null-account rows can't collide; the device-grant connect path always
1303
+ // populates chatgpt_account_id.
1304
+ wsAccount: uniqueIndex("codex_subscription_credentials_ws_account_idx")
1305
+ .on(table.workspaceId, table.chatgptAccountId)
1306
+ .where(sql`${table.chatgptAccountId} is not null`),
1307
+ workspace: index("codex_subscription_credentials_workspace_lookup_idx").on(table.workspaceId),
1308
+ // Composite identity is the defense-in-depth FK target for workspace-local
1309
+ // lease references in migration 0053.
1310
+ workspaceIdentity: uniqueIndex("codex_subscription_credentials_workspace_id_idx").on(
1311
+ table.workspaceId,
1312
+ table.id,
1313
+ ),
1314
+ workspaceAccountIdentity: uniqueIndex(
1315
+ "codex_subscription_credentials_workspace_account_id_idx",
1316
+ ).on(table.workspaceId, table.accountId, table.id),
1317
+ }),
1318
+ );
1319
+
1320
+ // Optional workspace-level credential used only for ChatGPT connected Apps.
1321
+ // Inference selection, usage, cooldown, allocator, pins, and leases never read
1322
+ // this row. A durable null row retains the OCC sequence after designation clear.
1323
+ export const codexAppsSettings = pgTable(
1324
+ "codex_apps_settings",
1325
+ {
1326
+ id: uuid("id").primaryKey().defaultRandom(),
1327
+ accountId: uuid("account_id")
1328
+ .notNull()
1329
+ .references(() => managedAccounts.id, { onDelete: "cascade" }),
1330
+ workspaceId: uuid("workspace_id")
1331
+ .notNull()
1332
+ .references(() => workspaces.id, { onDelete: "cascade" }),
1333
+ credentialId: uuid("credential_id"),
1334
+ version: integer("version").notNull().default(1),
1335
+ designatedAt: timestamp("designated_at", { withTimezone: true }),
1336
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
1337
+ },
1338
+ (table) => ({
1339
+ workspaceAccount: foreignKey({
1340
+ name: "codex_apps_settings_workspace_account_fk",
1341
+ columns: [table.workspaceId, table.accountId],
1342
+ foreignColumns: [workspaces.id, workspaces.accountId],
1343
+ }).onDelete("cascade"),
1344
+ credentialScope: foreignKey({
1345
+ name: "codex_apps_settings_credential_scope_fk",
1346
+ columns: [table.workspaceId, table.accountId, table.credentialId],
1347
+ foreignColumns: [
1348
+ codexSubscriptionCredentials.workspaceId,
1349
+ codexSubscriptionCredentials.accountId,
1350
+ codexSubscriptionCredentials.id,
1351
+ ],
1352
+ }).onDelete("cascade"),
1353
+ workspace: uniqueIndex("codex_apps_settings_workspace_idx").on(table.workspaceId),
1354
+ designationShape: check(
1355
+ "codex_apps_settings_designation_shape_chk",
1356
+ sql`${table.version} > 0 and (
1357
+ (${table.credentialId} is null and ${table.designatedAt} is null)
1358
+ or
1359
+ (${table.credentialId} is not null and ${table.designatedAt} is not null)
1360
+ )`,
1361
+ ),
1362
+ }),
1363
+ );
1364
+
1365
+ // One durable logical human redemption. `processing` means the fresh provider
1366
+ // detail preflight is still owed; `provider_started` means the POST may have
1367
+ // reached upstream and every retry must reuse upstreamIdempotencyKey without
1368
+ // requiring the credit to remain visible as available.
1369
+ export const codexResetRedemptionAttempts = pgTable(
1370
+ "codex_reset_redemption_attempts",
1371
+ {
1372
+ id: uuid("id").primaryKey(),
1373
+ accountId: uuid("account_id")
1374
+ .notNull()
1375
+ .references(() => managedAccounts.id, { onDelete: "cascade" }),
1376
+ workspaceId: uuid("workspace_id")
1377
+ .notNull()
1378
+ .references(() => workspaces.id, { onDelete: "cascade" }),
1379
+ credentialId: uuid("credential_id").notNull(),
1380
+ subjectId: text("subject_id").notNull(),
1381
+ browserSessionHash: text("browser_session_hash").notNull(),
1382
+ creditId: text("credit_id").notNull(),
1383
+ upstreamIdempotencyKey: uuid("upstream_idempotency_key").notNull().defaultRandom(),
1384
+ status: text("status").notNull().default("processing"),
1385
+ outcome: text("outcome"),
1386
+ claimHolderId: uuid("claim_holder_id"),
1387
+ claimExpiresAt: timestamp("claim_expires_at", { withTimezone: true }),
1388
+ confirmationExpiresAt: timestamp("confirmation_expires_at", {
1389
+ withTimezone: true,
1390
+ }).notNull(),
1391
+ providerStartedAt: timestamp("provider_started_at", { withTimezone: true }),
1392
+ completedAt: timestamp("completed_at", { withTimezone: true }),
1393
+ lastFailureKind: text("last_failure_kind"),
1394
+ retryCount: integer("retry_count").notNull().default(0),
1395
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
1396
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
1397
+ },
1398
+ (table) => ({
1399
+ workspaceAccount: foreignKey({
1400
+ name: "codex_reset_redemption_workspace_account_fk",
1401
+ columns: [table.workspaceId, table.accountId],
1402
+ foreignColumns: [workspaces.id, workspaces.accountId],
1403
+ }).onDelete("cascade"),
1404
+ upstreamKey: uniqueIndex("codex_reset_redemption_upstream_key_idx").on(
1405
+ table.upstreamIdempotencyKey,
1406
+ ),
1407
+ credentialCredit: uniqueIndex("codex_reset_redemption_credential_credit_idx")
1408
+ .on(table.workspaceId, table.credentialId, table.creditId)
1409
+ .where(
1410
+ sql`${table.status} <> 'completed' or ${table.outcome} in ('reset', 'alreadyRedeemed')`,
1411
+ ),
1412
+ workspaceCredential: index("codex_reset_redemption_workspace_credential_idx").on(
1413
+ table.workspaceId,
1414
+ table.credentialId,
1415
+ table.createdAt,
1416
+ ),
1417
+ claimExpiry: index("codex_reset_redemption_claim_expiry_idx")
1418
+ .on(table.claimExpiresAt)
1419
+ .where(sql`${table.status} <> 'completed'`),
1420
+ statusValid: check(
1421
+ "codex_reset_redemption_status_check",
1422
+ sql`${table.status} in ('processing', 'provider_started', 'completed')`,
1423
+ ),
1424
+ outcomeValid: check(
1425
+ "codex_reset_redemption_outcome_check",
1426
+ sql`${table.outcome} is null or ${table.outcome} in ('reset', 'nothingToReset', 'noCredit', 'alreadyRedeemed')`,
1427
+ ),
1428
+ completionConsistent: check(
1429
+ "codex_reset_redemption_completed_check",
1430
+ sql`(${table.status} = 'completed') = (${table.outcome} is not null and ${table.completedAt} is not null)`,
1431
+ ),
1432
+ retryCountValid: check(
1433
+ "codex_reset_redemption_retry_count_check",
1434
+ sql`${table.retryCount} >= 0`,
1435
+ ),
1436
+ humanSubjectValid: check(
1437
+ "codex_reset_redemption_human_subject_check",
1438
+ sql`${table.subjectId} like 'user:_%'`,
1439
+ ),
1440
+ }),
1441
+ );
1442
+
1443
+ // Generic external-service credential spine. credential_encrypted is the ONLY
1444
+ // secret-bearing column; normal API reads use metadata-only helpers below the DB
1445
+ // layer. Runtime token material is decrypted only by the broker accessor.
1446
+ export const connections = pgTable(
1447
+ "connections",
1448
+ {
1449
+ id: uuid("id").primaryKey().defaultRandom(),
1450
+ accountId: uuid("account_id")
1451
+ .notNull()
1452
+ .references(() => managedAccounts.id, { onDelete: "cascade" }),
1453
+ workspaceId: uuid("workspace_id")
1454
+ .notNull()
1455
+ .references(() => workspaces.id, { onDelete: "cascade" }),
1456
+ subjectId: text("subject_id"),
1457
+ providerDomain: text("provider_domain").notNull(),
1458
+ kind: text("kind").notNull(),
1459
+ status: text("status").notNull().default("active"),
1460
+ credentialEncrypted: text("credential_encrypted").notNull(),
1461
+ grantedScopes: jsonb("granted_scopes").$type<string[]>().notNull().default([]),
1462
+ expiresAt: timestamp("expires_at", { withTimezone: true }),
1463
+ lastRefreshAt: timestamp("last_refresh_at", { withTimezone: true }),
1464
+ lastUsedAt: timestamp("last_used_at", { withTimezone: true }),
1465
+ lastError: text("last_error"),
1466
+ version: integer("version").notNull().default(1),
1467
+ // Server-owned proof that the dedicated install route verified the exact
1468
+ // credential at this connection version. Generic/legacy writers cannot set
1469
+ // these columns, and migration 0131 clears them when protected fields change
1470
+ // without a fresh verification in the same statement.
1471
+ verifiedInstallAt: timestamp("verified_install_at", { withTimezone: true }),
1472
+ verifiedInstallVersion: integer("verified_install_version"),
1473
+ metadata: jsonb("metadata").$type<Record<string, unknown>>().notNull().default({}),
1474
+ createdBySubjectId: text("created_by_subject_id"),
1475
+ updatedBySubjectId: text("updated_by_subject_id"),
1476
+ // Explicit execution authority. Workspace rows have no member authority;
1477
+ // user rows bind one immutable organization membership and common resource
1478
+ // authority. `legacy_user` preserves pre-tenancy personal rows without
1479
+ // inventing an owner membership; it is ineligible for delegated use until
1480
+ // the migration cutover binds it. Public metadata projects only the opaque
1481
+ // authority id to the exact subject owner; membership, scope, origin, and
1482
+ // generation remain internal.
1483
+ authorityScope: text("authority_scope").notNull().default("workspace"),
1484
+ authorityId: uuid("authority_id"),
1485
+ ownerOrganizationMembershipId: uuid("owner_organization_membership_id"),
1486
+ originWorkspaceId: uuid("origin_workspace_id"),
1487
+ // Separate from credential version: refresh may rotate token material
1488
+ // without invalidating already accepted work, while reconnect/disconnect
1489
+ // and identity/status transitions advance this generation.
1490
+ authorityGeneration: bigint("authority_generation", { mode: "number" }).notNull().default(1),
1491
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
1492
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
1493
+ },
1494
+ (table) => ({
1495
+ workspaceProviderStatus: index("connections_workspace_provider_status_idx").on(
1496
+ table.workspaceId,
1497
+ table.providerDomain,
1498
+ table.status,
1499
+ ),
1500
+ workspaceSubjectProvider: index("connections_workspace_subject_provider_idx").on(
1501
+ table.workspaceId,
1502
+ table.subjectId,
1503
+ table.providerDomain,
1504
+ ),
1505
+ workspaceKind: index("connections_workspace_kind_idx").on(table.workspaceId, table.kind),
1506
+ workspaceExpires: index("connections_workspace_expires_idx").on(
1507
+ table.workspaceId,
1508
+ table.expiresAt,
1509
+ ),
1510
+ authorityIdentity: uniqueIndex("connections_authority_identity_uq")
1511
+ .on(table.accountId, table.authorityId)
1512
+ .where(sql`${table.authorityId} is not null`),
1513
+ ownerAuthority: index("connections_owner_authority_idx")
1514
+ .on(
1515
+ table.accountId,
1516
+ table.ownerOrganizationMembershipId,
1517
+ table.status,
1518
+ table.updatedAt,
1519
+ table.id,
1520
+ )
1521
+ .where(sql`${table.authorityScope} = 'user'`),
1522
+ authorityScopeValid: check(
1523
+ "connections_authority_scope_check",
1524
+ sql`${table.authorityScope} in ('workspace', 'user', 'legacy_user')`,
1525
+ ),
1526
+ authorityShapeValid: check(
1527
+ "connections_authority_shape_check",
1528
+ sql`(
1529
+ ${table.authorityScope} = 'workspace'
1530
+ and ${table.subjectId} is null
1531
+ and ${table.authorityId} is null
1532
+ and ${table.ownerOrganizationMembershipId} is null
1533
+ and ${table.originWorkspaceId} = ${table.workspaceId}
1534
+ ) or (
1535
+ ${table.authorityScope} = 'user'
1536
+ and ${table.subjectId} is not null
1537
+ and ${table.authorityId} is not null
1538
+ and ${table.ownerOrganizationMembershipId} is not null
1539
+ and ${table.originWorkspaceId} is not null
1540
+ ) or (
1541
+ ${table.authorityScope} = 'legacy_user'
1542
+ and ${table.subjectId} is not null
1543
+ and ${table.authorityId} is null
1544
+ and ${table.ownerOrganizationMembershipId} is null
1545
+ and ${table.originWorkspaceId} is not null
1546
+ )`,
1547
+ ),
1548
+ authorityGenerationValid: check(
1549
+ "connections_authority_generation_check",
1550
+ sql`${table.authorityGeneration} > 0`,
1551
+ ),
1552
+ authority: foreignKey({
1553
+ name: "connections_authority_fk",
1554
+ columns: [table.authorityId, table.accountId, table.ownerOrganizationMembershipId],
1555
+ foreignColumns: [
1556
+ organizationUserResourceAuthorities.id,
1557
+ organizationUserResourceAuthorities.accountId,
1558
+ organizationUserResourceAuthorities.organizationMembershipId,
1559
+ ],
1560
+ }).onDelete("restrict"),
1561
+ // Migration 0255 owns the same-organization origin FK because Drizzle
1562
+ // cannot express PostgreSQL's column-subset SET NULL action.
1563
+ }),
1564
+ );
1565
+
1566
+ /**
1567
+ * Monotonic authority head for one personal GitHub connection's selected
1568
+ * repository set. The head is separate from the selected rows so replacing
1569
+ * the set with an empty array still advances durable authority.
1570
+ */
1571
+ export const personalGitHubRepositorySelectionHeads = pgTable(
1572
+ "personal_github_repository_selection_heads",
1573
+ {
1574
+ connectionId: uuid("connection_id")
1575
+ .primaryKey()
1576
+ .references(() => connections.id, { onDelete: "cascade" }),
1577
+ accountId: uuid("account_id")
1578
+ .notNull()
1579
+ .references(() => managedAccounts.id, { onDelete: "cascade" }),
1580
+ originWorkspaceId: uuid("origin_workspace_id")
1581
+ .notNull()
1582
+ .references(() => workspaces.id, { onDelete: "cascade" }),
1583
+ ownerSubjectId: text("owner_subject_id").notNull(),
1584
+ providerPrincipalId: text("provider_principal_id").notNull(),
1585
+ credentialBindingId: uuid("credential_binding_id").notNull(),
1586
+ connectionAuthorityGeneration: bigint("connection_authority_generation", {
1587
+ mode: "number",
1588
+ }).notNull(),
1589
+ generation: bigint("generation", { mode: "number" }).notNull().default(1),
1590
+ updatedBySubjectId: text("updated_by_subject_id").notNull(),
1591
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
1592
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
1593
+ },
1594
+ (table) => ({
1595
+ tenantConnection: uniqueIndex("personal_github_repository_selection_heads_tenant_uq").on(
1596
+ table.accountId,
1597
+ table.originWorkspaceId,
1598
+ table.ownerSubjectId,
1599
+ table.connectionId,
1600
+ ),
1601
+ owner: index("personal_github_repository_selection_heads_owner_idx").on(
1602
+ table.accountId,
1603
+ table.ownerSubjectId,
1604
+ table.connectionId,
1605
+ ),
1606
+ generationValid: check(
1607
+ "personal_github_repository_selection_heads_generation_chk",
1608
+ sql`${table.connectionAuthorityGeneration} > 0 and ${table.generation} > 0`,
1609
+ ),
1610
+ }),
1611
+ );
1612
+
1613
+ /** Only explicitly selected repositories are persisted; discovery stays live. */
1614
+ export const personalGitHubRepositorySelections = pgTable(
1615
+ "personal_github_repository_selections",
1616
+ {
1617
+ connectionId: uuid("connection_id").notNull(),
1618
+ repositoryId: bigint("repository_id", { mode: "bigint" }).notNull(),
1619
+ accountId: uuid("account_id")
1620
+ .notNull()
1621
+ .references(() => managedAccounts.id, { onDelete: "cascade" }),
1622
+ originWorkspaceId: uuid("origin_workspace_id")
1623
+ .notNull()
1624
+ .references(() => workspaces.id, { onDelete: "cascade" }),
1625
+ ownerSubjectId: text("owner_subject_id").notNull(),
1626
+ canonicalFullName: text("canonical_full_name").notNull(),
1627
+ canonicalHttpsUri: text("canonical_https_uri").notNull(),
1628
+ defaultBranch: text("default_branch").notNull(),
1629
+ visibility: text("visibility").notNull(),
1630
+ private: boolean("private").notNull(),
1631
+ archived: boolean("archived").notNull(),
1632
+ disabled: boolean("disabled").notNull(),
1633
+ permissions: jsonb("permissions").$type<Record<string, boolean>>().notNull(),
1634
+ selectedAccess: text("selected_access").notNull(),
1635
+ selectionGeneration: bigint("selection_generation", {
1636
+ mode: "number",
1637
+ }).notNull(),
1638
+ selectedBySubjectId: text("selected_by_subject_id").notNull(),
1639
+ selectedAt: timestamp("selected_at", { withTimezone: true }).notNull().defaultNow(),
1640
+ verifiedAt: timestamp("verified_at", { withTimezone: true }).notNull().defaultNow(),
1641
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
1642
+ },
1643
+ (table) => ({
1644
+ identity: primaryKey({ columns: [table.connectionId, table.repositoryId] }),
1645
+ head: foreignKey({
1646
+ name: "personal_github_repository_selections_head_fk",
1647
+ columns: [table.accountId, table.originWorkspaceId, table.ownerSubjectId, table.connectionId],
1648
+ foreignColumns: [
1649
+ personalGitHubRepositorySelectionHeads.accountId,
1650
+ personalGitHubRepositorySelectionHeads.originWorkspaceId,
1651
+ personalGitHubRepositorySelectionHeads.ownerSubjectId,
1652
+ personalGitHubRepositorySelectionHeads.connectionId,
1653
+ ],
1654
+ }).onDelete("cascade"),
1655
+ tenant: index("personal_github_repository_selections_tenant_idx").on(
1656
+ table.accountId,
1657
+ table.originWorkspaceId,
1658
+ table.connectionId,
1659
+ ),
1660
+ shapeValid: check(
1661
+ "personal_github_repository_selections_shape_chk",
1662
+ sql`${table.repositoryId} > 0
1663
+ and ${table.selectionGeneration} > 0
1664
+ and ${table.selectedAccess} in ('read', 'write')
1665
+ and ${table.visibility} in ('public', 'private', 'internal')
1666
+ and ${table.private} = (${table.visibility} = 'private')
1667
+ and jsonb_typeof(${table.permissions}) = 'object'
1668
+ and ${table.permissions} ?& array['pull','push','admin','maintain','triage']
1669
+ and ${table.permissions} - array['pull','push','admin','maintain','triage']::text[] = '{}'::jsonb
1670
+ and jsonb_typeof(${table.permissions}->'pull') = 'boolean'
1671
+ and jsonb_typeof(${table.permissions}->'push') = 'boolean'
1672
+ and jsonb_typeof(${table.permissions}->'admin') = 'boolean'
1673
+ and jsonb_typeof(${table.permissions}->'maintain') = 'boolean'
1674
+ and jsonb_typeof(${table.permissions}->'triage') = 'boolean'
1675
+ and (${table.permissions}->'pull' = 'true'::jsonb
1676
+ or ${table.permissions}->'push' = 'true'::jsonb
1677
+ or ${table.permissions}->'admin' = 'true'::jsonb
1678
+ or ${table.permissions}->'maintain' = 'true'::jsonb
1679
+ or ${table.permissions}->'triage' = 'true'::jsonb)
1680
+ and (${table.selectedAccess} <> 'write' or (
1681
+ not ${table.archived} and not ${table.disabled}
1682
+ and (${table.permissions}->'push' = 'true'::jsonb
1683
+ or ${table.permissions}->'admin' = 'true'::jsonb
1684
+ or ${table.permissions}->'maintain' = 'true'::jsonb)
1685
+ ))
1686
+ and octet_length(${table.ownerSubjectId}) between 1 and 512
1687
+ and ${table.canonicalFullName} ~ '^[A-Za-z0-9][A-Za-z0-9-]{0,38}/[A-Za-z0-9_.-]{1,100}$'
1688
+ and octet_length(${table.canonicalFullName}) between 3 and 140
1689
+ and octet_length(${table.defaultBranch}) between 1 and 255`,
1690
+ ),
1691
+ }),
1692
+ );
1693
+
1694
+ /** Bounded idempotency receipt for an owner-issued full selection replacement. */
1695
+ export const personalGitHubRepositorySelectionOperations = pgTable(
1696
+ "personal_github_repository_selection_operations",
1697
+ {
1698
+ connectionId: uuid("connection_id").notNull(),
1699
+ idempotencyKey: text("idempotency_key").notNull(),
1700
+ accountId: uuid("account_id").notNull(),
1701
+ originWorkspaceId: uuid("origin_workspace_id").notNull(),
1702
+ ownerSubjectId: text("owner_subject_id").notNull(),
1703
+ requestDigest: bytea("request_digest").notNull(),
1704
+ resultingGeneration: bigint("resulting_generation", {
1705
+ mode: "number",
1706
+ }).notNull(),
1707
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
1708
+ },
1709
+ (table) => ({
1710
+ identity: primaryKey({
1711
+ columns: [table.connectionId, table.idempotencyKey],
1712
+ }),
1713
+ head: foreignKey({
1714
+ name: "personal_github_repository_selection_operations_head_fk",
1715
+ columns: [table.accountId, table.originWorkspaceId, table.ownerSubjectId, table.connectionId],
1716
+ foreignColumns: [
1717
+ personalGitHubRepositorySelectionHeads.accountId,
1718
+ personalGitHubRepositorySelectionHeads.originWorkspaceId,
1719
+ personalGitHubRepositorySelectionHeads.ownerSubjectId,
1720
+ personalGitHubRepositorySelectionHeads.connectionId,
1721
+ ],
1722
+ }).onDelete("cascade"),
1723
+ shapeValid: check(
1724
+ "personal_github_repository_selection_operations_shape_chk",
1725
+ sql`${table.resultingGeneration} > 0
1726
+ and octet_length(${table.idempotencyKey}) between 1 and 200
1727
+ and octet_length(${table.ownerSubjectId}) between 1 and 512
1728
+ and octet_length(${table.requestDigest}) = 32`,
1729
+ ),
1730
+ }),
1731
+ );
1732
+
1733
+ export const connectionUseOnceConsumptionReceipts = pgTable(
1734
+ "connection_use_once_consumption_receipts",
1735
+ {
1736
+ grantId: uuid("grant_id").primaryKey(),
1737
+ accountId: uuid("account_id")
1738
+ .notNull()
1739
+ .references(() => managedAccounts.id, { onDelete: "cascade" }),
1740
+ authorityId: uuid("authority_id").notNull(),
1741
+ authorityGeneration: bigint("authority_generation", { mode: "number" }).notNull(),
1742
+ grantGeneration: bigint("grant_generation", { mode: "number" }).notNull(),
1743
+ acceptedWorkKind: text("accepted_work_kind").notNull(),
1744
+ acceptedWorkId: uuid("accepted_work_id").notNull(),
1745
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
1746
+ },
1747
+ (table) => ({
1748
+ authority: foreignKey({
1749
+ name: "connection_once_receipts_authority_fk",
1750
+ columns: [table.authorityId, table.accountId],
1751
+ foreignColumns: [
1752
+ organizationUserResourceAuthorities.id,
1753
+ organizationUserResourceAuthorities.accountId,
1754
+ ],
1755
+ }).onDelete("cascade"),
1756
+ grant: foreignKey({
1757
+ name: "connection_once_receipts_grant_fk",
1758
+ columns: [table.grantId, table.accountId],
1759
+ foreignColumns: [organizationUserResourceGrants.id, organizationUserResourceGrants.accountId],
1760
+ }).onDelete("cascade"),
1761
+ generationValid: check(
1762
+ "connection_once_receipts_generation_check",
1763
+ sql`${table.authorityGeneration} > 0 and ${table.grantGeneration} > 0`,
1764
+ ),
1765
+ acceptedWorkKindValid: check(
1766
+ "connection_once_receipts_work_kind_check",
1767
+ sql`${table.acceptedWorkKind} in ('turn', 'scheduled_task')`,
1768
+ ),
1769
+ }),
1770
+ );
1771
+
1772
+ // One durable routing authority per installed Slack team. The active partial
1773
+ // unique index is the database fence that prevents a team from being routed to
1774
+ // two OpenGeni workspaces. Legacy ambiguous rows are retained as quarantined
1775
+ // evidence instead of deleting credentials or guessing a winner.
1776
+ export const slackInstallationBindings = pgTable(
1777
+ "slack_installation_bindings",
1778
+ {
1779
+ id: uuid("id").primaryKey().defaultRandom(),
1780
+ accountId: uuid("account_id")
1781
+ .notNull()
1782
+ .references(() => managedAccounts.id, { onDelete: "cascade" }),
1783
+ workspaceId: uuid("workspace_id")
1784
+ .notNull()
1785
+ .references(() => workspaces.id, { onDelete: "cascade" }),
1786
+ connectionId: uuid("connection_id")
1787
+ .notNull()
1788
+ .references(() => connections.id, { onDelete: "cascade" }),
1789
+ slackTeamId: text("slack_team_id").notNull(),
1790
+ slackTeamName: text("slack_team_name").notNull(),
1791
+ botId: text("bot_id").notNull(),
1792
+ botUserId: text("bot_user_id").notNull(),
1793
+ botDisplayName: text("bot_display_name").notNull(),
1794
+ state: text("state").$type<"active" | "quarantined">().notNull(),
1795
+ quarantineReason: text("quarantine_reason"),
1796
+ version: integer("version").notNull().default(1),
1797
+ createdBySubjectId: text("created_by_subject_id"),
1798
+ updatedBySubjectId: text("updated_by_subject_id"),
1799
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
1800
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
1801
+ },
1802
+ (table) => ({
1803
+ connection: uniqueIndex("slack_installation_bindings_connection_uq").on(table.connectionId),
1804
+ activeTeam: uniqueIndex("slack_installation_bindings_active_team_uq")
1805
+ .on(table.slackTeamId)
1806
+ .where(sql`${table.state} = 'active'`),
1807
+ workspaceState: index("slack_installation_bindings_workspace_state_idx").on(
1808
+ table.workspaceId,
1809
+ table.state,
1810
+ table.updatedAt,
1811
+ ),
1812
+ workspaceAccount: foreignKey({
1813
+ name: "slack_installation_bindings_workspace_account_fk",
1814
+ columns: [table.workspaceId, table.accountId],
1815
+ foreignColumns: [workspaces.id, workspaces.accountId],
1816
+ }).onDelete("cascade"),
1817
+ stateValid: check(
1818
+ "slack_installation_bindings_state_check",
1819
+ sql`${table.state} in ('active', 'quarantined')`,
1820
+ ),
1821
+ quarantineConsistent: check(
1822
+ "slack_installation_bindings_quarantine_check",
1823
+ sql`(${table.state} = 'active' and ${table.quarantineReason} is null)
1824
+ or (${table.state} = 'quarantined' and length(btrim(${table.quarantineReason})) > 0)`,
1825
+ ),
1826
+ identityBounded: check(
1827
+ "slack_installation_bindings_identity_check",
1828
+ sql`octet_length(${table.slackTeamId}) between 1 and 64
1829
+ and octet_length(${table.slackTeamName}) between 1 and 256
1830
+ and octet_length(${table.botId}) between 1 and 64
1831
+ and octet_length(${table.botUserId}) between 1 and 64
1832
+ and ${table.botDisplayName} in ('OpenGeni', 'OpenGeni Staging')`,
792
1833
  ),
793
- workspaceAccountIdentity: uniqueIndex(
794
- "codex_subscription_credentials_workspace_account_id_idx",
795
- ).on(table.workspaceId, table.accountId, table.id),
1834
+ versionPositive: check("slack_installation_bindings_version_check", sql`${table.version} > 0`),
796
1835
  }),
797
1836
  );
798
1837
 
799
- // Optional workspace-level credential used only for ChatGPT connected Apps.
800
- // Inference selection, usage, cooldown, allocator, pins, and leases never read
801
- // this row. A durable null row retains the OCC sequence after designation clear.
802
- export const codexAppsSettings = pgTable(
803
- "codex_apps_settings",
1838
+ /**
1839
+ * Per-channel Slack workspace routing within one organization.
1840
+ *
1841
+ * HOME tenancy (`accountId`/`workspaceId`) is the installation binding's own
1842
+ * workspace: it owns the bot credential every provider call is fenced on. The
1843
+ * TARGET (`targetAccountId`/`targetWorkspaceId`) is where the session, its
1844
+ * grant, and its events live. `slack_channel_routes_same_account_check` keeps
1845
+ * both sides in one organization; cross-organization routing is out of scope.
1846
+ *
1847
+ * The row is the durable ask-once memory: once a human picks in the Slack
1848
+ * picker, or an admin sets it in the web sheet, later messages in that channel
1849
+ * never ask again.
1850
+ */
1851
+ export const slackChannelRoutes = pgTable(
1852
+ "slack_channel_routes",
804
1853
  {
805
1854
  id: uuid("id").primaryKey().defaultRandom(),
806
1855
  accountId: uuid("account_id")
@@ -809,121 +1858,145 @@ export const codexAppsSettings = pgTable(
809
1858
  workspaceId: uuid("workspace_id")
810
1859
  .notNull()
811
1860
  .references(() => workspaces.id, { onDelete: "cascade" }),
812
- credentialId: uuid("credential_id"),
1861
+ connectionId: uuid("connection_id")
1862
+ .notNull()
1863
+ .references(() => connections.id, { onDelete: "cascade" }),
1864
+ slackTeamId: text("slack_team_id").notNull(),
1865
+ slackChannelId: text("slack_channel_id").notNull(),
1866
+ targetAccountId: uuid("target_account_id")
1867
+ .notNull()
1868
+ .references(() => managedAccounts.id, { onDelete: "cascade" }),
1869
+ targetWorkspaceId: uuid("target_workspace_id")
1870
+ .notNull()
1871
+ .references(() => workspaces.id, { onDelete: "cascade" }),
1872
+ decidedBySubjectId: text("decided_by_subject_id").notNull(),
1873
+ decidedBySlackUserId: text("decided_by_slack_user_id").notNull(),
1874
+ source: text("source").$type<"picker" | "admin">().notNull(),
813
1875
  version: integer("version").notNull().default(1),
814
- designatedAt: timestamp("designated_at", { withTimezone: true }),
1876
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
815
1877
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
816
1878
  },
817
1879
  (table) => ({
1880
+ channel: unique("slack_channel_routes_channel_uq").on(table.connectionId, table.slackChannelId),
818
1881
  workspaceAccount: foreignKey({
819
- name: "codex_apps_settings_workspace_account_fk",
1882
+ name: "slack_channel_routes_workspace_account_fk",
820
1883
  columns: [table.workspaceId, table.accountId],
821
1884
  foreignColumns: [workspaces.id, workspaces.accountId],
822
1885
  }).onDelete("cascade"),
823
- credentialScope: foreignKey({
824
- name: "codex_apps_settings_credential_scope_fk",
825
- columns: [table.workspaceId, table.accountId, table.credentialId],
826
- foreignColumns: [
827
- codexSubscriptionCredentials.workspaceId,
828
- codexSubscriptionCredentials.accountId,
829
- codexSubscriptionCredentials.id,
830
- ],
1886
+ targetWorkspaceAccount: foreignKey({
1887
+ name: "slack_channel_routes_target_workspace_account_fk",
1888
+ columns: [table.targetWorkspaceId, table.targetAccountId],
1889
+ foreignColumns: [workspaces.id, workspaces.accountId],
831
1890
  }).onDelete("cascade"),
832
- workspace: uniqueIndex("codex_apps_settings_workspace_idx").on(table.workspaceId),
833
- designationShape: check(
834
- "codex_apps_settings_designation_shape_chk",
835
- sql`${table.version} > 0 and (
836
- (${table.credentialId} is null and ${table.designatedAt} is null)
837
- or
838
- (${table.credentialId} is not null and ${table.designatedAt} is not null)
839
- )`,
1891
+ target: index("slack_channel_routes_target_idx").on(table.targetWorkspaceId, table.slackTeamId),
1892
+ sameAccount: check(
1893
+ "slack_channel_routes_same_account_check",
1894
+ sql`${table.targetAccountId} = ${table.accountId}`,
1895
+ ),
1896
+ bounded: check(
1897
+ "slack_channel_routes_bounds_check",
1898
+ sql`octet_length(${table.slackTeamId}) between 1 and 64
1899
+ and octet_length(${table.slackChannelId}) between 1 and 64
1900
+ and octet_length(${table.decidedBySubjectId}) between 1 and 1024
1901
+ and octet_length(${table.decidedBySlackUserId}) between 1 and 64
1902
+ and ${table.source} in ('picker', 'admin')
1903
+ and ${table.version} > 0`,
840
1904
  ),
841
1905
  }),
842
1906
  );
843
1907
 
844
- // One durable logical human redemption. `processing` means the fresh provider
845
- // detail preflight is still owed; `provider_started` means the POST may have
846
- // reached upstream and every retry must reuse upstreamIdempotencyKey without
847
- // requiring the credit to remain visible as available.
848
- export const codexResetRedemptionAttempts = pgTable(
849
- "codex_reset_redemption_attempts",
1908
+ /**
1909
+ * Per-Slack-human direct-message workspace routing, same HOME/TARGET split as
1910
+ * {@link slackChannelRoutes}. Absent a row, a DM derives that human's own
1911
+ * personal workspace from their active organization membership pointer; the
1912
+ * workspace id is never accepted from a Slack payload or from this table.
1913
+ */
1914
+ export const slackUserDmRoutes = pgTable(
1915
+ "slack_user_dm_routes",
850
1916
  {
851
- id: uuid("id").primaryKey(),
1917
+ id: uuid("id").primaryKey().defaultRandom(),
852
1918
  accountId: uuid("account_id")
853
1919
  .notNull()
854
1920
  .references(() => managedAccounts.id, { onDelete: "cascade" }),
855
1921
  workspaceId: uuid("workspace_id")
856
1922
  .notNull()
857
1923
  .references(() => workspaces.id, { onDelete: "cascade" }),
858
- credentialId: uuid("credential_id").notNull(),
859
- subjectId: text("subject_id").notNull(),
860
- browserSessionHash: text("browser_session_hash").notNull(),
861
- creditId: text("credit_id").notNull(),
862
- upstreamIdempotencyKey: uuid("upstream_idempotency_key").notNull().defaultRandom(),
863
- status: text("status").notNull().default("processing"),
864
- outcome: text("outcome"),
865
- claimHolderId: uuid("claim_holder_id"),
866
- claimExpiresAt: timestamp("claim_expires_at", { withTimezone: true }),
867
- confirmationExpiresAt: timestamp("confirmation_expires_at", {
868
- withTimezone: true,
869
- }).notNull(),
870
- providerStartedAt: timestamp("provider_started_at", { withTimezone: true }),
871
- completedAt: timestamp("completed_at", { withTimezone: true }),
872
- lastFailureKind: text("last_failure_kind"),
873
- retryCount: integer("retry_count").notNull().default(0),
1924
+ connectionId: uuid("connection_id")
1925
+ .notNull()
1926
+ .references(() => connections.id, { onDelete: "cascade" }),
1927
+ slackTeamId: text("slack_team_id").notNull(),
1928
+ slackUserId: text("slack_user_id").notNull(),
1929
+ targetAccountId: uuid("target_account_id")
1930
+ .notNull()
1931
+ .references(() => managedAccounts.id, { onDelete: "cascade" }),
1932
+ targetWorkspaceId: uuid("target_workspace_id")
1933
+ .notNull()
1934
+ .references(() => workspaces.id, { onDelete: "cascade" }),
1935
+ decidedBySubjectId: text("decided_by_subject_id").notNull(),
1936
+ decidedBySlackUserId: text("decided_by_slack_user_id").notNull(),
1937
+ source: text("source").$type<"picker" | "admin">().notNull(),
1938
+ version: integer("version").notNull().default(1),
874
1939
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
875
1940
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
876
1941
  },
877
1942
  (table) => ({
1943
+ user: unique("slack_user_dm_routes_user_uq").on(table.connectionId, table.slackUserId),
878
1944
  workspaceAccount: foreignKey({
879
- name: "codex_reset_redemption_workspace_account_fk",
1945
+ name: "slack_user_dm_routes_workspace_account_fk",
880
1946
  columns: [table.workspaceId, table.accountId],
881
1947
  foreignColumns: [workspaces.id, workspaces.accountId],
882
1948
  }).onDelete("cascade"),
883
- upstreamKey: uniqueIndex("codex_reset_redemption_upstream_key_idx").on(
884
- table.upstreamIdempotencyKey,
885
- ),
886
- credentialCredit: uniqueIndex("codex_reset_redemption_credential_credit_idx")
887
- .on(table.workspaceId, table.credentialId, table.creditId)
888
- .where(
889
- sql`${table.status} <> 'completed' or ${table.outcome} in ('reset', 'alreadyRedeemed')`,
890
- ),
891
- workspaceCredential: index("codex_reset_redemption_workspace_credential_idx").on(
892
- table.workspaceId,
893
- table.credentialId,
894
- table.createdAt,
895
- ),
896
- claimExpiry: index("codex_reset_redemption_claim_expiry_idx")
897
- .on(table.claimExpiresAt)
898
- .where(sql`${table.status} <> 'completed'`),
899
- statusValid: check(
900
- "codex_reset_redemption_status_check",
901
- sql`${table.status} in ('processing', 'provider_started', 'completed')`,
902
- ),
903
- outcomeValid: check(
904
- "codex_reset_redemption_outcome_check",
905
- sql`${table.outcome} is null or ${table.outcome} in ('reset', 'nothingToReset', 'noCredit', 'alreadyRedeemed')`,
906
- ),
907
- completionConsistent: check(
908
- "codex_reset_redemption_completed_check",
909
- sql`(${table.status} = 'completed') = (${table.outcome} is not null and ${table.completedAt} is not null)`,
910
- ),
911
- retryCountValid: check(
912
- "codex_reset_redemption_retry_count_check",
913
- sql`${table.retryCount} >= 0`,
1949
+ targetWorkspaceAccount: foreignKey({
1950
+ name: "slack_user_dm_routes_target_workspace_account_fk",
1951
+ columns: [table.targetWorkspaceId, table.targetAccountId],
1952
+ foreignColumns: [workspaces.id, workspaces.accountId],
1953
+ }).onDelete("cascade"),
1954
+ target: index("slack_user_dm_routes_target_idx").on(table.targetWorkspaceId, table.slackTeamId),
1955
+ sameAccount: check(
1956
+ "slack_user_dm_routes_same_account_check",
1957
+ sql`${table.targetAccountId} = ${table.accountId}`,
914
1958
  ),
915
- humanSubjectValid: check(
916
- "codex_reset_redemption_human_subject_check",
917
- sql`${table.subjectId} like 'user:_%'`,
1959
+ bounded: check(
1960
+ "slack_user_dm_routes_bounds_check",
1961
+ sql`octet_length(${table.slackTeamId}) between 1 and 64
1962
+ and octet_length(${table.slackUserId}) between 1 and 64
1963
+ and octet_length(${table.decidedBySubjectId}) between 1 and 1024
1964
+ and octet_length(${table.decidedBySlackUserId}) between 1 and 64
1965
+ and ${table.source} in ('picker', 'admin')
1966
+ and ${table.version} > 0`,
918
1967
  ),
919
1968
  }),
920
1969
  );
921
1970
 
922
- // Generic external-service credential spine. credential_encrypted is the ONLY
923
- // secret-bearing column; normal API reads use metadata-only helpers below the DB
924
- // layer. Runtime token material is decrypted only by the broker accessor.
925
- export const connections = pgTable(
926
- "connections",
1971
+ /**
1972
+ * The pending first-use Slack workspace picker.
1973
+ *
1974
+ * `slack_interaction_action_handles` cannot carry it: that table's `sessionId`
1975
+ * is NOT NULL and composite-FK'd to an existing `slack_interactions` row, while
1976
+ * a picker exists BEFORE any session.
1977
+ *
1978
+ * `inboxId` deliberately carries no foreign key. The `awaiting_choice` inbox row
1979
+ * is settled `processed` before the human answers - the answer arrives as its
1980
+ * own `block_action` inbox row - so this column is provenance, not a live edge.
1981
+ *
1982
+ * `requestText` mirrors the inbox's exact 12000 byte bound so a prompt can
1983
+ * always carry the originating row's text losslessly.
1984
+ *
1985
+ * `providerEventId` is the ORIGINAL event, and it is PROVENANCE, not the id the
1986
+ * answer re-materializes under. Migration 0335's comment claims the answer can
1987
+ * re-insert the original event id under the inbox's own
1988
+ * `(connection_id, provider_event_id)` dedupe unique; that is wrong, and the
1989
+ * migration's bytes are frozen so the claim is corrected here instead. The
1990
+ * original inbox row still exists and is settled `processed`, so re-inserting
1991
+ * its id conflicts and does nothing, and the chosen workspace would never start
1992
+ * any work at all. The answer must enqueue a DERIVED id -
1993
+ * `<originalProviderEventId>:route:<promptId>` - which is unique per prompt and
1994
+ * still idempotent, so a double-click cannot create two sessions. The
1995
+ * `(connection_id, provider_event_id)` unique on this table is what makes
1996
+ * Slack's retries of the same event unable to post a second picker.
1997
+ */
1998
+ export const slackRoutePrompts = pgTable(
1999
+ "slack_route_prompts",
927
2000
  {
928
2001
  id: uuid("id").primaryKey().defaultRandom(),
929
2002
  accountId: uuid("account_id")
@@ -932,54 +2005,107 @@ export const connections = pgTable(
932
2005
  workspaceId: uuid("workspace_id")
933
2006
  .notNull()
934
2007
  .references(() => workspaces.id, { onDelete: "cascade" }),
935
- subjectId: text("subject_id"),
936
- providerDomain: text("provider_domain").notNull(),
937
- kind: text("kind").notNull(),
938
- status: text("status").notNull().default("active"),
939
- credentialEncrypted: text("credential_encrypted").notNull(),
940
- grantedScopes: jsonb("granted_scopes").$type<string[]>().notNull().default([]),
941
- expiresAt: timestamp("expires_at", { withTimezone: true }),
942
- lastRefreshAt: timestamp("last_refresh_at", { withTimezone: true }),
943
- lastUsedAt: timestamp("last_used_at", { withTimezone: true }),
944
- lastError: text("last_error"),
945
- version: integer("version").notNull().default(1),
946
- // Server-owned proof that the dedicated install route verified the exact
947
- // credential at this connection version. Generic/legacy writers cannot set
948
- // these columns, and migration 0131 clears them when protected fields change
949
- // without a fresh verification in the same statement.
950
- verifiedInstallAt: timestamp("verified_install_at", { withTimezone: true }),
951
- verifiedInstallVersion: integer("verified_install_version"),
952
- metadata: jsonb("metadata").$type<Record<string, unknown>>().notNull().default({}),
953
- createdBySubjectId: text("created_by_subject_id"),
954
- updatedBySubjectId: text("updated_by_subject_id"),
2008
+ connectionId: uuid("connection_id")
2009
+ .notNull()
2010
+ .references(() => connections.id, { onDelete: "cascade" }),
2011
+ inboxId: uuid("inbox_id").notNull(),
2012
+ slackTeamId: text("slack_team_id").notNull(),
2013
+ slackUserId: text("slack_user_id").notNull(),
2014
+ slackChannelId: text("slack_channel_id").notNull(),
2015
+ slackMessageTs: text("slack_message_ts").notNull(),
2016
+ providerEventId: text("provider_event_id").notNull(),
2017
+ triggerKind: text("trigger_kind")
2018
+ .$type<
2019
+ | "app_mention"
2020
+ | "dm"
2021
+ | "reaction"
2022
+ | "slash_command"
2023
+ | "message_shortcut"
2024
+ | "thread_reply"
2025
+ | "block_action"
2026
+ >()
2027
+ .notNull(),
2028
+ requestText: text("request_text").notNull(),
2029
+ hasFiles: boolean("has_files").notNull().default(false),
2030
+ slackThreadTs: text("slack_thread_ts"),
2031
+ messageOperationId: uuid("message_operation_id").notNull(),
2032
+ status: text("status")
2033
+ .$type<"pending" | "answered" | "expired" | "cancelled">()
2034
+ .notNull()
2035
+ .default("pending"),
2036
+ answeredTargetAccountId: uuid("answered_target_account_id"),
2037
+ answeredTargetWorkspaceId: uuid("answered_target_workspace_id"),
2038
+ answeredBySubjectId: text("answered_by_subject_id"),
2039
+ answeredAt: timestamp("answered_at", { withTimezone: true }),
2040
+ expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
955
2041
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
956
2042
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
957
2043
  },
958
2044
  (table) => ({
959
- workspaceProviderStatus: index("connections_workspace_provider_status_idx").on(
960
- table.workspaceId,
961
- table.providerDomain,
962
- table.status,
2045
+ inbox: unique("slack_route_prompts_inbox_uq").on(table.connectionId, table.inboxId),
2046
+ providerEvent: unique("slack_route_prompts_event_uq").on(
2047
+ table.connectionId,
2048
+ table.providerEventId,
963
2049
  ),
964
- workspaceSubjectProvider: index("connections_workspace_subject_provider_idx").on(
2050
+ tenantIdentity: unique("slack_route_prompts_identity_uq").on(
2051
+ table.accountId,
965
2052
  table.workspaceId,
966
- table.subjectId,
967
- table.providerDomain,
2053
+ table.id,
968
2054
  ),
969
- workspaceKind: index("connections_workspace_kind_idx").on(table.workspaceId, table.kind),
970
- workspaceExpires: index("connections_workspace_expires_idx").on(
971
- table.workspaceId,
972
- table.expiresAt,
2055
+ workspaceAccount: foreignKey({
2056
+ name: "slack_route_prompts_workspace_account_fk",
2057
+ columns: [table.workspaceId, table.accountId],
2058
+ foreignColumns: [workspaces.id, workspaces.accountId],
2059
+ }).onDelete("cascade"),
2060
+ pending: index("slack_route_prompts_pending_idx")
2061
+ .on(table.expiresAt, table.id)
2062
+ .where(sql`${table.status} = 'pending'`),
2063
+ statusValid: check(
2064
+ "slack_route_prompts_status_check",
2065
+ sql`${table.status} in ('pending', 'answered', 'expired', 'cancelled')`,
2066
+ ),
2067
+ triggerValid: check(
2068
+ "slack_route_prompts_trigger_check",
2069
+ sql`${table.triggerKind} in (
2070
+ 'app_mention', 'dm', 'reaction', 'slash_command',
2071
+ 'message_shortcut', 'thread_reply', 'block_action'
2072
+ )`,
2073
+ ),
2074
+ answerConsistent: check(
2075
+ "slack_route_prompts_answer_check",
2076
+ sql`(${table.status} = 'answered') = (${table.answeredTargetWorkspaceId} is not null)
2077
+ and (${table.answeredTargetAccountId} is null) = (${table.answeredTargetWorkspaceId} is null)
2078
+ and (${table.answeredTargetAccountId} is null
2079
+ or ${table.answeredTargetAccountId} = ${table.accountId})
2080
+ and (${table.answeredTargetWorkspaceId} is null) = (${table.answeredAt} is null)`,
2081
+ ),
2082
+ bounded: check(
2083
+ "slack_route_prompts_bounds_check",
2084
+ sql`octet_length(${table.slackTeamId}) between 1 and 64
2085
+ and octet_length(${table.slackUserId}) between 1 and 64
2086
+ and octet_length(${table.slackChannelId}) between 1 and 64
2087
+ and octet_length(${table.slackMessageTs}) between 1 and 64
2088
+ and octet_length(${table.providerEventId}) between 1 and 256
2089
+ and octet_length(${table.requestText}) between 1 and 12000
2090
+ and (${table.slackThreadTs} is null
2091
+ or octet_length(${table.slackThreadTs}) between 1 and 64)
2092
+ and (${table.answeredBySubjectId} is null
2093
+ or octet_length(${table.answeredBySubjectId}) between 1 and 1024)`,
973
2094
  ),
974
2095
  }),
975
2096
  );
976
2097
 
977
- // One durable routing authority per installed Slack team. The active partial
978
- // unique index is the database fence that prevents a team from being routed to
979
- // two OpenGeni workspaces. Legacy ambiguous rows are retained as quarantined
980
- // evidence instead of deleting credentials or guessing a winner.
981
- export const slackInstallationBindings = pgTable(
982
- "slack_installation_bindings",
2098
+ /**
2099
+ * One row per workspace offered by a {@link slackRoutePrompts} card. The row id
2100
+ * is the Slack button `value`, so it must be a UUID: the block-action normalizer
2101
+ * already requires that shape, which is why the picker uses buttons rather than
2102
+ * a `static_select` (whose `selected_option.value` the normalizer never reads).
2103
+ *
2104
+ * These rows are a snapshot taken at prompt time and are NEVER authority. The
2105
+ * answer path re-authorizes the chosen workspace live.
2106
+ */
2107
+ export const slackRoutePromptOptions = pgTable(
2108
+ "slack_route_prompt_options",
983
2109
  {
984
2110
  id: uuid("id").primaryKey().defaultRandom(),
985
2111
  accountId: uuid("account_id")
@@ -988,55 +2114,54 @@ export const slackInstallationBindings = pgTable(
988
2114
  workspaceId: uuid("workspace_id")
989
2115
  .notNull()
990
2116
  .references(() => workspaces.id, { onDelete: "cascade" }),
991
- connectionId: uuid("connection_id")
2117
+ promptId: uuid("prompt_id").notNull(),
2118
+ candidateAccountId: uuid("candidate_account_id")
992
2119
  .notNull()
993
- .references(() => connections.id, { onDelete: "cascade" }),
994
- slackTeamId: text("slack_team_id").notNull(),
995
- slackTeamName: text("slack_team_name").notNull(),
996
- botId: text("bot_id").notNull(),
997
- botUserId: text("bot_user_id").notNull(),
998
- botDisplayName: text("bot_display_name").notNull(),
999
- state: text("state").$type<"active" | "quarantined">().notNull(),
1000
- quarantineReason: text("quarantine_reason"),
1001
- version: integer("version").notNull().default(1),
1002
- createdBySubjectId: text("created_by_subject_id"),
1003
- updatedBySubjectId: text("updated_by_subject_id"),
2120
+ .references(() => managedAccounts.id, { onDelete: "cascade" }),
2121
+ candidateWorkspaceId: uuid("candidate_workspace_id")
2122
+ .notNull()
2123
+ .references(() => workspaces.id, { onDelete: "cascade" }),
2124
+ candidateLabel: text("candidate_label").notNull(),
2125
+ position: integer("position").notNull(),
1004
2126
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
1005
- updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
1006
2127
  },
1007
2128
  (table) => ({
1008
- connection: uniqueIndex("slack_installation_bindings_connection_uq").on(table.connectionId),
1009
- activeTeam: uniqueIndex("slack_installation_bindings_active_team_uq")
1010
- .on(table.slackTeamId)
1011
- .where(sql`${table.state} = 'active'`),
1012
- workspaceState: index("slack_installation_bindings_workspace_state_idx").on(
1013
- table.workspaceId,
1014
- table.state,
1015
- table.updatedAt,
1016
- ),
1017
2129
  workspaceAccount: foreignKey({
1018
- name: "slack_installation_bindings_workspace_account_fk",
2130
+ name: "slack_route_prompt_options_workspace_account_fk",
1019
2131
  columns: [table.workspaceId, table.accountId],
1020
2132
  foreignColumns: [workspaces.id, workspaces.accountId],
1021
2133
  }).onDelete("cascade"),
1022
- stateValid: check(
1023
- "slack_installation_bindings_state_check",
1024
- sql`${table.state} in ('active', 'quarantined')`,
1025
- ),
1026
- quarantineConsistent: check(
1027
- "slack_installation_bindings_quarantine_check",
1028
- sql`(${table.state} = 'active' and ${table.quarantineReason} is null)
1029
- or (${table.state} = 'quarantined' and length(btrim(${table.quarantineReason})) > 0)`,
2134
+ promptIdentity: foreignKey({
2135
+ name: "slack_route_prompt_options_prompt_fk",
2136
+ columns: [table.accountId, table.workspaceId, table.promptId],
2137
+ foreignColumns: [
2138
+ slackRoutePrompts.accountId,
2139
+ slackRoutePrompts.workspaceId,
2140
+ slackRoutePrompts.id,
2141
+ ],
2142
+ }).onDelete("cascade"),
2143
+ candidateWorkspaceAccount: foreignKey({
2144
+ name: "slack_route_prompt_options_candidate_workspace_account_fk",
2145
+ columns: [table.candidateWorkspaceId, table.candidateAccountId],
2146
+ foreignColumns: [workspaces.id, workspaces.accountId],
2147
+ }).onDelete("cascade"),
2148
+ promptWorkspace: unique("slack_route_prompt_options_prompt_workspace_uq").on(
2149
+ table.promptId,
2150
+ table.candidateWorkspaceId,
1030
2151
  ),
1031
- identityBounded: check(
1032
- "slack_installation_bindings_identity_check",
1033
- sql`octet_length(${table.slackTeamId}) between 1 and 64
1034
- and octet_length(${table.slackTeamName}) between 1 and 256
1035
- and octet_length(${table.botId}) between 1 and 64
1036
- and octet_length(${table.botUserId}) between 1 and 64
1037
- and ${table.botDisplayName} = 'OpenGeni'`,
2152
+ promptPosition: unique("slack_route_prompt_options_prompt_position_uq").on(
2153
+ table.promptId,
2154
+ table.position,
2155
+ ),
2156
+ sameAccount: check(
2157
+ "slack_route_prompt_options_same_account_check",
2158
+ sql`${table.candidateAccountId} = ${table.accountId}`,
2159
+ ),
2160
+ bounded: check(
2161
+ "slack_route_prompt_options_bounds_check",
2162
+ sql`octet_length(${table.candidateLabel}) between 1 and 128
2163
+ and ${table.position} >= 0`,
1038
2164
  ),
1039
- versionPositive: check("slack_installation_bindings_version_check", sql`${table.version} > 0`),
1040
2165
  }),
1041
2166
  );
1042
2167
 
@@ -1143,6 +2268,9 @@ export const slackBotUserLinks = pgTable(
1143
2268
  slackUserId: text("slack_user_id").notNull(),
1144
2269
  subjectId: text("subject_id").notNull(),
1145
2270
  linkedBySubjectId: text("linked_by_subject_id").notNull(),
2271
+ // The exact interaction id that won this identity's one-time onboarding
2272
+ // hint. NULL means the hint has not been shown yet.
2273
+ firstTaskHintInteractionId: uuid("first_task_hint_interaction_id"),
1146
2274
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
1147
2275
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
1148
2276
  },
@@ -1368,6 +2496,21 @@ export const slackInteractionInbox = pgTable(
1368
2496
  retryAt: timestamp("retry_at", { withTimezone: true }),
1369
2497
  lastErrorCode: text("last_error_code"),
1370
2498
  reactionContextCheckpoint: jsonb("reaction_context_checkpoint").$type<unknown>(),
2499
+ // Workspace routing decision, persisted on the claimed row so a retry after
2500
+ // a crash re-uses the same decision rather than re-asking. `accountId` and
2501
+ // `workspaceId` above stay HOME (the installation's credential tenancy) and
2502
+ // stay NOT NULL: `workspace_rls_visible` is strict equality, so a NULL
2503
+ // workspace would make an un-routed row invisible to every tenant including
2504
+ // its own. NULL `routeState` means "legacy / never routed" and is exactly
2505
+ // pre-routing behaviour.
2506
+ routeState: text("route_state").$type<"resolved" | "awaiting_choice" | "denied">(),
2507
+ // Deliberately no foreign key: an inbox row is short-lived and
2508
+ // definer-claimed, so a runtime re-check degrades to "ask again" while an FK
2509
+ // would hard-fail on a workspace deleted mid-flight.
2510
+ targetAccountId: uuid("target_account_id"),
2511
+ targetWorkspaceId: uuid("target_workspace_id"),
2512
+ routePromptId: uuid("route_prompt_id"),
2513
+ routePromptExpiresAt: timestamp("route_prompt_expires_at", { withTimezone: true }),
1371
2514
  processedAt: timestamp("processed_at", { withTimezone: true }),
1372
2515
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
1373
2516
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
@@ -1390,6 +2533,73 @@ export const slackInteractionInbox = pgTable(
1390
2533
  }),
1391
2534
  );
1392
2535
 
2536
+ // Coalesced private App Home refresh authority. One row per installed Slack
2537
+ // connection/user serializes provider replacement calls while allowing event
2538
+ // retries and newer opens to converge on the latest requested revision.
2539
+ export const slackAppHomeRefreshes = pgTable(
2540
+ "slack_app_home_refreshes",
2541
+ {
2542
+ id: uuid("id").primaryKey().defaultRandom(),
2543
+ accountId: uuid("account_id")
2544
+ .notNull()
2545
+ .references(() => managedAccounts.id, { onDelete: "cascade" }),
2546
+ workspaceId: uuid("workspace_id")
2547
+ .notNull()
2548
+ .references(() => workspaces.id, { onDelete: "cascade" }),
2549
+ connectionId: uuid("connection_id")
2550
+ .notNull()
2551
+ .references(() => connections.id, { onDelete: "cascade" }),
2552
+ slackTeamId: text("slack_team_id").notNull(),
2553
+ slackUserId: text("slack_user_id").notNull(),
2554
+ providerEventId: text("provider_event_id").notNull(),
2555
+ providerViewHash: text("provider_view_hash"),
2556
+ desiredRevision: integer("desired_revision").notNull().default(1),
2557
+ processedRevision: integer("processed_revision").notNull().default(0),
2558
+ claimHolderId: uuid("claim_holder_id"),
2559
+ claimExpiresAt: timestamp("claim_expires_at", { withTimezone: true }),
2560
+ attemptCount: integer("attempt_count").notNull().default(0),
2561
+ retryAt: timestamp("retry_at", { withTimezone: true }),
2562
+ lastErrorCode: text("last_error_code"),
2563
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
2564
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
2565
+ },
2566
+ (table) => ({
2567
+ connectionUser: uniqueIndex("slack_app_home_refreshes_connection_user_uq").on(
2568
+ table.connectionId,
2569
+ table.slackUserId,
2570
+ ),
2571
+ pending: index("slack_app_home_refreshes_pending_idx")
2572
+ .on(table.retryAt, table.updatedAt, table.id)
2573
+ .where(sql`${table.processedRevision} < ${table.desiredRevision}`),
2574
+ workspaceAccount: foreignKey({
2575
+ name: "slack_app_home_refreshes_workspace_account_fk",
2576
+ columns: [table.workspaceId, table.accountId],
2577
+ foreignColumns: [workspaces.id, workspaces.accountId],
2578
+ }).onDelete("cascade"),
2579
+ bounds: check(
2580
+ "slack_app_home_refreshes_bounds_check",
2581
+ sql`octet_length(${table.slackTeamId}) between 1 and 64
2582
+ and octet_length(${table.slackUserId}) between 1 and 64
2583
+ and octet_length(${table.providerEventId}) between 1 and 256
2584
+ and (${table.providerViewHash} is null
2585
+ or octet_length(${table.providerViewHash}) between 1 and 256)
2586
+ and (${table.lastErrorCode} is null
2587
+ or octet_length(${table.lastErrorCode}) between 1 and 128)`,
2588
+ ),
2589
+ revisions: check(
2590
+ "slack_app_home_refreshes_revisions_check",
2591
+ sql`${table.desiredRevision} > 0
2592
+ and ${table.processedRevision} >= 0
2593
+ and ${table.processedRevision} <= ${table.desiredRevision}
2594
+ and ${table.attemptCount} >= 0`,
2595
+ ),
2596
+ claim: check(
2597
+ "slack_app_home_refreshes_claim_check",
2598
+ sql`(${table.claimHolderId} is null) = (${table.claimExpiresAt} is null)`,
2599
+ ),
2600
+ }),
2601
+ );
2602
+
1393
2603
  export const slackInteractions = pgTable(
1394
2604
  "slack_interactions",
1395
2605
  {
@@ -1426,6 +2636,15 @@ export const slackInteractions = pgTable(
1426
2636
  deliveryRetryAt: timestamp("delivery_retry_at", { withTimezone: true }),
1427
2637
  deliveryLastErrorCode: text("delivery_last_error_code"),
1428
2638
  ackSlackMessageTs: text("ack_slack_message_ts"),
2639
+ // Frozen once: whether this interaction's acknowledgement renders the
2640
+ // one-time onboarding hint. NULL means the decision has not been resolved.
2641
+ firstTaskHint: boolean("first_task_hint"),
2642
+ // The routed workspace's display name, frozen when the interaction binds.
2643
+ // A live lookup at post time would be wrong: a workspace rename between the
2644
+ // original post and a reconciliation makes `reconcilePostMessage`'s
2645
+ // byte-compare raise `post_reconciliation_mismatch`. NULL means no
2646
+ // per-message workspace line.
2647
+ routedWorkspaceLabel: text("routed_workspace_label"),
1429
2648
  progressCount: integer("progress_count").notNull().default(0),
1430
2649
  terminalDeliveryState: text("terminal_delivery_state")
1431
2650
  .$type<"open" | "completed" | "failed" | "cancelled" | "blocked">()
@@ -2440,6 +3659,9 @@ export const channels = pgTable(
2440
3659
  .references(() => workspaces.id, { onDelete: "cascade" }),
2441
3660
  name: text("name").notNull(),
2442
3661
  description: text("description"),
3662
+ pinned: boolean("pinned").notNull().default(false),
3663
+ // Explicit user order within the pinned and unpinned project groups.
3664
+ sortOrder: integer("sort_order").notNull().default(0),
2443
3665
  // Attribution string: 'user:<subject>' | 'session:<id>' | 'system'.
2444
3666
  createdBy: text("created_by"),
2445
3667
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
@@ -2464,10 +3686,10 @@ export const sessions = pgTable(
2464
3686
  status: text("status").notNull().default("queued"),
2465
3687
  initialMessage: losslessText("initial_message").notNull(),
2466
3688
  initialMessageCodecVersion: losslessCodecVersion("initial_message_codec_version"),
2467
- // Invisible host context frozen with the winning session create. The
2468
- // initial turn copies this value so an idempotent repair can never adopt a
2469
- // retrying caller's different instructions.
2470
- initialTurnInstructions: text("initial_turn_instructions"),
3689
+ // Model-visible application context frozen with the winning create. The
3690
+ // initial turn copies it into the canonical user message so an idempotent
3691
+ // repair cannot adopt a retrying caller's different message context.
3692
+ initialModelContext: text("initial_model_context"),
2471
3693
  title: text("title"),
2472
3694
  titleSource: text("title_source"),
2473
3695
  // Per-session agent persona/system instructions supplied at create (the
@@ -2500,6 +3722,9 @@ export const sessions = pgTable(
2500
3722
  ownerOrganizationMembershipId: uuid("owner_organization_membership_id"),
2501
3723
  ownerSubjectId: text("owner_subject_id"),
2502
3724
  visibility: text("visibility").notNull().default("workspace_shared"),
3725
+ createRequestedVisibility: text("create_requested_visibility")
3726
+ .notNull()
3727
+ .default("workspace_shared"),
2503
3728
  authorityEpoch: integer("authority_epoch").notNull().default(1),
2504
3729
  // Independent-copy provenance. A destination may use either visibility and
2505
3730
  // may live in another workspace in the same organization; no live process,
@@ -2510,6 +3735,10 @@ export const sessions = pgTable(
2510
3735
  forkedAt: timestamp("forked_at", { withTimezone: true }),
2511
3736
  forkedByOrganizationMembershipId: uuid("forked_by_organization_membership_id"),
2512
3737
  model: text("model").notNull(),
3738
+ // Immutable session-default execution policy. Follow-up composer drafts and
3739
+ // accepted turns are separate authorities and never mutate these columns.
3740
+ reasoningEffort: text("reasoning_effort").notNull(),
3741
+ latencyMode: text("latency_mode").notNull(),
2513
3742
  sandboxBackend: text("sandbox_backend").notNull(),
2514
3743
  // The OS this session's box runs. Defaults to 'linux' (today's only OS, so
2515
3744
  // every existing + new row is a behavior-preserving no-op). CHECK-constrained
@@ -2539,13 +3768,10 @@ export const sessions = pgTable(
2539
3768
  // sandbox. integer (NOT bigint) — the lease-epoch spike: int8 reads back as a
2540
3769
  // JS string and breaks the strict fence; int4 returns a number.
2541
3770
  activeEpoch: integer("active_epoch").notNull().default(0),
2542
- // The session's WORKING DIRECTORY the path/cwd base the (selfhosted) box's
2543
- // agent/terminal/file-dock operate under. A launch-workspace_root-relative
2544
- // subdir or an absolute machine path; surfaced alongside the active-sandbox
2545
- // pointer (readActiveSandbox) and written through the epoch-fenced
2546
- // setActiveSandbox CAS, NOT the row INSERT. NULL (the default) ⇒ today's
2547
- // behavior exactly — the agent substitutes its workspace_root for an empty cwd,
2548
- // so an unset working_dir is a byte-identical no-op. Create-time only (Stage A).
3771
+ // The session's effective absolute Connected Machine root. New attaches
3772
+ // resolve an optional relative request against the persisted Hello root and
3773
+ // write the result through the epoch-fenced setActiveSandbox CAS. Legacy
3774
+ // rows may still be null/relative and are resolved at establishment.
2549
3775
  workingDir: text("working_dir"),
2550
3776
  variableSetId: uuid("variable_set_id").references(() => workspaceVariableSets.id, {
2551
3777
  onDelete: "set null",
@@ -2577,6 +3803,12 @@ export const sessions = pgTable(
2577
3803
  .$type<McpPersonalConnectionDelegation[]>()
2578
3804
  .notNull()
2579
3805
  .default([]),
3806
+ // Initial accepted-work staging only. The initializer consumes this inside
3807
+ // the same transaction that inserts the first logical turn and grant
3808
+ // snapshots; runtime never authorizes from the session field.
3809
+ initialPersonalResourceAttachmentIntent: jsonb(
3810
+ "initial_personal_resource_attachment_intent",
3811
+ ).$type<PersonalResourceAttachmentIntent>(),
2580
3812
  initialXaiProviderAccountAuthoritySnapshot: jsonb(
2581
3813
  "initial_xai_provider_account_authority_snapshot",
2582
3814
  )
@@ -2593,8 +3825,8 @@ export const sessions = pgTable(
2593
3825
  // spawning a worker); null for direct API creates and scheduled-task runs.
2594
3826
  // When set, this worker's terminal-for-now transitions wake the parent so a
2595
3827
  // manager can orchestrate workers without busy-polling. The migration-owned
2596
- // self-reference uses ON DELETE RESTRICT so immutable hierarchy and
2597
- // authority lineage cannot be silently orphaned by a parent hard delete.
3828
+ // self-reference uses ON DELETE CASCADE so the explicit quiescent root-tree
3829
+ // deletion lifecycle removes the complete hierarchy atomically.
2598
3830
  parentSessionId: uuid("parent_session_id"),
2599
3831
  // Exact parent turn whose worker-signed attempt created this child. This is
2600
3832
  // private immutable authority lineage: child completion copies personal MCP
@@ -2676,6 +3908,14 @@ export const sessions = pgTable(
2676
3908
  }),
2677
3909
  },
2678
3910
  (table) => ({
3911
+ reasoningEffortValid: check(
3912
+ "sessions_reasoning_effort_check",
3913
+ sql`${table.reasoningEffort} in ('none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max')`,
3914
+ ),
3915
+ latencyModeValid: check(
3916
+ "sessions_latency_mode_check",
3917
+ sql`${table.latencyMode} in ('standard', 'priority', 'fast')`,
3918
+ ),
2679
3919
  accountIdentity: uniqueIndex("sessions_id_account_idx").on(table.id, table.accountId),
2680
3920
  workspaceIdentity: uniqueIndex("sessions_workspace_id_idx").on(table.workspaceId, table.id),
2681
3921
  workspaceCreated: index("sessions_workspace_created_idx").on(
@@ -2762,6 +4002,11 @@ export const sessions = pgTable(
2762
4002
  table.rootSessionId,
2763
4003
  table.nestedAgentDepth,
2764
4004
  ),
4005
+ initialModelContextValid: check(
4006
+ "sessions_initial_model_context_check",
4007
+ sql`${table.initialModelContext} is null
4008
+ or opengeni_private.model_context_value_valid(${table.initialModelContext})`,
4009
+ ),
2765
4010
  }),
2766
4011
  );
2767
4012
 
@@ -2797,6 +4042,18 @@ export const organizationUserResourceGrants = pgTable(
2797
4042
  table.workspaceId,
2798
4043
  table.status,
2799
4044
  ),
4045
+ activeIdentity: uniqueIndex("organization_user_resource_grants_active_identity_uq")
4046
+ .on(
4047
+ table.accountId,
4048
+ table.authorityId,
4049
+ table.workspaceId,
4050
+ table.action,
4051
+ table.mode,
4052
+ table.context,
4053
+ sql`coalesce(${table.sessionId}, '00000000-0000-0000-0000-000000000000'::uuid)`,
4054
+ sql`coalesce(${table.authorityEpoch}, 0)`,
4055
+ )
4056
+ .where(sql`${table.status} = 'active'`),
2800
4057
  authority: foreignKey({
2801
4058
  name: "organization_user_resource_grants_authority_fk",
2802
4059
  columns: [table.authorityId, table.accountId],
@@ -2949,7 +4206,7 @@ export const sessionRealtimeModes = pgTable(
2949
4206
  ),
2950
4207
  endReasonValid: check(
2951
4208
  "session_realtime_modes_end_reason_check",
2952
- sql`${table.endReason} is null or ${table.endReason} in ('user_stop', 'browser_unload', 'lease_expired')`,
4209
+ sql`${table.endReason} is null or ${table.endReason} in ('user_stop', 'browser_unload', 'lease_expired', 'authority_revoked')`,
2953
4210
  ),
2954
4211
  versionValid: check("session_realtime_modes_version_check", sql`${table.version} >= 1`),
2955
4212
  epochValid: check("session_realtime_modes_epoch_check", sql`${table.connectionEpoch} >= 1`),
@@ -3115,6 +4372,9 @@ export const sessionRealtimeEntries = pgTable(
3115
4372
  textCodecVersion: losslessCodecVersion("text_codec_version"),
3116
4373
  payload: losslessJsonb("payload").$type<Record<string, unknown>>().notNull().default({}),
3117
4374
  payloadCodecVersion: losslessCodecVersion("payload_codec_version"),
4375
+ // Application context for an exact provider-in delegation or finalized
4376
+ // transcript. It is materialized as ordinary user-role message content.
4377
+ modelContext: text("model_context"),
3118
4378
  clientAckedAt: timestamp("client_acked_at", { withTimezone: true }),
3119
4379
  providerAckedAt: timestamp("provider_acked_at", { withTimezone: true }),
3120
4380
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
@@ -3190,6 +4450,15 @@ export const sessionRealtimeEntries = pgTable(
3190
4450
  or (${table.kind} = 'assistant_transcript' and ${table.role} = 'assistant' and ${table.text} is not null)
3191
4451
  or (${table.kind} not in ('user_transcript', 'assistant_transcript') and ${table.role} is null)`,
3192
4452
  ),
4453
+ modelContextValid: check(
4454
+ "session_realtime_entries_model_context_check",
4455
+ sql`${table.modelContext} is null
4456
+ or (
4457
+ ${table.direction} = 'provider_in'
4458
+ and ${table.kind} in ('delegation_call', 'user_transcript', 'assistant_transcript')
4459
+ and opengeni_private.model_context_value_valid(${table.modelContext})
4460
+ )`,
4461
+ ),
3193
4462
  }),
3194
4463
  );
3195
4464
 
@@ -3240,10 +4509,11 @@ export const sessionSpawnDenials = pgTable(
3240
4509
  }),
3241
4510
  );
3242
4511
 
3243
- // Per-authenticated-subject session organization. This is deliberately a
3244
- // relation instead of a session column: one member's pin must never reorder a
3245
- // shared workspace for another member, and a session's own activity timestamps
3246
- // must remain agent/runtime truth. `subjectId` is the trusted AccessGrant
4512
+ // Per-authenticated-subject session organization and follow-up state. This is
4513
+ // deliberately a relation instead of session columns: one member's pin,
4514
+ // acknowledgment, or actively-working label must never affect another member,
4515
+ // and a session's own activity timestamps must remain agent/runtime truth.
4516
+ // `subjectId` is the trusted AccessGrant
3247
4517
  // subject, which is text because configured/delegated principals are not always
3248
4518
  // UUIDs. The account/workspace pair carries the standard forced-RLS boundary.
3249
4519
  export const sessionPins = pgTable(
@@ -3267,13 +4537,38 @@ export const sessionPins = pgTable(
3267
4537
  pinned: boolean("pinned").notNull().default(true),
3268
4538
  pinnedAt: timestamp("pinned_at", { withTimezone: true }).defaultNow(),
3269
4539
  version: integer("version").notNull().default(1),
4540
+ // A session is unread for this subject whenever its durable event sequence
4541
+ // has advanced beyond this explicit acknowledgment fence. Merely opening a
4542
+ // route never changes the fence.
4543
+ acknowledgedSequence: integer("acknowledged_sequence").notNull().default(0),
4544
+ activelyWorking: boolean("actively_working").notNull().default(false),
4545
+ attentionVersion: integer("attention_version").notNull().default(0),
4546
+ archived: boolean("archived").notNull().default(false),
4547
+ archivedAt: timestamp("archived_at", { withTimezone: true }),
4548
+ archiveVersion: integer("archive_version").notNull().default(0),
3270
4549
  },
3271
4550
  (table) => ({
3272
4551
  subjectNonempty: check(
3273
4552
  "session_pins_subject_nonempty",
3274
4553
  sql`length(btrim(${table.subjectId})) > 0`,
3275
4554
  ),
3276
- versionPositive: check("session_pins_version_positive", sql`${table.version} >= 1`),
4555
+ versionNonnegative: check("session_pins_version_nonnegative", sql`${table.version} >= 0`),
4556
+ acknowledgedSequenceFloor: check(
4557
+ "session_pins_acknowledged_sequence_floor",
4558
+ sql`${table.acknowledgedSequence} >= -1`,
4559
+ ),
4560
+ attentionVersionNonnegative: check(
4561
+ "session_pins_attention_version_nonnegative",
4562
+ sql`${table.attentionVersion} >= 0`,
4563
+ ),
4564
+ archiveVersionNonnegative: check(
4565
+ "session_pins_archive_version_nonnegative",
4566
+ sql`${table.archiveVersion} >= 0`,
4567
+ ),
4568
+ archiveStateConsistent: check(
4569
+ "session_pins_archive_state_consistent",
4570
+ sql`((${table.archived}) and (${table.archivedAt}) is not null) or ((not ${table.archived}) and (${table.archivedAt}) is null)`,
4571
+ ),
3277
4572
  stateConsistent: check(
3278
4573
  "session_pins_state_consistent",
3279
4574
  sql`((${table.pinned}) and (${table.pinnedAt}) is not null) or ((not ${table.pinned}) and (${table.pinnedAt}) is null)`,
@@ -3300,15 +4595,22 @@ export const sessionPins = pgTable(
3300
4595
  table.pinnedAt.desc(),
3301
4596
  table.sessionId.desc(),
3302
4597
  ),
4598
+ subjectArchived: index("session_pins_workspace_subject_archived_idx").on(
4599
+ table.workspaceId,
4600
+ table.subjectId,
4601
+ table.archived,
4602
+ table.archivedAt.desc(),
4603
+ table.sessionId.desc(),
4604
+ ),
3303
4605
  }),
3304
4606
  );
3305
4607
 
3306
- // A short-lived server-owned continuation snapshot for the pin-aware session
3307
- // list. The ordinary list is ordered by mutable activity, so a cursor cannot
3308
- // safely replay that order from updated_at alone across HTTP requests. The
3309
- // snapshot stores the already-ordered ordinary ids; the list query still joins
3310
- // live session rows for current lifecycle/title data and expires snapshots
3311
- // opportunistically.
4608
+ // Rolling-compatibility storage for pre-keyset session-list cursors. New page
4609
+ // one reads freeze the committed workspace activity revision in an opaque
4610
+ // updated_at/id keyset and create no row here. Old API replicas and cursors may
4611
+ // retain an already-ordered id array for its short TTL, so current replicas keep
4612
+ // reading, visibility-stripping, member-cleaning, and reaping those rows until
4613
+ // the compatibility table is retired by a later migration.
3312
4614
  export const sessionListSnapshots = pgTable(
3313
4615
  "session_list_snapshots",
3314
4616
  {
@@ -3318,6 +4620,7 @@ export const sessionListSnapshots = pgTable(
3318
4620
  subjectId: text("subject_id").notNull(),
3319
4621
  parentSessionFilter: text("parent_session_filter").notNull().default("all"),
3320
4622
  search: text("search"),
4623
+ archiveMode: text("archive_mode").$type<"active" | "archived">().notNull().default("active"),
3321
4624
  ordinarySessionIds: uuid("ordinary_session_ids")
3322
4625
  .array()
3323
4626
  .notNull()
@@ -3343,6 +4646,10 @@ export const sessionListSnapshots = pgTable(
3343
4646
  "session_list_snapshots_search_length",
3344
4647
  sql`${table.search} is null or length(${table.search}) <= 200`,
3345
4648
  ),
4649
+ archiveModeValid: check(
4650
+ "session_list_snapshots_archive_mode_valid",
4651
+ sql`${table.archiveMode} in ('active', 'archived')`,
4652
+ ),
3346
4653
  workspaceExpiry: index("session_list_snapshots_workspace_expiry_idx").on(
3347
4654
  table.workspaceId,
3348
4655
  table.subjectId,
@@ -4216,6 +5523,12 @@ export const documents = pgTable(
4216
5523
  authorityKind: text("authority_kind").notNull().default("workspace"),
4217
5524
  authorityWorkspaceId: uuid("authority_workspace_id"),
4218
5525
  authoritySubjectId: text("authority_subject_id"),
5526
+ // Activated personal documents follow their organization membership across
5527
+ // workspaces. Legacy personal rows retain a null authority id and their
5528
+ // original workspace anchor; originWorkspaceId is provenance only.
5529
+ authorityId: uuid("authority_id"),
5530
+ ownerOrganizationMembershipId: uuid("owner_organization_membership_id"),
5531
+ originWorkspaceId: uuid("origin_workspace_id").notNull(),
4219
5532
  // Per-document access controls. visibility 'private' restricts human reads to
4220
5533
  // created_by (a grant subject id, not a uuid); agent_access=false hides the
4221
5534
  // document from agent retrieval surfaces (docs MCP) while humans keep REST.
@@ -4274,16 +5587,29 @@ export const documents = pgTable(
4274
5587
  columns: [table.authorityWorkspaceId, table.accountId],
4275
5588
  foreignColumns: [workspaces.id, workspaces.accountId],
4276
5589
  }).onDelete("restrict"),
5590
+ userAuthority: foreignKey({
5591
+ name: "documents_user_authority_fk",
5592
+ columns: [table.authorityId, table.accountId, table.ownerOrganizationMembershipId],
5593
+ foreignColumns: [
5594
+ organizationUserResourceAuthorities.id,
5595
+ organizationUserResourceAuthorities.accountId,
5596
+ organizationUserResourceAuthorities.organizationMembershipId,
5597
+ ],
5598
+ }).onDelete("restrict"),
4277
5599
  authorityState: check(
4278
5600
  "documents_authority_chk",
4279
- sql`(${table.authorityKind} = 'organization' and ${table.authorityWorkspaceId} is null and ${table.authoritySubjectId} is null)
4280
- or (${table.authorityKind} = 'workspace' and ${table.authorityWorkspaceId} = ${table.workspaceId} and ${table.authoritySubjectId} is null)
4281
- or (${table.authorityKind} = 'personal' and ${table.authorityWorkspaceId} = ${table.workspaceId} and nullif(btrim(${table.authoritySubjectId}), '') is not null and octet_length(convert_to(${table.authoritySubjectId}, 'UTF8')) <= 1024 and ${table.authoritySubjectId} = ${table.createdBy})`,
5601
+ sql`(${table.authorityKind} = 'organization' and ${table.authorityWorkspaceId} is null and ${table.authoritySubjectId} is null and ${table.authorityId} is null and ${table.ownerOrganizationMembershipId} is null)
5602
+ or (${table.authorityKind} = 'workspace' and ${table.authorityWorkspaceId} = ${table.workspaceId} and ${table.authoritySubjectId} is null and ${table.authorityId} is null and ${table.ownerOrganizationMembershipId} is null)
5603
+ or (${table.authorityKind} = 'personal' and nullif(btrim(${table.authoritySubjectId}), '') is not null and octet_length(convert_to(${table.authoritySubjectId}, 'UTF8')) <= 1024 and ${table.authoritySubjectId} = ${table.createdBy} and ((${table.authorityWorkspaceId} = ${table.workspaceId} and ${table.authorityId} is null and ${table.ownerOrganizationMembershipId} is null) or (${table.authorityWorkspaceId} is null and ${table.authorityId} is not null and ${table.ownerOrganizationMembershipId} is not null)))`,
4282
5604
  ),
4283
5605
  authorityVisibility: check(
4284
5606
  "documents_authority_visibility_chk",
4285
5607
  sql`(${table.authorityKind} = 'personal') = (${table.visibility} = 'private')`,
4286
5608
  ),
5609
+ originWorkspace: check(
5610
+ "documents_origin_workspace_chk",
5611
+ sql`${table.originWorkspaceId} = ${table.workspaceId}`,
5612
+ ),
4287
5613
  visibilityState: check(
4288
5614
  "documents_visibility_chk",
4289
5615
  sql`${table.visibility} in ('workspace', 'private')`,
@@ -4338,6 +5664,8 @@ export const documentChunks = pgTable(
4338
5664
  authorityKind: text("authority_kind").notNull().default("workspace"),
4339
5665
  authorityWorkspaceId: uuid("authority_workspace_id"),
4340
5666
  authoritySubjectId: text("authority_subject_id"),
5667
+ authorityId: uuid("authority_id"),
5668
+ ownerOrganizationMembershipId: uuid("owner_organization_membership_id"),
4341
5669
  embedding: vector("embedding").notNull(),
4342
5670
  embeddingModel: text("embedding_model").notNull(),
4343
5671
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
@@ -4360,11 +5688,20 @@ export const documentChunks = pgTable(
4360
5688
  columns: [table.authorityWorkspaceId, table.accountId],
4361
5689
  foreignColumns: [workspaces.id, workspaces.accountId],
4362
5690
  }).onDelete("restrict"),
5691
+ userAuthority: foreignKey({
5692
+ name: "document_chunks_user_authority_fk",
5693
+ columns: [table.authorityId, table.accountId, table.ownerOrganizationMembershipId],
5694
+ foreignColumns: [
5695
+ organizationUserResourceAuthorities.id,
5696
+ organizationUserResourceAuthorities.accountId,
5697
+ organizationUserResourceAuthorities.organizationMembershipId,
5698
+ ],
5699
+ }).onDelete("restrict"),
4363
5700
  authorityState: check(
4364
5701
  "document_chunks_authority_chk",
4365
- sql`(${table.authorityKind} = 'organization' and ${table.authorityWorkspaceId} is null and ${table.authoritySubjectId} is null)
4366
- or (${table.authorityKind} = 'workspace' and ${table.authorityWorkspaceId} = ${table.workspaceId} and ${table.authoritySubjectId} is null)
4367
- or (${table.authorityKind} = 'personal' and ${table.authorityWorkspaceId} = ${table.workspaceId} and nullif(btrim(${table.authoritySubjectId}), '') is not null and octet_length(convert_to(${table.authoritySubjectId}, 'UTF8')) <= 1024)`,
5702
+ sql`(${table.authorityKind} = 'organization' and ${table.authorityWorkspaceId} is null and ${table.authoritySubjectId} is null and ${table.authorityId} is null and ${table.ownerOrganizationMembershipId} is null)
5703
+ or (${table.authorityKind} = 'workspace' and ${table.authorityWorkspaceId} = ${table.workspaceId} and ${table.authoritySubjectId} is null and ${table.authorityId} is null and ${table.ownerOrganizationMembershipId} is null)
5704
+ or (${table.authorityKind} = 'personal' and nullif(btrim(${table.authoritySubjectId}), '') is not null and octet_length(convert_to(${table.authoritySubjectId}, 'UTF8')) <= 1024 and ((${table.authorityWorkspaceId} = ${table.workspaceId} and ${table.authorityId} is null and ${table.ownerOrganizationMembershipId} is null) or (${table.authorityWorkspaceId} is null and ${table.authorityId} is not null and ${table.ownerOrganizationMembershipId} is not null)))`,
4368
5705
  ),
4369
5706
  }),
4370
5707
  );
@@ -4493,13 +5830,20 @@ export const sessionTurns = pgTable(
4493
5830
  temporalWorkflowId: text("temporal_workflow_id").notNull(),
4494
5831
  status: text("status").notNull(),
4495
5832
  source: text("source").notNull().default("user"),
5833
+ // Immutable user-facing admission intent. Physical execution still uses
5834
+ // status=queued until a worker claims the row; this field keeps that
5835
+ // implementation queue distinct from prompts genuinely waiting behind
5836
+ // other work. Null is reserved for rolling/legacy writers and non-human
5837
+ // turns, and is projected conservatively as visible queue work.
5838
+ promptRouting: text("prompt_routing"),
4496
5839
  position: bigint("position", { mode: "number" }).notNull(),
4497
5840
  prompt: losslessText("prompt").notNull(),
4498
5841
  promptCodecVersion: losslessCodecVersion("prompt_codec_version"),
4499
5842
  annotations: jsonb("annotations").$type<TimelineAnnotation[]>().notNull().default([]),
4500
- // Host context for this exact turn. System-level at runtime and deliberately
4501
- // separate from the visible prompt/event payload.
4502
- turnInstructions: text("turn_instructions"),
5843
+ // Application context for this exact user message. It is copied into the
5844
+ // canonical user-role history item at claim and omitted from public queue
5845
+ // projections; full event/audit data retains it.
5846
+ modelContext: text("model_context"),
4503
5847
  resources: jsonb("resources").$type<unknown[]>().notNull().default([]),
4504
5848
  tools: jsonb("tools").$type<unknown[]>().notNull().default([]),
4505
5849
  // false = inherit the durable session policy; true = this turn explicitly
@@ -4546,15 +5890,28 @@ export const sessionTurns = pgTable(
4546
5890
  .$type<McpPersonalConnectionDelegation[]>()
4547
5891
  .notNull()
4548
5892
  .default([]),
5893
+ // Credential-free public summary of a turn-bound personal Variable
5894
+ // Set/Rig attachment. Exact resource and grant identity lives only in the
5895
+ // immutable accepted-work snapshot tables.
5896
+ personalResourceAttachmentSummary: jsonb(
5897
+ "personal_resource_attachment_summary",
5898
+ ).$type<PersonalResourceAttachmentSummary>(),
5899
+ // 0 = legacy attempt-selected authority; 1 = immutable logical-turn
5900
+ // snapshots. Drained migration 0306 makes every new atomic attachment v1.
5901
+ personalResourceProtocolVersion: integer("personal_resource_protocol_version")
5902
+ .notNull()
5903
+ .default(0),
5904
+ // Immutable scheduled occurrence that accepted this logical turn. Null for
5905
+ // ordinary human, goal, child, and unrelated internal work.
5906
+ scheduledTaskRunId: uuid("scheduled_task_run_id"),
4549
5907
  xaiProviderAccountAuthoritySnapshot: jsonb("xai_provider_account_authority_snapshot")
4550
5908
  .$type<XaiProviderAccountAuthoritySnapshotV1>()
4551
5909
  .notNull()
4552
5910
  .default(WORKSPACE_XAI_PROVIDER_ACCOUNT_AUTHORITY_SNAPSHOT_V1),
4553
5911
  cancelledBy: text("cancelled_by"),
4554
5912
  cancelReason: text("cancel_reason"),
4555
- // Atomic per-turn codemode call budget counter (migration 0043). Incremented
4556
- // by a single conditional UPDATE at tools/call time; the row lock serializes
4557
- // concurrent reservations so exactly `codemodeMaxCallsPerTurn` succeed.
5913
+ // Leftover unused counter from the removed per-turn Codemode call cap
5914
+ // (migrations 0043/0205). New writers do not increment it; do not enforce.
4558
5915
  codemodeCallCount: integer("codemode_call_count").notNull().default(0),
4559
5916
  startedAt: timestamp("started_at", { withTimezone: true }),
4560
5917
  finishedAt: timestamp("finished_at", { withTimezone: true }),
@@ -4575,10 +5932,162 @@ export const sessionTurns = pgTable(
4575
5932
  oneCurrentInference: uniqueIndex("session_turns_one_current_inference_uq")
4576
5933
  .on(table.workspaceId, table.sessionId)
4577
5934
  .where(sql`${table.status} in ('running','requires_action','recovering','waiting_capacity')`),
5935
+ // Agent-monitoring "was this prompt's turn ever claimed" probe
5936
+ // (`excludeUnclaimedHumanPromptEventFilter`), migration 0322.
5937
+ unclaimedPromptTrigger: index("session_turns_unclaimed_prompt_trigger_idx")
5938
+ .on(table.workspaceId, table.sessionId, table.triggerEventId)
5939
+ .where(sql`${table.startedAt} is null`),
4578
5940
  latencyModeValid: check(
4579
5941
  "session_turns_latency_mode_check",
4580
5942
  sql`${table.latencyMode} in ('standard', 'priority', 'fast')`,
4581
5943
  ),
5944
+ modelContextValid: check(
5945
+ "session_turns_model_context_check",
5946
+ sql`${table.modelContext} is null
5947
+ or opengeni_private.model_context_value_valid(${table.modelContext})`,
5948
+ ),
5949
+ }),
5950
+ );
5951
+
5952
+ /** Immutable logical-turn receipt for atomic personal-resource issuance. */
5953
+ export const turnPersonalResourceAttachmentReceipts = pgTable(
5954
+ "turn_personal_resource_attachment_receipts",
5955
+ {
5956
+ turnId: uuid("turn_id")
5957
+ .primaryKey()
5958
+ .references(() => sessionTurns.id, { onDelete: "cascade" }),
5959
+ accountId: uuid("account_id").notNull(),
5960
+ workspaceId: uuid("workspace_id").notNull(),
5961
+ sessionId: uuid("session_id").notNull(),
5962
+ initiatingHumanSubjectId: text("initiating_human_subject_id").notNull(),
5963
+ ownerOrganizationMembershipId: uuid("owner_organization_membership_id").notNull(),
5964
+ membershipAuthorizationRevision: bigint("membership_authorization_revision", {
5965
+ mode: "number",
5966
+ }).notNull(),
5967
+ sessionVisibility: text("session_visibility").notNull(),
5968
+ sessionAuthorityEpoch: integer("session_authority_epoch").notNull(),
5969
+ grantMode: text("grant_mode").notNull(),
5970
+ sharedOutputWarningVersion: integer("shared_output_warning_version").notNull(),
5971
+ sharedOutputAcknowledged: boolean("shared_output_acknowledged").notNull(),
5972
+ requestDigest: bytea("request_digest").notNull(),
5973
+ resourceCount: integer("resource_count").notNull(),
5974
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
5975
+ },
5976
+ (table) => ({
5977
+ workspaceTurn: foreignKey({
5978
+ name: "turn_personal_resource_attachment_receipts_workspace_turn_fk",
5979
+ columns: [table.workspaceId, table.turnId],
5980
+ foreignColumns: [sessionTurns.workspaceId, sessionTurns.id],
5981
+ }).onDelete("cascade"),
5982
+ identity: check(
5983
+ "turn_personal_resource_attachment_receipts_identity_chk",
5984
+ sql`${table.sessionAuthorityEpoch} > 0
5985
+ and octet_length(${table.initiatingHumanSubjectId}) between 1 and 512
5986
+ and ${table.membershipAuthorizationRevision} > 0
5987
+ and ${table.resourceCount} between 1 and 28
5988
+ and ${table.grantMode} in ('once', 'session', 'always')
5989
+ and ${table.sessionVisibility} in ('user_private', 'workspace_shared')
5990
+ and ${table.sharedOutputWarningVersion} = 1
5991
+ and (${table.sessionVisibility} <> 'workspace_shared'
5992
+ or ${table.sharedOutputAcknowledged})`,
5993
+ ),
5994
+ }),
5995
+ );
5996
+
5997
+ /** Exact resource/grant authority frozen on one accepted logical turn. */
5998
+ export const turnPersonalResourceSnapshots = pgTable(
5999
+ "turn_personal_resource_snapshots",
6000
+ {
6001
+ turnId: uuid("turn_id").notNull(),
6002
+ accountId: uuid("account_id").notNull(),
6003
+ workspaceId: uuid("workspace_id").notNull(),
6004
+ sessionId: uuid("session_id").notNull(),
6005
+ resourceKind: text("resource_kind").notNull(),
6006
+ resourceId: uuid("resource_id").notNull(),
6007
+ resourceVersionId: uuid("resource_version_id"),
6008
+ selectionSources: text("selection_sources").array().notNull(),
6009
+ action: text("action").notNull(),
6010
+ originWorkspaceId: uuid("origin_workspace_id").notNull(),
6011
+ ownerOrganizationMembershipId: uuid("owner_organization_membership_id").notNull(),
6012
+ membershipAuthorizationRevision: bigint("membership_authorization_revision", {
6013
+ mode: "number",
6014
+ }).notNull(),
6015
+ authorityId: uuid("authority_id").notNull(),
6016
+ authorityGeneration: bigint("authority_generation", { mode: "number" }).notNull(),
6017
+ grantId: uuid("grant_id").notNull(),
6018
+ grantGeneration: bigint("grant_generation", { mode: "number" }).notNull(),
6019
+ grantMode: text("grant_mode").notNull(),
6020
+ grantContext: text("grant_context").notNull(),
6021
+ grantSessionId: uuid("grant_session_id"),
6022
+ grantAuthorityEpoch: integer("grant_authority_epoch"),
6023
+ canonicalDelegation: jsonb("canonical_delegation").$type<UserResourceDelegation>().notNull(),
6024
+ snapshotDigest: bytea("snapshot_digest").notNull(),
6025
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
6026
+ },
6027
+ (table) => ({
6028
+ pk: primaryKey({ columns: [table.turnId, table.resourceKind, table.resourceId] }),
6029
+ receipt: foreignKey({
6030
+ name: "turn_personal_resource_snapshots_receipt_fk",
6031
+ columns: [table.turnId],
6032
+ foreignColumns: [turnPersonalResourceAttachmentReceipts.turnId],
6033
+ }).onDelete("cascade"),
6034
+ authority: index("turn_personal_resource_snapshots_authority_idx").on(table.authorityId),
6035
+ grant: index("turn_personal_resource_snapshots_grant_idx").on(table.grantId),
6036
+ kind: check(
6037
+ "turn_personal_resource_snapshots_kind_chk",
6038
+ sql`(${table.resourceKind} = 'variable_set'
6039
+ and ${table.action} = 'variable_set.use'
6040
+ and ${table.resourceVersionId} is null)
6041
+ or (${table.resourceKind} = 'rig'
6042
+ and ${table.action} = 'rig.use'
6043
+ and ${table.resourceVersionId} is not null)
6044
+ or (${table.resourceKind} = 'connected_machine'
6045
+ and ${table.action} = 'connected_machine.use'
6046
+ and ${table.resourceVersionId} is null)`,
6047
+ ),
6048
+ generations: check(
6049
+ "turn_personal_resource_snapshots_generation_chk",
6050
+ sql`${table.membershipAuthorizationRevision} > 0
6051
+ and ${table.authorityGeneration} > 0
6052
+ and ${table.grantGeneration} > 0
6053
+ and cardinality(${table.selectionSources}) between 1 and 26`,
6054
+ ),
6055
+ grantShape: check(
6056
+ "turn_personal_resource_snapshots_grant_chk",
6057
+ sql`${table.grantMode} in ('once', 'session', 'always')
6058
+ and ${table.grantContext} in ('user_private', 'workspace_shared')
6059
+ and ((${table.grantMode} = 'always'
6060
+ and ${table.grantSessionId} is null
6061
+ and ${table.grantAuthorityEpoch} is null)
6062
+ or (${table.grantMode} in ('once', 'session')
6063
+ and ${table.grantSessionId} = ${table.sessionId}
6064
+ and ${table.grantAuthorityEpoch} > 0))`,
6065
+ ),
6066
+ }),
6067
+ );
6068
+
6069
+ /** One once grant may belong to one accepted logical turn only. */
6070
+ export const turnPersonalResourceOnceReceipts = pgTable(
6071
+ "turn_personal_resource_once_receipts",
6072
+ {
6073
+ grantId: uuid("grant_id").primaryKey(),
6074
+ turnId: uuid("turn_id").notNull(),
6075
+ accountId: uuid("account_id").notNull(),
6076
+ authorityId: uuid("authority_id").notNull(),
6077
+ authorityGeneration: bigint("authority_generation", { mode: "number" }).notNull(),
6078
+ grantGeneration: bigint("grant_generation", { mode: "number" }).notNull(),
6079
+ consumedAt: timestamp("consumed_at", { withTimezone: true }).notNull().defaultNow(),
6080
+ },
6081
+ (table) => ({
6082
+ receipt: foreignKey({
6083
+ name: "turn_personal_resource_once_receipts_turn_fk",
6084
+ columns: [table.turnId],
6085
+ foreignColumns: [turnPersonalResourceAttachmentReceipts.turnId],
6086
+ }).onDelete("cascade"),
6087
+ generations: check(
6088
+ "turn_personal_resource_once_receipts_generation_chk",
6089
+ sql`${table.authorityGeneration} > 0 and ${table.grantGeneration} > 0`,
6090
+ ),
4582
6091
  }),
4583
6092
  );
4584
6093
 
@@ -4670,6 +6179,9 @@ export const sessionTurnAttempts = pgTable(
4670
6179
  authorityEpoch: integer("authority_epoch").notNull(),
4671
6180
  authorityVisibility: text("authority_visibility").notNull(),
4672
6181
  authorityOwnerOrganizationMembershipId: uuid("authority_owner_organization_membership_id"),
6182
+ personalResourceProtocolVersion: integer("personal_resource_protocol_version")
6183
+ .notNull()
6184
+ .default(0),
4673
6185
  // Immutable policy snapshot captured under the session lock at claim.
4674
6186
  mcpApprovalPolicies: jsonb("mcp_approval_policies")
4675
6187
  .$type<Record<string, SessionMcpApprovalPolicy>>()
@@ -4698,7 +6210,7 @@ export const sessionTurnAttempts = pgTable(
4698
6210
  name: "session_turn_attempts_workspace_session_fk",
4699
6211
  columns: [table.workspaceId, table.sessionId],
4700
6212
  foreignColumns: [sessions.workspaceId, sessions.id],
4701
- }).onDelete("restrict"),
6213
+ }).onDelete("cascade"),
4702
6214
  workspaceTurn: foreignKey({
4703
6215
  name: "session_turn_attempts_workspace_turn_fk",
4704
6216
  columns: [table.workspaceId, table.turnId],
@@ -4782,6 +6294,202 @@ export const sessionTurnAttempts = pgTable(
4782
6294
  }),
4783
6295
  );
4784
6296
 
6297
+ // Per-turn startup SLO checkpoint ledger (migration 0318). One row per
6298
+ // (turn, milestone, outcome); the transaction whose
6299
+ // `insert ... on conflict do nothing returning` returns the row is the
6300
+ // canonical inserter and the sole metric receipt, so recovery and replay
6301
+ // conflict without re-reading the turn's session_events. `pre_ledger_history`
6302
+ // rows seal a turn whose startup predates the ledger.
6303
+ export const sessionTurnStartupMilestones = pgTable(
6304
+ "session_turn_startup_milestones",
6305
+ {
6306
+ accountId: uuid("account_id").notNull(),
6307
+ workspaceId: uuid("workspace_id").notNull(),
6308
+ sessionId: uuid("session_id").notNull(),
6309
+ turnId: uuid("turn_id").notNull(),
6310
+ milestone: text("milestone").notNull(),
6311
+ outcome: text("outcome").notNull(),
6312
+ canonicalSource: text("canonical_source").notNull(),
6313
+ eventId: uuid("event_id"),
6314
+ occurredAt: timestamp("occurred_at", { withTimezone: true }),
6315
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
6316
+ },
6317
+ (table) => ({
6318
+ pk: primaryKey({
6319
+ name: "session_turn_startup_milestones_pkey",
6320
+ columns: [table.workspaceId, table.turnId, table.milestone, table.outcome],
6321
+ }),
6322
+ workspaceAccount: foreignKey({
6323
+ name: "session_turn_startup_milestones_workspace_account_fk",
6324
+ columns: [table.workspaceId, table.accountId],
6325
+ foreignColumns: [workspaces.id, workspaces.accountId],
6326
+ }).onDelete("cascade"),
6327
+ workspaceSession: foreignKey({
6328
+ name: "session_turn_startup_milestones_workspace_session_fk",
6329
+ columns: [table.workspaceId, table.sessionId],
6330
+ foreignColumns: [sessions.workspaceId, sessions.id],
6331
+ }).onDelete("cascade"),
6332
+ workspaceTurn: foreignKey({
6333
+ name: "session_turn_startup_milestones_workspace_turn_fk",
6334
+ columns: [table.workspaceId, table.turnId],
6335
+ foreignColumns: [sessionTurns.workspaceId, sessionTurns.id],
6336
+ }).onDelete("cascade"),
6337
+ workspaceSessionIndex: index("session_turn_startup_milestones_workspace_session_idx").on(
6338
+ table.workspaceId,
6339
+ table.sessionId,
6340
+ ),
6341
+ milestoneCheck: check(
6342
+ "session_turn_startup_milestones_milestone_chk",
6343
+ sql`${table.milestone} in ('queue', 'provider_dispatch', 'first_byte')`,
6344
+ ),
6345
+ outcomeCheck: check(
6346
+ "session_turn_startup_milestones_outcome_chk",
6347
+ sql`${table.outcome} in ('completed', 'failed')`,
6348
+ ),
6349
+ checkpointCheck: check(
6350
+ "session_turn_startup_milestones_checkpoint_chk",
6351
+ sql`${table.outcome} = 'completed' or ${table.milestone} = 'first_byte'`,
6352
+ ),
6353
+ canonicalSourceCheck: check(
6354
+ "session_turn_startup_milestones_canonical_source_chk",
6355
+ sql`(${table.canonicalSource} = 'inserted_event' and ${table.eventId} is not null and ${table.occurredAt} is not null) or (${table.canonicalSource} = 'pre_ledger_history' and ${table.outcome} = 'completed' and ${table.eventId} is null and ${table.occurredAt} is null)`,
6356
+ ),
6357
+ }),
6358
+ );
6359
+
6360
+ // Credential-free authority captured with the accepted logical turn. Runtime
6361
+ // credential resolution loads this canonical row by exact turn + tool surface;
6362
+ // a caller-provided snapshot is never authority.
6363
+ export const turnConnectionAuthoritySnapshots = pgTable(
6364
+ "turn_connection_authority_snapshots",
6365
+ {
6366
+ accountId: uuid("account_id").notNull(),
6367
+ workspaceId: uuid("workspace_id").notNull(),
6368
+ sessionId: uuid("session_id").notNull(),
6369
+ turnId: uuid("turn_id").notNull(),
6370
+ serverId: text("server_id").notNull(),
6371
+ connectionId: uuid("connection_id").notNull(),
6372
+ connectionGeneration: bigint("connection_generation", { mode: "number" }).notNull(),
6373
+ originWorkspaceId: uuid("origin_workspace_id").notNull(),
6374
+ providerDomain: text("provider_domain").notNull(),
6375
+ connectionKind: text("connection_kind").notNull(),
6376
+ authorityScope: text("authority_scope").notNull(),
6377
+ authoritySource: text("authority_source").notNull(),
6378
+ ownerSubjectId: text("owner_subject_id"),
6379
+ ownerOrganizationMembershipId: uuid("owner_organization_membership_id"),
6380
+ membershipAuthorizationRevision: bigint("membership_authorization_revision", {
6381
+ mode: "number",
6382
+ }),
6383
+ authorityId: uuid("authority_id"),
6384
+ authorityGeneration: bigint("authority_generation", { mode: "number" }),
6385
+ grantId: uuid("grant_id"),
6386
+ grantGeneration: bigint("grant_generation", { mode: "number" }),
6387
+ grantMode: text("grant_mode"),
6388
+ grantContext: text("grant_context"),
6389
+ grantSessionId: uuid("grant_session_id"),
6390
+ grantAuthorityEpoch: integer("grant_authority_epoch"),
6391
+ sessionVisibility: text("session_visibility").notNull(),
6392
+ sessionAuthorityEpoch: integer("session_authority_epoch").notNull(),
6393
+ canonicalSnapshot: jsonb("canonical_snapshot").$type<Record<string, unknown>>().notNull(),
6394
+ snapshotDigest: bytea("snapshot_digest").notNull(),
6395
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
6396
+ },
6397
+ (table) => ({
6398
+ identity: primaryKey({ columns: [table.turnId, table.serverId] }),
6399
+ turn: foreignKey({
6400
+ name: "turn_connection_authority_turn_fk",
6401
+ columns: [table.workspaceId, table.turnId],
6402
+ foreignColumns: [sessionTurns.workspaceId, sessionTurns.id],
6403
+ }).onDelete("cascade"),
6404
+ connection: foreignKey({
6405
+ name: "turn_connection_authority_connection_fk",
6406
+ columns: [table.connectionId],
6407
+ foreignColumns: [connections.id],
6408
+ }).onDelete("restrict"),
6409
+ membership: foreignKey({
6410
+ name: "turn_connection_authority_membership_fk",
6411
+ columns: [table.ownerOrganizationMembershipId, table.accountId],
6412
+ foreignColumns: [organizationMemberships.id, organizationMemberships.accountId],
6413
+ }).onDelete("restrict"),
6414
+ authority: foreignKey({
6415
+ name: "turn_connection_authority_authority_fk",
6416
+ columns: [table.authorityId, table.accountId],
6417
+ foreignColumns: [
6418
+ organizationUserResourceAuthorities.id,
6419
+ organizationUserResourceAuthorities.accountId,
6420
+ ],
6421
+ }).onDelete("restrict"),
6422
+ grant: foreignKey({
6423
+ name: "turn_connection_authority_grant_fk",
6424
+ columns: [table.grantId, table.accountId],
6425
+ foreignColumns: [organizationUserResourceGrants.id, organizationUserResourceGrants.accountId],
6426
+ }).onDelete("restrict"),
6427
+ connectionLookup: index("turn_connection_authority_connection_idx").on(
6428
+ table.accountId,
6429
+ table.connectionId,
6430
+ table.turnId,
6431
+ table.serverId,
6432
+ ),
6433
+ serverValid: check(
6434
+ "turn_connection_authority_server_check",
6435
+ sql`octet_length(${table.serverId}) between 1 and 256`,
6436
+ ),
6437
+ generationValid: check(
6438
+ "turn_connection_authority_generation_check",
6439
+ sql`${table.connectionGeneration} > 0 and ${table.sessionAuthorityEpoch} > 0`,
6440
+ ),
6441
+ }),
6442
+ );
6443
+
6444
+ // One metadata-only authorization fact per caller-preallocated physical
6445
+ // provider request. No credential, header, body, provider response, or content
6446
+ // is retained in this table.
6447
+ export const connectionUseAuditFacts = pgTable(
6448
+ "connection_use_audit_facts",
6449
+ {
6450
+ physicalRequestId: uuid("physical_request_id").primaryKey(),
6451
+ usePhase: text("use_phase").notNull(),
6452
+ requestDigest: bytea("request_digest").notNull(),
6453
+ accountId: uuid("account_id").notNull(),
6454
+ workspaceId: uuid("workspace_id").notNull(),
6455
+ sessionId: uuid("session_id").notNull(),
6456
+ turnId: uuid("turn_id").notNull(),
6457
+ attemptId: uuid("attempt_id").notNull(),
6458
+ executionGeneration: integer("execution_generation").notNull(),
6459
+ serverId: text("server_id").notNull(),
6460
+ connectionId: uuid("connection_id"),
6461
+ connectionGeneration: bigint("connection_generation", { mode: "number" }),
6462
+ authorityScope: text("authority_scope"),
6463
+ ownerSubjectId: text("owner_subject_id"),
6464
+ authorityId: uuid("authority_id"),
6465
+ grantId: uuid("grant_id"),
6466
+ initiatorKind: text("initiator_kind"),
6467
+ initiatorSubjectId: text("initiator_subject_id"),
6468
+ initiatingHumanSubjectId: text("initiating_human_subject_id"),
6469
+ authorityEpoch: integer("authority_epoch"),
6470
+ authorityVisibility: text("authority_visibility"),
6471
+ authorityOwnerOrganizationMembershipId: uuid("authority_owner_organization_membership_id"),
6472
+ outcome: text("outcome").notNull(),
6473
+ denialReason: text("denial_reason"),
6474
+ occurredAt: timestamp("occurred_at", { withTimezone: true }).notNull().defaultNow(),
6475
+ },
6476
+ (table) => ({
6477
+ attempt: index("connection_use_audit_attempt_idx").on(
6478
+ table.workspaceId,
6479
+ table.attemptId,
6480
+ table.occurredAt,
6481
+ ),
6482
+ shapeValid: check(
6483
+ "connection_use_audit_shape_check",
6484
+ sql`octet_length(${table.serverId}) between 1 and 256
6485
+ and octet_length(${table.requestDigest}) = 32
6486
+ and ${table.executionGeneration} > 0
6487
+ and ${table.usePhase} in ('credential_resolution', 'provider_request')
6488
+ and ${table.outcome} in ('authorized', 'denied')`,
6489
+ ),
6490
+ }),
6491
+ );
6492
+
4785
6493
  // Immutable executable tool universe admitted to one exact attempt. Model MCP
4786
6494
  // and sandbox Codemode use the same digest and opaque identities from this row.
4787
6495
  export const sessionAttemptToolCatalogs = pgTable(
@@ -5185,7 +6893,7 @@ export const sessionCommandReceipts = pgTable(
5185
6893
  name: "session_command_receipts_target_session_fk",
5186
6894
  columns: [table.workspaceId, table.targetSessionId],
5187
6895
  foreignColumns: [sessions.workspaceId, sessions.id],
5188
- }).onDelete("restrict"),
6896
+ }).onDelete("cascade"),
5189
6897
  targetTurn: foreignKey({
5190
6898
  name: "session_command_receipts_target_turn_fk",
5191
6899
  columns: [table.workspaceId, table.targetTurnId],
@@ -5251,7 +6959,7 @@ export const workspaceControlEvents = pgTable(
5251
6959
  name: "workspace_control_events_root_session_fk",
5252
6960
  columns: [table.workspaceId, table.rootSessionId],
5253
6961
  foreignColumns: [sessions.workspaceId, sessions.id],
5254
- }).onDelete("restrict"),
6962
+ }).onDelete("cascade"),
5255
6963
  workspaceRevision: uniqueIndex("workspace_control_events_workspace_revision_uq").on(
5256
6964
  table.workspaceId,
5257
6965
  table.revision,
@@ -5299,7 +7007,7 @@ export const sessionAttemptInterruptions = pgTable(
5299
7007
  name: "session_attempt_interruptions_workspace_session_fk",
5300
7008
  columns: [table.workspaceId, table.sessionId],
5301
7009
  foreignColumns: [sessions.workspaceId, sessions.id],
5302
- }).onDelete("restrict"),
7010
+ }).onDelete("cascade"),
5303
7011
  operation: foreignKey({
5304
7012
  name: "session_attempt_interruptions_operation_fk",
5305
7013
  columns: [table.workspaceId, table.operationId],
@@ -5319,7 +7027,7 @@ export const sessionAttemptInterruptions = pgTable(
5319
7027
  .where(sql`${table.state} in ('pending', 'delivered', 'acknowledged')`),
5320
7028
  kindValid: check(
5321
7029
  "session_attempt_interruptions_kind_check",
5322
- sql`${table.kind} in ('session_pause', 'workspace_pause', 'steer', 'maintenance')`,
7030
+ sql`${table.kind} in ('session_pause', 'workspace_pause', 'steer', 'maintenance', 'authority_change', 'organization_membership_revoked')`,
5323
7031
  ),
5324
7032
  stateValid: check(
5325
7033
  "session_attempt_interruptions_state_check",
@@ -5459,6 +7167,9 @@ export const sessionSystemUpdates = pgTable(
5459
7167
  .$type<XaiProviderAccountAuthoritySnapshotV1>()
5460
7168
  .notNull()
5461
7169
  .default(WORKSPACE_XAI_PROVIDER_ACCOUNT_AUTHORITY_SNAPSHOT_V1),
7170
+ // Private scheduled-occurrence authority linkage. Public update/event
7171
+ // projections intentionally omit this producer identifier.
7172
+ scheduledTaskRunId: uuid("scheduled_task_run_id"),
5462
7173
  // pending is visible queue truth; delivered means its exact model-memory
5463
7174
  // batch was durably claimed. Terminal cancellation/supersession is explicit.
5464
7175
  state: text("state").notNull().default("pending"),
@@ -5474,7 +7185,7 @@ export const sessionSystemUpdates = pgTable(
5474
7185
  (table) => ({
5475
7186
  kindValid: check(
5476
7187
  "system_updates_kind_check",
5477
- sql`${table.kind} in ('scheduled_occurrence', 'goal_continuation', 'agent_message', 'agent_steer_instruction', 'child_terminal_result', 'media_generation_result')`,
7188
+ sql`${table.kind} in ('scheduled_occurrence', 'goal_continuation', 'agent_message', 'agent_steer_instruction', 'child_terminal_result', 'media_generation_result', 'child_requires_action', 'child_requires_action_resolved', 'child_paused', 'child_waiting_capacity', 'child_progress')`,
5478
7189
  ),
5479
7190
  payloadKindValid: check(
5480
7191
  "system_updates_payload_kind_check",
@@ -5495,6 +7206,11 @@ export const sessionSystemUpdates = pgTable(
5495
7206
  table.state,
5496
7207
  table.createdAt,
5497
7208
  ),
7209
+ // Producer-side supersession / resolution of one child's pending notices
7210
+ // of one kind on the parent (migration 0325).
7211
+ pendingKindSource: index("session_system_updates_pending_kind_source_idx")
7212
+ .on(table.workspaceId, table.sessionId, table.kind, table.sourceId)
7213
+ .where(sql`${table.state} = 'pending'`),
5498
7214
  onePendingSteer: uniqueIndex("session_system_updates_one_pending_steer_idx")
5499
7215
  .on(table.workspaceId, table.sessionId)
5500
7216
  .where(sql`${table.kind} = 'agent_steer_instruction' and ${table.state} = 'pending'`),
@@ -5561,11 +7277,11 @@ export const sessionSystemUpdateOutbox = pgTable(
5561
7277
  (table) => ({
5562
7278
  kindValid: check(
5563
7279
  "system_update_outbox_kind_check",
5564
- sql`${table.kind} = 'child_terminal_result'`,
7280
+ sql`${table.kind} in ('child_terminal_result', 'child_requires_action', 'child_requires_action_resolved', 'child_paused', 'child_waiting_capacity', 'child_progress')`,
5565
7281
  ),
5566
7282
  payloadKindValid: check(
5567
7283
  "system_update_outbox_payload_kind_check",
5568
- sql`${table.payload} ->> 'type' = 'child_terminal_result'`,
7284
+ sql`${table.payload} ->> 'type' = ${table.kind}`,
5569
7285
  ),
5570
7286
  dedupe: uniqueIndex("session_system_update_outbox_dedupe_uq").on(
5571
7287
  table.workspaceId,
@@ -5647,6 +7363,7 @@ export const sessionGoals = pgTable(
5647
7363
  status: text("status").notNull().default("active"), // active | paused | completed
5648
7364
  text: text("text").notNull(),
5649
7365
  successCriteria: text("success_criteria"),
7366
+ rootConstraints: jsonb("root_constraints").$type<string[]>().notNull().default([]),
5650
7367
  evidence: text("evidence"), // set by goal_complete
5651
7368
  rationale: text("rationale"), // set by goal_pause
5652
7369
  pausedReason: text("paused_reason"), // agent | user_pause | api | no_progress | max_auto_continuations | limits
@@ -5680,6 +7397,15 @@ export const sessionGoals = pgTable(
5680
7397
  })
5681
7398
  .notNull()
5682
7399
  .default(0),
7400
+ // Agent-declared continuation hold (`goal_wait`, migration 0317). Honored
7401
+ // only while `continuationHoldTurnId` is still the latest finished turn and
7402
+ // the deadline has not passed; it never consumes the wake/observed ledger.
7403
+ // Any newer finished turn, a passed deadline, or a goal mutation clears all
7404
+ // four columns together.
7405
+ continuationHoldTurnId: uuid("continuation_hold_turn_id"),
7406
+ continuationHoldUntil: timestamp("continuation_hold_until", { withTimezone: true }),
7407
+ continuationHoldReason: text("continuation_hold_reason"),
7408
+ continuationHoldSetAt: timestamp("continuation_hold_set_at", { withTimezone: true }),
5683
7409
  metadata: jsonb("metadata").$type<Record<string, unknown>>().notNull().default({}),
5684
7410
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
5685
7411
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
@@ -5698,6 +7424,10 @@ export const sessionGoals = pgTable(
5698
7424
  "session_goals_continuation_revision_check",
5699
7425
  sql`${table.continuationWakeRevision} >= 0 and ${table.continuationObservedRevision} >= 0 and ${table.continuationObservedRevision} <= ${table.continuationWakeRevision} and ${table.continuationWakeRevision} <= 9007199254740991 and ${table.continuationObservedRevision} <= 9007199254740991`,
5700
7426
  ),
7427
+ continuationHoldValid: check(
7428
+ "session_goals_continuation_hold_check",
7429
+ sql`(${table.continuationHoldTurnId} is null and ${table.continuationHoldUntil} is null and ${table.continuationHoldReason} is null and ${table.continuationHoldSetAt} is null) or (${table.continuationHoldTurnId} is not null and ${table.continuationHoldUntil} is not null and ${table.continuationHoldSetAt} is not null and (${table.continuationHoldReason} is null or octet_length(${table.continuationHoldReason}) <= 2048))`,
7430
+ ),
5701
7431
  }),
5702
7432
  );
5703
7433
 
@@ -5720,12 +7450,14 @@ export const sessionGoalRevisions = pgTable(
5720
7450
  resultObjectiveRevision: integer("result_objective_revision"),
5721
7451
  text: text("text").notNull(),
5722
7452
  successCriteria: text("success_criteria"),
7453
+ rootConstraints: jsonb("root_constraints").$type<string[]>().notNull().default([]),
5723
7454
  mutationPolicy: text("mutation_policy").$type<SessionGoalMutationPolicy>().notNull(),
5724
7455
  rationale: text("rationale").notNull(),
5725
7456
  actor: text("actor").$type<"agent" | "api" | "scheduled_task">().notNull(),
5726
7457
  actorTurnId: uuid("actor_turn_id"),
5727
7458
  actorAttemptId: uuid("actor_attempt_id"),
5728
7459
  proposalId: uuid("proposal_id"),
7460
+ rollbackOfRevisionId: uuid("rollback_of_revision_id"),
5729
7461
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
5730
7462
  },
5731
7463
  (table) => ({
@@ -5754,6 +7486,11 @@ export const sessionGoalRevisions = pgTable(
5754
7486
  columns: [table.workspaceId, table.proposalId],
5755
7487
  foreignColumns: [table.workspaceId, table.id],
5756
7488
  }).onDelete("restrict"),
7489
+ rollbackOfRevision: foreignKey({
7490
+ name: "session_goal_revisions_rollback_of_revision_fk",
7491
+ columns: [table.workspaceId, table.rollbackOfRevisionId],
7492
+ foreignColumns: [table.workspaceId, table.id],
7493
+ }).onDelete("restrict"),
5757
7494
  appliedRevision: uniqueIndex("session_goal_revisions_applied_revision_uq")
5758
7495
  .on(table.workspaceId, table.goalId, table.resultObjectiveRevision)
5759
7496
  .where(sql`${table.disposition} = 'applied'`),
@@ -5763,6 +7500,14 @@ export const sessionGoalRevisions = pgTable(
5763
7500
  table.createdAt,
5764
7501
  table.id,
5765
7502
  ),
7503
+ proposalDecision: uniqueIndex("session_goal_revisions_proposal_decision_uq")
7504
+ .on(table.workspaceId, table.proposalId)
7505
+ .where(
7506
+ sql`${table.proposalId} is not null and ${table.disposition} in ('applied', 'rejected')`,
7507
+ ),
7508
+ rollbackRequest: uniqueIndex("session_goal_revisions_rollback_request_uq")
7509
+ .on(table.workspaceId, table.goalId, table.rollbackOfRevisionId, table.baseObjectiveRevision)
7510
+ .where(sql`${table.disposition} = 'applied' and ${table.rollbackOfRevisionId} is not null`),
5766
7511
  dispositionValid: check(
5767
7512
  "session_goal_revisions_disposition_chk",
5768
7513
  sql`${table.disposition} in ('applied', 'proposed', 'rejected')`,
@@ -5780,6 +7525,12 @@ export const sessionGoalRevisions = pgTable(
5780
7525
  sql`(${table.disposition} = 'applied' and ${table.resultObjectiveRevision} = ${table.baseObjectiveRevision} + 1)
5781
7526
  or (${table.disposition} in ('proposed', 'rejected') and ${table.resultObjectiveRevision} is null)`,
5782
7527
  ),
7528
+ lineageShape: check(
7529
+ "session_goal_revisions_lineage_shape_chk",
7530
+ sql`(${table.disposition} = 'proposed' and ${table.proposalId} is null and ${table.rollbackOfRevisionId} is null)
7531
+ or (${table.disposition} = 'rejected' and ${table.proposalId} is not null and ${table.rollbackOfRevisionId} is null)
7532
+ or (${table.disposition} = 'applied' and not (${table.proposalId} is not null and ${table.rollbackOfRevisionId} is not null))`,
7533
+ ),
5783
7534
  }),
5784
7535
  );
5785
7536
 
@@ -6256,6 +8007,12 @@ export const sessionPendingToolCalls = pgTable(
6256
8007
  callType: text("call_type").notNull(),
6257
8008
  callItem: losslessJsonb("call_item").$type<Record<string, unknown>>().notNull(),
6258
8009
  callItemCodecVersion: losslessCodecVersion("call_item_codec_version"),
8010
+ interruptionKind: text("interruption_kind"),
8011
+ tiedReasoningItems: losslessJsonb("tied_reasoning_items")
8012
+ .$type<Array<Record<string, unknown>>>()
8013
+ .notNull()
8014
+ .default([]),
8015
+ tiedReasoningItemsCodecVersion: losslessCodecVersion("tied_reasoning_items_codec_version"),
6259
8016
  modelToolOutputTruncationTokens: integer("model_tool_output_truncation_tokens"),
6260
8017
  resultItem: losslessJsonb("result_item").$type<Record<string, unknown>>(),
6261
8018
  resultItemCodecVersion: losslessCodecVersion("result_item_codec_version"),
@@ -6280,6 +8037,14 @@ export const sessionPendingToolCalls = pgTable(
6280
8037
  table.sessionId,
6281
8038
  table.turnId,
6282
8039
  ),
8040
+ interruptionKindValid: check(
8041
+ "session_pending_tool_calls_interruption_kind_chk",
8042
+ sql`${table.interruptionKind} is null or ${table.interruptionKind} in ('human_input', 'approval', 'interaction_intervention')`,
8043
+ ),
8044
+ tiedReasoningItemsArray: check(
8045
+ "session_pending_tool_calls_tied_reasoning_items_chk",
8046
+ sql`jsonb_typeof(${table.tiedReasoningItems}) = 'array'`,
8047
+ ),
6283
8048
  }),
6284
8049
  );
6285
8050
 
@@ -6718,6 +8483,11 @@ export const sandboxLeaseHolders = pgTable(
6718
8483
  holderId: text("holder_id").notNull(),
6719
8484
  // The attributing session within the (possibly shared) group.
6720
8485
  subjectId: uuid("subject_id"),
8486
+ // Viewer holders only (0281): the authenticated viewer subject and the
8487
+ // session authority epoch observed at attach - identities and epochs
8488
+ // only, mirrored into the scoped stream token's claims.
8489
+ viewerSubjectId: text("viewer_subject_id"),
8490
+ viewerAuthorityEpoch: integer("viewer_authority_epoch"),
6721
8491
  lastHeartbeatAt: timestamp("last_heartbeat_at", { withTimezone: true }).notNull().defaultNow(),
6722
8492
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
6723
8493
  },
@@ -6739,6 +8509,14 @@ export const sandboxLeaseHolders = pgTable(
6739
8509
 
6740
8510
  export const sandboxWorkspaceMutationActorKindValues = ["turn", "direct", "process"] as const;
6741
8511
  export const sandboxWorkspaceMutationHolderKindValues = ["turn", "direct", "process"] as const;
8512
+ // The causal principal behind an admitted workspace writer. `subject`/`service`
8513
+ // are the canonical `session_turns` initiator vocabulary; `legacy_unattributed`
8514
+ // exists only for rows written before migration 0277 recorded any authority.
8515
+ export const sandboxWorkspaceMutationInitiatorKindValues = [
8516
+ "subject",
8517
+ "service",
8518
+ "legacy_unattributed",
8519
+ ] as const;
6742
8520
 
6743
8521
  // Durable admission ledger for every provider operation that may mutate a
6744
8522
  // persistable /workspace. The row is inserted atomically with the lease's
@@ -6783,6 +8561,29 @@ export const sandboxWorkspaceMutationAdmissions = pgTable(
6783
8561
  routeEpoch: integer("route_epoch").notNull(),
6784
8562
  workspaceGeneration: integer("workspace_generation").notNull(),
6785
8563
  operation: text("operation").notNull(),
8564
+ // Exact authority admitted with this operation (migration 0277). Identities
8565
+ // and epochs only, never a secret value. `legacy_unattributed` marks a
8566
+ // pre-0277 `direct`/`process` row whose authority was never recorded; a
8567
+ // post-0277 writer never produces it and new admission refuses it.
8568
+ initiatorKind: text("initiator_kind", {
8569
+ enum: sandboxWorkspaceMutationInitiatorKindValues,
8570
+ })
8571
+ .notNull()
8572
+ .default("legacy_unattributed"),
8573
+ initiatorSubjectId: text("initiator_subject_id").notNull().default("unattributed-legacy"),
8574
+ initiatingHumanSubjectId: text("initiating_human_subject_id"),
8575
+ // The grant identity that authorized the causal human, plus the
8576
+ // authorization revision observed at admission. The revision is audit
8577
+ // evidence: a role change is not a revocation and must not fence a writer.
8578
+ initiatorOrganizationMembershipId: uuid("initiator_organization_membership_id"),
8579
+ initiatorAuthorizationRevision: bigint("initiator_authorization_revision", { mode: "number" }),
8580
+ // Session tenancy authority observed when the operation was admitted; the
8581
+ // same triple `session_turn_attempts` freezes for a turn.
8582
+ authorityEpoch: integer("authority_epoch"),
8583
+ authorityVisibility: text("authority_visibility", {
8584
+ enum: ["user_private", "workspace_shared"],
8585
+ }),
8586
+ authorityOwnerOrganizationMembershipId: uuid("authority_owner_organization_membership_id"),
6786
8587
  providerOutcome: text("provider_outcome", {
6787
8588
  enum: ["resolved", "rejected", "retained"],
6788
8589
  }),
@@ -6833,6 +8634,65 @@ export const sandboxWorkspaceMutationAdmissions = pgTable(
6833
8634
  table.actorKind,
6834
8635
  table.actorId,
6835
8636
  ),
8637
+ initiatorMembership: foreignKey({
8638
+ name: "sandbox_workspace_mutation_admissions_initiator_membership_fk",
8639
+ columns: [table.initiatorOrganizationMembershipId, table.accountId],
8640
+ foreignColumns: [organizationMemberships.id, organizationMemberships.accountId],
8641
+ }).onDelete("restrict"),
8642
+ authorityOwnerMembership: foreignKey({
8643
+ name: "sandbox_workspace_mutation_admissions_authority_owner_fk",
8644
+ columns: [table.authorityOwnerOrganizationMembershipId, table.accountId],
8645
+ foreignColumns: [organizationMemberships.id, organizationMemberships.accountId],
8646
+ }).onDelete("restrict"),
8647
+ initiator: index("sandbox_workspace_mutation_admissions_initiator_idx")
8648
+ .on(table.accountId, table.initiatorOrganizationMembershipId)
8649
+ .where(sql`${table.settledAt} is null`),
8650
+ initiatorValid: check(
8651
+ "sandbox_workspace_mutation_admissions_initiator_check",
8652
+ sql`(
8653
+ ${table.initiatorKind} = 'legacy_unattributed'
8654
+ and ${table.initiatorSubjectId} = 'unattributed-legacy'
8655
+ and ${table.initiatingHumanSubjectId} is null
8656
+ and ${table.initiatorOrganizationMembershipId} is null
8657
+ and ${table.initiatorAuthorizationRevision} is null
8658
+ ) or (
8659
+ ${table.initiatorKind} in ('subject', 'service')
8660
+ and length(btrim(${table.initiatorSubjectId})) between 1 and 1024
8661
+ and ${table.initiatorSubjectId} <> 'unattributed-legacy'
8662
+ and (
8663
+ ${table.initiatingHumanSubjectId} is null
8664
+ or length(btrim(${table.initiatingHumanSubjectId})) between 1 and 1024
8665
+ )
8666
+ and (
8667
+ ${table.initiatorOrganizationMembershipId} is null
8668
+ or ${table.initiatingHumanSubjectId} is not null
8669
+ )
8670
+ and (
8671
+ ${table.initiatorAuthorizationRevision} is null
8672
+ or (
8673
+ ${table.initiatorAuthorizationRevision} > 0
8674
+ and ${table.initiatorOrganizationMembershipId} is not null
8675
+ )
8676
+ )
8677
+ )`,
8678
+ ),
8679
+ tenancyValid: check(
8680
+ "sandbox_workspace_mutation_admissions_tenancy_check",
8681
+ sql`(
8682
+ ${table.authorityEpoch} is null
8683
+ and ${table.authorityVisibility} is null
8684
+ and ${table.authorityOwnerOrganizationMembershipId} is null
8685
+ ) or (
8686
+ ${table.authorityEpoch} is not null
8687
+ and ${table.authorityVisibility} is not null
8688
+ and ${table.authorityEpoch} > 0
8689
+ and ${table.authorityVisibility} in ('user_private', 'workspace_shared')
8690
+ and (
8691
+ ${table.authorityVisibility} <> 'user_private'
8692
+ or ${table.authorityOwnerOrganizationMembershipId} is not null
8693
+ )
8694
+ )`,
8695
+ ),
6836
8696
  generationValid: check(
6837
8697
  "sandbox_workspace_mutation_admissions_generation_check",
6838
8698
  sql`${table.workspaceGeneration} > 0
@@ -6927,6 +8787,23 @@ export const sandboxRetainedProcesses = pgTable(
6927
8787
  routeTargetId: uuid("route_target_id"),
6928
8788
  routeEpoch: integer("route_epoch").notNull(),
6929
8789
  providerSessionId: integer("provider_session_id").notNull(),
8790
+ // Authority frozen when the process was retained (migration 0277). A
8791
+ // `legacy_unattributed` process keeps running; only its next workspace
8792
+ // mutation is refused, because nothing may invent an owner for it.
8793
+ initiatorKind: text("initiator_kind", {
8794
+ enum: sandboxWorkspaceMutationInitiatorKindValues,
8795
+ })
8796
+ .notNull()
8797
+ .default("legacy_unattributed"),
8798
+ initiatorSubjectId: text("initiator_subject_id").notNull().default("unattributed-legacy"),
8799
+ initiatingHumanSubjectId: text("initiating_human_subject_id"),
8800
+ initiatorOrganizationMembershipId: uuid("initiator_organization_membership_id"),
8801
+ initiatorAuthorizationRevision: bigint("initiator_authorization_revision", { mode: "number" }),
8802
+ authorityEpoch: integer("authority_epoch"),
8803
+ authorityVisibility: text("authority_visibility", {
8804
+ enum: ["user_private", "workspace_shared"],
8805
+ }),
8806
+ authorityOwnerOrganizationMembershipId: uuid("authority_owner_organization_membership_id"),
6930
8807
  state: text("state", { enum: sandboxRetainedProcessStateValues }).notNull().default("active"),
6931
8808
  exitCode: integer("exit_code"),
6932
8809
  settlementReason: text("settlement_reason"),
@@ -7009,6 +8886,68 @@ export const sandboxRetainedProcesses = pgTable(
7009
8886
  activeInventory: index("sandbox_retained_processes_active_inventory_idx")
7010
8887
  .on(table.ownerActorKind, table.workspaceId, table.ownerTurnId, table.ownerAttemptId)
7011
8888
  .where(sql`${table.state} = 'active'`),
8889
+ initiatorMembership: foreignKey({
8890
+ name: "sandbox_retained_processes_initiator_membership_fk",
8891
+ columns: [table.initiatorOrganizationMembershipId, table.accountId],
8892
+ foreignColumns: [organizationMemberships.id, organizationMemberships.accountId],
8893
+ }).onDelete("restrict"),
8894
+ authorityOwnerMembership: foreignKey({
8895
+ name: "sandbox_retained_processes_authority_owner_fk",
8896
+ columns: [table.authorityOwnerOrganizationMembershipId, table.accountId],
8897
+ foreignColumns: [organizationMemberships.id, organizationMemberships.accountId],
8898
+ }).onDelete("restrict"),
8899
+ initiator: index("sandbox_retained_processes_initiator_idx")
8900
+ .on(table.accountId, table.initiatorOrganizationMembershipId)
8901
+ .where(sql`${table.state} = 'active'`),
8902
+ initiatingHuman: index("sandbox_retained_processes_initiating_human_idx")
8903
+ .on(table.accountId, table.initiatingHumanSubjectId)
8904
+ .where(sql`${table.state} = 'active'`),
8905
+ initiatorValid: check(
8906
+ "sandbox_retained_processes_initiator_check",
8907
+ sql`(
8908
+ ${table.initiatorKind} = 'legacy_unattributed'
8909
+ and ${table.initiatorSubjectId} = 'unattributed-legacy'
8910
+ and ${table.initiatingHumanSubjectId} is null
8911
+ and ${table.initiatorOrganizationMembershipId} is null
8912
+ and ${table.initiatorAuthorizationRevision} is null
8913
+ ) or (
8914
+ ${table.initiatorKind} in ('subject', 'service')
8915
+ and length(btrim(${table.initiatorSubjectId})) between 1 and 1024
8916
+ and ${table.initiatorSubjectId} <> 'unattributed-legacy'
8917
+ and (
8918
+ ${table.initiatingHumanSubjectId} is null
8919
+ or length(btrim(${table.initiatingHumanSubjectId})) between 1 and 1024
8920
+ )
8921
+ and (
8922
+ ${table.initiatorOrganizationMembershipId} is null
8923
+ or ${table.initiatingHumanSubjectId} is not null
8924
+ )
8925
+ and (
8926
+ ${table.initiatorAuthorizationRevision} is null
8927
+ or (
8928
+ ${table.initiatorAuthorizationRevision} > 0
8929
+ and ${table.initiatorOrganizationMembershipId} is not null
8930
+ )
8931
+ )
8932
+ )`,
8933
+ ),
8934
+ tenancyValid: check(
8935
+ "sandbox_retained_processes_tenancy_check",
8936
+ sql`(
8937
+ ${table.authorityEpoch} is null
8938
+ and ${table.authorityVisibility} is null
8939
+ and ${table.authorityOwnerOrganizationMembershipId} is null
8940
+ ) or (
8941
+ ${table.authorityEpoch} is not null
8942
+ and ${table.authorityVisibility} is not null
8943
+ and ${table.authorityEpoch} > 0
8944
+ and ${table.authorityVisibility} in ('user_private', 'workspace_shared')
8945
+ and (
8946
+ ${table.authorityVisibility} <> 'user_private'
8947
+ or ${table.authorityOwnerOrganizationMembershipId} is not null
8948
+ )
8949
+ )`,
8950
+ ),
7012
8951
  identityValid: check(
7013
8952
  "sandbox_retained_processes_identity_check",
7014
8953
  sql`${table.leaseEpoch} >= 0
@@ -7111,6 +9050,166 @@ export const sandboxRetainedProcesses = pgTable(
7111
9050
  }),
7112
9051
  );
7113
9052
 
9053
+ export const sessionBackgroundCommands = pgTable(
9054
+ "session_background_commands",
9055
+ {
9056
+ id: uuid("id").primaryKey().defaultRandom(),
9057
+ accountId: uuid("account_id").notNull(),
9058
+ workspaceId: uuid("workspace_id").notNull(),
9059
+ sessionId: uuid("session_id").notNull(),
9060
+ provider: text("provider", { enum: ["managed", "connected_machine"] }).notNull(),
9061
+ state: text("state", { enum: ["running", "stopping", "exited", "lost"] })
9062
+ .notNull()
9063
+ .default("running"),
9064
+ retainedProcessId: uuid("retained_process_id"),
9065
+ controlWorkspaceId: uuid("control_workspace_id"),
9066
+ enrollmentId: uuid("enrollment_id"),
9067
+ connectionInstanceId: text("connection_instance_id"),
9068
+ opId: text("op_id"),
9069
+ commandPreview: text("command_preview").notNull().default(""),
9070
+ cancelRequestedAt: timestamp("cancel_requested_at", { withTimezone: true }),
9071
+ cancelRequestedBy: text("cancel_requested_by"),
9072
+ exitCode: integer("exit_code"),
9073
+ settlementReason: text("settlement_reason"),
9074
+ startedAt: timestamp("started_at", { withTimezone: true }).notNull().defaultNow(),
9075
+ settledAt: timestamp("settled_at", { withTimezone: true }),
9076
+ reconcileAfter: timestamp("reconcile_after", { withTimezone: true }).notNull().defaultNow(),
9077
+ reconcileClaimId: uuid("reconcile_claim_id"),
9078
+ reconcileClaimedAt: timestamp("reconcile_claimed_at", { withTimezone: true }),
9079
+ reconcileAttempts: integer("reconcile_attempts").notNull().default(0),
9080
+ lastReconcileOutcome: text("last_reconcile_outcome"),
9081
+ reconcileProofOutcome: text("reconcile_proof_outcome", { enum: ["exited", "lost"] }),
9082
+ reconcileProofExitCode: integer("reconcile_proof_exit_code"),
9083
+ reconcileProofReason: text("reconcile_proof_reason"),
9084
+ reconcileProofObservedAt: timestamp("reconcile_proof_observed_at", { withTimezone: true }),
9085
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
9086
+ },
9087
+ (table) => ({
9088
+ workspaceAccount: foreignKey({
9089
+ name: "session_background_commands_workspace_fk",
9090
+ columns: [table.workspaceId, table.accountId],
9091
+ foreignColumns: [workspaces.id, workspaces.accountId],
9092
+ }).onDelete("cascade"),
9093
+ workspaceSession: foreignKey({
9094
+ name: "session_background_commands_session_fk",
9095
+ columns: [table.workspaceId, table.sessionId],
9096
+ foreignColumns: [sessions.workspaceId, sessions.id],
9097
+ }).onDelete("cascade"),
9098
+ controlWorkspaceAccount: foreignKey({
9099
+ name: "session_background_commands_control_workspace_fk",
9100
+ columns: [table.controlWorkspaceId, table.accountId],
9101
+ foreignColumns: [workspaces.id, workspaces.accountId],
9102
+ }).onDelete("restrict"),
9103
+ retainedProcess: foreignKey({
9104
+ name: "session_background_commands_process_fk",
9105
+ columns: [table.retainedProcessId],
9106
+ foreignColumns: [sandboxRetainedProcesses.id],
9107
+ }).onDelete("restrict"),
9108
+ managedProcess: uniqueIndex("session_background_commands_process_uq")
9109
+ .on(table.retainedProcessId)
9110
+ .where(sql`${table.retainedProcessId} is not null`),
9111
+ connectedOp: uniqueIndex("session_background_commands_connected_op_uq")
9112
+ .on(table.controlWorkspaceId, table.enrollmentId, table.connectionInstanceId, table.opId)
9113
+ .where(sql`${table.provider} = 'connected_machine'`),
9114
+ activeSession: index("session_background_commands_active_session_idx")
9115
+ .on(table.workspaceId, table.sessionId, table.state, table.startedAt, table.id)
9116
+ .where(sql`${table.state} in ('running', 'stopping')`),
9117
+ stopping: index("session_background_commands_stopping_idx")
9118
+ .on(table.reconcileAfter, table.cancelRequestedAt, table.id)
9119
+ .where(sql`${table.state} in ('running', 'stopping')`),
9120
+ providerValid: check(
9121
+ "session_background_commands_provider_check",
9122
+ sql`${table.provider} in ('managed', 'connected_machine')`,
9123
+ ),
9124
+ stateValid: check(
9125
+ "session_background_commands_state_check",
9126
+ sql`${table.state} in ('running', 'stopping', 'exited', 'lost')`,
9127
+ ),
9128
+ providerIdentityValid: check(
9129
+ "session_background_commands_provider_identity_check",
9130
+ sql`(
9131
+ ${table.provider} = 'managed'
9132
+ and ${table.retainedProcessId} is not null
9133
+ and ${table.controlWorkspaceId} is null
9134
+ and ${table.enrollmentId} is null
9135
+ and ${table.connectionInstanceId} is null
9136
+ and ${table.opId} is null
9137
+ ) or (
9138
+ ${table.provider} = 'connected_machine'
9139
+ and ${table.retainedProcessId} is null
9140
+ and ${table.controlWorkspaceId} is not null
9141
+ and ${table.enrollmentId} is not null
9142
+ and ${table.connectionInstanceId} is not null
9143
+ and octet_length(${table.connectionInstanceId}) between 1 and 128
9144
+ and ${table.opId} is not null
9145
+ and octet_length(${table.opId}) between 1 and 256
9146
+ )`,
9147
+ ),
9148
+ lifecycleValid: check(
9149
+ "session_background_commands_lifecycle_check",
9150
+ sql`(
9151
+ ${table.state} = 'running'
9152
+ and ${table.cancelRequestedAt} is null
9153
+ and ${table.cancelRequestedBy} is null
9154
+ and ${table.exitCode} is null
9155
+ and ${table.settlementReason} is null
9156
+ and ${table.settledAt} is null
9157
+ ) or (
9158
+ ${table.state} = 'stopping'
9159
+ and ${table.cancelRequestedAt} is not null
9160
+ and ${table.cancelRequestedBy} is not null
9161
+ and octet_length(btrim(${table.cancelRequestedBy})) between 1 and 1024
9162
+ and ${table.exitCode} is null
9163
+ and ${table.settlementReason} is null
9164
+ and ${table.settledAt} is null
9165
+ ) or (
9166
+ ${table.state} = 'exited'
9167
+ and ${table.settledAt} is not null
9168
+ and octet_length(btrim(${table.settlementReason})) between 1 and 512
9169
+ ) or (
9170
+ ${table.state} = 'lost'
9171
+ and ${table.exitCode} is null
9172
+ and ${table.settledAt} is not null
9173
+ and octet_length(btrim(${table.settlementReason})) between 1 and 512
9174
+ )`,
9175
+ ),
9176
+ previewValid: check(
9177
+ "session_background_commands_preview_check",
9178
+ sql`octet_length(${table.commandPreview}) <= 2048`,
9179
+ ),
9180
+ reconcileValid: check(
9181
+ "session_background_commands_reconcile_check",
9182
+ sql`${table.reconcileAttempts} >= 0
9183
+ and (
9184
+ (${table.reconcileClaimId} is null and ${table.reconcileClaimedAt} is null)
9185
+ or (${table.reconcileClaimId} is not null and ${table.reconcileClaimedAt} is not null)
9186
+ )
9187
+ and (
9188
+ ${table.lastReconcileOutcome} is null
9189
+ or octet_length(${table.lastReconcileOutcome}) between 1 and 64
9190
+ )
9191
+ and (
9192
+ (
9193
+ ${table.reconcileProofOutcome} is null
9194
+ and ${table.reconcileProofExitCode} is null
9195
+ and ${table.reconcileProofReason} is null
9196
+ and ${table.reconcileProofObservedAt} is null
9197
+ ) or (
9198
+ ${table.reconcileProofOutcome} = 'exited'
9199
+ and ${table.reconcileProofExitCode} is not null
9200
+ and octet_length(btrim(${table.reconcileProofReason})) between 1 and 512
9201
+ and ${table.reconcileProofObservedAt} is not null
9202
+ ) or (
9203
+ ${table.reconcileProofOutcome} = 'lost'
9204
+ and ${table.reconcileProofExitCode} is null
9205
+ and octet_length(btrim(${table.reconcileProofReason})) between 1 and 512
9206
+ and ${table.reconcileProofObservedAt} is not null
9207
+ )
9208
+ )`,
9209
+ ),
9210
+ }),
9211
+ );
9212
+
7114
9213
  // The recording lifecycle states (P4.3). Exported so the activity + the query
7115
9214
  // layer share one source of truth for the §3.1 state machine.
7116
9215
  export const sessionRecordingStateValues = [
@@ -7331,6 +9430,17 @@ export const enrollmentExposureValues = ["whole-machine"] as const;
7331
9430
  export const enrollmentStatusValues = ["active", "revoked"] as const;
7332
9431
  export const enrollmentOsValues = ["linux", "macos", "windows"] as const;
7333
9432
  export const sandboxKindValues = ["modal", "selfhosted"] as const;
9433
+ export const agentUpdateStatusValues = [
9434
+ "requested",
9435
+ "accepted",
9436
+ "waiting_for_idle",
9437
+ "downloading",
9438
+ "verifying",
9439
+ "applying",
9440
+ "restarting",
9441
+ "succeeded",
9442
+ "failed",
9443
+ ] as const;
7334
9444
 
7335
9445
  // One row per registered machine. The agent's ed25519 PUBLIC key IS the machine
7336
9446
  // identity (the NATS control-plane subject the agent subscribes to maps to it).
@@ -7348,6 +9458,11 @@ export const enrollments = pgTable(
7348
9458
  workspaceId: uuid("workspace_id")
7349
9459
  .notNull()
7350
9460
  .references(() => workspaces.id, { onDelete: "cascade" }),
9461
+ authorityScope: text("authority_scope").notNull().default("workspace"),
9462
+ authorityId: uuid("authority_id"),
9463
+ ownerOrganizationMembershipId: uuid("owner_organization_membership_id"),
9464
+ originWorkspaceId: uuid("origin_workspace_id"),
9465
+ generation: bigint("generation", { mode: "number" }).notNull().default(1),
7351
9466
  // The agent's ed25519 public key (the machine identity).
7352
9467
  pubkey: text("pubkey").notNull(),
7353
9468
  exposure: text("exposure", { enum: enrollmentExposureValues })
@@ -7366,13 +9481,49 @@ export const enrollments = pgTable(
7366
9481
  // from every connect Hello alongside has_display.
7367
9482
  desktopUnavailableReason: text("desktop_unavailable_reason"),
7368
9483
  allowScreenControl: boolean("allow_screen_control").notNull().default(false),
9484
+ // Optional per-connection command policy. NULL is the unrestricted default.
9485
+ // Values remain within JavaScript's exact-integer wire range; local runner and
9486
+ // ancestor host policy may only tighten them at execution.
9487
+ operationMemoryMaxBytes: bigint("operation_memory_max_bytes", {
9488
+ mode: "number",
9489
+ }),
9490
+ operationMemoryHighBytes: bigint("operation_memory_high_bytes", {
9491
+ mode: "number",
9492
+ }),
9493
+ operationCpuMaxMillicores: bigint("operation_cpu_max_millicores", {
9494
+ mode: "number",
9495
+ }),
9496
+ operationPolicyRevision: integer("operation_policy_revision").notNull().default(0),
9497
+ operationPolicyUpdatedAt: timestamp("operation_policy_updated_at", {
9498
+ withTimezone: true,
9499
+ }),
7369
9500
  status: text("status", { enum: enrollmentStatusValues }).notNull().default("active"),
7370
9501
  // Credential-family fence. Existing rows/migration-era bearers are generation
7371
9502
  // 1; every successful re-enrollment increments this atomically before a new
7372
9503
  // bearer is signed. Auth and self-revoke require an exact row/claim match.
7373
9504
  credentialGeneration: integer("credential_generation").notNull().default(1),
9505
+ // One live runner instance owns this enrollment's data-plane address at a
9506
+ // time. Auth-callout claims/renews the lease before NATS grants a connection;
9507
+ // every operational subject includes the instance id, so an older still-open
9508
+ // socket cannot receive work after a successor claims authority.
9509
+ connectionInstanceId: text("connection_instance_id"),
9510
+ connectionGeneration: integer("connection_generation").notNull().default(0),
9511
+ connectionLeaseExpiresAt: timestamp("connection_lease_expires_at", {
9512
+ withTimezone: true,
9513
+ }),
9514
+ // Durable diagnostics for valid cloned credentials / duplicate daemons that
9515
+ // were blocked while another process held the live authority lease.
9516
+ connectionDuplicateDeniedCount: integer("connection_duplicate_denied_count")
9517
+ .notNull()
9518
+ .default(0),
9519
+ connectionDuplicateDeniedAt: timestamp("connection_duplicate_denied_at", {
9520
+ withTimezone: true,
9521
+ }),
7374
9522
  os: text("os", { enum: enrollmentOsValues }).notNull().default("linux"),
7375
9523
  arch: text("arch").notNull().default("x86_64"),
9524
+ // Exact absolute launch root reported by the authoritative runner Hello.
9525
+ // Null means no current runner has supplied a usable root yet.
9526
+ workspaceRoot: text("workspace_root"),
7376
9527
  // Heartbeat liveness cursor. Null until the first connect.
7377
9528
  lastSeenAt: timestamp("last_seen_at", { withTimezone: true }),
7378
9529
  // Clean going-offline marker (migration 0049). Set when the machine announces a
@@ -7383,18 +9534,154 @@ export const enrollments = pgTable(
7383
9534
  // (the default) ⇒ no goodbye pending — today's last_seen-aging behavior.
7384
9535
  wentOfflineAt: timestamp("went_offline_at", { withTimezone: true }),
7385
9536
  wentOfflineReason: text("went_offline_reason"),
9537
+ // Exact running build + negotiated capability truth, refreshed from the
9538
+ // authoritative process Hello. Null means an older agent has not reported it.
9539
+ agentVersion: text("agent_version"),
9540
+ agentBinarySha256: text("agent_binary_sha256"),
9541
+ agentUpdateChannel: text("agent_update_channel", {
9542
+ enum: ["stable", "beta"] as const,
9543
+ }),
9544
+ agentCapabilities: jsonb("agent_capabilities")
9545
+ .$type<Record<string, boolean>>()
9546
+ .notNull()
9547
+ .default({}),
9548
+ // One current/most-recent self-update. The accepting connection coordinates
9549
+ // fence every progress write; a successor Hello is the only success signal.
9550
+ agentUpdateOperationId: uuid("agent_update_operation_id"),
9551
+ agentUpdateStatus: text("agent_update_status", {
9552
+ enum: agentUpdateStatusValues,
9553
+ }),
9554
+ agentUpdateTargetVersion: text("agent_update_target_version"),
9555
+ agentUpdateExpectedBinarySha256: text("agent_update_expected_binary_sha256"),
9556
+ agentUpdateErrorCode: text("agent_update_error_code"),
9557
+ agentUpdateRetryable: boolean("agent_update_retryable").notNull().default(false),
9558
+ agentUpdateRolledBack: boolean("agent_update_rolled_back").notNull().default(false),
9559
+ agentUpdateConnectionInstanceId: text("agent_update_connection_instance_id"),
9560
+ agentUpdateConnectionGeneration: integer("agent_update_connection_generation"),
9561
+ agentUpdateRequestedAt: timestamp("agent_update_requested_at", {
9562
+ withTimezone: true,
9563
+ }),
9564
+ agentUpdateUpdatedAt: timestamp("agent_update_updated_at", {
9565
+ withTimezone: true,
9566
+ }),
9567
+ agentUpdateCompletedAt: timestamp("agent_update_completed_at", {
9568
+ withTimezone: true,
9569
+ }),
7386
9570
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
7387
9571
  revokedAt: timestamp("revoked_at", { withTimezone: true }),
7388
9572
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
7389
9573
  },
7390
9574
  (table) => ({
7391
- // One enrollment per (workspace, pubkey): a re-enroll is an idempotent upsert.
7392
- workspacePubkey: uniqueIndex("enrollments_workspace_pubkey_idx").on(
7393
- table.workspaceId,
7394
- table.pubkey,
7395
- ),
9575
+ // One enrollment identity per authority boundary and pubkey. Revocation keeps
9576
+ // the identity so a re-enroll can reactivate the same durable row.
9577
+ workspacePubkey: uniqueIndex("enrollments_workspace_pubkey_idx")
9578
+ .on(table.workspaceId, table.pubkey)
9579
+ .where(sql`${table.authorityScope} = 'workspace'`),
9580
+ organizationPubkey: uniqueIndex("enrollments_organization_pubkey_idx")
9581
+ .on(table.accountId, table.pubkey)
9582
+ .where(sql`${table.authorityScope} = 'organization'`),
9583
+ userPubkey: uniqueIndex("enrollments_user_pubkey_idx")
9584
+ .on(table.accountId, table.ownerOrganizationMembershipId, table.pubkey)
9585
+ .where(sql`${table.authorityScope} = 'user'`),
7396
9586
  // List a workspace's ACTIVE machines without scanning revoked rows.
7397
9587
  workspaceStatus: index("enrollments_workspace_status_idx").on(table.workspaceId, table.status),
9588
+ authorityShape: check(
9589
+ "enrollments_authority_shape_check",
9590
+ sql`(
9591
+ ${table.authorityScope} in ('organization', 'workspace')
9592
+ and ${table.authorityId} is null
9593
+ and ${table.ownerOrganizationMembershipId} is null
9594
+ ) or (
9595
+ ${table.authorityScope} = 'user'
9596
+ and ${table.authorityId} is not null
9597
+ and ${table.ownerOrganizationMembershipId} is not null
9598
+ )`,
9599
+ ),
9600
+ authorityScopeValid: check(
9601
+ "enrollments_authority_scope_check",
9602
+ sql`${table.authorityScope} in ('organization', 'workspace', 'user')`,
9603
+ ),
9604
+ generationPositive: check("enrollments_generation_check", sql`${table.generation} > 0`),
9605
+ authority: foreignKey({
9606
+ name: "enrollments_authority_fk",
9607
+ columns: [table.authorityId, table.accountId, table.ownerOrganizationMembershipId],
9608
+ foreignColumns: [
9609
+ organizationUserResourceAuthorities.id,
9610
+ organizationUserResourceAuthorities.accountId,
9611
+ organizationUserResourceAuthorities.organizationMembershipId,
9612
+ ],
9613
+ }).onDelete("restrict"),
9614
+ connectionAuthorityShape: check(
9615
+ "enrollments_connection_authority_shape_chk",
9616
+ sql`(${table.connectionInstanceId} is null and ${table.connectionLeaseExpiresAt} is null)
9617
+ or (${table.connectionInstanceId} is not null
9618
+ and length(${table.connectionInstanceId}) between 1 and 128
9619
+ and ${table.connectionLeaseExpiresAt} is not null)`,
9620
+ ),
9621
+ connectionGenerationNonnegative: check(
9622
+ "enrollments_connection_generation_chk",
9623
+ sql`${table.connectionGeneration} >= 0`,
9624
+ ),
9625
+ operationMemoryMaxShape: check(
9626
+ "enrollments_operation_memory_max_shape_chk",
9627
+ sql`${table.operationMemoryMaxBytes} is null or (${table.operationMemoryMaxBytes} > 0 and ${table.operationMemoryMaxBytes} <= 9007199254740991)`,
9628
+ ),
9629
+ operationMemoryHighShape: check(
9630
+ "enrollments_operation_memory_high_shape_chk",
9631
+ sql`${table.operationMemoryHighBytes} is null or (${table.operationMemoryHighBytes} > 0 and ${table.operationMemoryHighBytes} <= 9007199254740991)`,
9632
+ ),
9633
+ operationMemoryOrder: check(
9634
+ "enrollments_operation_memory_order_chk",
9635
+ sql`${table.operationMemoryMaxBytes} is null or ${table.operationMemoryHighBytes} is null or ${table.operationMemoryHighBytes} <= ${table.operationMemoryMaxBytes}`,
9636
+ ),
9637
+ operationCpuShape: check(
9638
+ "enrollments_operation_cpu_shape_chk",
9639
+ sql`${table.operationCpuMaxMillicores} is null or ${table.operationCpuMaxMillicores} between 1 and 4294967295`,
9640
+ ),
9641
+ operationPolicyRevisionNonnegative: check(
9642
+ "enrollments_operation_policy_revision_chk",
9643
+ sql`${table.operationPolicyRevision} >= 0`,
9644
+ ),
9645
+ agentBinarySha256Shape: check(
9646
+ "enrollments_agent_binary_sha256_chk",
9647
+ sql`${table.agentBinarySha256} is null or ${table.agentBinarySha256} ~ '^[0-9a-f]{64}$'`,
9648
+ ),
9649
+ agentUpdateChannelShape: check(
9650
+ "enrollments_agent_update_channel_chk",
9651
+ sql`${table.agentUpdateChannel} is null or ${table.agentUpdateChannel} in ('stable', 'beta')`,
9652
+ ),
9653
+ agentUpdateExpectedBinarySha256Shape: check(
9654
+ "enrollments_agent_update_expected_binary_sha256_chk",
9655
+ sql`${table.agentUpdateExpectedBinarySha256} is null or ${table.agentUpdateExpectedBinarySha256} ~ '^[0-9a-f]{64}$'`,
9656
+ ),
9657
+ agentUpdateStateShape: check(
9658
+ "enrollments_agent_update_state_shape_chk",
9659
+ sql`(
9660
+ (${table.agentUpdateOperationId} is null
9661
+ and ${table.agentUpdateStatus} is null
9662
+ and ${table.agentUpdateTargetVersion} is null
9663
+ and ${table.agentUpdateConnectionInstanceId} is null
9664
+ and ${table.agentUpdateConnectionGeneration} is null
9665
+ and ${table.agentUpdateRequestedAt} is null
9666
+ and ${table.agentUpdateUpdatedAt} is null)
9667
+ or
9668
+ (${table.agentUpdateOperationId} is not null
9669
+ and ${table.agentUpdateStatus} in (
9670
+ 'requested', 'accepted', 'waiting_for_idle', 'downloading', 'verifying',
9671
+ 'applying', 'restarting', 'succeeded', 'failed'
9672
+ )
9673
+ and ${table.agentUpdateTargetVersion} is not null
9674
+ and ${table.agentUpdateConnectionInstanceId} is not null
9675
+ and ${table.agentUpdateConnectionGeneration} is not null
9676
+ and ${table.agentUpdateConnectionGeneration} >= 0
9677
+ and ${table.agentUpdateRequestedAt} is not null
9678
+ and ${table.agentUpdateUpdatedAt} is not null)
9679
+ )`,
9680
+ ),
9681
+ connectionDuplicateDeniedCountNonnegative: check(
9682
+ "enrollments_connection_duplicate_denied_count_chk",
9683
+ sql`${table.connectionDuplicateDeniedCount} >= 0`,
9684
+ ),
7398
9685
  }),
7399
9686
  );
7400
9687
 
@@ -7696,6 +9983,12 @@ export const scheduledTasks = pgTable(
7696
9983
  .$type<XaiProviderAccountAuthoritySnapshotV1>()
7697
9984
  .notNull()
7698
9985
  .default(WORKSPACE_XAI_PROVIDER_ACCOUNT_AUTHORITY_SNAPSHOT_V1),
9986
+ authorityRevision: bigint("authority_revision", { mode: "number" }).notNull().default(1),
9987
+ // The migration-owned BEFORE INSERT/UPDATE trigger replaces this client
9988
+ // placeholder with the canonical whole-row execution digest.
9989
+ executionDigest: text("execution_digest")
9990
+ .notNull()
9991
+ .$defaultFn(() => ""),
7699
9992
  reusableSessionId: uuid("reusable_session_id").references(() => sessions.id, {
7700
9993
  onDelete: "set null",
7701
9994
  }),
@@ -7707,6 +10000,7 @@ export const scheduledTasks = pgTable(
7707
10000
  // in migration 0047 (forward-reference pattern). Consumed in M3.
7708
10001
  rigId: uuid("rig_id"),
7709
10002
  metadata: jsonb("metadata").$type<Record<string, unknown>>().notNull().default({}),
10003
+ deletedAt: timestamp("deleted_at", { withTimezone: true }),
7710
10004
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
7711
10005
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
7712
10006
  },
@@ -7738,6 +10032,12 @@ export const temporalScheduleCleanupOutbox = pgTable(
7738
10032
  accountId: uuid("account_id").notNull(),
7739
10033
  workspaceId: uuid("workspace_id").notNull(),
7740
10034
  temporalScheduleId: text("temporal_schedule_id").notNull(),
10035
+ scheduledTaskId: uuid("scheduled_task_id"),
10036
+ connectorCleanupSubjectId: text("connector_cleanup_subject_id"),
10037
+ connectorCleanupSnapshot: jsonb("connector_cleanup_snapshot"),
10038
+ connectorCleanupCompletedAt: timestamp("connector_cleanup_completed_at", {
10039
+ withTimezone: true,
10040
+ }),
7741
10041
  claimId: uuid("claim_id"),
7742
10042
  claimUntil: timestamp("claim_until", { withTimezone: true }),
7743
10043
  attemptCount: integer("attempt_count").notNull().default(0),
@@ -7761,6 +10061,58 @@ export const temporalScheduleCleanupOutbox = pgTable(
7761
10061
  and ${table.attemptCount} >= 0
7762
10062
  and ((${table.claimId} is null and ${table.claimUntil} is null)
7763
10063
  or (${table.claimId} is not null and ${table.claimUntil} is not null))
10064
+ and ((${table.scheduledTaskId} is null and ${table.connectorCleanupSubjectId} is null
10065
+ and ${table.connectorCleanupSnapshot} is null)
10066
+ or (${table.scheduledTaskId} is not null and ${table.connectorCleanupSubjectId} is not null
10067
+ and ${table.connectorCleanupSnapshot} is not null))
10068
+ and (${table.connectorCleanupSnapshot} is not null
10069
+ or ${table.connectorCleanupCompletedAt} is null)
10070
+ and (${table.connectorCleanupSubjectId} is null
10071
+ or octet_length(${table.connectorCleanupSubjectId}) between 1 and 4096)
10072
+ and (${table.connectorCleanupSnapshot} is null or (
10073
+ jsonb_typeof(${table.connectorCleanupSnapshot}) = 'object'
10074
+ and ${table.connectorCleanupSnapshot} ?& array[
10075
+ 'version','taskId','accountId','workspaceId','connectorKind','connectionId',
10076
+ 'connectionVersion','sourceId','sourceLifecycleGeneration',
10077
+ 'sourceConfigGeneration','externalSourceId','subjectId'
10078
+ ]
10079
+ and ${table.connectorCleanupSnapshot} - array[
10080
+ 'version','taskId','accountId','workspaceId','connectorKind','connectionId',
10081
+ 'connectionVersion','sourceId','sourceLifecycleGeneration',
10082
+ 'sourceConfigGeneration','externalSourceId','subjectId'
10083
+ ]::text[] = '{}'::jsonb
10084
+ and jsonb_typeof(${table.connectorCleanupSnapshot}->'version') = 'number'
10085
+ and jsonb_typeof(${table.connectorCleanupSnapshot}->'taskId') = 'string'
10086
+ and jsonb_typeof(${table.connectorCleanupSnapshot}->'accountId') = 'string'
10087
+ and jsonb_typeof(${table.connectorCleanupSnapshot}->'workspaceId') = 'string'
10088
+ and jsonb_typeof(${table.connectorCleanupSnapshot}->'connectorKind') = 'string'
10089
+ and jsonb_typeof(${table.connectorCleanupSnapshot}->'connectionId') = 'string'
10090
+ and jsonb_typeof(${table.connectorCleanupSnapshot}->'connectionVersion') = 'number'
10091
+ and jsonb_typeof(${table.connectorCleanupSnapshot}->'sourceId') = 'string'
10092
+ and jsonb_typeof(${table.connectorCleanupSnapshot}->'sourceLifecycleGeneration') = 'number'
10093
+ and jsonb_typeof(${table.connectorCleanupSnapshot}->'sourceConfigGeneration') = 'number'
10094
+ and jsonb_typeof(${table.connectorCleanupSnapshot}->'externalSourceId') = 'string'
10095
+ and jsonb_typeof(${table.connectorCleanupSnapshot}->'subjectId') = 'string'
10096
+ and ${table.connectorCleanupSnapshot}->>'version' = '1'
10097
+ and ${table.connectorCleanupSnapshot}->>'connectorKind' in ('google_drive','atlassian')
10098
+ and ${table.connectorCleanupSnapshot}->>'subjectId' = ${table.connectorCleanupSubjectId}
10099
+ and ${table.connectorCleanupSnapshot}->>'taskId' = ${table.scheduledTaskId}::text
10100
+ and ${table.connectorCleanupSnapshot}->>'accountId' = ${table.accountId}::text
10101
+ and ${table.connectorCleanupSnapshot}->>'workspaceId' = ${table.workspaceId}::text
10102
+ and octet_length(${table.connectorCleanupSnapshot}->>'externalSourceId') between 1 and 2048
10103
+ and octet_length(${table.connectorCleanupSnapshot}->>'subjectId') between 1 and 4096
10104
+ and ${table.connectorCleanupSnapshot}->>'taskId' ~* '^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'
10105
+ and ${table.connectorCleanupSnapshot}->>'accountId' ~* '^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'
10106
+ and ${table.connectorCleanupSnapshot}->>'workspaceId' ~* '^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'
10107
+ and ${table.connectorCleanupSnapshot}->>'connectionId' ~* '^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'
10108
+ and ${table.connectorCleanupSnapshot}->>'sourceId' ~* '^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'
10109
+ and ${table.connectorCleanupSnapshot}->>'connectionVersion' ~ '^[1-9][0-9]*$'
10110
+ and ${table.connectorCleanupSnapshot}->>'sourceLifecycleGeneration' ~ '^[1-9][0-9]*$'
10111
+ and ${table.connectorCleanupSnapshot}->>'sourceConfigGeneration' ~ '^[1-9][0-9]*$'
10112
+ and (${table.connectorCleanupSnapshot}->>'connectionVersion')::numeric <= 9007199254740991
10113
+ and (${table.connectorCleanupSnapshot}->>'sourceLifecycleGeneration')::numeric <= 9007199254740991
10114
+ and (${table.connectorCleanupSnapshot}->>'sourceConfigGeneration')::numeric <= 9007199254740991
10115
+ ))
7764
10116
  and (${table.lastError} is null or length(${table.lastError}) <= 2000)`,
7765
10117
  ),
7766
10118
  }),
@@ -7778,14 +10130,16 @@ export const scheduledTaskRuns = pgTable(
7778
10130
  .references(() => workspaces.id, { onDelete: "cascade" }),
7779
10131
  taskId: uuid("task_id")
7780
10132
  .notNull()
7781
- .references(() => scheduledTasks.id, { onDelete: "cascade" }),
10133
+ .references(() => scheduledTasks.id),
10134
+ taskAuthorityRevision: bigint("task_authority_revision", {
10135
+ mode: "number",
10136
+ }),
10137
+ taskExecutionDigest: text("task_execution_digest"),
7782
10138
  status: text("status").notNull().default("queued"),
7783
10139
  triggerType: text("trigger_type").notNull(),
7784
10140
  scheduledAt: timestamp("scheduled_at", { withTimezone: true }),
7785
10141
  firedAt: timestamp("fired_at", { withTimezone: true }).notNull().defaultNow(),
7786
- sessionId: uuid("session_id").references(() => sessions.id, {
7787
- onDelete: "set null",
7788
- }),
10142
+ sessionId: uuid("session_id").references(() => sessions.id),
7789
10143
  triggerEventId: uuid("trigger_event_id"),
7790
10144
  actionKind: text("action_kind").notNull().default("agent_turn"),
7791
10145
  knowledgeSyncRunId: uuid("knowledge_sync_run_id"),
@@ -7794,6 +10148,10 @@ export const scheduledTaskRuns = pgTable(
7794
10148
  // Stable Temporal producer identity. Activity replay/re-dispatch returns
7795
10149
  // the exact run instead of allocating a second schedule source row.
7796
10150
  producerKey: text("producer_key"),
10151
+ // Complete credential-free accepted execution truth. It is private worker
10152
+ // authority and never appears in public scheduled-run projections.
10153
+ acceptedExecutionSnapshot: jsonb("accepted_execution_snapshot").$type<unknown>(),
10154
+ acceptedExecutionDigest: text("accepted_execution_digest"),
7797
10155
  error: text("error"),
7798
10156
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
7799
10157
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
@@ -7900,45 +10258,186 @@ export const githubInstallations = pgTable(
7900
10258
  )
7901
10259
  `,
7902
10260
  ),
7903
- authorityNonce: uniqueIndex("github_installations_authority_nonce_uq")
7904
- .on(table.authorityNonce)
7905
- .where(sql`${table.authorityNonce} is not null`),
10261
+ authorityNonce: uniqueIndex("github_installations_authority_nonce_uq")
10262
+ .on(table.authorityNonce)
10263
+ .where(sql`${table.authorityNonce} is not null`),
10264
+ }),
10265
+ );
10266
+
10267
+ export const githubInstallationRepositories = pgTable(
10268
+ "github_installation_repositories",
10269
+ {
10270
+ id: uuid("id").primaryKey().defaultRandom(),
10271
+ accountId: uuid("account_id")
10272
+ .notNull()
10273
+ .references(() => managedAccounts.id, { onDelete: "cascade" }),
10274
+ workspaceId: uuid("workspace_id")
10275
+ .notNull()
10276
+ .references(() => workspaces.id, { onDelete: "cascade" }),
10277
+ installationId: integer("installation_id").notNull(),
10278
+ repositoryId: bigint("repository_id", { mode: "number" }).notNull(),
10279
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
10280
+ },
10281
+ (table) => ({
10282
+ installationRepository: uniqueIndex("github_install_repo_workspace_installation_repo_idx").on(
10283
+ table.workspaceId,
10284
+ table.installationId,
10285
+ table.repositoryId,
10286
+ ),
10287
+ workspaceInstallation: index("github_install_repo_workspace_installation_idx").on(
10288
+ table.workspaceId,
10289
+ table.installationId,
10290
+ ),
10291
+ workspaceAccount: foreignKey({
10292
+ columns: [table.workspaceId, table.accountId],
10293
+ foreignColumns: [workspaces.id, workspaces.accountId],
10294
+ name: "github_installation_repositories_workspace_account_fk",
10295
+ }).onDelete("cascade"),
10296
+ installationBinding: foreignKey({
10297
+ columns: [table.workspaceId, table.installationId],
10298
+ foreignColumns: [githubInstallations.workspaceId, githubInstallations.installationId],
10299
+ name: "github_installation_repositories_installation_fk",
10300
+ }).onDelete("cascade"),
10301
+ }),
10302
+ );
10303
+
10304
+ export const prReviewAppRegistrations = pgTable(
10305
+ "pr_review_app_registrations",
10306
+ {
10307
+ id: uuid("id").primaryKey().defaultRandom(),
10308
+ sourceId: uuid("source_id").notNull(),
10309
+ accountId: uuid("account_id")
10310
+ .notNull()
10311
+ .references(() => managedAccounts.id, { onDelete: "cascade" }),
10312
+ workspaceId: uuid("workspace_id")
10313
+ .notNull()
10314
+ .references(() => workspaces.id, { onDelete: "cascade" }),
10315
+ name: text("name").notNull(),
10316
+ provider: text("provider").notNull(),
10317
+ providerBaseUrl: text("provider_base_url").notNull(),
10318
+ appId: text("app_id"),
10319
+ credentialKind: text("credential_kind").notNull(),
10320
+ credentialEncrypted: text("credential_encrypted"),
10321
+ accessTokenExpiresAt: timestamp("access_token_expires_at", { withTimezone: true }),
10322
+ webhookAuthKind: text("webhook_auth_kind").notNull(),
10323
+ webhookUsername: text("webhook_username"),
10324
+ status: text("status").notNull().default("active"),
10325
+ createdBySubjectId: text("created_by_subject_id").notNull(),
10326
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
10327
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
10328
+ },
10329
+ (table) => ({
10330
+ workspaceRegistration: uniqueIndex("pr_review_app_registrations_workspace_id_uq").on(
10331
+ table.workspaceId,
10332
+ table.id,
10333
+ ),
10334
+ workspaceRegistrationProvider: uniqueIndex(
10335
+ "pr_review_app_registrations_workspace_id_provider_uq",
10336
+ ).on(table.workspaceId, table.id, table.provider),
10337
+ workspaceProviderName: uniqueIndex("pr_review_app_registrations_workspace_provider_name_uq").on(
10338
+ table.workspaceId,
10339
+ table.provider,
10340
+ table.name,
10341
+ ),
10342
+ workspaceStatus: index("pr_review_app_registrations_workspace_status_idx").on(
10343
+ table.workspaceId,
10344
+ table.status,
10345
+ ),
10346
+ providerCheck: check(
10347
+ "pr_review_app_registrations_provider_chk",
10348
+ sql`${table.provider} in ('github', 'gitlab', 'azure_devops')`,
10349
+ ),
10350
+ credentialCheck: check(
10351
+ "pr_review_app_registrations_credential_chk",
10352
+ sql`(
10353
+ (${table.provider} = 'github' and ${table.credentialKind} = 'github_app' and ${table.credentialEncrypted} is not null and ${table.appId} is not null)
10354
+ or
10355
+ (${table.provider} in ('gitlab', 'azure_devops') and ${table.credentialKind} = 'provider_token' and ${table.credentialEncrypted} is not null)
10356
+ )`,
10357
+ ),
10358
+ webhookAuthCheck: check(
10359
+ "pr_review_app_registrations_webhook_auth_chk",
10360
+ sql`(
10361
+ (${table.provider} = 'github' and ${table.webhookAuthKind} = 'hmac_sha256')
10362
+ or (${table.provider} = 'gitlab' and ${table.webhookAuthKind} = 'shared_token')
10363
+ or (${table.provider} = 'azure_devops' and ${table.webhookAuthKind} = 'basic' and ${table.webhookUsername} is not null)
10364
+ )`,
10365
+ ),
10366
+ statusCheck: check(
10367
+ "pr_review_app_registrations_status_chk",
10368
+ sql`${table.status} in ('active', 'disabled')`,
10369
+ ),
10370
+ workspaceAccount: foreignKey({
10371
+ columns: [table.workspaceId, table.accountId],
10372
+ foreignColumns: [workspaces.id, workspaces.accountId],
10373
+ name: "pr_review_app_registrations_workspace_account_fk",
10374
+ }).onDelete("cascade"),
7906
10375
  }),
7907
10376
  );
7908
10377
 
7909
- export const githubInstallationRepositories = pgTable(
7910
- "github_installation_repositories",
10378
+ export const prReviewRepositoryBindings = pgTable(
10379
+ "pr_review_repository_bindings",
7911
10380
  {
7912
10381
  id: uuid("id").primaryKey().defaultRandom(),
10382
+ triggerId: uuid("trigger_id").notNull(),
7913
10383
  accountId: uuid("account_id")
7914
10384
  .notNull()
7915
10385
  .references(() => managedAccounts.id, { onDelete: "cascade" }),
7916
10386
  workspaceId: uuid("workspace_id")
7917
10387
  .notNull()
7918
10388
  .references(() => workspaces.id, { onDelete: "cascade" }),
7919
- installationId: integer("installation_id").notNull(),
7920
- repositoryId: bigint("repository_id", { mode: "number" }).notNull(),
10389
+ registrationId: uuid("registration_id").notNull(),
10390
+ provider: text("provider").notNull(),
10391
+ repositoryUri: text("repository_uri").notNull(),
10392
+ repositoryFullName: text("repository_full_name").notNull(),
10393
+ providerRepositoryId: text("provider_repository_id").notNull(),
10394
+ installationId: text("installation_id"),
10395
+ projectId: text("project_id"),
10396
+ model: text("model"),
10397
+ additionalInstructions: text("additional_instructions"),
10398
+ status: text("status").notNull().default("active"),
10399
+ createdBySubjectId: text("created_by_subject_id").notNull(),
7921
10400
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
10401
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
7922
10402
  },
7923
10403
  (table) => ({
7924
- installationRepository: uniqueIndex("github_install_repo_workspace_installation_repo_idx").on(
10404
+ workspaceBinding: uniqueIndex("pr_review_repository_bindings_workspace_id_uq").on(
7925
10405
  table.workspaceId,
7926
- table.installationId,
7927
- table.repositoryId,
10406
+ table.id,
7928
10407
  ),
7929
- workspaceInstallation: index("github_install_repo_workspace_installation_idx").on(
10408
+ workspaceBindingRegistrationProvider: uniqueIndex(
10409
+ "pr_review_repo_workspace_registration_provider_uq",
10410
+ ).on(table.workspaceId, table.id, table.registrationId, table.provider),
10411
+ registrationRepository: uniqueIndex("pr_review_repository_bindings_registration_repo_uq").on(
10412
+ table.registrationId,
10413
+ table.providerRepositoryId,
10414
+ ),
10415
+ triggerIdentity: uniqueIndex("pr_review_repository_bindings_trigger_uq").on(table.triggerId),
10416
+ workspaceStatus: index("pr_review_repository_bindings_workspace_status_idx").on(
7930
10417
  table.workspaceId,
7931
- table.installationId,
10418
+ table.status,
10419
+ ),
10420
+ providerCheck: check(
10421
+ "pr_review_repository_bindings_provider_chk",
10422
+ sql`${table.provider} in ('github', 'gitlab', 'azure_devops')`,
10423
+ ),
10424
+ statusCheck: check(
10425
+ "pr_review_repository_bindings_status_chk",
10426
+ sql`${table.status} in ('active', 'disabled')`,
7932
10427
  ),
7933
10428
  workspaceAccount: foreignKey({
7934
10429
  columns: [table.workspaceId, table.accountId],
7935
10430
  foreignColumns: [workspaces.id, workspaces.accountId],
7936
- name: "github_installation_repositories_workspace_account_fk",
10431
+ name: "pr_review_repository_bindings_workspace_account_fk",
7937
10432
  }).onDelete("cascade"),
7938
- installationBinding: foreignKey({
7939
- columns: [table.workspaceId, table.installationId],
7940
- foreignColumns: [githubInstallations.workspaceId, githubInstallations.installationId],
7941
- name: "github_installation_repositories_installation_fk",
10433
+ registration: foreignKey({
10434
+ columns: [table.workspaceId, table.registrationId, table.provider],
10435
+ foreignColumns: [
10436
+ prReviewAppRegistrations.workspaceId,
10437
+ prReviewAppRegistrations.id,
10438
+ prReviewAppRegistrations.provider,
10439
+ ],
10440
+ name: "pr_review_repository_bindings_registration_fk",
7942
10441
  }).onDelete("cascade"),
7943
10442
  }),
7944
10443
  );
@@ -8060,6 +10559,8 @@ export const modelCallFacts = pgTable(
8060
10559
  mode: "number",
8061
10560
  }),
8062
10561
  pricingSource: text("pricing_source"),
10562
+ contextContributions:
10563
+ jsonb("context_contributions").$type<readonly ModelContextContributionSummary[]>(),
8063
10564
  occurredAt: timestamp("occurred_at", { withTimezone: true }).notNull(),
8064
10565
  recordedAt: timestamp("recorded_at", { withTimezone: true }).notNull().defaultNow(),
8065
10566
  },
@@ -8130,6 +10631,10 @@ export const modelCallFacts = pgTable(
8130
10631
  "model_call_facts_model_bytes_check",
8131
10632
  sql`octet_length(${table.model}) between 1 and 512`,
8132
10633
  ),
10634
+ contextContributionsValid: check(
10635
+ "model_call_facts_context_contributions_check",
10636
+ sql`opengeni_private.model_context_contributions_valid(${table.contextContributions})`,
10637
+ ),
8133
10638
  }),
8134
10639
  );
8135
10640
 
@@ -8433,6 +10938,10 @@ export const packInstallations = pgTable(
8433
10938
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
8434
10939
  },
8435
10940
  (table) => ({
10941
+ workspaceIdentity: uniqueIndex("pack_installations_workspace_id_uq").on(
10942
+ table.workspaceId,
10943
+ table.id,
10944
+ ),
8436
10945
  workspacePack: uniqueIndex("pack_installations_workspace_pack_idx").on(
8437
10946
  table.workspaceId,
8438
10947
  table.packId,
@@ -8479,6 +10988,376 @@ export const packInstallationComponents = pgTable(
8479
10988
  }),
8480
10989
  );
8481
10990
 
10991
+ export const automationSources = pgTable(
10992
+ "automation_sources",
10993
+ {
10994
+ id: uuid("id").primaryKey().defaultRandom(),
10995
+ endpointId: uuid("endpoint_id").notNull().defaultRandom(),
10996
+ accountId: uuid("account_id")
10997
+ .notNull()
10998
+ .references(() => managedAccounts.id, { onDelete: "cascade" }),
10999
+ workspaceId: uuid("workspace_id")
11000
+ .notNull()
11001
+ .references(() => workspaces.id, { onDelete: "cascade" }),
11002
+ name: text("name").notNull(),
11003
+ adapterId: text("adapter_id").notNull(),
11004
+ configuration: jsonb("configuration").$type<Record<string, unknown>>().notNull().default({}),
11005
+ webhookSecretEncrypted: text("webhook_secret_encrypted").notNull(),
11006
+ status: text("status").notNull().default("active"),
11007
+ version: integer("version").notNull().default(1),
11008
+ packInstallationId: uuid("pack_installation_id"),
11009
+ packConnectorId: text("pack_connector_id"),
11010
+ createdBySubjectId: text("created_by_subject_id").notNull(),
11011
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
11012
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
11013
+ },
11014
+ (table) => ({
11015
+ endpoint: uniqueIndex("automation_sources_endpoint_uq").on(table.endpointId),
11016
+ workspaceIdentity: uniqueIndex("automation_sources_workspace_id_uq").on(
11017
+ table.workspaceId,
11018
+ table.id,
11019
+ ),
11020
+ workspaceStatus: index("automation_sources_workspace_status_idx").on(
11021
+ table.workspaceId,
11022
+ table.status,
11023
+ ),
11024
+ shape: check(
11025
+ "automation_sources_shape_chk",
11026
+ sql`${table.status} in ('active', 'disabled')
11027
+ and ${table.version} > 0
11028
+ and octet_length(${table.name}) between 1 and 512
11029
+ and octet_length(${table.adapterId}) between 1 and 128
11030
+ and octet_length(${table.createdBySubjectId}) between 1 and 4096
11031
+ and jsonb_typeof(${table.configuration}) = 'object'
11032
+ and ((${table.packInstallationId} is null and ${table.packConnectorId} is null)
11033
+ or (${table.packInstallationId} is not null
11034
+ and octet_length(${table.packConnectorId}) between 1 and 128))`,
11035
+ ),
11036
+ workspaceAccount: foreignKey({
11037
+ columns: [table.workspaceId, table.accountId],
11038
+ foreignColumns: [workspaces.id, workspaces.accountId],
11039
+ name: "automation_sources_workspace_account_fk",
11040
+ }).onDelete("cascade"),
11041
+ packInstallation: foreignKey({
11042
+ columns: [table.workspaceId, table.packInstallationId],
11043
+ foreignColumns: [packInstallations.workspaceId, packInstallations.id],
11044
+ name: "automation_sources_pack_installation_fk",
11045
+ }).onDelete("cascade"),
11046
+ }),
11047
+ );
11048
+
11049
+ /** Credential-free global webhook routing. Possession of the opaque endpoint
11050
+ * UUID locates the tenant; authentication still requires the encrypted secret
11051
+ * on the FORCE-RLS source row before any payload is parsed or accepted. */
11052
+ export const automationWebhookEndpoints = pgTable(
11053
+ "automation_webhook_endpoints",
11054
+ {
11055
+ endpointId: uuid("endpoint_id").primaryKey(),
11056
+ accountId: uuid("account_id")
11057
+ .notNull()
11058
+ .references(() => managedAccounts.id, { onDelete: "cascade" }),
11059
+ workspaceId: uuid("workspace_id")
11060
+ .notNull()
11061
+ .references(() => workspaces.id, { onDelete: "cascade" }),
11062
+ sourceId: uuid("source_id")
11063
+ .notNull()
11064
+ .references(() => automationSources.id, { onDelete: "cascade" }),
11065
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
11066
+ },
11067
+ (table) => ({
11068
+ source: uniqueIndex("automation_webhook_endpoints_source_uq").on(table.sourceId),
11069
+ workspaceAccount: foreignKey({
11070
+ columns: [table.workspaceId, table.accountId],
11071
+ foreignColumns: [workspaces.id, workspaces.accountId],
11072
+ name: "automation_webhook_endpoints_workspace_account_fk",
11073
+ }).onDelete("cascade"),
11074
+ }),
11075
+ );
11076
+
11077
+ export const automationTriggers = pgTable(
11078
+ "automation_triggers",
11079
+ {
11080
+ id: uuid("id").primaryKey().defaultRandom(),
11081
+ accountId: uuid("account_id")
11082
+ .notNull()
11083
+ .references(() => managedAccounts.id, { onDelete: "cascade" }),
11084
+ workspaceId: uuid("workspace_id")
11085
+ .notNull()
11086
+ .references(() => workspaces.id, { onDelete: "cascade" }),
11087
+ sourceId: uuid("source_id").notNull(),
11088
+ name: text("name").notNull(),
11089
+ status: text("status").notNull().default("active"),
11090
+ currentRevision: integer("current_revision").notNull().default(1),
11091
+ packInstallationId: uuid("pack_installation_id"),
11092
+ packTemplateId: text("pack_template_id"),
11093
+ createdBySubjectId: text("created_by_subject_id").notNull(),
11094
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
11095
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
11096
+ },
11097
+ (table) => ({
11098
+ workspaceIdentity: uniqueIndex("automation_triggers_workspace_id_uq").on(
11099
+ table.workspaceId,
11100
+ table.id,
11101
+ ),
11102
+ workspaceSourceIdentity: uniqueIndex("automation_triggers_workspace_source_id_uq").on(
11103
+ table.workspaceId,
11104
+ table.id,
11105
+ table.sourceId,
11106
+ ),
11107
+ workspaceStatus: index("automation_triggers_workspace_status_idx").on(
11108
+ table.workspaceId,
11109
+ table.status,
11110
+ ),
11111
+ source: foreignKey({
11112
+ columns: [table.workspaceId, table.sourceId],
11113
+ foreignColumns: [automationSources.workspaceId, automationSources.id],
11114
+ name: "automation_triggers_source_fk",
11115
+ }).onDelete("cascade"),
11116
+ packInstallation: foreignKey({
11117
+ columns: [table.workspaceId, table.packInstallationId],
11118
+ foreignColumns: [packInstallations.workspaceId, packInstallations.id],
11119
+ name: "automation_triggers_pack_installation_fk",
11120
+ }).onDelete("cascade"),
11121
+ workspaceAccount: foreignKey({
11122
+ columns: [table.workspaceId, table.accountId],
11123
+ foreignColumns: [workspaces.id, workspaces.accountId],
11124
+ name: "automation_triggers_workspace_account_fk",
11125
+ }).onDelete("cascade"),
11126
+ shape: check(
11127
+ "automation_triggers_shape_chk",
11128
+ sql`${table.status} in ('active', 'paused', 'disabled')
11129
+ and ${table.currentRevision} > 0
11130
+ and octet_length(${table.name}) between 1 and 512
11131
+ and octet_length(${table.createdBySubjectId}) between 1 and 4096
11132
+ and ((${table.packInstallationId} is null and ${table.packTemplateId} is null)
11133
+ or (${table.packInstallationId} is not null and octet_length(${table.packTemplateId}) between 1 and 128))`,
11134
+ ),
11135
+ }),
11136
+ );
11137
+
11138
+ export const automationTriggerRevisions = pgTable(
11139
+ "automation_trigger_revisions",
11140
+ {
11141
+ triggerId: uuid("trigger_id").notNull(),
11142
+ revision: integer("revision").notNull(),
11143
+ accountId: uuid("account_id").notNull(),
11144
+ workspaceId: uuid("workspace_id").notNull(),
11145
+ adapterId: text("adapter_id").notNull(),
11146
+ eventTypes: jsonb("event_types").$type<string[]>().notNull(),
11147
+ configuration: jsonb("configuration").$type<Record<string, unknown>>().notNull().default({}),
11148
+ parameters: jsonb("parameters").$type<Record<string, unknown>>().notNull().default({}),
11149
+ sessionTemplate: jsonb("session_template").$type<AutomationSessionTemplate>().notNull(),
11150
+ createdBySubjectId: text("created_by_subject_id").notNull(),
11151
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
11152
+ },
11153
+ (table) => ({
11154
+ identity: primaryKey({ columns: [table.triggerId, table.revision] }),
11155
+ trigger: foreignKey({
11156
+ columns: [table.workspaceId, table.triggerId],
11157
+ foreignColumns: [automationTriggers.workspaceId, automationTriggers.id],
11158
+ name: "automation_trigger_revisions_trigger_fk",
11159
+ }).onDelete("cascade"),
11160
+ workspaceAccount: foreignKey({
11161
+ columns: [table.workspaceId, table.accountId],
11162
+ foreignColumns: [workspaces.id, workspaces.accountId],
11163
+ name: "automation_trigger_revisions_workspace_account_fk",
11164
+ }).onDelete("cascade"),
11165
+ shape: check(
11166
+ "automation_trigger_revisions_shape_chk",
11167
+ sql`${table.revision} > 0
11168
+ and octet_length(${table.adapterId}) between 1 and 128
11169
+ and jsonb_typeof(${table.eventTypes}) = 'array'
11170
+ and jsonb_array_length(${table.eventTypes}) between 1 and 64
11171
+ and jsonb_typeof(${table.configuration}) = 'object'
11172
+ and jsonb_typeof(${table.parameters}) = 'object'
11173
+ and jsonb_typeof(${table.sessionTemplate}) = 'object'`,
11174
+ ),
11175
+ }),
11176
+ );
11177
+
11178
+ export const automationTriggerEvents = pgTable(
11179
+ "automation_trigger_events",
11180
+ {
11181
+ id: uuid("id").primaryKey().defaultRandom(),
11182
+ accountId: uuid("account_id").notNull(),
11183
+ workspaceId: uuid("workspace_id").notNull(),
11184
+ sourceId: uuid("source_id").notNull(),
11185
+ sourceVersion: integer("source_version").notNull(),
11186
+ sourceConfiguration: jsonb("source_configuration").$type<Record<string, unknown>>().notNull(),
11187
+ matchedTriggerRevisions: jsonb("matched_trigger_revisions")
11188
+ .$type<Array<{ triggerId: string; revision: number }>>()
11189
+ .notNull(),
11190
+ deliveryKey: text("delivery_key").notNull(),
11191
+ requestDigest: text("request_digest").notNull(),
11192
+ adapterId: text("adapter_id").notNull(),
11193
+ eventType: text("event_type").notNull(),
11194
+ occurrenceKey: text("occurrence_key").notNull(),
11195
+ normalizedEvent: jsonb("normalized_event").$type<Record<string, unknown>>().notNull(),
11196
+ status: text("status").notNull().default("accepted"),
11197
+ ignoredReason: text("ignored_reason"),
11198
+ errorCode: text("error_code"),
11199
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
11200
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
11201
+ },
11202
+ (table) => ({
11203
+ sourceDelivery: uniqueIndex("automation_trigger_events_source_delivery_uq").on(
11204
+ table.sourceId,
11205
+ table.deliveryKey,
11206
+ ),
11207
+ workspaceCreated: index("automation_trigger_events_workspace_created_idx").on(
11208
+ table.workspaceId,
11209
+ table.createdAt,
11210
+ ),
11211
+ workspaceSourceIdentity: uniqueIndex("automation_trigger_events_workspace_source_id_uq").on(
11212
+ table.workspaceId,
11213
+ table.sourceId,
11214
+ table.id,
11215
+ ),
11216
+ source: foreignKey({
11217
+ columns: [table.workspaceId, table.sourceId],
11218
+ foreignColumns: [automationSources.workspaceId, automationSources.id],
11219
+ name: "automation_trigger_events_source_fk",
11220
+ }).onDelete("cascade"),
11221
+ workspaceAccount: foreignKey({
11222
+ columns: [table.workspaceId, table.accountId],
11223
+ foreignColumns: [workspaces.id, workspaces.accountId],
11224
+ name: "automation_trigger_events_workspace_account_fk",
11225
+ }).onDelete("cascade"),
11226
+ shape: check(
11227
+ "automation_trigger_events_shape_chk",
11228
+ sql`${table.status} in ('accepted', 'ignored', 'failed')
11229
+ and ${table.sourceVersion} > 0
11230
+ and jsonb_typeof(${table.sourceConfiguration}) = 'object'
11231
+ and jsonb_typeof(${table.matchedTriggerRevisions}) = 'array'
11232
+ and jsonb_array_length(${table.matchedTriggerRevisions}) <= 32
11233
+ and ${table.requestDigest} ~ '^[0-9a-f]{64}$'
11234
+ and octet_length(${table.deliveryKey}) between 1 and 1024
11235
+ and octet_length(${table.eventType}) between 1 and 256
11236
+ and octet_length(${table.occurrenceKey}) between 1 and 1024
11237
+ and jsonb_typeof(${table.normalizedEvent}) = 'object'`,
11238
+ ),
11239
+ }),
11240
+ );
11241
+
11242
+ export const automationRuns = pgTable(
11243
+ "automation_runs",
11244
+ {
11245
+ id: uuid("id").primaryKey().defaultRandom(),
11246
+ accountId: uuid("account_id").notNull(),
11247
+ workspaceId: uuid("workspace_id").notNull(),
11248
+ sourceId: uuid("source_id").notNull(),
11249
+ triggerId: uuid("trigger_id").notNull(),
11250
+ triggerRevision: integer("trigger_revision").notNull(),
11251
+ eventId: uuid("event_id").notNull(),
11252
+ occurrenceKey: text("occurrence_key").notNull(),
11253
+ acceptedExecution: jsonb("accepted_execution").$type<AutomationAcceptedExecution>().notNull(),
11254
+ status: text("status").notNull().default("queued"),
11255
+ sessionId: uuid("session_id").references(() => sessions.id, { onDelete: "set null" }),
11256
+ errorCode: text("error_code"),
11257
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
11258
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
11259
+ },
11260
+ (table) => ({
11261
+ triggerOccurrence: uniqueIndex("automation_runs_trigger_occurrence_uq").on(
11262
+ table.triggerId,
11263
+ table.occurrenceKey,
11264
+ ),
11265
+ workspaceStatus: index("automation_runs_workspace_status_idx").on(
11266
+ table.workspaceId,
11267
+ table.status,
11268
+ table.createdAt,
11269
+ ),
11270
+ workspaceTriggerIdentity: uniqueIndex("automation_runs_workspace_trigger_id_uq").on(
11271
+ table.workspaceId,
11272
+ table.triggerId,
11273
+ table.id,
11274
+ ),
11275
+ source: foreignKey({
11276
+ columns: [table.workspaceId, table.sourceId],
11277
+ foreignColumns: [automationSources.workspaceId, automationSources.id],
11278
+ name: "automation_runs_source_fk",
11279
+ }).onDelete("restrict"),
11280
+ triggerSource: foreignKey({
11281
+ columns: [table.workspaceId, table.triggerId, table.sourceId],
11282
+ foreignColumns: [
11283
+ automationTriggers.workspaceId,
11284
+ automationTriggers.id,
11285
+ automationTriggers.sourceId,
11286
+ ],
11287
+ name: "automation_runs_trigger_source_fk",
11288
+ }).onDelete("restrict"),
11289
+ triggerRevisionFk: foreignKey({
11290
+ columns: [table.triggerId, table.triggerRevision],
11291
+ foreignColumns: [automationTriggerRevisions.triggerId, automationTriggerRevisions.revision],
11292
+ name: "automation_runs_trigger_revision_fk",
11293
+ }).onDelete("restrict"),
11294
+ event: foreignKey({
11295
+ columns: [table.workspaceId, table.sourceId, table.eventId],
11296
+ foreignColumns: [
11297
+ automationTriggerEvents.workspaceId,
11298
+ automationTriggerEvents.sourceId,
11299
+ automationTriggerEvents.id,
11300
+ ],
11301
+ name: "automation_runs_event_fk",
11302
+ }).onDelete("restrict"),
11303
+ workspaceAccount: foreignKey({
11304
+ columns: [table.workspaceId, table.accountId],
11305
+ foreignColumns: [workspaces.id, workspaces.accountId],
11306
+ name: "automation_runs_workspace_account_fk",
11307
+ }).onDelete("cascade"),
11308
+ shape: check(
11309
+ "automation_runs_shape_chk",
11310
+ sql`${table.triggerRevision} > 0
11311
+ and ${table.status} in ('queued', 'dispatching', 'dispatched', 'skipped', 'failed')
11312
+ and octet_length(${table.occurrenceKey}) between 1 and 1024
11313
+ and jsonb_typeof(${table.acceptedExecution}) = 'object'`,
11314
+ ),
11315
+ }),
11316
+ );
11317
+
11318
+ export const automationRunEventLinks = pgTable(
11319
+ "automation_run_event_links",
11320
+ {
11321
+ runId: uuid("run_id")
11322
+ .notNull()
11323
+ .references(() => automationRuns.id, { onDelete: "cascade" }),
11324
+ eventId: uuid("event_id")
11325
+ .notNull()
11326
+ .references(() => automationTriggerEvents.id, { onDelete: "cascade" }),
11327
+ accountId: uuid("account_id").notNull(),
11328
+ workspaceId: uuid("workspace_id").notNull(),
11329
+ sourceId: uuid("source_id").notNull(),
11330
+ triggerId: uuid("trigger_id").notNull(),
11331
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
11332
+ },
11333
+ (table) => ({
11334
+ identity: primaryKey({ columns: [table.runId, table.eventId] }),
11335
+ eventTrigger: uniqueIndex("automation_run_event_links_event_trigger_uq").on(
11336
+ table.eventId,
11337
+ table.triggerId,
11338
+ ),
11339
+ run: foreignKey({
11340
+ columns: [table.workspaceId, table.triggerId, table.runId],
11341
+ foreignColumns: [automationRuns.workspaceId, automationRuns.triggerId, automationRuns.id],
11342
+ name: "automation_run_event_links_run_fk",
11343
+ }).onDelete("cascade"),
11344
+ event: foreignKey({
11345
+ columns: [table.workspaceId, table.sourceId, table.eventId],
11346
+ foreignColumns: [
11347
+ automationTriggerEvents.workspaceId,
11348
+ automationTriggerEvents.sourceId,
11349
+ automationTriggerEvents.id,
11350
+ ],
11351
+ name: "automation_run_event_links_event_fk",
11352
+ }).onDelete("cascade"),
11353
+ workspaceAccount: foreignKey({
11354
+ columns: [table.workspaceId, table.accountId],
11355
+ foreignColumns: [workspaces.id, workspaces.accountId],
11356
+ name: "automation_run_event_links_workspace_account_fk",
11357
+ }).onDelete("cascade"),
11358
+ }),
11359
+ );
11360
+
8482
11361
  export const workspacePacks = pgTable(
8483
11362
  "workspace_packs",
8484
11363
  {
@@ -9223,22 +12102,31 @@ export const rigs = pgTable(
9223
12102
  description: text("description"),
9224
12103
  // Attribution string: 'user:<subject>' | 'session:<id>' | 'system'.
9225
12104
  createdBy: text("created_by"),
9226
- // Inert migration-0230 identity foundation. Existing DAO/API/runtime paths
9227
- // omit these fields and keep their historical workspace authority.
9228
12105
  authorityScope: text("authority_scope").notNull().default("workspace"),
9229
12106
  authorityId: uuid("authority_id"),
9230
12107
  ownerOrganizationMembershipId: uuid("owner_organization_membership_id"),
9231
12108
  originWorkspaceId: uuid("origin_workspace_id"),
12109
+ generation: bigint("generation", { mode: "number" }).notNull().default(1),
12110
+ status: text("status").notNull().default("active"),
12111
+ revokedAt: timestamp("revoked_at", { withTimezone: true }),
9232
12112
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
9233
12113
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
9234
12114
  },
9235
12115
  (table) => ({
9236
- workspaceName: uniqueIndex("rigs_workspace_name_idx").on(table.workspaceId, table.name),
12116
+ workspaceName: uniqueIndex("rigs_workspace_name_active_idx")
12117
+ .on(table.workspaceId, table.name)
12118
+ .where(sql`${table.authorityScope} = 'workspace' and ${table.status} = 'active'`),
12119
+ organizationName: uniqueIndex("rigs_organization_name_active_idx")
12120
+ .on(table.accountId, table.name)
12121
+ .where(sql`${table.authorityScope} = 'organization' and ${table.status} = 'active'`),
12122
+ userName: uniqueIndex("rigs_user_name_active_idx")
12123
+ .on(table.accountId, table.ownerOrganizationMembershipId, table.name)
12124
+ .where(sql`${table.authorityScope} = 'user' and ${table.status} = 'active'`),
9237
12125
  workspaceCreated: index("rigs_workspace_created_idx").on(table.workspaceId, table.createdAt),
9238
12126
  authorityShape: check(
9239
12127
  "rigs_authority_shape_check",
9240
12128
  sql`(
9241
- ${table.authorityScope} = 'workspace'
12129
+ ${table.authorityScope} in ('organization', 'workspace')
9242
12130
  and ${table.authorityId} is null
9243
12131
  and ${table.ownerOrganizationMembershipId} is null
9244
12132
  ) or (
@@ -9249,7 +12137,14 @@ export const rigs = pgTable(
9249
12137
  ),
9250
12138
  authorityScopeValid: check(
9251
12139
  "rigs_authority_scope_check",
9252
- sql`${table.authorityScope} in ('workspace', 'user')`,
12140
+ sql`${table.authorityScope} in ('organization', 'workspace', 'user')`,
12141
+ ),
12142
+ generationPositive: check("rigs_generation_check", sql`${table.generation} > 0`),
12143
+ statusValid: check("rigs_status_check", sql`${table.status} in ('active', 'revoked')`),
12144
+ revocationShape: check(
12145
+ "rigs_revocation_check",
12146
+ sql`(${table.status} = 'active' and ${table.revokedAt} is null)
12147
+ or (${table.status} = 'revoked' and ${table.revokedAt} is not null)`,
9253
12148
  ),
9254
12149
  authority: foreignKey({
9255
12150
  name: "rigs_authority_fk",
@@ -9367,6 +12262,9 @@ export * from "./preference-registry-schema";
9367
12262
  export * from "./memory-governance-schema";
9368
12263
  export * from "./scoped-knowledge-schema";
9369
12264
  export * from "./task-notes-schema";
12265
+ export * from "./company-brain-context-selection-schema";
12266
+ export * from "./governed-learning-evaluator-schema";
12267
+ export * from "./governed-learning-activation-schema";
9370
12268
  export * from "./knowledge-source-sync-schema";
9371
12269
  export * from "./transcription-recordings-schema";
9372
12270
  export * from "./interaction-schema";