@opengeni/db 1.0.1 → 3.2.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 (271) 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-4SR4HKKA.js → chunk-2EH4WRHL.js} +19 -11
  7. package/dist/chunk-2EH4WRHL.js.map +1 -0
  8. package/dist/{chunk-CUPM3FGB.js → chunk-2PRS4J5Q.js} +3 -3
  9. package/dist/chunk-6HMM2R5T.js +390 -0
  10. package/dist/chunk-6HMM2R5T.js.map +1 -0
  11. package/dist/{chunk-ZWREYEWA.js → chunk-AZHW7R5V.js} +56 -11
  12. package/dist/chunk-AZHW7R5V.js.map +1 -0
  13. package/dist/{chunk-LTG3S6ZY.js → chunk-E6CLMKEE.js} +2 -2
  14. package/dist/{chunk-6ZYKCRFO.js → chunk-KRLPEMLH.js} +7591 -4417
  15. package/dist/chunk-KRLPEMLH.js.map +1 -0
  16. package/dist/chunk-NAB6FKFN.js +516 -0
  17. package/dist/chunk-NAB6FKFN.js.map +1 -0
  18. package/dist/{chunk-SK7YYHBI.js → chunk-SIBYBA2D.js} +46 -1
  19. package/dist/chunk-SIBYBA2D.js.map +1 -0
  20. package/dist/chunk-UZNXG44K.js +4508 -0
  21. package/dist/chunk-UZNXG44K.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 +85 -0
  33. package/dist/connection-token-resolver.d.ts +30 -1
  34. package/dist/database.d.ts +23 -4
  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 +1606 -81
  43. package/dist/index.js +21898 -8147
  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 +29 -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 +43 -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 +268 -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 +28 -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/drizzle/0342_slack_route_prompt_single_pending.sql +32 -0
  189. package/drizzle/0343_personal_document_force_rls_lock_repair.sql +146 -0
  190. package/drizzle/0344_private_session_visibility_transition_gate.sql +85 -0
  191. package/drizzle/0345_tenant_scoped_session_tenancy_fence.sql +2839 -0
  192. package/drizzle/0346_document_migration_audit_surface.sql +454 -0
  193. package/drizzle/0347_connection_authority_convergence_evidence.sql +396 -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 +167 -0
  212. package/src/connection-token-resolver.ts +263 -31
  213. package/src/database.ts +87 -2
  214. package/src/editable-artifacts-schema.ts +10 -5
  215. package/src/editable-artifacts.ts +21 -11
  216. package/src/governed-learning-activation-schema.ts +121 -0
  217. package/src/governed-learning-activation.ts +588 -0
  218. package/src/governed-learning-evaluator-schema.ts +72 -0
  219. package/src/governed-learning-evaluator.ts +217 -0
  220. package/src/index.ts +11676 -1550
  221. package/src/insights.ts +107 -0
  222. package/src/integration-bindings.ts +88 -6
  223. package/src/integration-facets.ts +287 -84
  224. package/src/interaction-placement-loss.ts +234 -0
  225. package/src/managed-human-provisioning.ts +116 -0
  226. package/src/memory-domain.ts +27 -32
  227. package/src/migrate.ts +77 -1
  228. package/src/new-session-drafts.ts +229 -35
  229. package/src/organization-membership-backfill.ts +735 -0
  230. package/src/organization-membership-lifecycle.ts +995 -0
  231. package/src/pr-review.ts +890 -0
  232. package/src/preference-registry.ts +31 -1
  233. package/src/provision-roles.ts +905 -6
  234. package/src/runtime-posture-cli.ts +2 -0
  235. package/src/runtime-posture.ts +1317 -72
  236. package/src/schema.ts +3316 -428
  237. package/src/scoped-knowledge-schema.ts +16 -8
  238. package/src/scoped-knowledge.ts +1 -1
  239. package/src/session-background-commands.ts +759 -0
  240. package/src/session-control.ts +640 -74
  241. package/src/session-goal-pacing.ts +126 -0
  242. package/src/session-queue-commands.ts +441 -85
  243. package/src/session-realtime-context.ts +4 -6
  244. package/src/session-realtime-ledger.ts +28 -14
  245. package/src/session-realtime-mirror.ts +1 -1
  246. package/src/session-tenancy.ts +410 -49
  247. package/src/session-tool-call-settlement.ts +38 -29
  248. package/src/slack-routing-personal-workspace.ts +67 -0
  249. package/src/slack-routing.ts +867 -0
  250. package/src/slack-task-policy-schema.ts +5 -0
  251. package/src/slack-task-policy.ts +18 -0
  252. package/src/slack-user-link-access.ts +138 -11
  253. package/src/task-notes-schema.ts +74 -0
  254. package/src/task-notes.ts +124 -2
  255. package/src/turn-initiator.ts +8 -0
  256. package/src/user-resource-authority.ts +377 -0
  257. package/src/workspace-authority.ts +160 -0
  258. package/src/workspace-instruction-policies-schema.ts +53 -1
  259. package/src/workspace-instruction-policies.ts +528 -77
  260. package/src/workspace-learning-policy.ts +21 -5
  261. package/src/workspace-membership-permissions.ts +9 -0
  262. package/dist/chunk-4SR4HKKA.js.map +0 -1
  263. package/dist/chunk-6ZYKCRFO.js.map +0 -1
  264. package/dist/chunk-NXAFLCQX.js +0 -2468
  265. package/dist/chunk-NXAFLCQX.js.map +0 -1
  266. package/dist/chunk-SK7YYHBI.js.map +0 -1
  267. package/dist/chunk-WD5ROSLL.js +0 -117
  268. package/dist/chunk-WD5ROSLL.js.map +0 -1
  269. package/dist/chunk-ZWREYEWA.js.map +0 -1
  270. /package/dist/{chunk-CUPM3FGB.js.map → chunk-2PRS4J5Q.js.map} +0 -0
  271. /package/dist/{chunk-LTG3S6ZY.js.map → chunk-E6CLMKEE.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,
@@ -293,10 +293,8 @@ export async function flushSessionRealtimeTranscriptTailInTransaction(
293
293
  subjectId: input.ownerSubjectId,
294
294
  subjectLabel: "Realtime",
295
295
  actor: {
296
- type: "service",
296
+ type: "human",
297
297
  subjectId: input.ownerSubjectId,
298
- subjectLabel: "Realtime",
299
- context: { source: SESSION_REALTIME_TAIL_SOURCE, realtimeId: mode.id },
300
298
  },
301
299
  operationKey: deterministicUuid(`opengeni:session-realtime-tail-flush:${mode.id}`),
302
300
  delivery: "steer",
@@ -308,7 +306,7 @@ export async function flushSessionRealtimeTranscriptTailInTransaction(
308
306
  context: rendered.context,
309
307
  },
310
308
  resources: [],
311
- reasoningEffortFallback: reasoning.success ? reasoning.data : "medium",
309
+ reasoningEffortFallback: reasoningEffort,
312
310
  turnMetadata: {
313
311
  realtimeTailFlush: {
314
312
  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,
@@ -1179,10 +1185,8 @@ async function admitRealtimeDelegationInTransaction(
1179
1185
  subjectId: input.ownerSubjectId,
1180
1186
  subjectLabel: "Realtime",
1181
1187
  actor: {
1182
- type: "service",
1188
+ type: "human",
1183
1189
  subjectId: input.ownerSubjectId,
1184
- subjectLabel: "Realtime",
1185
- context: provenance,
1186
1190
  },
1187
1191
  operationKey: incoming.operationId,
1188
1192
  delivery: "steer",
@@ -1193,15 +1197,12 @@ async function admitRealtimeDelegationInTransaction(
1193
1197
  text: inputTranscript,
1194
1198
  context: incoming.text!,
1195
1199
  },
1200
+ mirrorToRealtime: false,
1196
1201
  resources: [],
1197
1202
  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",
1203
+ reasoningEffort: latestReasoning.success ? latestReasoning.data : sessionReasoning,
1204
+ latencyMode: latestLatency.success ? latestLatency.data : sessionLatency,
1205
+ reasoningEffortFallback: sessionReasoning,
1205
1206
  turnMetadata: {
1206
1207
  realtimeDelegation: { ...provenance, inputTranscript },
1207
1208
  },
@@ -1323,8 +1324,17 @@ export async function syncSessionRealtimeLedgerInTransaction(
1323
1324
  throw new Error("Realtime ledger batch exceeds the server limit");
1324
1325
  }
1325
1326
  // 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");
1327
+ // order before owner proof locks the session row. The prefix is shared while
1328
+ // the session branch is active; the delegation Steer below re-enters the same
1329
+ // admission helper and observes this already-held prefix.
1330
+ await lockWorkspaceInferenceControlForAdmission(db, {
1331
+ workspaceId: input.workspaceId,
1332
+ sessionId: input.sessionId,
1333
+ resumePausedBranch: true,
1334
+ ...(input.controlLockTimeoutMs !== undefined
1335
+ ? { lockTimeoutMs: input.controlLockTimeoutMs }
1336
+ : {}),
1337
+ });
1328
1338
  const mode = await assertSessionRealtimeOwnerInTransaction(db, input);
1329
1339
  if (mode.connectionEpoch !== input.connectionEpoch) {
1330
1340
  throw new SessionRealtimeConflictError(
@@ -1500,6 +1510,10 @@ export async function syncSessionRealtimeLedgerInTransaction(
1500
1510
  entry.id,
1501
1511
  modelContext,
1502
1512
  );
1513
+ // Human prompt admission can mirror a provider-out context row into this
1514
+ // ledger. Continue after that canonical append instead of reusing its
1515
+ // sequence for the next provider-in entry in this batch.
1516
+ nextSequence = await nextLedgerSequence(db, input.realtimeId);
1503
1517
  const [linked] = await db
1504
1518
  .update(schema.sessionRealtimeEntries)
1505
1519
  .set({ turnId: admission.turnId, updatedAt: now })
@@ -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,178 @@ 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 === "55000") {
299
+ throw new SessionTenancyNotActivatedError({ cause: error });
300
+ }
301
+ if (state === "42501" || state === "P0002") {
302
+ throw new SessionTenancyAccessError({ cause: error });
303
+ }
304
+ if (state === "22023") {
305
+ throw new SessionTenancyInvalidRequestError({ cause: error });
306
+ }
307
+ throw error;
308
+ }
309
+
55
310
  export function canonicalSessionVisibilityTransitionHash(
56
311
  input: Pick<
57
312
  TransitionSessionVisibilityInput,
@@ -73,17 +328,32 @@ export function canonicalSessionVisibilityTransitionHash(
73
328
  export function canonicalSessionForkHash(
74
329
  input: Pick<
75
330
  ForkSessionContentInput,
76
- "sourceSessionId" | "destinationWorkspaceId" | "destinationVisibility"
331
+ | "sourceSessionId"
332
+ | "destinationWorkspaceId"
333
+ | "destinationVisibility"
334
+ | "workspaceSharedAcknowledged"
77
335
  >,
78
336
  ): string {
337
+ const legacyCompatiblePrivateRequest =
338
+ input.destinationVisibility === "user_private" && !input.workspaceSharedAcknowledged;
79
339
  return createHash("sha256")
80
340
  .update(
81
- JSON.stringify({
82
- version: 1,
83
- sourceSessionId: input.sourceSessionId,
84
- destinationWorkspaceId: input.destinationWorkspaceId,
85
- destinationVisibility: input.destinationVisibility,
86
- }),
341
+ JSON.stringify(
342
+ legacyCompatiblePrivateRequest
343
+ ? {
344
+ version: 1,
345
+ sourceSessionId: input.sourceSessionId,
346
+ destinationWorkspaceId: input.destinationWorkspaceId,
347
+ destinationVisibility: input.destinationVisibility,
348
+ }
349
+ : {
350
+ version: 2,
351
+ sourceSessionId: input.sourceSessionId,
352
+ destinationWorkspaceId: input.destinationWorkspaceId,
353
+ destinationVisibility: input.destinationVisibility,
354
+ workspaceSharedAcknowledged: input.workspaceSharedAcknowledged,
355
+ },
356
+ ),
87
357
  )
88
358
  .digest("hex");
89
359
  }
@@ -98,27 +368,33 @@ export async function transitionSessionVisibility(
98
368
  if (!input.operationKey.trim()) throw new Error("operationKey must not be empty");
99
369
  const requestHash = canonicalSessionVisibilityTransitionHash(input);
100
370
  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
371
+ await assertSessionTenancyProductActivated(db, input.workspaceId);
372
+ try {
373
+ return await withWorkspaceSubjectSessionActivityRls(
374
+ db,
375
+ input.workspaceId,
376
+ input.actorSubjectId,
377
+ async (scopedDb) => {
378
+ const rows = await rawRows<{
379
+ operationId: string;
380
+ eventId: string | null;
381
+ eventSequence: number | null;
382
+ visibility: SessionTenancyVisibility;
383
+ authorityEpoch: number;
384
+ ownerOrganizationMembershipId: string | null;
385
+ changed: boolean;
386
+ replay: boolean;
387
+ interruptedAttemptCount: number;
388
+ cancelledTurnCount: number;
389
+ cancelledUpdateCount: number;
390
+ pausedGoalCount: number;
391
+ revokedGrantCount: number;
392
+ }>(
393
+ scopedDb,
394
+ sql`select
121
395
  operation_id as "operationId",
396
+ event_id as "eventId",
397
+ event_sequence as "eventSequence",
122
398
  visibility,
123
399
  authority_epoch as "authorityEpoch",
124
400
  owner_organization_membership_id as "ownerOrganizationMembershipId",
@@ -137,14 +413,20 @@ export async function transitionSessionVisibility(
137
413
  ${input.targetVisibility},
138
414
  ${input.expectedAuthorityEpoch},
139
415
  ${input.operationKey},
140
- ${requestHash}
416
+ ${requestHash},
417
+ ${SESSION_TENANCY_ACTIVATION_VERSION}
141
418
  )`,
142
- );
143
- const result = rows[0];
144
- if (!result) throw new Error("Session visibility transition returned no result");
145
- return result;
146
- },
147
- );
419
+ );
420
+ const result = rows[0];
421
+ if (!result) throw new Error("Session visibility transition returned no result");
422
+ return result;
423
+ },
424
+ undefined,
425
+ "none",
426
+ );
427
+ } catch (error) {
428
+ mapSessionTenancyPersistenceError(error, { authorityEpochConflict: true });
429
+ }
148
430
  }
149
431
 
150
432
  export async function forkSessionContent(
@@ -152,17 +434,24 @@ export async function forkSessionContent(
152
434
  input: ForkSessionContentInput,
153
435
  ): Promise<ForkSessionContentResult> {
154
436
  if (!input.operationKey.trim()) throw new Error("operationKey must not be empty");
437
+ if (input.destinationWorkspaceId !== input.sourceWorkspaceId) {
438
+ throw new Error("The first session fork contract is same-workspace only");
439
+ }
155
440
  const { accountId } = await rlsContextForWorkspace(db, input.sourceWorkspaceId);
441
+ await assertSessionTenancyProductActivated(db, input.sourceWorkspaceId);
156
442
  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
443
+ try {
444
+ return await withWorkspaceSubjectRls(
445
+ db,
446
+ input.sourceWorkspaceId,
447
+ input.actorSubjectId,
448
+ async (scopedDb) => {
449
+ const rows = await rawRows<ForkSessionContentResult>(
450
+ scopedDb,
451
+ sql`select
165
452
  operation_id as "operationId",
453
+ event_id as "eventId",
454
+ event_sequence as "eventSequence",
166
455
  session_id as "sessionId",
167
456
  workspace_id as "workspaceId",
168
457
  visibility,
@@ -176,13 +465,85 @@ export async function forkSessionContent(
176
465
  ${input.actorSubjectId},
177
466
  ${input.destinationWorkspaceId}::uuid,
178
467
  ${input.destinationVisibility},
468
+ ${input.workspaceSharedAcknowledged},
179
469
  ${input.operationKey},
180
- ${requestHash}
470
+ ${requestHash},
471
+ ${SESSION_TENANCY_ACTIVATION_VERSION}
181
472
  )`,
182
- );
183
- const result = rows[0];
184
- if (!result) throw new Error("Session fork returned no result");
185
- return result;
186
- },
187
- );
473
+ );
474
+ const result = rows[0];
475
+ if (!result) throw new Error("Session fork returned no result");
476
+ return result;
477
+ },
478
+ undefined,
479
+ "none",
480
+ );
481
+ } catch (error) {
482
+ // A private destination inside a shared workspace carries the same
483
+ // organization owner/admin product decision the create path enforces
484
+ // (migration 0323), and the fork routine raises the same 55000. Surface it
485
+ // as the not-activated product state rather than an opaque database error.
486
+ if (nestedPostgresSqlState(error) === "55000") {
487
+ throw new SessionTenancyNotActivatedError({ cause: error });
488
+ }
489
+ mapSessionTenancyPersistenceError(error, { authorityEpochConflict: false });
490
+ }
491
+ }
492
+
493
+ /**
494
+ * Recover one exact committed fork before mutable source-session authorization.
495
+ *
496
+ * The database capability requires the authenticated actor's active workspace
497
+ * authority and matches the complete actor/workspace/source/key/request-hash
498
+ * tuple. A fresh key returns null; changed intent raises the ordinary tenancy
499
+ * idempotency conflict. This is receipt recovery, not session discovery.
500
+ */
501
+ export async function replayAppliedSessionFork(
502
+ db: Database,
503
+ input: ForkSessionContentInput,
504
+ ): Promise<ForkSessionContentResult | null> {
505
+ if (!input.operationKey.trim()) throw new Error("operationKey must not be empty");
506
+ if (input.destinationWorkspaceId !== input.sourceWorkspaceId) {
507
+ throw new Error("The first session fork contract is same-workspace only");
508
+ }
509
+ const { accountId } = await rlsContextForWorkspace(db, input.sourceWorkspaceId);
510
+ await assertSessionTenancyProductActivated(db, input.sourceWorkspaceId);
511
+ const requestHash = canonicalSessionForkHash(input);
512
+ try {
513
+ return await withWorkspaceSubjectRls(
514
+ db,
515
+ input.sourceWorkspaceId,
516
+ input.actorSubjectId,
517
+ async (scopedDb) => {
518
+ const rows = await rawRows<ForkSessionContentResult>(
519
+ scopedDb,
520
+ sql`select
521
+ operation_id as "operationId",
522
+ event_id as "eventId",
523
+ event_sequence as "eventSequence",
524
+ session_id as "sessionId",
525
+ workspace_id as "workspaceId",
526
+ visibility,
527
+ authority_epoch as "authorityEpoch",
528
+ copied_history_item_count as "copiedHistoryItemCount",
529
+ replay
530
+ from replay_applied_session_fork(
531
+ ${accountId}::uuid,
532
+ ${input.sourceWorkspaceId}::uuid,
533
+ ${input.sourceSessionId}::uuid,
534
+ ${input.actorSubjectId},
535
+ ${input.destinationWorkspaceId}::uuid,
536
+ ${input.destinationVisibility},
537
+ ${input.workspaceSharedAcknowledged},
538
+ ${input.operationKey},
539
+ ${requestHash},
540
+ ${SESSION_TENANCY_ACTIVATION_VERSION}
541
+ )`,
542
+ );
543
+ return rows[0] ?? null;
544
+ },
545
+ );
546
+ } catch (error) {
547
+ mapSessionTenancyPersistenceError(error, { authorityEpochConflict: false });
548
+ }
188
549
  }