@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
@@ -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
  }
@@ -1,6 +1,6 @@
1
1
  import type { SessionEvent } from "@opengeni/contracts";
2
- import { boundModelToolOutputItem } from "@opengeni/codex";
3
- import { and, asc, eq, sql } from "drizzle-orm";
2
+ import { canonicalizePersistedHistoryItem, omitOutputOnlyHistoryItemFields } from "@opengeni/codex";
3
+ import { and, asc, eq, inArray, sql } from "drizzle-orm";
4
4
  import type { Database } from "./database";
5
5
  import { fromPostgresLosslessJson, LOSSLESS_CONTENT_CODEC_VERSION } from "./lossless-json";
6
6
  import * as schema from "./schema";
@@ -85,6 +85,8 @@ export type ClosePendingSessionToolCallsInput = {
85
85
  reason: string;
86
86
  sequence: number;
87
87
  now: Date;
88
+ turnAssociation?: "current" | null;
89
+ preserveInterruptionRows?: boolean;
88
90
  };
89
91
 
90
92
  function mapEvent(row: typeof schema.sessionEvents.$inferSelect): SessionEvent {
@@ -107,10 +109,13 @@ function mapEvent(row: typeof schema.sessionEvents.$inferSelect): SessionEvent {
107
109
  }
108
110
 
109
111
  /**
110
- * Close every unresolved raw tool call for a logical turn while its owning
111
- * session/turn locks are held. Durable results win; otherwise the model gets an
112
- * explicit interrupted/outcome-unknown result for protocols that can represent
113
- * one. Repeated settlement is an exact no-op after ledger deletion.
112
+ * Close raw tool calls for a logical turn while its owning session/turn locks
113
+ * are held. Recoverable transitions preserve interruption rows because those
114
+ * rows are the exact open-suffix resume authority for a replacement attempt;
115
+ * ordinary in-flight rows still receive durable outcome-unknown settlement.
116
+ * Terminal and superseding transitions close every row. Durable results win;
117
+ * repeated settlement is an exact no-op after the selected ledger rows are
118
+ * deleted.
114
119
  */
115
120
  export async function closePendingSessionToolCallsInTransaction(
116
121
  tx: Database,
@@ -128,18 +133,20 @@ export async function closePendingSessionToolCallsInTransaction(
128
133
  )
129
134
  .orderBy(asc(schema.sessionPendingToolCalls.createdAt), asc(schema.sessionPendingToolCalls.id))
130
135
  .for("update");
131
- const pending = pendingRows.map((row) => ({
132
- ...row,
133
- callItem: fromPostgresLosslessJson(row.callItem, row.callItemCodecVersion),
134
- resultItem:
135
- row.resultItem === null
136
- ? null
137
- : fromPostgresLosslessJson(row.resultItem, row.resultItemCodecVersion),
138
- eventOutput:
139
- row.eventOutput === null
140
- ? null
141
- : fromPostgresLosslessJson(row.eventOutput, row.eventOutputCodecVersion),
142
- }));
136
+ const pending = pendingRows
137
+ .filter((row) => !(input.preserveInterruptionRows && row.interruptionKind !== null))
138
+ .map((row) => ({
139
+ ...row,
140
+ callItem: fromPostgresLosslessJson(row.callItem, row.callItemCodecVersion),
141
+ resultItem:
142
+ row.resultItem === null
143
+ ? null
144
+ : fromPostgresLosslessJson(row.resultItem, row.resultItemCodecVersion),
145
+ eventOutput:
146
+ row.eventOutput === null
147
+ ? null
148
+ : fromPostgresLosslessJson(row.eventOutput, row.eventOutputCodecVersion),
149
+ }));
143
150
  if (pending.length === 0) return { sequence: input.sequence, events: [], closed: 0 };
144
151
 
145
152
  const history = await tx
@@ -250,7 +257,7 @@ export async function closePendingSessionToolCallsInTransaction(
250
257
  sessionId: input.sessionId,
251
258
  turnId: input.turnId,
252
259
  position: nextPosition++,
253
- item: resolution.call.callItem,
260
+ item: omitOutputOnlyHistoryItemFields(resolution.call.callItem),
254
261
  itemCodecVersion: LOSSLESS_CONTENT_CODEC_VERSION,
255
262
  active: true,
256
263
  });
@@ -274,7 +281,7 @@ export async function closePendingSessionToolCallsInTransaction(
274
281
  sessionId: input.sessionId,
275
282
  turnId: input.turnId,
276
283
  position: nextPosition++,
277
- item: boundModelToolOutputItem(
284
+ item: canonicalizePersistedHistoryItem(
278
285
  resolution.result,
279
286
  resolution.call.modelToolOutputTruncationTokens ?? undefined,
280
287
  ),
@@ -297,7 +304,7 @@ export async function closePendingSessionToolCallsInTransaction(
297
304
  turnId: input.turnId,
298
305
  turnGeneration: resolution.call.executionGeneration,
299
306
  turnAttemptId: resolution.call.attemptId,
300
- turnAssociation: "current",
307
+ turnAssociation: input.turnAssociation === undefined ? "current" : input.turnAssociation,
301
308
  payload: {
302
309
  id: resolution.call.callId,
303
310
  output: resolution.interrupted
@@ -341,14 +348,16 @@ export async function closePendingSessionToolCallsInTransaction(
341
348
  reason: input.reason,
342
349
  now: input.now,
343
350
  });
344
- await tx
345
- .delete(schema.sessionPendingToolCalls)
346
- .where(
347
- and(
348
- eq(schema.sessionPendingToolCalls.workspaceId, input.workspaceId),
349
- eq(schema.sessionPendingToolCalls.sessionId, input.sessionId),
350
- eq(schema.sessionPendingToolCalls.turnId, input.turnId),
351
+ await tx.delete(schema.sessionPendingToolCalls).where(
352
+ and(
353
+ eq(schema.sessionPendingToolCalls.workspaceId, input.workspaceId),
354
+ eq(schema.sessionPendingToolCalls.sessionId, input.sessionId),
355
+ eq(schema.sessionPendingToolCalls.turnId, input.turnId),
356
+ inArray(
357
+ schema.sessionPendingToolCalls.id,
358
+ pending.map((call) => call.id),
351
359
  ),
352
- );
360
+ ),
361
+ );
353
362
  return { sequence, events: inserted.map(mapEvent), closed: pending.length };
354
363
  }