@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
@@ -428,13 +428,7 @@ export const WORKSPACE_MEMORY_BLOCK_EMPTY = `## Workspace memory
428
428
  This workspace has shared long-lived memory, currently empty. Your context is lost when the session ends; memory is not. When you learn something durably useful — a preference, an environment fact, a procedure that worked, a decision and its reason — save it with memory_save (one crisp, self-contained fact per record). Never store secrets.`;
429
429
 
430
430
  export const MEMORY_SEARCH_TOOL_DESCRIPTION =
431
- "Search this workspace's shared long-lived memory (semantic + keyword). Use it before starting a new non-trivial task when the injected notes or current conversation do not already answer how the workspace does something. Results persist in conversation context: do not repeat the same search as routine setup on every continuation, resume, or interrupted turn. Returns scored records with ids.";
432
-
433
- export const MEMORY_SAVE_TOOL_DESCRIPTION =
434
- "Save one durable, future-useful fact to this workspace's shared memory: a stable preference, an environment fact, a procedure that worked, or a decision and its reason. Write it compactly (1–3 sentences), self-contained (no 'this session/above' references, absolute dates, name concrete things), so a future session can act on it alone. Do NOT save: session-specific state, speculation, anything derivable from the repo/docs, or near-duplicates of existing memories (search first — to refine or replace an existing record pass replaces_id). Most turns have nothing worth saving.";
435
-
436
- export const MEMORY_CORRECT_TOOL_DESCRIPTION =
437
- "Flag a workspace memory as wrong or outdated the moment you discover it — this is the most valuable memory action, because a wrong memory misleads every future session. Pass the record's id (as shown in [brackets]); optionally give replacement_text with the corrected fact, otherwise the record is archived.";
431
+ "Search this workspace's shared long-lived memory (semantic + keyword). Use it before starting a new non-trivial task when the injected notes or current conversation do not already answer how the workspace does something. Results persist in conversation context: do not repeat the same search as routine setup on every continuation, resume, or interrupted turn. Returns scored records with ids. Indexed workspace documents are a separate store: search those with `knowledge_search`/`knowledge_get` (or `search_documents`) on the Document Search (docs) MCP server, not with this tool.";
438
432
 
439
433
  // ---------------------------------------------------------------------------
440
434
  // Text normalization + hashing (MUST match migration 0045 backfill exactly)
