@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
@@ -0,0 +1,234 @@
1
+ import { and, eq, inArray, sql } from "drizzle-orm";
2
+ import { type Database, withRlsContext } from "./database";
3
+ import {
4
+ advanceWorkspaceInteractionRevision,
5
+ readWorkspaceInteractionRevision,
6
+ } from "./interaction-revisions";
7
+ import * as schema from "./schema";
8
+
9
+ export const SOURCE_PLACEMENT_CHANGED = "source_placement_changed";
10
+
11
+ const CONNECTED_PLACEMENT_LIVE_LIFECYCLES = [
12
+ "starting",
13
+ "active",
14
+ "suspending",
15
+ "restoring",
16
+ "ending",
17
+ ] as const;
18
+
19
+ export type TerminalizeStaleConnectedInteractionPlacementResult = {
20
+ sourcePlacementChanged: boolean;
21
+ changed: boolean;
22
+ browserSessionIds: string[];
23
+ computerSessionIds: string[];
24
+ revision: number;
25
+ };
26
+
27
+ /**
28
+ * Reconcile interaction resources after their source task moves away from a
29
+ * Connected Machine. The source row is the race fence: a stale route lookup
30
+ * may never retire resources while that machine is still the active placement.
31
+ * Browser and Desktop resources are settled together because headed browsers
32
+ * can share one physical controller with a linked Desktop.
33
+ */
34
+ export async function terminalizeStaleConnectedInteractionPlacement(
35
+ db: Database,
36
+ input: {
37
+ accountId: string;
38
+ workspaceId: string;
39
+ sourceSessionId: string;
40
+ connectedSandboxId: string;
41
+ },
42
+ ): Promise<TerminalizeStaleConnectedInteractionPlacementResult> {
43
+ return await withRlsContext(
44
+ db,
45
+ { accountId: input.accountId, workspaceId: input.workspaceId },
46
+ async (scopedDb) =>
47
+ await scopedDb.transaction(async (tx) => {
48
+ const [source] = await tx
49
+ .select({ activeSandboxId: schema.sessions.activeSandboxId })
50
+ .from(schema.sessions)
51
+ .where(
52
+ and(
53
+ eq(schema.sessions.accountId, input.accountId),
54
+ eq(schema.sessions.workspaceId, input.workspaceId),
55
+ eq(schema.sessions.id, input.sourceSessionId),
56
+ ),
57
+ )
58
+ .for("update")
59
+ .limit(1);
60
+
61
+ const sourcePlacementChanged =
62
+ source !== undefined && source.activeSandboxId !== input.connectedSandboxId;
63
+ if (!sourcePlacementChanged) {
64
+ return {
65
+ sourcePlacementChanged: false,
66
+ changed: false,
67
+ browserSessionIds: [],
68
+ computerSessionIds: [],
69
+ revision: await readWorkspaceInteractionRevision(tx, input.workspaceId),
70
+ };
71
+ }
72
+
73
+ const browserRows = await tx
74
+ .select({ id: schema.browserSessions.id })
75
+ .from(schema.browserSessions)
76
+ .innerJoin(
77
+ schema.browserSessionAssociations,
78
+ and(
79
+ eq(schema.browserSessionAssociations.browserSessionId, schema.browserSessions.id),
80
+ eq(schema.browserSessionAssociations.workspaceId, input.workspaceId),
81
+ eq(schema.browserSessionAssociations.sessionId, input.sourceSessionId),
82
+ eq(schema.browserSessionAssociations.relationship, "created"),
83
+ ),
84
+ )
85
+ .where(
86
+ and(
87
+ eq(schema.browserSessions.accountId, input.accountId),
88
+ eq(schema.browserSessions.workspaceId, input.workspaceId),
89
+ eq(schema.browserSessions.placementKind, "connected_machine"),
90
+ eq(schema.browserSessions.connectedSandboxId, input.connectedSandboxId),
91
+ inArray(schema.browserSessions.lifecycle, [...CONNECTED_PLACEMENT_LIVE_LIFECYCLES]),
92
+ ),
93
+ );
94
+ const computerRows = await tx
95
+ .select({ id: schema.computerSessions.id })
96
+ .from(schema.computerSessions)
97
+ .innerJoin(
98
+ schema.computerSessionAssociations,
99
+ and(
100
+ eq(schema.computerSessionAssociations.computerSessionId, schema.computerSessions.id),
101
+ eq(schema.computerSessionAssociations.workspaceId, input.workspaceId),
102
+ eq(schema.computerSessionAssociations.sessionId, input.sourceSessionId),
103
+ eq(schema.computerSessionAssociations.relationship, "created"),
104
+ ),
105
+ )
106
+ .where(
107
+ and(
108
+ eq(schema.computerSessions.accountId, input.accountId),
109
+ eq(schema.computerSessions.workspaceId, input.workspaceId),
110
+ eq(schema.computerSessions.placementKind, "connected_machine"),
111
+ eq(schema.computerSessions.connectedSandboxId, input.connectedSandboxId),
112
+ inArray(schema.computerSessions.lifecycle, [...CONNECTED_PLACEMENT_LIVE_LIFECYCLES]),
113
+ ),
114
+ );
115
+
116
+ const browserSessionIds = [...new Set(browserRows.map((row) => row.id))].sort();
117
+ const computerSessionIds = [...new Set(computerRows.map((row) => row.id))].sort();
118
+ const resourceIds = [...browserSessionIds, ...computerSessionIds];
119
+ if (resourceIds.length === 0) {
120
+ return {
121
+ sourcePlacementChanged: true,
122
+ changed: false,
123
+ browserSessionIds,
124
+ computerSessionIds,
125
+ revision: await readWorkspaceInteractionRevision(tx, input.workspaceId),
126
+ };
127
+ }
128
+
129
+ const operationRows = await tx
130
+ .select({ operationId: schema.interactionOperations.operationId })
131
+ .from(schema.interactionOperations)
132
+ .where(
133
+ and(
134
+ eq(schema.interactionOperations.accountId, input.accountId),
135
+ eq(schema.interactionOperations.workspaceId, input.workspaceId),
136
+ inArray(schema.interactionOperations.resourceId, resourceIds),
137
+ inArray(schema.interactionOperations.state, ["prepared", "dispatched"]),
138
+ ),
139
+ );
140
+ const operationIds = operationRows.map((row) => row.operationId).sort();
141
+
142
+ // Stable lock order matches the attached-device generation-loss path.
143
+ for (const operationId of operationIds) {
144
+ await tx.execute(sql`
145
+ select operation_id from interaction_operations
146
+ where workspace_id = ${input.workspaceId} and operation_id = ${operationId}
147
+ for update
148
+ `);
149
+ }
150
+ for (const browserSessionId of browserSessionIds) {
151
+ await tx.execute(sql`
152
+ select id from browser_sessions
153
+ where workspace_id = ${input.workspaceId} and id = ${browserSessionId}
154
+ for update
155
+ `);
156
+ }
157
+ for (const computerSessionId of computerSessionIds) {
158
+ await tx.execute(sql`
159
+ select id from computer_sessions
160
+ where workspace_id = ${input.workspaceId} and id = ${computerSessionId}
161
+ for update
162
+ `);
163
+ }
164
+
165
+ const now = new Date();
166
+ if (operationIds.length > 0) {
167
+ await tx
168
+ .update(schema.interactionOperations)
169
+ .set({
170
+ state: "outcome_unknown",
171
+ errorCode: "outcome_unknown",
172
+ errorMessage: "The source task moved to another placement",
173
+ errorRetryable: false,
174
+ errorDetails: { reason: SOURCE_PLACEMENT_CHANGED },
175
+ settledAt: now,
176
+ updatedAt: now,
177
+ })
178
+ .where(
179
+ and(
180
+ eq(schema.interactionOperations.workspaceId, input.workspaceId),
181
+ inArray(schema.interactionOperations.operationId, operationIds),
182
+ inArray(schema.interactionOperations.state, ["prepared", "dispatched"]),
183
+ ),
184
+ );
185
+ }
186
+ if (browserSessionIds.length > 0) {
187
+ await tx
188
+ .update(schema.browserSessions)
189
+ .set({
190
+ lifecycle: "lost",
191
+ failureCode: SOURCE_PLACEMENT_CHANGED,
192
+ updatedAt: now,
193
+ })
194
+ .where(
195
+ and(
196
+ eq(schema.browserSessions.workspaceId, input.workspaceId),
197
+ inArray(schema.browserSessions.id, browserSessionIds),
198
+ inArray(schema.browserSessions.lifecycle, [...CONNECTED_PLACEMENT_LIVE_LIFECYCLES]),
199
+ ),
200
+ );
201
+ }
202
+ if (computerSessionIds.length > 0) {
203
+ await tx
204
+ .update(schema.computerSessions)
205
+ .set({
206
+ lifecycle: "lost",
207
+ failureCode: SOURCE_PLACEMENT_CHANGED,
208
+ updatedAt: now,
209
+ })
210
+ .where(
211
+ and(
212
+ eq(schema.computerSessions.workspaceId, input.workspaceId),
213
+ inArray(schema.computerSessions.id, computerSessionIds),
214
+ inArray(schema.computerSessions.lifecycle, [
215
+ ...CONNECTED_PLACEMENT_LIVE_LIFECYCLES,
216
+ ]),
217
+ ),
218
+ );
219
+ }
220
+ const revision = await advanceWorkspaceInteractionRevision(
221
+ tx,
222
+ input.accountId,
223
+ input.workspaceId,
224
+ );
225
+ return {
226
+ sourcePlacementChanged: true,
227
+ changed: true,
228
+ browserSessionIds,
229
+ computerSessionIds,
230
+ revision,
231
+ };
232
+ }),
233
+ );
234
+ }
@@ -0,0 +1,116 @@
1
+ import { and, eq, sql } from "drizzle-orm";
2
+ import type { Database } from "./database";
3
+ import { rawRows, setRlsContext } from "./database";
4
+ import * as schema from "./schema";
5
+
6
+ export type ManagedHumanPersonalWorkspaceProvisioning = {
7
+ organizationMembershipId: string;
8
+ personalWorkspaceId: string;
9
+ };
10
+
11
+ /**
12
+ * Converge the deterministic managed-human personal workspace, its inference
13
+ * control row, and the organization-membership anchor through the exact 0219
14
+ * `ensure_managed_human_personal_workspace` lifecycle seam.
15
+ *
16
+ * This is the single implementation of that preparation. The Better Auth
17
+ * managed-access hook and the operator membership backfill driver both call it,
18
+ * so neither owns a second copy of the deterministic personal-workspace
19
+ * identity. All authority - account binding, owner-membership proof, workspace
20
+ * identity, control-row validation, membership status - stays inside the
21
+ * SECURITY DEFINER seam, which fails closed; nothing here writes an
22
+ * organization-tenancy table directly.
23
+ *
24
+ * Must be called inside a transaction whose RLS context is already the exact
25
+ * account with no workspace. It restores that context before returning.
26
+ */
27
+ export async function ensureManagedHumanPersonalWorkspace(
28
+ tx: Database,
29
+ input: { accountId: string; subjectId: string },
30
+ ): Promise<ManagedHumanPersonalWorkspaceProvisioning> {
31
+ const personalWorkspaceExternalSource = "opengeni:organization-membership";
32
+ const personalWorkspaceExternalId = `${input.accountId}:${input.subjectId}`;
33
+ let [personalWorkspace] = await tx
34
+ .select()
35
+ .from(schema.workspaces)
36
+ .where(
37
+ and(
38
+ eq(schema.workspaces.externalSource, personalWorkspaceExternalSource),
39
+ eq(schema.workspaces.externalId, personalWorkspaceExternalId),
40
+ ),
41
+ )
42
+ .limit(1);
43
+ if (!personalWorkspace) {
44
+ [personalWorkspace] = await tx
45
+ .insert(schema.workspaces)
46
+ .values({
47
+ accountId: input.accountId,
48
+ name: "Personal workspace",
49
+ slug: null,
50
+ externalSource: personalWorkspaceExternalSource,
51
+ externalId: personalWorkspaceExternalId,
52
+ })
53
+ .onConflictDoUpdate({
54
+ target: [schema.workspaces.externalSource, schema.workspaces.externalId],
55
+ set: { updatedAt: new Date() },
56
+ })
57
+ .returning();
58
+ }
59
+ if (!personalWorkspace) {
60
+ throw new Error("Failed to ensure personal workspace");
61
+ }
62
+ if (
63
+ personalWorkspace.accountId !== input.accountId ||
64
+ personalWorkspace.externalSource !== personalWorkspaceExternalSource ||
65
+ personalWorkspace.externalId !== personalWorkspaceExternalId
66
+ ) {
67
+ throw new Error("Managed personal workspace identity conflict");
68
+ }
69
+
70
+ await setRlsContext(tx, {
71
+ accountId: input.accountId,
72
+ workspaceId: personalWorkspace.id,
73
+ });
74
+ const [personalWorkspaceControl] = await tx
75
+ .select({ workspaceId: schema.workspaceInferenceControls.workspaceId })
76
+ .from(schema.workspaceInferenceControls)
77
+ .where(eq(schema.workspaceInferenceControls.workspaceId, personalWorkspace.id))
78
+ .limit(1);
79
+ if (!personalWorkspaceControl) {
80
+ await tx
81
+ .insert(schema.workspaceInferenceControls)
82
+ .values({
83
+ workspaceId: personalWorkspace.id,
84
+ accountId: input.accountId,
85
+ })
86
+ .onConflictDoNothing();
87
+ }
88
+ await setRlsContext(tx, {
89
+ accountId: input.accountId,
90
+ workspaceId: null,
91
+ });
92
+
93
+ const [provisionedMembership] = await rawRows<{
94
+ organization_membership_id: string;
95
+ personal_workspace_id: string;
96
+ }>(
97
+ tx,
98
+ sql`
99
+ select * from ensure_managed_human_personal_workspace(
100
+ ${input.accountId},
101
+ ${input.subjectId},
102
+ ${personalWorkspace.id}
103
+ )
104
+ `,
105
+ );
106
+ if (
107
+ !provisionedMembership ||
108
+ provisionedMembership.personal_workspace_id !== personalWorkspace.id
109
+ ) {
110
+ throw new Error("Managed organization membership provisioning did not converge");
111
+ }
112
+ return {
113
+ organizationMembershipId: provisionedMembership.organization_membership_id,
114
+ personalWorkspaceId: provisionedMembership.personal_workspace_id,
115
+ };
116
+ }
@@ -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
  */