@opengeni/db 1.0.1 → 3.1.0-canary.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (265) hide show
  1. package/dist/attached-browser-devices.d.ts +7 -0
  2. package/dist/automations.d.ts +154 -0
  3. package/dist/canonical-human-identities.js +3 -3
  4. package/dist/capability-integrations.d.ts +13 -0
  5. package/dist/child-lifecycle-notices.d.ts +68 -0
  6. package/dist/{chunk-LTG3S6ZY.js → chunk-EULAN2NP.js} +2 -2
  7. package/dist/{chunk-CUPM3FGB.js → chunk-NJVZPWXY.js} +3 -3
  8. package/dist/{chunk-4SR4HKKA.js → chunk-NMUBV7Z5.js} +19 -11
  9. package/dist/chunk-NMUBV7Z5.js.map +1 -0
  10. package/dist/chunk-OF5OBB76.js +4457 -0
  11. package/dist/chunk-OF5OBB76.js.map +1 -0
  12. package/dist/{chunk-6ZYKCRFO.js → chunk-PR2WMLOQ.js} +7580 -4417
  13. package/dist/chunk-PR2WMLOQ.js.map +1 -0
  14. package/dist/{chunk-ZWREYEWA.js → chunk-SCPUJS53.js} +41 -4
  15. package/dist/chunk-SCPUJS53.js.map +1 -0
  16. package/dist/{chunk-SK7YYHBI.js → chunk-SIBYBA2D.js} +46 -1
  17. package/dist/chunk-SIBYBA2D.js.map +1 -0
  18. package/dist/chunk-TIKF3KTI.js +383 -0
  19. package/dist/chunk-TIKF3KTI.js.map +1 -0
  20. package/dist/chunk-ZZ5LJEFX.js +516 -0
  21. package/dist/chunk-ZZ5LJEFX.js.map +1 -0
  22. package/dist/codemode-operations.d.ts +1 -6
  23. package/dist/company-brain-context-selection-schema.d.ts +705 -0
  24. package/dist/company-brain-context-selection.d.ts +94 -0
  25. package/dist/company-brain-governed-writes.d.ts +24 -0
  26. package/dist/company-brain.d.ts +55 -0
  27. package/dist/company-profile-agent-admin.d.ts +15 -0
  28. package/dist/company-profile-schema.d.ts +619 -2
  29. package/dist/company-profile.d.ts +9 -4
  30. package/dist/connection-authority.d.ts +95 -0
  31. package/dist/connection-scopes.d.ts +8 -0
  32. package/dist/connection-tenancy-backfill.d.ts +41 -0
  33. package/dist/connection-token-resolver.d.ts +30 -1
  34. package/dist/database.d.ts +19 -0
  35. package/dist/editable-artifact-durable-export.js +2 -2
  36. package/dist/editable-artifacts.d.ts +2 -2
  37. package/dist/editable-artifacts.js +3 -3
  38. package/dist/governed-learning-activation-schema.d.ts +984 -0
  39. package/dist/governed-learning-activation.d.ts +98 -0
  40. package/dist/governed-learning-evaluator-schema.d.ts +633 -0
  41. package/dist/governed-learning-evaluator.d.ts +28 -0
  42. package/dist/index.d.ts +1595 -81
  43. package/dist/index.js +21571 -8126
  44. package/dist/index.js.map +1 -1
  45. package/dist/insights.d.ts +18 -0
  46. package/dist/integration-bindings.d.ts +1 -0
  47. package/dist/integration-facets.d.ts +30 -0
  48. package/dist/interaction-placement-loss.d.ts +22 -0
  49. package/dist/managed-human-provisioning.d.ts +25 -0
  50. package/dist/memory-domain.d.ts +3 -3
  51. package/dist/migrate.d.ts +11 -1
  52. package/dist/migrate.js +1 -1
  53. package/dist/new-session-drafts.d.ts +43 -5
  54. package/dist/organization-membership-backfill.d.ts +174 -0
  55. package/dist/organization-membership-lifecycle.d.ts +162 -0
  56. package/dist/pr-review.d.ts +107 -0
  57. package/dist/preference-registry-schema.d.ts +2 -0
  58. package/dist/preference-registry.d.ts +2 -0
  59. package/dist/provision-roles.js +1 -1
  60. package/dist/runtime-posture.d.ts +22 -8
  61. package/dist/schema.d.ts +24914 -13703
  62. package/dist/schema.js +99 -1
  63. package/dist/scoped-knowledge-schema.d.ts +52 -1
  64. package/dist/session-background-commands.d.ts +125 -0
  65. package/dist/session-background-commands.js +34 -0
  66. package/dist/session-background-commands.js.map +1 -0
  67. package/dist/session-control.d.ts +156 -3
  68. package/dist/session-goal-pacing.d.ts +68 -0
  69. package/dist/session-queue-commands.d.ts +41 -4
  70. package/dist/session-realtime-ledger.d.ts +2 -0
  71. package/dist/session-tenancy.d.ts +71 -1
  72. package/dist/session-tenancy.js +25 -3
  73. package/dist/session-tool-call-settlement.d.ts +9 -4
  74. package/dist/slack-routing-personal-workspace.d.ts +23 -0
  75. package/dist/slack-routing.d.ts +153 -0
  76. package/dist/slack-task-policy-schema.d.ts +34 -0
  77. package/dist/slack-task-policy.d.ts +8 -0
  78. package/dist/slack-user-link-access.d.ts +1 -0
  79. package/dist/task-notes-schema.d.ts +594 -0
  80. package/dist/task-notes.d.ts +39 -0
  81. package/dist/turn-initiator.d.ts +2 -0
  82. package/dist/user-resource-authority.d.ts +109 -0
  83. package/dist/video-generation.js +3 -3
  84. package/dist/workspace-authority.d.ts +81 -0
  85. package/dist/workspace-instruction-policies-schema.d.ts +279 -0
  86. package/dist/workspace-instruction-policies.d.ts +62 -0
  87. package/dist/workspace-learning-policy.d.ts +1 -0
  88. package/dist/workspace-membership-permissions.d.ts +2 -0
  89. package/drizzle/0240_enrollment_connection_authority.sql +39 -0
  90. package/drizzle/0241_atomic_personal_resource_delegation.sql +1000 -0
  91. package/drizzle/0241_enrollment_agent_runtime.sql +70 -0
  92. package/drizzle/0244_slack_app_home_refresh_queue.sql +120 -0
  93. package/drizzle/0245_model_context_contribution_facts.sql +88 -0
  94. package/drizzle/0246_integration_personal_instance_authority.sql +70 -0
  95. package/drizzle/0247_terraform_stacks_provenance_repair.sql +428 -0
  96. package/drizzle/0248_terraform_stacks_component_resolution_fence.sql +83 -0
  97. package/drizzle/0249_personal_resource_delegation_authority_correction.sql +662 -0
  98. package/drizzle/0250_direct_retained_process_owner_liveness.sql +199 -0
  99. package/drizzle/0251_connected_machine_operation_policy.sql +45 -0
  100. package/drizzle/0252_scheduled_personal_resource_delegation.sql +1867 -0
  101. package/drizzle/0253_common_user_resource_authority_lifecycle.sql +1555 -0
  102. package/drizzle/0254_scoped_variable_set_authority.sql +1255 -0
  103. package/drizzle/0255_company_brain_governed_write_proposals.sql +524 -0
  104. package/drizzle/0256_connection_authority_delegation.sql +703 -0
  105. package/drizzle/0257_goal_revision_decisions_and_root_constraints.sql +436 -0
  106. package/drizzle/0258_three_scope_document_knowledge_authority.sql +1273 -0
  107. package/drizzle/0259_company_brain_context_selection_receipts.sql +869 -0
  108. package/drizzle/0260_task_note_knowledge_promotion.sql +820 -0
  109. package/drizzle/0261_preference_knowledge_proposal_actor_binding.sql +471 -0
  110. package/drizzle/0262_scoped_connected_machines_and_rigs.sql +1700 -0
  111. package/drizzle/0263_organization_membership_lifecycle.sql +3284 -0
  112. package/drizzle/0264_connection_authority_runtime_activation.sql +1084 -0
  113. package/drizzle/0266_company_brain_context_receipt_inspection.sql +156 -0
  114. package/drizzle/0268_governed_learning_decision_receipts.sql +825 -0
  115. package/drizzle/0269_governed_learning_activation_controller.sql +1874 -0
  116. package/drizzle/0270_governed_learning_history_inspection.sql +170 -0
  117. package/drizzle/0271_company_brain_retrieval_only_default.sql +524 -0
  118. package/drizzle/0272_human_confirmed_learning_activation.sql +591 -0
  119. package/drizzle/0273_scheduled_variable_set_materialization.sql +268 -0
  120. package/drizzle/0274_human_confirmed_knowledge_review.sql +310 -0
  121. package/drizzle/0275_scheduled_connection_authority.sql +5527 -0
  122. package/drizzle/0276_onboarding_proposal_initiating_human_guc.sql +198 -0
  123. package/drizzle/0277_workspace_writer_authority_attribution.sql +393 -0
  124. package/drizzle/0278_workspace_membership_removal_fencing.sql +1174 -0
  125. package/drizzle/0279_workspace_connection_use_lane.sql +343 -0
  126. package/drizzle/0280_connection_and_variable_set_audit_attribution.sql +773 -0
  127. package/drizzle/0281_viewer_holder_authority_claims.sql +52 -0
  128. package/drizzle/0282_variable_set_session_attach_attribution.sql +178 -0
  129. package/drizzle/0283_editable_spreadsheet_authored_state.sql +82 -0
  130. package/drizzle/0284_truthful_human_confirmed_review_reason.sql +930 -0
  131. package/drizzle/0285_organization_tenancy_inventory.sql +329 -0
  132. package/drizzle/0286_widen_task_note_expiry_ceiling.sql +391 -0
  133. package/drizzle/0287_open_suffix_pending_tool_calls.sql +18 -0
  134. package/drizzle/0288_attached_browser_reenrollment.sql +64 -0
  135. package/drizzle/0289_session_composer_policy_authority.sql +597 -0
  136. package/drizzle/0290_organization_membership_backfill.sql +268 -0
  137. package/drizzle/0291_resource_authority_classification_assertion.sql +371 -0
  138. package/drizzle/0292_truthful_tenancy_inventory_counters.sql +291 -0
  139. package/drizzle/0293_confirm_time_rule_rebaseline.sql +615 -0
  140. package/drizzle/0294_preference_activation_authority.sql +237 -0
  141. package/drizzle/0295_retire_legacy_standing_memory_mode.sql +476 -0
  142. package/drizzle/0296_force_rls_backfill_noop_repair.sql +215 -0
  143. package/drizzle/0297_session_ownership_classification_and_backfill.sql +752 -0
  144. package/drizzle/0298_organization_tenancy_parity.sql +609 -0
  145. package/drizzle/0299_organization_membership_lock_order.sql +291 -0
  146. package/drizzle/0300_tenancy_backfill_ledger.sql +459 -0
  147. package/drizzle/0301_session_snapshot_and_pin_visibility.sql +181 -0
  148. package/drizzle/0302_personal_workspace_session_ownership.sql +303 -0
  149. package/drizzle/0303_session_tenancy_product_activation.sql +919 -0
  150. package/drizzle/0304_personal_workspace_private_session_reads.sql +97 -0
  151. package/drizzle/0305_personal_resource_grant_management.sql +584 -0
  152. package/drizzle/0306_atomic_personal_resource_attachments.sql +1374 -0
  153. package/drizzle/0307_session_attention_state.sql +51 -0
  154. package/drizzle/0308_session_archives.sql +35 -0
  155. package/drizzle/0309_channel_project_pins.sql +6 -0
  156. package/drizzle/0310_channel_project_order.sql +27 -0
  157. package/drizzle/0311_company_scope_and_private_session_create.sql +656 -0
  158. package/drizzle/0312_quiescent_session_tree_deletion.sql +39 -0
  159. package/drizzle/0313_private_child_session_authority.sql +365 -0
  160. package/drizzle/0314_unregistered_organization_invitations.sql +606 -0
  161. package/drizzle/0315_personal_github_repository_selection.sql +624 -0
  162. package/drizzle/0316_human_confirmed_activation_resumed_generation.sql +869 -0
  163. package/drizzle/0317_session_goal_continuation_hold.sql +64 -0
  164. package/drizzle/0318_session_turn_startup_milestones.sql +115 -0
  165. package/drizzle/0319_event_triggered_automations.sql +254 -0
  166. package/drizzle/0320_pr_review_pack.sql +113 -0
  167. package/drizzle/0321_slack_bot_environment_display_name.sql +15 -0
  168. package/drizzle/0322_session_turns_unclaimed_prompt_trigger_index.sql +5 -0
  169. package/drizzle/0323_organization_private_session_enablement.sql +489 -0
  170. package/drizzle/0324_human_confirmed_company_profile_agent_admin.sql +856 -0
  171. package/drizzle/0325_child_lifecycle_notices.sql +44 -0
  172. package/drizzle/0326_interaction_operation_error_codes.sql +62 -0
  173. package/drizzle/0327_slack_first_task_hint.sql +29 -0
  174. package/drizzle/0328_session_background_commands.sql +590 -0
  175. package/drizzle/0329_slack_orchestration_delivery_events.sql +91 -0
  176. package/drizzle/0330_api_key_descriptions.sql +20 -0
  177. package/drizzle/0331_managed_organization_creation.sql +163 -0
  178. package/drizzle/0332_organization_shared_workspace_control_plane.sql +539 -0
  179. package/drizzle/0333_session_turn_prompt_routing.sql +45 -0
  180. package/drizzle/0334_connected_machine_workspace_root.sql +99 -0
  181. package/drizzle/0335_slack_workspace_routing.sql +460 -0
  182. package/drizzle/0336_atomic_session_fork_visibility.sql +667 -0
  183. package/drizzle/0337_slack_routed_action_handles.sql +189 -0
  184. package/drizzle/0338_atomic_connected_machine_attachments.sql +410 -0
  185. package/drizzle/0339_document_authority_reclassification.sql +1153 -0
  186. package/drizzle/0340_tenancy_backfill_activation_evidence.sql +1347 -0
  187. package/drizzle/0341_slack_routing_probe_organization_fence.sql +109 -0
  188. package/package.json +10 -6
  189. package/src/attached-browser-devices.ts +233 -5
  190. package/src/automations.ts +911 -0
  191. package/src/browser-sessions.ts +10 -0
  192. package/src/capability-integrations.ts +175 -1
  193. package/src/child-lifecycle-notices.ts +241 -0
  194. package/src/codemode-operations.ts +1 -35
  195. package/src/company-brain-context-selection-schema.ts +100 -0
  196. package/src/company-brain-context-selection.ts +359 -0
  197. package/src/company-brain-governed-writes.ts +1170 -0
  198. package/src/company-brain.ts +309 -0
  199. package/src/company-profile-agent-admin.ts +225 -0
  200. package/src/company-profile-schema.ts +121 -1
  201. package/src/company-profile.ts +21 -0
  202. package/src/computer-sessions.ts +10 -1
  203. package/src/connection-authority.ts +290 -0
  204. package/src/connection-scopes.ts +20 -0
  205. package/src/connection-tenancy-backfill.ts +81 -0
  206. package/src/connection-token-resolver.ts +263 -31
  207. package/src/database.ts +75 -2
  208. package/src/editable-artifacts-schema.ts +10 -5
  209. package/src/editable-artifacts.ts +21 -11
  210. package/src/governed-learning-activation-schema.ts +121 -0
  211. package/src/governed-learning-activation.ts +588 -0
  212. package/src/governed-learning-evaluator-schema.ts +72 -0
  213. package/src/governed-learning-evaluator.ts +217 -0
  214. package/src/index.ts +11651 -1548
  215. package/src/insights.ts +107 -0
  216. package/src/integration-bindings.ts +88 -6
  217. package/src/integration-facets.ts +287 -84
  218. package/src/interaction-placement-loss.ts +234 -0
  219. package/src/managed-human-provisioning.ts +116 -0
  220. package/src/memory-domain.ts +27 -32
  221. package/src/migrate.ts +77 -1
  222. package/src/new-session-drafts.ts +229 -35
  223. package/src/organization-membership-backfill.ts +735 -0
  224. package/src/organization-membership-lifecycle.ts +985 -0
  225. package/src/pr-review.ts +890 -0
  226. package/src/preference-registry.ts +31 -1
  227. package/src/provision-roles.ts +877 -6
  228. package/src/runtime-posture-cli.ts +2 -0
  229. package/src/runtime-posture.ts +1291 -76
  230. package/src/schema.ts +3303 -428
  231. package/src/scoped-knowledge-schema.ts +16 -8
  232. package/src/scoped-knowledge.ts +1 -1
  233. package/src/session-background-commands.ts +759 -0
  234. package/src/session-control.ts +640 -74
  235. package/src/session-goal-pacing.ts +126 -0
  236. package/src/session-queue-commands.ts +437 -84
  237. package/src/session-realtime-context.ts +3 -3
  238. package/src/session-realtime-ledger.ts +22 -11
  239. package/src/session-realtime-mirror.ts +1 -1
  240. package/src/session-tenancy.ts +403 -49
  241. package/src/session-tool-call-settlement.ts +38 -29
  242. package/src/slack-routing-personal-workspace.ts +67 -0
  243. package/src/slack-routing.ts +469 -0
  244. package/src/slack-task-policy-schema.ts +5 -0
  245. package/src/slack-task-policy.ts +18 -0
  246. package/src/slack-user-link-access.ts +17 -1
  247. package/src/task-notes-schema.ts +74 -0
  248. package/src/task-notes.ts +124 -2
  249. package/src/turn-initiator.ts +8 -0
  250. package/src/user-resource-authority.ts +377 -0
  251. package/src/workspace-authority.ts +160 -0
  252. package/src/workspace-instruction-policies-schema.ts +53 -1
  253. package/src/workspace-instruction-policies.ts +528 -77
  254. package/src/workspace-learning-policy.ts +21 -5
  255. package/src/workspace-membership-permissions.ts +9 -0
  256. package/dist/chunk-4SR4HKKA.js.map +0 -1
  257. package/dist/chunk-6ZYKCRFO.js.map +0 -1
  258. package/dist/chunk-NXAFLCQX.js +0 -2468
  259. package/dist/chunk-NXAFLCQX.js.map +0 -1
  260. package/dist/chunk-SK7YYHBI.js.map +0 -1
  261. package/dist/chunk-WD5ROSLL.js +0 -117
  262. package/dist/chunk-WD5ROSLL.js.map +0 -1
  263. package/dist/chunk-ZWREYEWA.js.map +0 -1
  264. /package/dist/{chunk-LTG3S6ZY.js.map → chunk-EULAN2NP.js.map} +0 -0
  265. /package/dist/{chunk-CUPM3FGB.js.map → chunk-NJVZPWXY.js.map} +0 -0