@@ -469,7 +463,7 @@ export function isMemoryTextTooLong(text: string): boolean {
469
463
  // ---------------------------------------------------------------------------
470
464
 
471
465
  export function estimateMemoryTokens(text: string): number {
472
- return Math.ceil(text.length / 4);
466
+ return Math.ceil(Buffer.byteLength(text, "utf8") / 4);
473
467
  }
474
468
 
475
469
  // Short id shown in the block/tool output = first 8 chars of the uuid. Tools
@@ -491,46 +485,47 @@ export type MemoryBlockRecord = {
491
485
  // into kind sections. Episodic is excluded. Returns null if nothing renders
492
486
  // (no non-episodic records) — the caller substitutes the empty-state block.
493
487
  export function renderWorkspaceMemoryBlock(records: readonly MemoryBlockRecord[]): string | null {
494
- const renderable = records.filter((record) => record.kind !== "episodic");
495
- if (renderable.length === 0) {
488
+ if (!records.some((record) => record.kind !== "episodic")) {
496
489
  return null;
497
490
  }
491
+ const selected = selectWorkspaceMemoryBlockRecords(records);
492
+
493
+ const lines: string[] = [WORKSPACE_MEMORY_BLOCK_HEADER_POPULATED];
494
+ for (const kind of MEMORY_BLOCK_KIND_ORDER) {
495
+ const inSection = selected.filter((record) => record.kind === kind);
496
+ if (inSection.length === 0) {
497
+ continue;
498
+ }
499
+ lines.push("", `### ${MEMORY_KIND_SECTION_TITLES[kind]}`);
500
+ for (const record of inSection) {
501
+ lines.push(renderMemoryEntry(record));
502
+ }
503
+ }
504
+ return lines.join("\n");
505
+ }
498
506
 
499
- // Greedy budget fill in priority order. We track the running token estimate of
500
- // the whole block (header + section titles introduced so far + entries).
507
+ /** Canonical whole-entry prompt-budget selector, shared with receipt validation. */
508
+ export function selectWorkspaceMemoryBlockRecords(
509
+ records: readonly MemoryBlockRecord[],
510
+ ): readonly MemoryBlockRecord[] {
511
+ const renderable = records.filter((record) => record.kind !== "episodic");
501
512
  const headerTokens = estimateMemoryTokens(WORKSPACE_MEMORY_BLOCK_HEADER_POPULATED);
502
513
  let usedTokens = headerTokens;
503
514
  const seenSections = new Set<KnowledgeMemoryKind>();
504
515
  const selected: MemoryBlockRecord[] = [];
505
516
  for (const record of renderable) {
506
517
  const entryLine = renderMemoryEntry(record);
507
- let cost = estimateMemoryTokens(entryLine) + 1; // +1 for the entry's newline
518
+ let cost = estimateMemoryTokens(entryLine) + 1;
508
519
  if (!seenSections.has(record.kind)) {
509
520
  const sectionTitle = `### ${MEMORY_KIND_SECTION_TITLES[record.kind]}`;
510
- cost += estimateMemoryTokens(sectionTitle) + 2; // title + blank line separator
511
- }
512
- if (usedTokens + cost > WORKSPACE_MEMORY_BLOCK_TOKEN_BUDGET) {
513
- // Skip entries that don't fit instead of stopping: one oversized entry
514
- // must not starve smaller lower-priority records of the remaining budget.
515
- continue;
521
+ cost += estimateMemoryTokens(sectionTitle) + 2;
516
522
  }
523
+ if (usedTokens + cost > WORKSPACE_MEMORY_BLOCK_TOKEN_BUDGET) continue;
517
524
  usedTokens += cost;
518
525
  seenSections.add(record.kind);
519
526
  selected.push(record);
520
527
  }
521
-
522
- const lines: string[] = [WORKSPACE_MEMORY_BLOCK_HEADER_POPULATED];
523
- for (const kind of MEMORY_BLOCK_KIND_ORDER) {
524
- const inSection = selected.filter((record) => record.kind === kind);
525
- if (inSection.length === 0) {
526
- continue;
527
- }
528
- lines.push("", `### ${MEMORY_KIND_SECTION_TITLES[kind]}`);
529
- for (const record of inSection) {
530
- lines.push(renderMemoryEntry(record));
531
- }
532
- }
533
- return lines.join("\n");
528
+ return selected;
534
529
  }
535
530
 
536
531
  function renderMemoryEntry(record: MemoryBlockRecord): string {
package/src/migrate.ts CHANGED
@@ -6,6 +6,12 @@ import postgres from "postgres";
6
6
  const DEFAULT_DATABASE_URL = "postgres://opengeni:opengeni@127.0.0.1:5432/opengeni";
7
7
  const DEFAULT_MAX_NESTED_AGENT_DEPTH = 3;
8
8
  const MAX_NESTED_AGENT_DEPTH = 2_147_483_647;
9
+ const DEFAULT_APPLICATION_DATABASE_ROLE = "opengeni_app";
10
+ const GOAL_REVISION_CUTOVER_MIGRATION = "0257_goal_revision_decisions_and_root_constraints.sql";
11
+ const ATOMIC_PERSONAL_RESOURCE_CUTOVER_MIGRATION = "0306_atomic_personal_resource_attachments.sql";
12
+ const UNREGISTERED_INVITATION_CUTOVER_MIGRATION = "0314_unregistered_organization_invitations.sql";
13
+ const ATOMIC_CONNECTED_MACHINE_CUTOVER_MIGRATION = "0338_atomic_connected_machine_attachments.sql";
14
+ const MAX_MIGRATION_APPLICATION_ROLES = 16;
9
15
  const batchedBackfillDirective =
10
16
  /^-- opengeni:batched-backfill batch-size=(\d+) lock-timeout=(\d+(?:ms|s|min)) statement-timeout=(\d+(?:ms|s|min))$/;
11
17
  const concurrentIndexDirective = /^-- opengeni:concurrent-index lock-timeout=(\d+(?:ms|s|min))$/;
@@ -30,6 +36,12 @@ export interface ConcurrentIndexMigration {
30
36
 
31
37
  export type MigrationRuntimeOptions = {
32
38
  maxNestedAgentDepth?: number;
39
+ /**
40
+ * Exact database login roles that may run an OpenGeni API or worker against
41
+ * this target. Maintenance cutovers use this list to reject a live mixed-
42
+ * version fleet. Dedicated-schema and custom-role deployments must supply it.
43
+ */
44
+ applicationDatabaseRoles?: string[];
33
45
  };
34
46
 
35
47
  type DeploymentDepthPolicy = {
@@ -230,6 +242,53 @@ function deploymentDepthPolicy(
230
242
  return { maxNestedAgentDepth: value, source: "deployment" };
231
243
  }
232
244
 
245
+ function migrationApplicationRoles(
246
+ schema: string | undefined,
247
+ options: MigrationRuntimeOptions | undefined,
248
+ ): string[] {
249
+ const configured = options?.applicationDatabaseRoles;
250
+ const environment = process.env.OPENGENI_MIGRATION_APPLICATION_DATABASE_ROLES?.trim();
251
+ const explicit =
252
+ configured !== undefined
253
+ ? configured
254
+ : environment
255
+ ? environment.split(",").map((role) => role.trim())
256
+ : undefined;
257
+
258
+ if (!explicit) {
259
+ const configuredRuntimeRoles = [
260
+ process.env.OPENGENI_APP_DATABASE_USER?.trim(),
261
+ process.env.OPENGENI_RUNTIME_DATABASE_ROLE?.trim(),
262
+ ].filter((role): role is string => Boolean(role) && role !== DEFAULT_APPLICATION_DATABASE_ROLE);
263
+ if (schema || configuredRuntimeRoles.length > 0) {
264
+ throw new Error(
265
+ "Migration 0257 requires the exact application database roles via " +
266
+ "MigrationRuntimeOptions.applicationDatabaseRoles or " +
267
+ "OPENGENI_MIGRATION_APPLICATION_DATABASE_ROLES for dedicated-schema or custom-role deployments",
268
+ );
269
+ }
270
+ return [DEFAULT_APPLICATION_DATABASE_ROLE];
271
+ }
272
+
273
+ if (explicit.length < 1 || explicit.length > MAX_MIGRATION_APPLICATION_ROLES) {
274
+ throw new Error(
275
+ `Migration application database roles must contain 1-${MAX_MIGRATION_APPLICATION_ROLES} entries`,
276
+ );
277
+ }
278
+ const normalized = [...new Set(explicit)].sort((left, right) =>
279
+ Buffer.compare(Buffer.from(left, "utf8"), Buffer.from(right, "utf8")),
280
+ );
281
+ if (
282
+ normalized.length !== explicit.length ||
283
+ normalized.some((role) => role.length === 0 || Buffer.byteLength(role, "utf8") > 63)
284
+ ) {
285
+ throw new Error(
286
+ "Migration application database roles must be unique, non-empty Postgres role names of at most 63 UTF-8 bytes",
287
+ );
288
+ }
289
+ return normalized;
290
+ }
291
+
233
292
  async function persistDeploymentDepthPolicy(
234
293
  sql: postgres.Sql,
235
294
  policy: DeploymentDepthPolicy,
@@ -277,6 +336,9 @@ async function persistDeploymentDepthPolicy(
277
336
  * 2. `public` stays LAST on the path so `gen_random_uuid()` (pgcrypto) and the
278
337
  * `vector` type — both installed into `public` by 0000 — still resolve. The
279
338
  * `opengeni_private.*` helpers are always called with an absolute prefix.
339
+ * Dedicated-schema callers must also pass the exact API/worker database login
340
+ * identities in `applicationDatabaseRoles`; maintenance cutovers cannot infer
341
+ * host-managed or scoped runtime roles from the admin connection.
280
342
  *
281
343
  * `OPENGENI_DB_SCHEMA` defaults UNSET → `public` → standalone, so the default
282
344
  * binding never regresses.
@@ -312,6 +374,19 @@ export async function migrate(
312
374
  );
313
375
  const appliedRows = await sql`SELECT "name" FROM "schema_migrations"`;
314
376
  const applied = new Set(appliedRows.map((row) => row.name as string));
377
+ if (
378
+ !applied.has(GOAL_REVISION_CUTOVER_MIGRATION) ||
379
+ !applied.has(ATOMIC_PERSONAL_RESOURCE_CUTOVER_MIGRATION) ||
380
+ !applied.has(UNREGISTERED_INVITATION_CUTOVER_MIGRATION) ||
381
+ !applied.has(ATOMIC_CONNECTED_MACHINE_CUTOVER_MIGRATION)
382
+ ) {
383
+ const applicationRoles = migrationApplicationRoles(schema, runtimeOptions);
384
+ await sql`select set_config(
385
+ 'opengeni.migration_application_roles',
386
+ ${JSON.stringify(applicationRoles)},
387
+ false
388
+ )`;
389
+ }
315
390
  for (const file of files) {
316
391
  if (applied.has(file)) {
317
392
  continue;
@@ -332,7 +407,8 @@ export async function migrate(
332
407
  * SDK entry point (Step I): run the migration chain over a host-supplied admin
333
408
  * connection string against an explicit target schema. This is the embedded
334
409
  * topology's named entry — a host calls `runMigrations(adminConnection,
335
- * targetSchema)` from its own provisioning code instead of relying on env vars.
410
+ * targetSchema, { applicationDatabaseRoles })` from its own provisioning code
411
+ * instead of relying on env vars.
336
412
  * `targetSchema` undefined → `public` → standalone behavior. Thin wrapper over
337
413
  * `migrate` so there is one migration engine.
338
414
  */
@@ -1,17 +1,44 @@
1
1
  import type {
2
2
  NewSessionDraftOptions,
3
+ NewSessionSelectionHistory,
3
4
  LatencyMode,
4
5
  ReasoningEffort,
5
6
  RepositoryResourceRef,
6
7
  ResourceRef,
7
8
  ToolRef,
8
9
  } from "@opengeni/contracts";
9
- import { and, eq } from "drizzle-orm";
10
+ import { stableJson } from "@opengeni/contracts";
11
+ import { and, eq, sql } from "drizzle-orm";
10
12
  import type { Database } from "./database";
11
13
  import * as schema from "./schema";
14
+ import {
15
+ accountIdInRlsScope,
16
+ subjectHasLiveWorkspaceAuthorityInScope,
17
+ } from "./workspace-authority";
12
18
 
13
19
  export type NewSessionDraftRow = typeof schema.newSessionDrafts.$inferSelect;
14
20
 
21
+ async function lockNewSessionDraftIdentity(
22
+ db: Database,
23
+ workspaceId: string,
24
+ subjectId: string,
25
+ ): Promise<void> {
26
+ await db.execute(
27
+ sql`select pg_advisory_xact_lock(hashtextextended(${`new-session-draft:${workspaceId}:${subjectId}`}, 0))`,
28
+ );
29
+ }
30
+
31
+ export type NewSessionDraftSnapshot = {
32
+ text: string;
33
+ resources: ResourceRef[];
34
+ tools: ToolRef[];
35
+ toolsProvided: boolean;
36
+ model: string;
37
+ reasoningEffort: ReasoningEffort;
38
+ latencyMode: LatencyMode;
39
+ options: NewSessionDraftOptions;
40
+ };
41
+
15
42
  export class NewSessionDraftConflictError extends Error {
16
43
  readonly name = "NewSessionDraftConflictError";
17
44
 
@@ -31,13 +58,54 @@ export class NewSessionDraftAccessError extends Error {
31
58
  type StoredNewSessionDraftOptions = NewSessionDraftOptions & {
32
59
  /** JSONB-only compatibility marker; deliberately not part of public options. */
33
60
  toolsProvided?: boolean;
61
+ /** Successful-create preference state, separate from transient draft edits. */
62
+ selectionHistory?: NewSessionSelectionHistory;
34
63
  };
35
64
 
65
+ const REMEMBERED_WORKING_DIR_MAX_LENGTH = 4096;
66
+
36
67
  function storedOptions(
37
68
  options: NewSessionDraftOptions,
38
69
  toolsProvided: boolean,
70
+ selectionHistory: NewSessionSelectionHistory = { projects: [] },
39
71
  ): StoredNewSessionDraftOptions {
40
- return { ...options, toolsProvided };
72
+ return { ...options, toolsProvided, selectionHistory };
73
+ }
74
+
75
+ export function newSessionSelectionHistory(row: NewSessionDraftRow): NewSessionSelectionHistory {
76
+ const raw = (row.sessionOptions as StoredNewSessionDraftOptions).selectionHistory;
77
+ if (!raw || !Array.isArray(raw.projects)) return { projects: [] };
78
+ return {
79
+ projects: raw.projects.slice(0, 50).flatMap((project) => {
80
+ if (!project || (project.channelId !== null && typeof project.channelId !== "string")) {
81
+ return [];
82
+ }
83
+ return [
84
+ {
85
+ channelId: project.channelId,
86
+ targetSandboxId:
87
+ typeof project.targetSandboxId === "string" ? project.targetSandboxId : null,
88
+ machines: Array.isArray(project.machines)
89
+ ? project.machines.slice(0, 20).flatMap((machine) =>
90
+ machine && typeof machine.sandboxId === "string"
91
+ ? [
92
+ {
93
+ sandboxId: machine.sandboxId,
94
+ workingDir:
95
+ typeof machine.workingDir === "string" &&
96
+ machine.workingDir.length > 0 &&
97
+ machine.workingDir.length <= REMEMBERED_WORKING_DIR_MAX_LENGTH
98
+ ? machine.workingDir
99
+ : null,
100
+ },
101
+ ]
102
+ : [],
103
+ )
104
+ : [],
105
+ },
106
+ ];
107
+ }),
108
+ };
41
109
  }
42
110
 
43
111
  export function newSessionDraftToolsProvided(row: NewSessionDraftRow): boolean {
@@ -53,6 +121,7 @@ export function newSessionDraftToolsProvided(row: NewSessionDraftRow): boolean {
53
121
  export function publicNewSessionDraftOptions(row: NewSessionDraftRow): NewSessionDraftOptions {
54
122
  const options = { ...(row.sessionOptions as StoredNewSessionDraftOptions) };
55
123
  delete options.toolsProvided;
124
+ delete options.selectionHistory;
56
125
  return options;
57
126
  }
58
127
 
@@ -87,10 +156,16 @@ export async function saveNewSessionDraftInTransaction(
87
156
  toolsProvided: boolean;
88
157
  model: string;
89
158
  reasoningEffort: ReasoningEffort;
90
- latencyMode?: LatencyMode;
159
+ latencyMode: LatencyMode;
91
160
  options: NewSessionDraftOptions;
92
161
  /** API-key and delegated service subjects have no workspace-membership row. */
93
162
  requireWorkspaceMembership?: boolean;
163
+ /**
164
+ * May this caller use the owner-only managed personal-workspace exception?
165
+ * See `PersonalWorkspaceOwnerException` in `@opengeni/db`. Absent means the
166
+ * historical bare-membership fence, unchanged.
167
+ */
168
+ personalWorkspaceOwnerException?: boolean;
94
169
  },
95
170
  ): Promise<NewSessionDraftRow> {
96
171
  if (input.requireWorkspaceMembership !== false) {
@@ -109,9 +184,36 @@ export async function saveNewSessionDraftInTransaction(
109
184
  )
110
185
  .for("key share")
111
186
  .limit(1);
112
- if (!membership) throw new NewSessionDraftAccessError();
187
+ // The owner of a managed personal workspace never has a membership row there
188
+ // (migration 0219 raises on one), so the bare probe above would 403 the
189
+ // composer inside the one workspace they always belong to. Ask the canonical
190
+ // resolver instead of re-deriving that rule — it runs on this same
191
+ // transaction handle, and there is no membership row for removeWorkspaceMember()
192
+ // to delete in a personal workspace, so nothing is lost by taking no
193
+ // `FOR KEY SHARE` on this path.
194
+ if (
195
+ !membership &&
196
+ !(
197
+ input.personalWorkspaceOwnerException === true &&
198
+ (await subjectHasLiveWorkspaceAuthorityInScope(db, {
199
+ // Scope-derived, NOT `input.accountId`: the applied RLS GUC is the
200
+ // tenant this transaction actually runs under, so an authority
201
+ // decision never reads a caller-supplied account. Matches
202
+ // listSessionsForSubject and setSessionPin.
203
+ accountId: await accountIdInRlsScope(db),
204
+ workspaceId: input.workspaceId,
205
+ subjectId: input.subjectId,
206
+ }))
207
+ )
208
+ ) {
209
+ throw new NewSessionDraftAccessError();
210
+ }
113
211
  }
114
- const current = await getNewSessionDraftInTransaction(db, { ...input, lock: true });
212
+ await lockNewSessionDraftIdentity(db, input.workspaceId, input.subjectId);
213
+ const current = await getNewSessionDraftInTransaction(db, {
214
+ ...input,
215
+ lock: true,
216
+ });
115
217
  const currentRevision = current?.revision ?? 0;
116
218
  if (currentRevision !== input.expectedRevision) {
117
219
  throw new NewSessionDraftConflictError(currentRevision);
@@ -128,10 +230,14 @@ export async function saveNewSessionDraftInTransaction(
128
230
  tools: input.tools,
129
231
  model: input.model,
130
232
  reasoningEffort: input.reasoningEffort,
131
- latencyMode: input.latencyMode ?? "standard",
233
+ latencyMode: input.latencyMode,
132
234
  // Keep the explicit/omitted policy in the existing JSONB extension point;
133
235
  // adding a column here would turn a client preference into a migration.
134
- sessionOptions: storedOptions(input.options, input.toolsProvided),
236
+ sessionOptions: storedOptions(
237
+ input.options,
238
+ input.toolsProvided,
239
+ current ? newSessionSelectionHistory(current) : { projects: [] },
240
+ ),
135
241
  updatedAt: new Date(),
136
242
  };
137
243
  if (current) {
@@ -155,7 +261,10 @@ export async function saveNewSessionDraftInTransaction(
155
261
  })
156
262
  .returning();
157
263
  if (inserted) return inserted;
158
- const raced = await getNewSessionDraftInTransaction(db, { ...input, lock: true });
264
+ const raced = await getNewSessionDraftInTransaction(db, {
265
+ ...input,
266
+ lock: true,
267
+ });
159
268
  throw new NewSessionDraftConflictError(raced?.revision ?? 0);
160
269
  }
161
270
 
@@ -177,38 +286,94 @@ function safeRepositoryResource(resource: RepositoryResourceRef): RepositoryReso
177
286
  };
178
287
  }
179
288
 
180
- function safeWorkingDir(value: unknown, targetSandboxId: string | undefined): string | undefined {
181
- if (!targetSandboxId || typeof value !== "string") return undefined;
182
- const trimmed = value.trim();
183
- // Only a workspace-root-relative path is safe to remember. Absolute host
184
- // paths and traversal would leak or unexpectedly target a different machine
185
- // location after the user changes machines.
186
- if (
187
- !trimmed ||
188
- trimmed === "." ||
189
- trimmed === ".." ||
190
- trimmed.includes("\u0000") ||
191
- trimmed.startsWith("/") ||
192
- trimmed.startsWith("\\") ||
193
- /^[A-Za-z]:[\\/]/.test(trimmed) ||
194
- trimmed.split(/[\\/]+/).some((part) => part === "..")
195
- ) {
196
- return undefined;
289
+ export type AcceptedNewSessionSelection = {
290
+ channelId: string | null;
291
+ targetSandboxId: string | null;
292
+ workingDir: string | null;
293
+ };
294
+
295
+ export function rememberNewSessionSelection(
296
+ history: NewSessionSelectionHistory,
297
+ selection: AcceptedNewSessionSelection,
298
+ ): NewSessionSelectionHistory {
299
+ const existingProject = history.projects.find(
300
+ (project) => project.channelId === selection.channelId,
301
+ );
302
+ let machines = existingProject?.machines ?? [];
303
+ if (selection.targetSandboxId) {
304
+ machines = [
305
+ {
306
+ sandboxId: selection.targetSandboxId,
307
+ workingDir:
308
+ selection.workingDir && selection.workingDir.length <= REMEMBERED_WORKING_DIR_MAX_LENGTH
309
+ ? selection.workingDir
310
+ : null,
311
+ },
312
+ ...machines.filter((machine) => machine.sandboxId !== selection.targetSandboxId),
313
+ ].slice(0, 20);
197
314
  }
198
- return trimmed;
315
+ return {
316
+ projects: [
317
+ {
318
+ channelId: selection.channelId,
319
+ targetSandboxId: selection.targetSandboxId,
320
+ machines,
321
+ },
322
+ ...history.projects.filter((project) => project.channelId !== selection.channelId),
323
+ ].slice(0, 50),
324
+ };
325
+ }
326
+
327
+ /**
328
+ * Record a successful create without consuming the editable draft. Realtime
329
+ * session creation uses this path because it must preserve the actor's pending
330
+ * text/files for a later ordinary session. The hidden preference update shares
331
+ * the draft identity lock with saves, and deliberately leaves its OCC revision
332
+ * unchanged because no browser-editable field changed.
333
+ */
334
+ export async function rememberNewSessionSelectionInTransaction(
335
+ db: Database,
336
+ input: {
337
+ workspaceId: string;
338
+ subjectId: string;
339
+ acceptedSelection: AcceptedNewSessionSelection;
340
+ },
341
+ ): Promise<boolean> {
342
+ await lockNewSessionDraftIdentity(db, input.workspaceId, input.subjectId);
343
+ const current = await getNewSessionDraftInTransaction(db, { ...input, lock: true });
344
+ if (!current) return false;
345
+ const [updated] = await db
346
+ .update(schema.newSessionDrafts)
347
+ .set({
348
+ sessionOptions: storedOptions(
349
+ publicNewSessionDraftOptions(current),
350
+ newSessionDraftToolsProvided(current),
351
+ rememberNewSessionSelection(newSessionSelectionHistory(current), input.acceptedSelection),
352
+ ),
353
+ })
354
+ .where(eq(schema.newSessionDrafts.id, current.id))
355
+ .returning({ id: schema.newSessionDrafts.id });
356
+ return Boolean(updated);
199
357
  }
200
358
 
201
359
  /**
202
360
  * Replace one exact accepted draft with the next-create safe seed. The row is
203
- * locked before the revision check so a concurrent save either commits first
204
- * and wins (this returns false), or observes the incremented seed revision and
205
- * reports a typed OCC conflict. A missing row is an idempotent no-op.
361
+ * identity-serialized before the revision check so even the absent-row
362
+ * revision-zero case cannot race a first save. Exact create callers receive a
363
+ * typed conflict; legacy revision-only callers retain the old false/no-op
364
+ * result. A missing row is an idempotent no-op.
206
365
  */
207
366
  export async function seedNewSessionDraftInTransaction(
208
367
  db: Database,
209
- input: { workspaceId: string; subjectId: string; expectedRevision: number },
368
+ input: {
369
+ workspaceId: string;
370
+ subjectId: string;
371
+ expectedRevision: number;
372
+ expectedSnapshot?: NewSessionDraftSnapshot;
373
+ acceptedSelection?: AcceptedNewSessionSelection;
374
+ },
210
375
  ): Promise<boolean> {
211
- if (input.expectedRevision === 0) return false;
376
+ await lockNewSessionDraftIdentity(db, input.workspaceId, input.subjectId);
212
377
  const [current] = await db
213
378
  .select()
214
379
  .from(schema.newSessionDrafts)
@@ -220,7 +385,27 @@ export async function seedNewSessionDraftInTransaction(
220
385
  )
221
386
  .for("update")
222
387
  .limit(1);
223
- if (!current || current.revision !== input.expectedRevision) return false;
388
+ if (!current || current.revision !== input.expectedRevision) {
389
+ if (input.expectedSnapshot) {
390
+ throw new NewSessionDraftConflictError(current?.revision ?? 0);
391
+ }
392
+ return false;
393
+ }
394
+ if (
395
+ input.expectedSnapshot &&
396
+ stableJson({
397
+ text: current.text,
398
+ resources: current.resources,
399
+ tools: newSessionDraftToolsProvided(current) ? current.tools : [],
400
+ toolsProvided: newSessionDraftToolsProvided(current),
401
+ model: current.model,
402
+ reasoningEffort: current.reasoningEffort,
403
+ latencyMode: current.latencyMode,
404
+ options: publicNewSessionDraftOptions(current),
405
+ }) !== stableJson(input.expectedSnapshot)
406
+ ) {
407
+ throw new NewSessionDraftConflictError(current.revision);
408
+ }
224
409
 
225
410
  const options = current.sessionOptions as StoredNewSessionDraftOptions;
226
411
  const targetSandboxId =
@@ -228,8 +413,8 @@ export async function seedNewSessionDraftInTransaction(
228
413
  const safeOptions: NewSessionDraftOptions = {
229
414
  ...(options.sandboxBackend ? { sandboxBackend: options.sandboxBackend } : {}),
230
415
  ...(targetSandboxId ? { targetSandboxId } : {}),
231
- ...(safeWorkingDir(options.workingDir, targetSandboxId)
232
- ? { workingDir: safeWorkingDir(options.workingDir, targetSandboxId) }
416
+ ...(targetSandboxId && typeof options.workingDir === "string"
417
+ ? { workingDir: options.workingDir }
233
418
  : {}),
234
419
  ...(options.variableSetId ? { variableSetId: options.variableSetId } : {}),
235
420
  ...(options.rigId ? { rigId: options.rigId } : {}),
@@ -252,7 +437,16 @@ export async function seedNewSessionDraftInTransaction(
252
437
  model: current.model,
253
438
  reasoningEffort: current.reasoningEffort,
254
439
  latencyMode: current.latencyMode,
255
- sessionOptions: storedOptions(safeOptions, newSessionDraftToolsProvided(current)),
440
+ sessionOptions: storedOptions(
441
+ safeOptions,
442
+ newSessionDraftToolsProvided(current),
443
+ input.acceptedSelection
444
+ ? rememberNewSessionSelection(
445
+ newSessionSelectionHistory(current),
446
+ input.acceptedSelection,
447
+ )
448
+ : newSessionSelectionHistory(current),
449
+ ),
256
450
  updatedAt: new Date(),
257
451
  })
258
452
  .where(