@opengeni/db 1.0.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 (265) 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-LTG3S6ZY.js → chunk-EULAN2NP.js} +2 -2
  7. package/dist/{chunk-CUPM3FGB.js → chunk-NJVZPWXY.js} +3 -3
  8. package/dist/{chunk-4SR4HKKA.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-6ZYKCRFO.js → chunk-PR2WMLOQ.js} +7580 -4417
  13. package/dist/chunk-PR2WMLOQ.js.map +1 -0
  14. package/dist/{chunk-ZWREYEWA.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.js +2 -2
  36. package/dist/editable-artifacts.d.ts +2 -2
  37. package/dist/editable-artifacts.js +3 -3
  38. package/dist/governed-learning-activation-schema.d.ts +984 -0
  39. package/dist/governed-learning-activation.d.ts +98 -0
  40. package/dist/governed-learning-evaluator-schema.d.ts +633 -0
  41. package/dist/governed-learning-evaluator.d.ts +28 -0
  42. package/dist/index.d.ts +1595 -81
  43. package/dist/index.js +21571 -8126
  44. package/dist/index.js.map +1 -1
  45. package/dist/insights.d.ts +18 -0
  46. package/dist/integration-bindings.d.ts +1 -0
  47. package/dist/integration-facets.d.ts +30 -0
  48. package/dist/interaction-placement-loss.d.ts +22 -0
  49. package/dist/managed-human-provisioning.d.ts +25 -0
  50. package/dist/memory-domain.d.ts +3 -3
  51. package/dist/migrate.d.ts +11 -1
  52. package/dist/migrate.js +1 -1
  53. package/dist/new-session-drafts.d.ts +43 -5
  54. package/dist/organization-membership-backfill.d.ts +174 -0
  55. package/dist/organization-membership-lifecycle.d.ts +162 -0
  56. package/dist/pr-review.d.ts +107 -0
  57. package/dist/preference-registry-schema.d.ts +2 -0
  58. package/dist/preference-registry.d.ts +2 -0
  59. package/dist/provision-roles.js +1 -1
  60. package/dist/runtime-posture.d.ts +22 -8
  61. package/dist/schema.d.ts +24914 -13703
  62. package/dist/schema.js +99 -1
  63. package/dist/scoped-knowledge-schema.d.ts +52 -1
  64. package/dist/session-background-commands.d.ts +125 -0
  65. package/dist/session-background-commands.js +34 -0
  66. package/dist/session-background-commands.js.map +1 -0
  67. package/dist/session-control.d.ts +156 -3
  68. package/dist/session-goal-pacing.d.ts +68 -0
  69. package/dist/session-queue-commands.d.ts +41 -4
  70. package/dist/session-realtime-ledger.d.ts +2 -0
  71. package/dist/session-tenancy.d.ts +71 -1
  72. package/dist/session-tenancy.js +25 -3
  73. package/dist/session-tool-call-settlement.d.ts +9 -4
  74. package/dist/slack-routing-personal-workspace.d.ts +23 -0
  75. package/dist/slack-routing.d.ts +153 -0
  76. package/dist/slack-task-policy-schema.d.ts +34 -0
  77. package/dist/slack-task-policy.d.ts +8 -0
  78. package/dist/slack-user-link-access.d.ts +1 -0
  79. package/dist/task-notes-schema.d.ts +594 -0
  80. package/dist/task-notes.d.ts +39 -0
  81. package/dist/turn-initiator.d.ts +2 -0
  82. package/dist/user-resource-authority.d.ts +109 -0
  83. package/dist/video-generation.js +3 -3
  84. package/dist/workspace-authority.d.ts +81 -0
  85. package/dist/workspace-instruction-policies-schema.d.ts +279 -0
  86. package/dist/workspace-instruction-policies.d.ts +62 -0
  87. package/dist/workspace-learning-policy.d.ts +1 -0
  88. package/dist/workspace-membership-permissions.d.ts +2 -0
  89. package/drizzle/0240_enrollment_connection_authority.sql +39 -0
  90. package/drizzle/0241_atomic_personal_resource_delegation.sql +1000 -0
  91. package/drizzle/0241_enrollment_agent_runtime.sql +70 -0
  92. package/drizzle/0244_slack_app_home_refresh_queue.sql +120 -0
  93. package/drizzle/0245_model_context_contribution_facts.sql +88 -0
  94. package/drizzle/0246_integration_personal_instance_authority.sql +70 -0
  95. package/drizzle/0247_terraform_stacks_provenance_repair.sql +428 -0
  96. package/drizzle/0248_terraform_stacks_component_resolution_fence.sql +83 -0
  97. package/drizzle/0249_personal_resource_delegation_authority_correction.sql +662 -0
  98. package/drizzle/0250_direct_retained_process_owner_liveness.sql +199 -0
  99. package/drizzle/0251_connected_machine_operation_policy.sql +45 -0
  100. package/drizzle/0252_scheduled_personal_resource_delegation.sql +1867 -0
  101. package/drizzle/0253_common_user_resource_authority_lifecycle.sql +1555 -0
  102. package/drizzle/0254_scoped_variable_set_authority.sql +1255 -0
  103. package/drizzle/0255_company_brain_governed_write_proposals.sql +524 -0
  104. package/drizzle/0256_connection_authority_delegation.sql +703 -0
  105. package/drizzle/0257_goal_revision_decisions_and_root_constraints.sql +436 -0
  106. package/drizzle/0258_three_scope_document_knowledge_authority.sql +1273 -0
  107. package/drizzle/0259_company_brain_context_selection_receipts.sql +869 -0
  108. package/drizzle/0260_task_note_knowledge_promotion.sql +820 -0
  109. package/drizzle/0261_preference_knowledge_proposal_actor_binding.sql +471 -0
  110. package/drizzle/0262_scoped_connected_machines_and_rigs.sql +1700 -0
  111. package/drizzle/0263_organization_membership_lifecycle.sql +3284 -0
  112. package/drizzle/0264_connection_authority_runtime_activation.sql +1084 -0
  113. package/drizzle/0266_company_brain_context_receipt_inspection.sql +156 -0
  114. package/drizzle/0268_governed_learning_decision_receipts.sql +825 -0
  115. package/drizzle/0269_governed_learning_activation_controller.sql +1874 -0
  116. package/drizzle/0270_governed_learning_history_inspection.sql +170 -0
  117. package/drizzle/0271_company_brain_retrieval_only_default.sql +524 -0
  118. package/drizzle/0272_human_confirmed_learning_activation.sql +591 -0
  119. package/drizzle/0273_scheduled_variable_set_materialization.sql +268 -0
  120. package/drizzle/0274_human_confirmed_knowledge_review.sql +310 -0
  121. package/drizzle/0275_scheduled_connection_authority.sql +5527 -0
  122. package/drizzle/0276_onboarding_proposal_initiating_human_guc.sql +198 -0
  123. package/drizzle/0277_workspace_writer_authority_attribution.sql +393 -0
  124. package/drizzle/0278_workspace_membership_removal_fencing.sql +1174 -0
  125. package/drizzle/0279_workspace_connection_use_lane.sql +343 -0
  126. package/drizzle/0280_connection_and_variable_set_audit_attribution.sql +773 -0
  127. package/drizzle/0281_viewer_holder_authority_claims.sql +52 -0
  128. package/drizzle/0282_variable_set_session_attach_attribution.sql +178 -0
  129. package/drizzle/0283_editable_spreadsheet_authored_state.sql +82 -0
  130. package/drizzle/0284_truthful_human_confirmed_review_reason.sql +930 -0
  131. package/drizzle/0285_organization_tenancy_inventory.sql +329 -0
  132. package/drizzle/0286_widen_task_note_expiry_ceiling.sql +391 -0
  133. package/drizzle/0287_open_suffix_pending_tool_calls.sql +18 -0
  134. package/drizzle/0288_attached_browser_reenrollment.sql +64 -0
  135. package/drizzle/0289_session_composer_policy_authority.sql +597 -0
  136. package/drizzle/0290_organization_membership_backfill.sql +268 -0
  137. package/drizzle/0291_resource_authority_classification_assertion.sql +371 -0
  138. package/drizzle/0292_truthful_tenancy_inventory_counters.sql +291 -0
  139. package/drizzle/0293_confirm_time_rule_rebaseline.sql +615 -0
  140. package/drizzle/0294_preference_activation_authority.sql +237 -0
  141. package/drizzle/0295_retire_legacy_standing_memory_mode.sql +476 -0
  142. package/drizzle/0296_force_rls_backfill_noop_repair.sql +215 -0
  143. package/drizzle/0297_session_ownership_classification_and_backfill.sql +752 -0
  144. package/drizzle/0298_organization_tenancy_parity.sql +609 -0
  145. package/drizzle/0299_organization_membership_lock_order.sql +291 -0
  146. package/drizzle/0300_tenancy_backfill_ledger.sql +459 -0
  147. package/drizzle/0301_session_snapshot_and_pin_visibility.sql +181 -0
  148. package/drizzle/0302_personal_workspace_session_ownership.sql +303 -0
  149. package/drizzle/0303_session_tenancy_product_activation.sql +919 -0
  150. package/drizzle/0304_personal_workspace_private_session_reads.sql +97 -0
  151. package/drizzle/0305_personal_resource_grant_management.sql +584 -0
  152. package/drizzle/0306_atomic_personal_resource_attachments.sql +1374 -0
  153. package/drizzle/0307_session_attention_state.sql +51 -0
  154. package/drizzle/0308_session_archives.sql +35 -0
  155. package/drizzle/0309_channel_project_pins.sql +6 -0
  156. package/drizzle/0310_channel_project_order.sql +27 -0
  157. package/drizzle/0311_company_scope_and_private_session_create.sql +656 -0
  158. package/drizzle/0312_quiescent_session_tree_deletion.sql +39 -0
  159. package/drizzle/0313_private_child_session_authority.sql +365 -0
  160. package/drizzle/0314_unregistered_organization_invitations.sql +606 -0
  161. package/drizzle/0315_personal_github_repository_selection.sql +624 -0
  162. package/drizzle/0316_human_confirmed_activation_resumed_generation.sql +869 -0
  163. package/drizzle/0317_session_goal_continuation_hold.sql +64 -0
  164. package/drizzle/0318_session_turn_startup_milestones.sql +115 -0
  165. package/drizzle/0319_event_triggered_automations.sql +254 -0
  166. package/drizzle/0320_pr_review_pack.sql +113 -0
  167. package/drizzle/0321_slack_bot_environment_display_name.sql +15 -0
  168. package/drizzle/0322_session_turns_unclaimed_prompt_trigger_index.sql +5 -0
  169. package/drizzle/0323_organization_private_session_enablement.sql +489 -0
  170. package/drizzle/0324_human_confirmed_company_profile_agent_admin.sql +856 -0
  171. package/drizzle/0325_child_lifecycle_notices.sql +44 -0
  172. package/drizzle/0326_interaction_operation_error_codes.sql +62 -0
  173. package/drizzle/0327_slack_first_task_hint.sql +29 -0
  174. package/drizzle/0328_session_background_commands.sql +590 -0
  175. package/drizzle/0329_slack_orchestration_delivery_events.sql +91 -0
  176. package/drizzle/0330_api_key_descriptions.sql +20 -0
  177. package/drizzle/0331_managed_organization_creation.sql +163 -0
  178. package/drizzle/0332_organization_shared_workspace_control_plane.sql +539 -0
  179. package/drizzle/0333_session_turn_prompt_routing.sql +45 -0
  180. package/drizzle/0334_connected_machine_workspace_root.sql +99 -0
  181. package/drizzle/0335_slack_workspace_routing.sql +460 -0
  182. package/drizzle/0336_atomic_session_fork_visibility.sql +667 -0
  183. package/drizzle/0337_slack_routed_action_handles.sql +189 -0
  184. package/drizzle/0338_atomic_connected_machine_attachments.sql +410 -0
  185. package/drizzle/0339_document_authority_reclassification.sql +1153 -0
  186. package/drizzle/0340_tenancy_backfill_activation_evidence.sql +1347 -0
  187. package/drizzle/0341_slack_routing_probe_organization_fence.sql +109 -0
  188. package/package.json +10 -6
  189. package/src/attached-browser-devices.ts +233 -5
  190. package/src/automations.ts +911 -0
  191. package/src/browser-sessions.ts +10 -0
  192. package/src/capability-integrations.ts +175 -1
  193. package/src/child-lifecycle-notices.ts +241 -0
  194. package/src/codemode-operations.ts +1 -35
  195. package/src/company-brain-context-selection-schema.ts +100 -0
  196. package/src/company-brain-context-selection.ts +359 -0
  197. package/src/company-brain-governed-writes.ts +1170 -0
  198. package/src/company-brain.ts +309 -0
  199. package/src/company-profile-agent-admin.ts +225 -0
  200. package/src/company-profile-schema.ts +121 -1
  201. package/src/company-profile.ts +21 -0
  202. package/src/computer-sessions.ts +10 -1
  203. package/src/connection-authority.ts +290 -0
  204. package/src/connection-scopes.ts +20 -0
  205. package/src/connection-tenancy-backfill.ts +81 -0
  206. package/src/connection-token-resolver.ts +263 -31
  207. package/src/database.ts +75 -2
  208. package/src/editable-artifacts-schema.ts +10 -5
  209. package/src/editable-artifacts.ts +21 -11
  210. package/src/governed-learning-activation-schema.ts +121 -0
  211. package/src/governed-learning-activation.ts +588 -0
  212. package/src/governed-learning-evaluator-schema.ts +72 -0
  213. package/src/governed-learning-evaluator.ts +217 -0
  214. package/src/index.ts +11651 -1548
  215. package/src/insights.ts +107 -0
  216. package/src/integration-bindings.ts +88 -6
  217. package/src/integration-facets.ts +287 -84
  218. package/src/interaction-placement-loss.ts +234 -0
  219. package/src/managed-human-provisioning.ts +116 -0
  220. package/src/memory-domain.ts +27 -32
  221. package/src/migrate.ts +77 -1
  222. package/src/new-session-drafts.ts +229 -35
  223. package/src/organization-membership-backfill.ts +735 -0
  224. package/src/organization-membership-lifecycle.ts +985 -0
  225. package/src/pr-review.ts +890 -0
  226. package/src/preference-registry.ts +31 -1
  227. package/src/provision-roles.ts +877 -6
  228. package/src/runtime-posture-cli.ts +2 -0
  229. package/src/runtime-posture.ts +1291 -76
  230. package/src/schema.ts +3303 -428
  231. package/src/scoped-knowledge-schema.ts +16 -8
  232. package/src/scoped-knowledge.ts +1 -1
  233. package/src/session-background-commands.ts +759 -0
  234. package/src/session-control.ts +640 -74
  235. package/src/session-goal-pacing.ts +126 -0
  236. package/src/session-queue-commands.ts +437 -84
  237. package/src/session-realtime-context.ts +3 -3
  238. package/src/session-realtime-ledger.ts +22 -11
  239. package/src/session-realtime-mirror.ts +1 -1
  240. package/src/session-tenancy.ts +403 -49
  241. package/src/session-tool-call-settlement.ts +38 -29
  242. package/src/slack-routing-personal-workspace.ts +67 -0
  243. package/src/slack-routing.ts +469 -0
  244. package/src/slack-task-policy-schema.ts +5 -0
  245. package/src/slack-task-policy.ts +18 -0
  246. package/src/slack-user-link-access.ts +17 -1
  247. package/src/task-notes-schema.ts +74 -0
  248. package/src/task-notes.ts +124 -2
  249. package/src/turn-initiator.ts +8 -0
  250. package/src/user-resource-authority.ts +377 -0
  251. package/src/workspace-authority.ts +160 -0
  252. package/src/workspace-instruction-policies-schema.ts +53 -1
  253. package/src/workspace-instruction-policies.ts +528 -77
  254. package/src/workspace-learning-policy.ts +21 -5
  255. package/src/workspace-membership-permissions.ts +9 -0
  256. package/dist/chunk-4SR4HKKA.js.map +0 -1
  257. package/dist/chunk-6ZYKCRFO.js.map +0 -1
  258. package/dist/chunk-NXAFLCQX.js +0 -2468
  259. package/dist/chunk-NXAFLCQX.js.map +0 -1
  260. package/dist/chunk-SK7YYHBI.js.map +0 -1
  261. package/dist/chunk-WD5ROSLL.js +0 -117
  262. package/dist/chunk-WD5ROSLL.js.map +0 -1
  263. package/dist/chunk-ZWREYEWA.js.map +0 -1
  264. /package/dist/{chunk-LTG3S6ZY.js.map → chunk-EULAN2NP.js.map} +0 -0
  265. /package/dist/{chunk-CUPM3FGB.js.map → chunk-NJVZPWXY.js.map} +0 -0
@@ -275,7 +275,7 @@ export async function flushSessionRealtimeTranscriptTailInTransaction(
275
275
  }
276
276
 
277
277
  const [session] = await db
278
- .select({ metadata: schema.sessions.metadata })
278
+ .select({ reasoningEffort: schema.sessions.reasoningEffort })
279
279
  .from(schema.sessions)
280
280
  .where(
281
281
  and(
@@ -285,7 +285,7 @@ export async function flushSessionRealtimeTranscriptTailInTransaction(
285
285
  )
286
286
  .limit(1);
287
287
  if (!session) throw new Error(`Realtime tail session ${input.sessionId} disappeared`);
288
- const reasoning = ReasoningEffort.safeParse(session.metadata.reasoningEffort);
288
+ const reasoningEffort = ReasoningEffort.parse(session.reasoningEffort);
289
289
  const admitted = await submitHumanPromptInTransaction(db, {
290
290
  accountId: input.accountId,
291
291
  workspaceId: input.workspaceId,
@@ -308,7 +308,7 @@ export async function flushSessionRealtimeTranscriptTailInTransaction(
308
308
  context: rendered.context,
309
309
  },
310
310
  resources: [],
311
- reasoningEffortFallback: reasoning.success ? reasoning.data : "medium",
311
+ reasoningEffortFallback: reasoningEffort,
312
312
  turnMetadata: {
313
313
  realtimeTailFlush: {
314
314
  source: SESSION_REALTIME_TAIL_SOURCE,
@@ -15,7 +15,10 @@ import {
15
15
  SessionRealtimeConflictError,
16
16
  type AssertSessionRealtimeOwnerInput,
17
17
  } from "./session-realtime";
18
- import { lockSessionEventWriteRows, lockWorkspaceInferenceControl } from "./session-control";
18
+ import {
19
+ lockSessionEventWriteRows,
20
+ lockWorkspaceInferenceControlForAdmission,
21
+ } from "./session-control";
19
22
  import { submitHumanPromptInTransaction } from "./session-queue-commands";
20
23
  import { mirrorSessionRealtimeContextInTransaction } from "./session-realtime-mirror";
21
24
 
@@ -159,6 +162,8 @@ export type SessionRealtimeInboundEntryInput = {
159
162
  };
160
163
 
161
164
  export type SyncSessionRealtimeLedgerInput = AssertSessionRealtimeOwnerInput & {
165
+ /** Request-scoped callers bound the control prefix wait; lifecycle callers omit it. */
166
+ controlLockTimeoutMs?: number;
162
167
  connectionId: string;
163
168
  connectionEpoch: number;
164
169
  entries?: SessionRealtimeInboundEntryInput[] | undefined;
@@ -1142,7 +1147,8 @@ async function admitRealtimeDelegationInTransaction(
1142
1147
  if (!session || session.accountId !== accountId || session.status === "cancelled") {
1143
1148
  throw new SessionRealtimeConflictError("REALTIME_NOT_FOUND", "Session not found");
1144
1149
  }
1145
- const reasoning = ReasoningEffort.safeParse(session.metadata.reasoningEffort);
1150
+ const sessionReasoning = ReasoningEffort.parse(session.reasoningEffort);
1151
+ const sessionLatency = LatencyMode.parse(session.latencyMode);
1146
1152
  const [latestStarted] = await db
1147
1153
  .select({
1148
1154
  model: schema.sessionTurns.model,
@@ -1195,13 +1201,9 @@ async function admitRealtimeDelegationInTransaction(
1195
1201
  },
1196
1202
  resources: [],
1197
1203
  model: latestStarted?.model ?? session.model,
1198
- reasoningEffort: latestReasoning.success
1199
- ? latestReasoning.data
1200
- : reasoning.success
1201
- ? reasoning.data
1202
- : "medium",
1203
- latencyMode: latestLatency.success ? latestLatency.data : "standard",
1204
- reasoningEffortFallback: reasoning.success ? reasoning.data : "medium",
1204
+ reasoningEffort: latestReasoning.success ? latestReasoning.data : sessionReasoning,
1205
+ latencyMode: latestLatency.success ? latestLatency.data : sessionLatency,
1206
+ reasoningEffortFallback: sessionReasoning,
1205
1207
  turnMetadata: {
1206
1208
  realtimeDelegation: { ...provenance, inputTranscript },
1207
1209
  },
@@ -1323,8 +1325,17 @@ export async function syncSessionRealtimeLedgerInTransaction(
1323
1325
  throw new Error("Realtime ledger batch exceeds the server limit");
1324
1326
  }
1325
1327
  // Realtime sync may admit canonical Steer work. Preserve the global lock
1326
- // order before owner proof locks the session row.
1327
- await lockWorkspaceInferenceControl(db, input.workspaceId, "update");
1328
+ // order before owner proof locks the session row. The prefix is shared while
1329
+ // the session branch is active; the delegation Steer below re-enters the same
1330
+ // admission helper and observes this already-held prefix.
1331
+ await lockWorkspaceInferenceControlForAdmission(db, {
1332
+ workspaceId: input.workspaceId,
1333
+ sessionId: input.sessionId,
1334
+ resumePausedBranch: true,
1335
+ ...(input.controlLockTimeoutMs !== undefined
1336
+ ? { lockTimeoutMs: input.controlLockTimeoutMs }
1337
+ : {}),
1338
+ });
1328
1339
  const mode = await assertSessionRealtimeOwnerInTransaction(db, input);
1329
1340
  if (mode.connectionEpoch !== input.connectionEpoch) {
1330
1341
  throw new SessionRealtimeConflictError(
@@ -215,7 +215,7 @@ export function renderRealtimeHumanInputRequestContext(input: {
215
215
  lines.push(" </options>");
216
216
  }
217
217
  lines.push(
218
- ` <allow_other>${String(question.allowOther)}</allow_other>`,
218
+ ` <allow_other>${String(question.kind !== "text")}</allow_other>`,
219
219
  " </question>",
220
220
  );
221
221
  }
@@ -1,15 +1,93 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { sql } from "drizzle-orm";
3
+ import {
4
+ SessionTenancyBlocker as SessionTenancyBlockerSchema,
5
+ type SessionTenancyBlocker,
6
+ } from "@opengeni/contracts";
3
7
  import {
4
8
  rawRows,
5
9
  type Database,
6
10
  rlsContextForWorkspace,
11
+ withWorkspaceRls,
7
12
  withWorkspaceSubjectRls,
8
13
  withWorkspaceSubjectSessionActivityRls,
9
14
  } from "./database";
15
+ import { nestedPostgresSqlState } from "./persistence-errors";
16
+
17
+ const SESSION_TENANCY_ACTIVATION_VERSION = 1;
18
+
19
+ export class SessionTenancyConflictError extends Error {
20
+ readonly name = "SessionTenancyConflictError";
21
+ constructor(
22
+ readonly reason: "not_quiescent" | "authority_epoch" | "operation_reuse",
23
+ readonly blocker: SessionTenancyBlocker | null = null,
24
+ options?: ErrorOptions,
25
+ ) {
26
+ super(
27
+ reason === "not_quiescent"
28
+ ? "Session must be fully quiescent before its tenancy can change"
29
+ : reason === "authority_epoch"
30
+ ? "Session authority changed before the operation committed"
31
+ : "Session tenancy operation key was reused with different input",
32
+ options,
33
+ );
34
+ }
35
+ }
36
+
37
+ export class SessionTenancyNotActivatedError extends Error {
38
+ readonly name = "SessionTenancyNotActivatedError";
39
+ constructor(options?: ErrorOptions) {
40
+ super("Session tenancy product surface is not activated for this organization", options);
41
+ }
42
+ }
43
+
44
+ export class SessionTenancyAccessError extends Error {
45
+ readonly name = "SessionTenancyAccessError";
46
+ constructor(options?: ErrorOptions) {
47
+ super("Session tenancy target is unavailable", options);
48
+ }
49
+ }
50
+
51
+ export class SessionTenancyInvalidRequestError extends Error {
52
+ readonly name = "SessionTenancyInvalidRequestError";
53
+ constructor(options?: ErrorOptions) {
54
+ super("Session tenancy request is invalid", options);
55
+ }
56
+ }
57
+
58
+ function isRecord(value: unknown): value is Record<string, unknown> {
59
+ return Boolean(value && typeof value === "object");
60
+ }
61
+
62
+ /** Extract only the fixed quiescence DETAIL vocabulary, never arbitrary driver detail. */
63
+ export function nestedSessionTenancyBlocker(error: unknown): SessionTenancyBlocker | null {
64
+ const queue: unknown[] = [error];
65
+ const seen = new Set<unknown>();
66
+ while (queue.length > 0 && seen.size < 64) {
67
+ const current = queue.shift();
68
+ if (!isRecord(current) || seen.has(current)) continue;
69
+ seen.add(current);
70
+ for (const key of ["detail", "detailMessage"] as const) {
71
+ const parsed = SessionTenancyBlockerSchema.safeParse(current[key]);
72
+ if (parsed.success) return parsed.data;
73
+ }
74
+ for (const key of ["cause", "original", "driverError", "error", "errors"] as const) {
75
+ const nested = current[key];
76
+ if (Array.isArray(nested)) queue.push(...nested);
77
+ else if (nested !== undefined) queue.push(nested);
78
+ }
79
+ }
80
+ return null;
81
+ }
10
82
 
11
83
  export type SessionTenancyVisibility = "user_private" | "workspace_shared";
12
84
 
85
+ export type PrivateSessionCreatePolicy = {
86
+ personalWorkspace: boolean;
87
+ platformAvailable: boolean;
88
+ organizationEnabled: boolean;
89
+ };
90
+
13
91
  export type TransitionSessionVisibilityInput = {
14
92
  workspaceId: string;
15
93
  sessionId: string;
@@ -21,6 +99,8 @@ export type TransitionSessionVisibilityInput = {
21
99
 
22
100
  export type TransitionSessionVisibilityResult = {
23
101
  operationId: string;
102
+ eventId: string | null;
103
+ eventSequence: number | null;
24
104
  visibility: SessionTenancyVisibility;
25
105
  authorityEpoch: number;
26
106
  ownerOrganizationMembershipId: string | null;
@@ -39,11 +119,14 @@ export type ForkSessionContentInput = {
39
119
  actorSubjectId: string;
40
120
  destinationWorkspaceId: string;
41
121
  destinationVisibility: SessionTenancyVisibility;
122
+ workspaceSharedAcknowledged: boolean;
42
123
  operationKey: string;
43
124
  };
44
125
 
45
126
  export type ForkSessionContentResult = {
46
127
  operationId: string;
128
+ eventId: string;
129
+ eventSequence: number;
47
130
  sessionId: string;
48
131
  workspaceId: string;
49
132
  visibility: SessionTenancyVisibility;
@@ -52,6 +135,175 @@ export type ForkSessionContentResult = {
52
135
  replay: boolean;
53
136
  };
54
137
 
138
+ export async function sessionTenancyProductActivated(
139
+ db: Database,
140
+ workspaceId: string,
141
+ ): Promise<boolean> {
142
+ const { accountId } = await rlsContextForWorkspace(db, workspaceId);
143
+ return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
144
+ const rows = await rawRows<{ activated: boolean }>(
145
+ scopedDb,
146
+ sql`select session_tenancy_product_activated(
147
+ ${accountId}::uuid,
148
+ ${SESSION_TENANCY_ACTIVATION_VERSION}
149
+ ) as activated`,
150
+ );
151
+ return rows[0]?.activated === true;
152
+ });
153
+ }
154
+
155
+ export async function getPrivateSessionCreatePolicy(
156
+ db: Database,
157
+ input: { workspaceId: string; actorSubjectId: string },
158
+ ): Promise<PrivateSessionCreatePolicy> {
159
+ const { accountId } = await rlsContextForWorkspace(db, input.workspaceId);
160
+ return await withWorkspaceSubjectRls(
161
+ db,
162
+ input.workspaceId,
163
+ input.actorSubjectId,
164
+ async (scopedDb) => {
165
+ const rows = await rawRows<{
166
+ personalWorkspace: boolean;
167
+ platformAvailable: boolean;
168
+ organizationEnabled: boolean;
169
+ }>(
170
+ scopedDb,
171
+ sql`select
172
+ personal_workspace as "personalWorkspace",
173
+ platform_available as "platformAvailable",
174
+ organization_enabled as "organizationEnabled"
175
+ from get_private_session_create_policy(
176
+ ${accountId}::uuid,
177
+ ${input.workspaceId}::uuid,
178
+ ${input.actorSubjectId}
179
+ )`,
180
+ );
181
+ const policy = rows[0];
182
+ if (!policy) throw new SessionTenancyAccessError();
183
+ return policy;
184
+ },
185
+ );
186
+ }
187
+
188
+ /** Open the exact transaction-local trigger capability used by atomic private create. */
189
+ export async function openPrivateSessionCreateCapability(
190
+ db: Database,
191
+ input: { accountId: string; workspaceId: string; sessionId: string; actorSubjectId: string },
192
+ ): Promise<{ capabilityId: string; ownerMembershipId: string }> {
193
+ let rows: Array<{ capabilityId: string; ownerMembershipId: string }>;
194
+ try {
195
+ rows = await rawRows<{ capabilityId: string; ownerMembershipId: string }>(
196
+ db,
197
+ sql`select
198
+ capability_id as "capabilityId",
199
+ owner_membership_id as "ownerMembershipId"
200
+ from open_private_session_create_capability(
201
+ ${input.accountId}::uuid,
202
+ ${input.workspaceId}::uuid,
203
+ ${input.sessionId}::uuid,
204
+ ${input.actorSubjectId}
205
+ )`,
206
+ );
207
+ } catch (error) {
208
+ // The definer raises 55000 only when the organization owner/admin setting
209
+ // does not permit a new private session in this shared workspace; every
210
+ // authority failure remains 42501 and propagates unchanged.
211
+ if (nestedPostgresSqlState(error) === "55000") {
212
+ throw new SessionTenancyNotActivatedError({ cause: error });
213
+ }
214
+ throw error;
215
+ }
216
+ const capabilityId = rows[0]?.capabilityId;
217
+ const ownerMembershipId = rows[0]?.ownerMembershipId;
218
+ if (!capabilityId || !ownerMembershipId) {
219
+ throw new Error("Private session create capability was not returned");
220
+ }
221
+ return { capabilityId, ownerMembershipId };
222
+ }
223
+
224
+ /** Open the exact transaction-local capability for one private child insert. */
225
+ export async function openPrivateChildSessionCreateCapability(
226
+ db: Database,
227
+ input: {
228
+ accountId: string;
229
+ workspaceId: string;
230
+ sessionId: string;
231
+ parentSessionId: string;
232
+ actorTurnId: string;
233
+ actorAttemptId: string;
234
+ actorExecutionGeneration: number;
235
+ },
236
+ ): Promise<{ capabilityId: string; ownerMembershipId: string; ownerSubjectId: string }> {
237
+ const rows = await rawRows<{
238
+ capabilityId: string;
239
+ ownerMembershipId: string;
240
+ ownerSubjectId: string;
241
+ }>(
242
+ db,
243
+ sql`select
244
+ capability_id as "capabilityId",
245
+ owner_membership_id as "ownerMembershipId",
246
+ owner_subject_id as "ownerSubjectId"
247
+ from open_private_child_session_create_capability(
248
+ ${input.accountId}::uuid,
249
+ ${input.workspaceId}::uuid,
250
+ ${input.sessionId}::uuid,
251
+ ${input.parentSessionId}::uuid,
252
+ ${input.actorTurnId}::uuid,
253
+ ${input.actorAttemptId}::uuid,
254
+ ${input.actorExecutionGeneration}::integer
255
+ )`,
256
+ );
257
+ const capabilityId = rows[0]?.capabilityId;
258
+ const ownerMembershipId = rows[0]?.ownerMembershipId;
259
+ const ownerSubjectId = rows[0]?.ownerSubjectId;
260
+ if (!capabilityId || !ownerMembershipId || !ownerSubjectId) {
261
+ throw new Error("Private child session create capability was not returned");
262
+ }
263
+ return { capabilityId, ownerMembershipId, ownerSubjectId };
264
+ }
265
+
266
+ export async function closePrivateSessionCreateCapability(
267
+ db: Database,
268
+ capabilityId: string,
269
+ ): Promise<void> {
270
+ await db.execute(sql`select close_private_session_create_capability(${capabilityId}::uuid)`);
271
+ }
272
+
273
+ async function assertSessionTenancyProductActivated(
274
+ db: Database,
275
+ workspaceId: string,
276
+ ): Promise<void> {
277
+ if (!(await sessionTenancyProductActivated(db, workspaceId))) {
278
+ throw new SessionTenancyNotActivatedError();
279
+ }
280
+ }
281
+
282
+ function mapSessionTenancyPersistenceError(
283
+ error: unknown,
284
+ options: { authorityEpochConflict: boolean },
285
+ ): never {
286
+ const state = nestedPostgresSqlState(error);
287
+ if (state === "55P03") {
288
+ throw new SessionTenancyConflictError("not_quiescent", nestedSessionTenancyBlocker(error), {
289
+ cause: error,
290
+ });
291
+ }
292
+ if (state === "40001" && options.authorityEpochConflict) {
293
+ throw new SessionTenancyConflictError("authority_epoch", null, { cause: error });
294
+ }
295
+ if (state === "23505") {
296
+ throw new SessionTenancyConflictError("operation_reuse", null, { cause: error });
297
+ }
298
+ if (state === "42501" || state === "P0002") {
299
+ throw new SessionTenancyAccessError({ cause: error });
300
+ }
301
+ if (state === "22023") {
302
+ throw new SessionTenancyInvalidRequestError({ cause: error });
303
+ }
304
+ throw error;
305
+ }
306
+
55
307
  export function canonicalSessionVisibilityTransitionHash(
56
308
  input: Pick<
57
309
  TransitionSessionVisibilityInput,
@@ -73,17 +325,32 @@ export function canonicalSessionVisibilityTransitionHash(
73
325
  export function canonicalSessionForkHash(
74
326
  input: Pick<
75
327
  ForkSessionContentInput,
76
- "sourceSessionId" | "destinationWorkspaceId" | "destinationVisibility"
328
+ | "sourceSessionId"
329
+ | "destinationWorkspaceId"
330
+ | "destinationVisibility"
331
+ | "workspaceSharedAcknowledged"
77
332
  >,
78
333
  ): string {
334
+ const legacyCompatiblePrivateRequest =
335
+ input.destinationVisibility === "user_private" && !input.workspaceSharedAcknowledged;
79
336
  return createHash("sha256")
80
337
  .update(
81
- JSON.stringify({
82
- version: 1,
83
- sourceSessionId: input.sourceSessionId,
84
- destinationWorkspaceId: input.destinationWorkspaceId,
85
- destinationVisibility: input.destinationVisibility,
86
- }),
338
+ JSON.stringify(
339
+ legacyCompatiblePrivateRequest
340
+ ? {
341
+ version: 1,
342
+ sourceSessionId: input.sourceSessionId,
343
+ destinationWorkspaceId: input.destinationWorkspaceId,
344
+ destinationVisibility: input.destinationVisibility,
345
+ }
346
+ : {
347
+ version: 2,
348
+ sourceSessionId: input.sourceSessionId,
349
+ destinationWorkspaceId: input.destinationWorkspaceId,
350
+ destinationVisibility: input.destinationVisibility,
351
+ workspaceSharedAcknowledged: input.workspaceSharedAcknowledged,
352
+ },
353
+ ),
87
354
  )
88
355
  .digest("hex");
89
356
  }
@@ -98,27 +365,33 @@ export async function transitionSessionVisibility(
98
365
  if (!input.operationKey.trim()) throw new Error("operationKey must not be empty");
99
366
  const requestHash = canonicalSessionVisibilityTransitionHash(input);
100
367
  const { accountId } = await rlsContextForWorkspace(db, input.workspaceId);
101
- return await withWorkspaceSubjectSessionActivityRls(
102
- db,
103
- input.workspaceId,
104
- input.actorSubjectId,
105
- async (scopedDb) => {
106
- const rows = await rawRows<{
107
- operationId: string;
108
- visibility: SessionTenancyVisibility;
109
- authorityEpoch: number;
110
- ownerOrganizationMembershipId: string | null;
111
- changed: boolean;
112
- replay: boolean;
113
- interruptedAttemptCount: number;
114
- cancelledTurnCount: number;
115
- cancelledUpdateCount: number;
116
- pausedGoalCount: number;
117
- revokedGrantCount: number;
118
- }>(
119
- scopedDb,
120
- sql`select
368
+ await assertSessionTenancyProductActivated(db, input.workspaceId);
369
+ try {
370
+ return await withWorkspaceSubjectSessionActivityRls(
371
+ db,
372
+ input.workspaceId,
373
+ input.actorSubjectId,
374
+ async (scopedDb) => {
375
+ const rows = await rawRows<{
376
+ operationId: string;
377
+ eventId: string | null;
378
+ eventSequence: number | null;
379
+ visibility: SessionTenancyVisibility;
380
+ authorityEpoch: number;
381
+ ownerOrganizationMembershipId: string | null;
382
+ changed: boolean;
383
+ replay: boolean;
384
+ interruptedAttemptCount: number;
385
+ cancelledTurnCount: number;
386
+ cancelledUpdateCount: number;
387
+ pausedGoalCount: number;
388
+ revokedGrantCount: number;
389
+ }>(
390
+ scopedDb,
391
+ sql`select
121
392
  operation_id as "operationId",
393
+ event_id as "eventId",
394
+ event_sequence as "eventSequence",
122
395
  visibility,
123
396
  authority_epoch as "authorityEpoch",
124
397
  owner_organization_membership_id as "ownerOrganizationMembershipId",
@@ -137,14 +410,18 @@ export async function transitionSessionVisibility(
137
410
  ${input.targetVisibility},
138
411
  ${input.expectedAuthorityEpoch},
139
412
  ${input.operationKey},
140
- ${requestHash}
413
+ ${requestHash},
414
+ ${SESSION_TENANCY_ACTIVATION_VERSION}
141
415
  )`,
142
- );
143
- const result = rows[0];
144
- if (!result) throw new Error("Session visibility transition returned no result");
145
- return result;
146
- },
147
- );
416
+ );
417
+ const result = rows[0];
418
+ if (!result) throw new Error("Session visibility transition returned no result");
419
+ return result;
420
+ },
421
+ );
422
+ } catch (error) {
423
+ mapSessionTenancyPersistenceError(error, { authorityEpochConflict: true });
424
+ }
148
425
  }
149
426
 
150
427
  export async function forkSessionContent(
@@ -152,17 +429,24 @@ export async function forkSessionContent(
152
429
  input: ForkSessionContentInput,
153
430
  ): Promise<ForkSessionContentResult> {
154
431
  if (!input.operationKey.trim()) throw new Error("operationKey must not be empty");
432
+ if (input.destinationWorkspaceId !== input.sourceWorkspaceId) {
433
+ throw new Error("The first session fork contract is same-workspace only");
434
+ }
155
435
  const { accountId } = await rlsContextForWorkspace(db, input.sourceWorkspaceId);
436
+ await assertSessionTenancyProductActivated(db, input.sourceWorkspaceId);
156
437
  const requestHash = canonicalSessionForkHash(input);
157
- return await withWorkspaceSubjectRls(
158
- db,
159
- input.sourceWorkspaceId,
160
- input.actorSubjectId,
161
- async (scopedDb) => {
162
- const rows = await rawRows<ForkSessionContentResult>(
163
- scopedDb,
164
- sql`select
438
+ try {
439
+ return await withWorkspaceSubjectRls(
440
+ db,
441
+ input.sourceWorkspaceId,
442
+ input.actorSubjectId,
443
+ async (scopedDb) => {
444
+ const rows = await rawRows<ForkSessionContentResult>(
445
+ scopedDb,
446
+ sql`select
165
447
  operation_id as "operationId",
448
+ event_id as "eventId",
449
+ event_sequence as "eventSequence",
166
450
  session_id as "sessionId",
167
451
  workspace_id as "workspaceId",
168
452
  visibility,
@@ -176,13 +460,83 @@ export async function forkSessionContent(
176
460
  ${input.actorSubjectId},
177
461
  ${input.destinationWorkspaceId}::uuid,
178
462
  ${input.destinationVisibility},
463
+ ${input.workspaceSharedAcknowledged},
179
464
  ${input.operationKey},
180
- ${requestHash}
465
+ ${requestHash},
466
+ ${SESSION_TENANCY_ACTIVATION_VERSION}
181
467
  )`,
182
- );
183
- const result = rows[0];
184
- if (!result) throw new Error("Session fork returned no result");
185
- return result;
186
- },
187
- );
468
+ );
469
+ const result = rows[0];
470
+ if (!result) throw new Error("Session fork returned no result");
471
+ return result;
472
+ },
473
+ );
474
+ } catch (error) {
475
+ // A private destination inside a shared workspace carries the same
476
+ // organization owner/admin product decision the create path enforces
477
+ // (migration 0323), and the fork routine raises the same 55000. Surface it
478
+ // as the not-activated product state rather than an opaque database error.
479
+ if (nestedPostgresSqlState(error) === "55000") {
480
+ throw new SessionTenancyNotActivatedError({ cause: error });
481
+ }
482
+ mapSessionTenancyPersistenceError(error, { authorityEpochConflict: false });
483
+ }
484
+ }
485
+
486
+ /**
487
+ * Recover one exact committed fork before mutable source-session authorization.
488
+ *
489
+ * The database capability requires the authenticated actor's active workspace
490
+ * authority and matches the complete actor/workspace/source/key/request-hash
491
+ * tuple. A fresh key returns null; changed intent raises the ordinary tenancy
492
+ * idempotency conflict. This is receipt recovery, not session discovery.
493
+ */
494
+ export async function replayAppliedSessionFork(
495
+ db: Database,
496
+ input: ForkSessionContentInput,
497
+ ): Promise<ForkSessionContentResult | null> {
498
+ if (!input.operationKey.trim()) throw new Error("operationKey must not be empty");
499
+ if (input.destinationWorkspaceId !== input.sourceWorkspaceId) {
500
+ throw new Error("The first session fork contract is same-workspace only");
501
+ }
502
+ const { accountId } = await rlsContextForWorkspace(db, input.sourceWorkspaceId);
503
+ await assertSessionTenancyProductActivated(db, input.sourceWorkspaceId);
504
+ const requestHash = canonicalSessionForkHash(input);
505
+ try {
506
+ return await withWorkspaceSubjectRls(
507
+ db,
508
+ input.sourceWorkspaceId,
509
+ input.actorSubjectId,
510
+ async (scopedDb) => {
511
+ const rows = await rawRows<ForkSessionContentResult>(
512
+ scopedDb,
513
+ sql`select
514
+ operation_id as "operationId",
515
+ event_id as "eventId",
516
+ event_sequence as "eventSequence",
517
+ session_id as "sessionId",
518
+ workspace_id as "workspaceId",
519
+ visibility,
520
+ authority_epoch as "authorityEpoch",
521
+ copied_history_item_count as "copiedHistoryItemCount",
522
+ replay
523
+ from replay_applied_session_fork(
524
+ ${accountId}::uuid,
525
+ ${input.sourceWorkspaceId}::uuid,
526
+ ${input.sourceSessionId}::uuid,
527
+ ${input.actorSubjectId},
528
+ ${input.destinationWorkspaceId}::uuid,
529
+ ${input.destinationVisibility},
530
+ ${input.workspaceSharedAcknowledged},
531
+ ${input.operationKey},
532
+ ${requestHash},
533
+ ${SESSION_TENANCY_ACTIVATION_VERSION}
534
+ )`,
535
+ );
536
+ return rows[0] ?? null;
537
+ },
538
+ );
539
+ } catch (error) {
540
+ mapSessionTenancyPersistenceError(error, { authorityEpochConflict: false });
541
+ }
188
542
  }