@@ -0,0 +1,109 @@
1
+ -- deployment-mode: rolling
2
+ -- Fence both Slack routing tenancy probes on the organization the caller is
3
+ -- acting for.
4
+ --
5
+ -- Neither probe carried an account predicate. Both are SECURITY DEFINER and
6
+ -- both return tenancy ids, so a caller holding another organization's
7
+ -- connection UUID plus a route key or a handle id learned that organization's
8
+ -- account, workspace and interaction ids. Exploitability is low today, because
9
+ -- connection ids are never caller-supplied and the Slack signature check
10
+ -- already fences the team, but the predicate is one line and every sibling
11
+ -- authority routine carries it.
12
+ --
13
+ -- Rolling by expand-and-contract: the fenced three-argument routines are added
14
+ -- alongside the existing two-argument ones rather than replacing them, so an
15
+ -- old image keeps resolving thread continuation and button clicks through the
16
+ -- signature it knows. A later migration drops the unfenced pair once the fleet
17
+ -- is on an image that calls the fenced one; dropping them here would break
18
+ -- every running old worker the moment this commits.
19
+
20
+ SET LOCAL lock_timeout = '5s';
21
+ SET LOCAL statement_timeout = '10min';
22
+
23
+ DO $slack_fenced_interaction_probe$
24
+ DECLARE data_schema text := current_schema();
25
+ BEGIN
26
+ EXECUTE format($ddl$
27
+ CREATE FUNCTION opengeni_private.resolve_slack_interaction_tenancy(
28
+ p_account_id uuid,
29
+ p_connection_id uuid,
30
+ p_route_key text
31
+ )
32
+ RETURNS TABLE (account_id uuid, workspace_id uuid, interaction_id uuid)
33
+ LANGUAGE plpgsql
34
+ SECURITY DEFINER
35
+ SET search_path = %1$I, pg_catalog
36
+ AS $body$
37
+ BEGIN
38
+ IF p_account_id IS NULL OR p_connection_id IS NULL OR p_route_key IS NULL THEN
39
+ RETURN;
40
+ END IF;
41
+ INSERT INTO opengeni_private.slack_routing_runtime_capabilities (
42
+ backend_pid, transaction_id, capability_kind
43
+ ) VALUES (
44
+ pg_catalog.pg_backend_pid(), pg_catalog.pg_current_xact_id(), 'interaction_tenancy'
45
+ ) ON CONFLICT DO NOTHING;
46
+ BEGIN
47
+ RETURN QUERY
48
+ SELECT I.account_id, I.workspace_id, I.id
49
+ FROM slack_interactions I
50
+ WHERE I.account_id = p_account_id
51
+ AND I.connection_id = p_connection_id
52
+ AND I.route_key = p_route_key
53
+ LIMIT 1;
54
+ DELETE FROM opengeni_private.slack_routing_runtime_capabilities
55
+ WHERE backend_pid = pg_catalog.pg_backend_pid()
56
+ AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
57
+ AND capability_kind = 'interaction_tenancy';
58
+ RETURN;
59
+ EXCEPTION WHEN OTHERS THEN
60
+ DELETE FROM opengeni_private.slack_routing_runtime_capabilities
61
+ WHERE backend_pid = pg_catalog.pg_backend_pid()
62
+ AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
63
+ AND capability_kind = 'interaction_tenancy';
64
+ RAISE;
65
+ END;
66
+ END;
67
+ $body$
68
+ $ddl$, data_schema);
69
+ END
70
+ $slack_fenced_interaction_probe$;
71
+
72
+ REVOKE ALL ON FUNCTION opengeni_private.resolve_slack_interaction_tenancy(uuid, uuid, text)
73
+ FROM PUBLIC;
74
+
75
+ CREATE FUNCTION opengeni_private.resolve_slack_action_handle_tenancy(
76
+ p_account_id uuid,
77
+ p_connection_id uuid,
78
+ p_handle_id uuid
79
+ )
80
+ RETURNS TABLE (account_id uuid, workspace_id uuid)
81
+ LANGUAGE sql
82
+ STABLE
83
+ SECURITY DEFINER
84
+ SET search_path = opengeni_private, pg_catalog
85
+ AS $$
86
+ SELECT T.account_id, T.workspace_id
87
+ FROM slack_action_handle_tenancy T
88
+ WHERE T.account_id = p_account_id
89
+ AND T.handle_id = p_handle_id
90
+ AND T.connection_id = p_connection_id
91
+ LIMIT 1
92
+ $$;
93
+
94
+ REVOKE ALL ON FUNCTION
95
+ opengeni_private.resolve_slack_action_handle_tenancy(uuid, uuid, uuid) FROM PUBLIC;
96
+
97
+ DO $grants$
98
+ BEGIN
99
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
100
+ GRANT EXECUTE ON FUNCTION
101
+ opengeni_private.resolve_slack_interaction_tenancy(uuid, uuid, text) TO opengeni_app;
102
+ GRANT EXECUTE ON FUNCTION
103
+ opengeni_private.resolve_slack_action_handle_tenancy(uuid, uuid, uuid) TO opengeni_app;
104
+ END IF;
105
+ END
106
+ $grants$;
107
+
108
+ RESET statement_timeout;
109
+ RESET lock_timeout;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/db",
3
- "version": "1.0.1",
3
+ "version": "3.1.0-canary.0",
4
4
  "description": "OpenGeni persistence: Drizzle schema, RLS-scoped query layer, the SQL migration runner, and role provisioning.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -54,6 +54,10 @@
54
54
  "./session-tenancy": {
55
55
  "types": "./dist/session-tenancy.d.ts",
56
56
  "import": "./dist/session-tenancy.js"
57
+ },
58
+ "./session-background-commands": {
59
+ "types": "./dist/session-background-commands.d.ts",
60
+ "import": "./dist/session-background-commands.js"
57
61
  }
58
62
  },
59
63
  "publishConfig": {
@@ -70,11 +74,11 @@
70
74
  "prepublishOnly": "bash ../../scripts/prepublish-guard"
71
75
  },
72
76
  "dependencies": {
73
- "@opengeni/codemode": "^0.4.4",
74
- "@opengeni/codex": "^0.2.17",
75
- "@opengeni/config": "^0.16.4",
76
- "@opengeni/contracts": "^1.0.1",
77
- "@opengeni/network": "^0.2.2",
77
+ "@opengeni/codemode": "^0.4.12-canary.0",
78
+ "@opengeni/codex": "^0.2.18-canary.0",
79
+ "@opengeni/config": "^0.19.0-canary.0",
80
+ "@opengeni/contracts": "^2.2.0-canary.0",
81
+ "@opengeni/network": "^0.2.2-canary.0",
78
82
  "drizzle-orm": "^0.45.2",
79
83
  "postgres": "^3.4.7"
80
84
  },
@@ -8,7 +8,7 @@ import {
8
8
  type AttachedBrowserDeviceListResponse as AttachedBrowserDeviceListResponseValue,
9
9
  type AttachedBrowserInventorySnapshot as AttachedBrowserInventorySnapshotValue,
10
10
  } from "@opengeni/contracts";
11
- import { and, asc, desc, eq, inArray, isNull } from "drizzle-orm";
11
+ import { and, asc, desc, eq, inArray, isNull, sql } from "drizzle-orm";
12
12
  import { type Database, withRlsContext } from "./database";
13
13
  import {
14
14
  advanceWorkspaceInteractionRevision,
@@ -16,6 +16,12 @@ import {
16
16
  } from "./interaction-revisions";
17
17
  import * as schema from "./schema";
18
18
 
19
+ const ATTACHED_DEVICE_LIVE_LIFECYCLES = ["starting", "active", "suspending", "restoring"] as const;
20
+ // `ending` stays off this list so an in-flight `/end` can finish physical
21
+ // teardown (`stopCapture` + helper exit) after Chrome reconnects.
22
+
23
+ const CONTROLLER_TRANSITION_EXPIRED = "controller_transition_expired";
24
+
19
25
  type DeviceRow = typeof schema.attachedBrowserDevices.$inferSelect;
20
26
 
21
27
  const CONSISTENT_READ = {
@@ -156,13 +162,29 @@ export async function reconcileAttachedBrowserInventory(
156
162
  .where(eq(schema.attachedBrowserDevices.workspaceId, input.workspaceId))
157
163
  .for("update");
158
164
  const byId = new Map(rows.map((row) => [row.id, row]));
165
+ const generationChangedDeviceIds = new Set<string>();
159
166
  let changed = false;
160
167
  for (const device of snapshot.devices) {
161
168
  const existing = byId.get(device.id);
169
+ let reclaimingRevokedEnrollment = false;
162
170
  if (existing && existing.enrollmentId !== input.enrollmentId) {
163
- throw new AttachedBrowserInventoryConflictError(
164
- "attached browser device id is already owned by another enrollment",
165
- );
171
+ const [previousEnrollment] = await tx
172
+ .select({ status: schema.enrollments.status })
173
+ .from(schema.enrollments)
174
+ .where(
175
+ and(
176
+ eq(schema.enrollments.id, existing.enrollmentId),
177
+ eq(schema.enrollments.accountId, input.accountId),
178
+ eq(schema.enrollments.workspaceId, input.workspaceId),
179
+ ),
180
+ )
181
+ .limit(1);
182
+ if (previousEnrollment?.status !== "revoked") {
183
+ throw new AttachedBrowserInventoryConflictError(
184
+ "attached browser device id is already owned by another active enrollment",
185
+ );
186
+ }
187
+ reclaimingRevokedEnrollment = true;
166
188
  }
167
189
  if (!existing) {
168
190
  await tx.insert(schema.attachedBrowserDevices).values({
@@ -189,10 +211,15 @@ export async function reconcileAttachedBrowserInventory(
189
211
  changed = true;
190
212
  continue;
191
213
  }
192
- const materialChanged = !sameAnnouncement(existing, device);
214
+ if (existing.connectionGeneration !== device.connectionGeneration) {
215
+ generationChangedDeviceIds.add(device.id);
216
+ }
217
+ const materialChanged =
218
+ reclaimingRevokedEnrollment || !sameAnnouncement(existing, device);
193
219
  await tx
194
220
  .update(schema.attachedBrowserDevices)
195
221
  .set({
222
+ enrollmentId: input.enrollmentId,
196
223
  name: device.name,
197
224
  profileLabel: device.profileLabel,
198
225
  browserName: device.browserName,
@@ -217,6 +244,15 @@ export async function reconcileAttachedBrowserInventory(
217
244
  changed ||= materialChanged;
218
245
  }
219
246
 
247
+ if (
248
+ await terminalizeStaleAttachedDeviceSessions(tx, {
249
+ workspaceId: input.workspaceId,
250
+ deviceIds: [...generationChangedDeviceIds],
251
+ })
252
+ ) {
253
+ changed = true;
254
+ }
255
+
220
256
  const announced = new Set(snapshot.devices.map((device) => device.id));
221
257
  const disconnectedIds = rows
222
258
  .filter(
@@ -275,6 +311,153 @@ export async function reconcileAttachedBrowserInventory(
275
311
  );
276
312
  }
277
313
 
314
+ /** A new physical Chrome generation cannot inherit a prior controller token.
315
+ * Mark every still-live BrowserSession/ComputerSession on that exact device
316
+ * lost in place. Never rewrite placementInstanceId onto the successor. */
317
+ async function terminalizeStaleAttachedDeviceSessions(
318
+ tx: Database,
319
+ input: { workspaceId: string; deviceIds: readonly string[] },
320
+ ): Promise<boolean> {
321
+ if (input.deviceIds.length === 0) return false;
322
+
323
+ const browserRows = await tx
324
+ .select({ id: schema.browserSessions.id })
325
+ .from(schema.browserSessions)
326
+ .where(
327
+ and(
328
+ eq(schema.browserSessions.workspaceId, input.workspaceId),
329
+ eq(schema.browserSessions.placementKind, "attached_device"),
330
+ inArray(schema.browserSessions.deviceId, [...input.deviceIds]),
331
+ inArray(schema.browserSessions.lifecycle, [...ATTACHED_DEVICE_LIVE_LIFECYCLES]),
332
+ ),
333
+ );
334
+ const computerRows = await tx
335
+ .select({ id: schema.computerSessions.id })
336
+ .from(schema.computerSessions)
337
+ .where(
338
+ and(
339
+ eq(schema.computerSessions.workspaceId, input.workspaceId),
340
+ eq(schema.computerSessions.placementKind, "attached_device"),
341
+ inArray(schema.computerSessions.deviceId, [...input.deviceIds]),
342
+ inArray(schema.computerSessions.lifecycle, [...ATTACHED_DEVICE_LIVE_LIFECYCLES]),
343
+ ),
344
+ );
345
+ if (browserRows.length === 0 && computerRows.length === 0) return false;
346
+
347
+ const browserIds = browserRows.map((row) => row.id).sort();
348
+ const computerIds = computerRows.map((row) => row.id).sort();
349
+ const resourceIds = [...browserIds, ...computerIds];
350
+ const operations = await tx
351
+ .select({ operationId: schema.interactionOperations.operationId })
352
+ .from(schema.interactionOperations)
353
+ .where(
354
+ and(
355
+ eq(schema.interactionOperations.workspaceId, input.workspaceId),
356
+ inArray(schema.interactionOperations.resourceId, resourceIds),
357
+ inArray(schema.interactionOperations.state, ["prepared", "dispatched"]),
358
+ ),
359
+ );
360
+ const operationIds = operations.map((row) => row.operationId).sort();
361
+ for (const operationId of operationIds) {
362
+ await tx.execute(sql`
363
+ select operation_id from interaction_operations
364
+ where workspace_id = ${input.workspaceId} and operation_id = ${operationId}
365
+ for update
366
+ `);
367
+ }
368
+ for (const browserSessionId of browserIds) {
369
+ await tx.execute(sql`
370
+ select id from browser_sessions
371
+ where workspace_id = ${input.workspaceId} and id = ${browserSessionId}
372
+ for update
373
+ `);
374
+ }
375
+ for (const computerSessionId of computerIds) {
376
+ await tx.execute(sql`
377
+ select id from computer_sessions
378
+ where workspace_id = ${input.workspaceId} and id = ${computerSessionId}
379
+ for update
380
+ `);
381
+ }
382
+
383
+ const now = new Date();
384
+ if (operationIds.length > 0) {
385
+ await tx
386
+ .update(schema.interactionOperations)
387
+ .set({
388
+ state: "outcome_unknown",
389
+ errorCode: "outcome_unknown",
390
+ errorMessage: "Attached Chrome connection generation changed",
391
+ errorRetryable: false,
392
+ errorDetails: { reason: "connection_generation_changed" },
393
+ settledAt: now,
394
+ updatedAt: now,
395
+ })
396
+ .where(
397
+ and(
398
+ eq(schema.interactionOperations.workspaceId, input.workspaceId),
399
+ inArray(schema.interactionOperations.operationId, operationIds),
400
+ inArray(schema.interactionOperations.state, ["prepared", "dispatched"]),
401
+ ),
402
+ );
403
+ }
404
+ if (browserIds.length > 0) {
405
+ await tx
406
+ .update(schema.browserSessions)
407
+ .set({
408
+ lifecycle: "lost",
409
+ failureCode: CONTROLLER_TRANSITION_EXPIRED,
410
+ updatedAt: now,
411
+ })
412
+ .where(
413
+ and(
414
+ eq(schema.browserSessions.workspaceId, input.workspaceId),
415
+ inArray(schema.browserSessions.id, browserIds),
416
+ inArray(schema.browserSessions.lifecycle, [...ATTACHED_DEVICE_LIVE_LIFECYCLES]),
417
+ ),
418
+ );
419
+ }
420
+ if (computerIds.length > 0) {
421
+ await tx
422
+ .update(schema.computerSessions)
423
+ .set({
424
+ lifecycle: "lost",
425
+ failureCode: CONTROLLER_TRANSITION_EXPIRED,
426
+ updatedAt: now,
427
+ })
428
+ .where(
429
+ and(
430
+ eq(schema.computerSessions.workspaceId, input.workspaceId),
431
+ inArray(schema.computerSessions.id, computerIds),
432
+ inArray(schema.computerSessions.lifecycle, [...ATTACHED_DEVICE_LIVE_LIFECYCLES]),
433
+ ),
434
+ );
435
+ }
436
+ return true;
437
+ }
438
+
439
+ /** Lock the device row first (same order as inventory) and prove the live
440
+ * physical generation still matches this controller placement. */
441
+ export async function attachedDeviceGenerationMatches(
442
+ tx: Database,
443
+ input: { workspaceId: string; deviceId: string; placementInstanceId: string },
444
+ ): Promise<boolean> {
445
+ const [device] = await tx
446
+ .select({
447
+ connectionGeneration: schema.attachedBrowserDevices.connectionGeneration,
448
+ })
449
+ .from(schema.attachedBrowserDevices)
450
+ .where(
451
+ and(
452
+ eq(schema.attachedBrowserDevices.workspaceId, input.workspaceId),
453
+ eq(schema.attachedBrowserDevices.id, input.deviceId),
454
+ ),
455
+ )
456
+ .for("update")
457
+ .limit(1);
458
+ return device?.connectionGeneration === input.placementInstanceId;
459
+ }
460
+
278
461
  /** Immediately mark every live Chrome profile on a cleanly disconnected agent
279
462
  * offline. Historical endpoint rows remain discoverable and reconnect in place. */
280
463
  export async function disconnectAttachedBrowserDevices(
@@ -336,8 +519,53 @@ export async function listAttachedBrowserDevices(
336
519
  asc(schema.attachedBrowserDevices.name),
337
520
  asc(schema.attachedBrowserDevices.id),
338
521
  );
522
+ const inventories = await scopedDb
523
+ .select({
524
+ enrollmentId: schema.attachedBrowserInventories.enrollmentId,
525
+ bridgeGeneration: schema.attachedBrowserInventories.bridgeGeneration,
526
+ revision: schema.attachedBrowserInventories.revision,
527
+ observedAt: schema.attachedBrowserInventories.observedAt,
528
+ enrollmentStatus: schema.enrollments.status,
529
+ connectionLeaseExpiresAt: schema.enrollments.connectionLeaseExpiresAt,
530
+ wentOfflineAt: schema.enrollments.wentOfflineAt,
531
+ })
532
+ .from(schema.attachedBrowserInventories)
533
+ .innerJoin(
534
+ schema.enrollments,
535
+ and(
536
+ eq(schema.enrollments.id, schema.attachedBrowserInventories.enrollmentId),
537
+ eq(schema.enrollments.workspaceId, schema.attachedBrowserInventories.workspaceId),
538
+ ),
539
+ )
540
+ .where(eq(schema.attachedBrowserInventories.workspaceId, input.workspaceId))
541
+ .orderBy(
542
+ desc(schema.attachedBrowserInventories.observedAt),
543
+ asc(schema.attachedBrowserInventories.enrollmentId),
544
+ );
545
+ const connectedProfilesByEnrollment = new Map<string, number>();
546
+ for (const row of rows) {
547
+ if (row.disconnectedAt !== null) continue;
548
+ connectedProfilesByEnrollment.set(
549
+ row.enrollmentId,
550
+ (connectedProfilesByEnrollment.get(row.enrollmentId) ?? 0) + 1,
551
+ );
552
+ }
339
553
  return AttachedBrowserDeviceListResponse.parse({
340
554
  revision: await readWorkspaceInteractionRevision(scopedDb, input.workspaceId),
555
+ bridges: inventories.map((inventory) => ({
556
+ enrollmentId: inventory.enrollmentId,
557
+ state:
558
+ inventory.enrollmentStatus === "active" &&
559
+ inventory.wentOfflineAt === null &&
560
+ inventory.connectionLeaseExpiresAt !== null &&
561
+ inventory.connectionLeaseExpiresAt.getTime() > Date.now()
562
+ ? "online"
563
+ : "offline",
564
+ bridgeGeneration: inventory.bridgeGeneration,
565
+ inventoryRevision: inventory.revision,
566
+ connectedProfileCount: connectedProfilesByEnrollment.get(inventory.enrollmentId) ?? 0,
567
+ lastSeenAt: inventory.observedAt.toISOString(),
568
+ })),
341
569
  devices: rows.map(deviceFromRow),
342
570
  });
343
571
  },