@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
package/src/task-notes.ts CHANGED
@@ -1,14 +1,17 @@
1
1
  import {
2
2
  TASK_NOTE_LIST_DEFAULT_LIMIT,
3
3
  TASK_NOTE_LIST_MAX_LIMIT,
4
+ TASK_NOTE_MAX_LIFETIME_DAYS,
4
5
  TaskNote,
5
6
  TaskNoteKind,
6
7
  TaskNoteListResponse,
7
8
  TaskNoteMutationResult,
8
9
  TaskNoteReason,
10
+ TaskNoteReplacementResult,
9
11
  TaskNoteText,
10
12
  type TaskNoteKind as TaskNoteKindType,
11
13
  } from "@opengeni/contracts";
14
+ import { createHash } from "node:crypto";
12
15
  import { sql } from "drizzle-orm";
13
16
  import type { Database } from "./database";
14
17
  import { rawRows, withRlsContext } from "./database";
@@ -36,6 +39,16 @@ export type ArchiveTaskNoteInput = TaskNoteAttemptClaims & {
36
39
  reason: string;
37
40
  };
38
41
 
42
+ export type ReplaceTaskNoteInput = TaskNoteAttemptClaims & {
43
+ operationId: string;
44
+ replacedNoteId: string;
45
+ expectedReplacedVersion: number;
46
+ replacementKind: TaskNoteKindType;
47
+ replacementText: string;
48
+ replacementExpiresInDays: number;
49
+ reason: string;
50
+ };
51
+
39
52
  export type ListTaskNotesInput = TaskNoteAttemptClaims & {
40
53
  includeArchived?: boolean;
41
54
  limit?: number;
@@ -58,6 +71,27 @@ type TaskNoteRow = {
58
71
  replayed?: boolean;
59
72
  };
60
73
 
74
+ type TaskNoteReplacementRow = {
75
+ operation_id: string;
76
+ input_hash: string;
77
+ replaced_note_id: string;
78
+ replaced_note_version: number;
79
+ replacement_note_id: string;
80
+ root_session_id: string;
81
+ replacement_kind: string;
82
+ replacement_text: string;
83
+ replacement_status: string;
84
+ replacement_version: number;
85
+ replacement_expires_at: Date | string;
86
+ replacement_created_at: Date | string;
87
+ replacement_updated_at: Date | string;
88
+ replacement_archived_at: Date | string | null;
89
+ replacement_actor_kind: string;
90
+ replacement_source_session_id: string;
91
+ replacement_source_turn_id: string;
92
+ replayed: boolean;
93
+ };
94
+
61
95
  function iso(value: Date | string): string {
62
96
  return (value instanceof Date ? value : new Date(value)).toISOString();
63
97
  }
@@ -88,6 +122,20 @@ function assertAttemptClaims(claims: TaskNoteAttemptClaims): void {
88
122
  }
89
123
  }
90
124
 
125
+ function derivedTaskNoteOperationId(operationId: string, label: "archive" | "create"): string {
126
+ const bytes = Buffer.from(
127
+ createHash("sha256")
128
+ .update(`task-note-replacement:v1:${operationId}:${label}`, "utf8")
129
+ .digest("hex")
130
+ .slice(0, 32),
131
+ "hex",
132
+ );
133
+ bytes[6] = (bytes[6]! & 0x0f) | 0x50;
134
+ bytes[8] = (bytes[8]! & 0x3f) | 0x80;
135
+ const hex = bytes.toString("hex");
136
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
137
+ }
138
+
91
139
  export async function createTaskNote(db: Database, input: CreateTaskNoteInput) {
92
140
  assertAttemptClaims(input);
93
141
  TaskNoteKind.parse(input.kind);
@@ -95,9 +143,9 @@ export async function createTaskNote(db: Database, input: CreateTaskNoteInput) {
95
143
  if (
96
144
  !Number.isSafeInteger(input.expiresInDays) ||
97
145
  input.expiresInDays < 1 ||
98
- input.expiresInDays > 30
146
+ input.expiresInDays > TASK_NOTE_MAX_LIFETIME_DAYS
99
147
  ) {
100
- throw new Error("Task note expiry must be 1-30 whole days");
148
+ throw new Error(`Task note expiry must be 1-${TASK_NOTE_MAX_LIFETIME_DAYS} whole days`);
101
149
  }
102
150
  const rows = await withRlsContext(
103
151
  db,
@@ -150,6 +198,80 @@ export async function archiveTaskNote(db: Database, input: ArchiveTaskNoteInput)
150
198
  return TaskNoteMutationResult.parse({ note: noteFromRow(rows[0]!), replayed: rows[0]!.replayed });
151
199
  }
152
200
 
201
+ /**
202
+ * Atomically archive one exact active note and create its immutable replacement.
203
+ * Exact retries return the original linked receipt; changed input or authority fails.
204
+ */
205
+ export async function replaceTaskNote(db: Database, input: ReplaceTaskNoteInput) {
206
+ assertAttemptClaims(input);
207
+ TaskNoteKind.parse(input.replacementKind);
208
+ TaskNoteText.parse(input.replacementText);
209
+ TaskNoteReason.parse(input.reason);
210
+ if (
211
+ input.expectedReplacedVersion !== 1 ||
212
+ !Number.isSafeInteger(input.replacementExpiresInDays) ||
213
+ input.replacementExpiresInDays < 1 ||
214
+ input.replacementExpiresInDays > TASK_NOTE_MAX_LIFETIME_DAYS
215
+ ) {
216
+ throw new Error(
217
+ `Task-note replacement requires version 1 and a 1-${TASK_NOTE_MAX_LIFETIME_DAYS} day expiry`,
218
+ );
219
+ }
220
+ const archiveOperationId = derivedTaskNoteOperationId(input.operationId, "archive");
221
+ const createOperationId = derivedTaskNoteOperationId(input.operationId, "create");
222
+ const rows = await withRlsContext(
223
+ db,
224
+ { accountId: input.accountId, workspaceId: input.workspaceId },
225
+ async (tx) =>
226
+ await rawRows<TaskNoteReplacementRow>(
227
+ tx,
228
+ sql`SELECT * FROM replace_task_note_for_attempt(
229
+ ${input.accountId}::uuid,
230
+ ${input.workspaceId}::uuid,
231
+ ${input.sessionId}::uuid,
232
+ ${input.turnId}::uuid,
233
+ ${input.attemptId}::uuid,
234
+ ${input.executionGeneration}::integer,
235
+ ${input.operationId}::uuid,
236
+ ${archiveOperationId}::uuid,
237
+ ${createOperationId}::uuid,
238
+ ${input.replacedNoteId}::uuid,
239
+ ${input.expectedReplacedVersion}::integer,
240
+ ${input.replacementKind}::text,
241
+ ${input.replacementText}::text,
242
+ ${input.replacementExpiresInDays}::integer,
243
+ ${input.reason}::text
244
+ )`,
245
+ ),
246
+ );
247
+ const row = rows[0];
248
+ if (!row || rows.length !== 1) throw new Error("Task-note replacement returned no receipt");
249
+ return TaskNoteReplacementResult.parse({
250
+ operationId: row.operation_id,
251
+ inputHash: row.input_hash,
252
+ replaces: {
253
+ noteId: row.replaced_note_id,
254
+ archivedVersion: row.replaced_note_version,
255
+ },
256
+ replacement: noteFromRow({
257
+ note_id: row.replacement_note_id,
258
+ root_session_id: row.root_session_id,
259
+ kind: row.replacement_kind,
260
+ note_text: row.replacement_text,
261
+ status: row.replacement_status,
262
+ version: row.replacement_version,
263
+ expires_at: row.replacement_expires_at,
264
+ created_at: row.replacement_created_at,
265
+ updated_at: row.replacement_updated_at,
266
+ archived_at: row.replacement_archived_at,
267
+ actor_kind: row.replacement_actor_kind,
268
+ source_session_id: row.replacement_source_session_id,
269
+ source_turn_id: row.replacement_source_turn_id,
270
+ }),
271
+ replayed: row.replayed,
272
+ });
273
+ }
274
+
153
275
  export async function listTaskNotes(db: Database, input: ListTaskNotesInput) {
154
276
  assertAttemptClaims(input);
155
277
  const limit = input.limit ?? TASK_NOTE_LIST_DEFAULT_LIMIT;
@@ -16,6 +16,8 @@ export const UNATTRIBUTED_LEGACY_INITIATOR: TurnInitiator = {
16
16
  export type FrozenTurnInitiator = {
17
17
  initiator: TurnInitiator;
18
18
  context: TurnInitiatorContext;
19
+ /** Exact causal human frozen only after the caller/attempt authority is validated. */
20
+ initiatingHumanSubjectId?: string | null;
19
21
  };
20
22
 
21
23
  const MAX_AGENT_PROVENANCE_HOPS = 32;
@@ -100,6 +102,7 @@ export async function frozenInitiatorForCommandActor(
100
102
  ...(actor.subjectLabel ? { label: actor.subjectLabel } : {}),
101
103
  },
102
104
  context: { ...(actor.context ?? {}) },
105
+ initiatingHumanSubjectId: null,
103
106
  };
104
107
  }
105
108
  if (actor.type !== "agent_attempt") {
@@ -110,6 +113,7 @@ export async function frozenInitiatorForCommandActor(
110
113
  ...(subjectLabel ? { label: subjectLabel } : {}),
111
114
  },
112
115
  context: {},
116
+ initiatingHumanSubjectId: actor.subjectId,
113
117
  };
114
118
  }
115
119
 
@@ -118,6 +122,7 @@ export async function frozenInitiatorForCommandActor(
118
122
  initiatorKind: schema.sessionTurns.initiatorKind,
119
123
  initiatorSubjectId: schema.sessionTurns.initiatorSubjectId,
120
124
  initiatorContext: schema.sessionTurns.initiatorContext,
125
+ initiatingHumanSubjectId: schema.sessionTurns.initiatingHumanSubjectId,
121
126
  })
122
127
  .from(schema.sessionTurns)
123
128
  .where(
@@ -151,5 +156,8 @@ export async function frozenInitiatorForCommandActor(
151
156
  via: clipped,
152
157
  ...(hops.length > clipped.length ? { viaTruncated: true } : {}),
153
158
  },
159
+ initiatingHumanSubjectId:
160
+ turn.initiatingHumanSubjectId ??
161
+ (turn.initiatorKind === "subject" ? turn.initiatorSubjectId : null),
154
162
  };
155
163
  }
@@ -0,0 +1,377 @@
1
+ import {
2
+ PersonalResourceAttachmentSummary,
3
+ type PersonalResourceAttachmentIntent,
4
+ USER_RESOURCE_ACTION_BY_KIND,
5
+ type UserResourceDelegation,
6
+ type UserResourceKind,
7
+ } from "@opengeni/contracts";
8
+ import { sql } from "drizzle-orm";
9
+ import { rawRows, setSubjectRlsContext, withRlsContext, type Database } from "./database";
10
+ import { nestedPostgresSqlState } from "./persistence-errors";
11
+
12
+ export type UserResourceAuthorityGrant = {
13
+ grantId: string;
14
+ targetWorkspaceId: string;
15
+ targetSessionId: string | null;
16
+ action: (typeof USER_RESOURCE_ACTION_BY_KIND)[UserResourceKind];
17
+ mode: "once" | "session" | "always";
18
+ context: "user_private" | "workspace_shared";
19
+ authorityEpoch: number | null;
20
+ generation: number;
21
+ status: "active" | "consumed" | "revoked" | "expired";
22
+ expiresAt: string | null;
23
+ delegation: UserResourceDelegation;
24
+ };
25
+
26
+ export type UserResourceAuthoritySummary = {
27
+ authorityId: string;
28
+ resourceKind: UserResourceKind;
29
+ resourceId: string;
30
+ originWorkspaceId: string | null;
31
+ generation: number;
32
+ status: "active" | "retained" | "revoked";
33
+ grants: UserResourceAuthorityGrant[];
34
+ };
35
+
36
+ export type AcceptedTurnPersonalResourceAttachment = {
37
+ summary: PersonalResourceAttachmentSummary;
38
+ replay: boolean;
39
+ };
40
+
41
+ export class PersonalResourceAttachmentAcceptanceError extends Error {
42
+ readonly name = "PersonalResourceAttachmentAcceptanceError";
43
+ constructor(
44
+ readonly kind: "invalid" | "forbidden" | "conflict",
45
+ options?: ErrorOptions,
46
+ ) {
47
+ super(
48
+ kind === "invalid"
49
+ ? "The personal-resource attachment request is invalid"
50
+ : kind === "forbidden"
51
+ ? "The personal-resource attachment is not authorized"
52
+ : "The personal-resource attachment conflicts with accepted work",
53
+ options,
54
+ );
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Issue and freeze the locked session's personal Variable Set/Rig/Connected
60
+ * Machine closure on
61
+ * one already-inserted logical turn. The caller owns the surrounding accepted-
62
+ * work transaction; this helper must never be called as a standalone grant
63
+ * mutation.
64
+ */
65
+ export async function acceptTurnPersonalResourceAttachmentInTransaction(
66
+ db: Database,
67
+ input: {
68
+ accountId: string;
69
+ workspaceId: string;
70
+ sessionId: string;
71
+ turnId: string;
72
+ subjectId: string;
73
+ intent: PersonalResourceAttachmentIntent & { expectedAuthorityEpoch: number };
74
+ },
75
+ ): Promise<AcceptedTurnPersonalResourceAttachment> {
76
+ await setSubjectRlsContext(db, input.subjectId);
77
+ let row:
78
+ | {
79
+ grantMode: "once" | "session" | "always";
80
+ grantContext: "user_private" | "workspace_shared";
81
+ resourceCount: number;
82
+ resourceKinds: Array<"variable_set" | "rig" | "connected_machine">;
83
+ sharedOutputWarningVersion: 1;
84
+ replay: boolean;
85
+ }
86
+ | undefined;
87
+ try {
88
+ [row] = await rawRows(
89
+ db,
90
+ sql`
91
+ select grant_mode as "grantMode", grant_context as "grantContext",
92
+ resource_count::int as "resourceCount", resource_kinds as "resourceKinds",
93
+ shared_output_warning_version::int as "sharedOutputWarningVersion", replay
94
+ from accept_turn_personal_resource_attachment(
95
+ ${input.accountId}::uuid, ${input.workspaceId}::uuid,
96
+ ${input.sessionId}::uuid, ${input.turnId}::uuid, ${input.intent.mode},
97
+ ${input.intent.expectedAuthorityEpoch},
98
+ ${input.intent.workspaceSharedAcknowledged},
99
+ ${input.intent.sharedOutputWarningVersion}
100
+ )
101
+ `,
102
+ );
103
+ } catch (error) {
104
+ const sqlState = nestedPostgresSqlState(error);
105
+ if (sqlState === "22023") {
106
+ throw new PersonalResourceAttachmentAcceptanceError("invalid", { cause: error });
107
+ }
108
+ if (sqlState === "42501") {
109
+ throw new PersonalResourceAttachmentAcceptanceError("forbidden", { cause: error });
110
+ }
111
+ if (sqlState === "23505") {
112
+ throw new PersonalResourceAttachmentAcceptanceError("conflict", { cause: error });
113
+ }
114
+ throw error;
115
+ }
116
+ if (!row) throw new Error("atomic personal-resource attachment was not returned");
117
+ return {
118
+ summary: PersonalResourceAttachmentSummary.parse({
119
+ mode: row.grantMode,
120
+ context: row.grantContext,
121
+ resourceCount: row.resourceCount,
122
+ resourceKinds: row.resourceKinds,
123
+ sharedOutputWarningVersion: row.sharedOutputWarningVersion,
124
+ }),
125
+ replay: row.replay,
126
+ };
127
+ }
128
+
129
+ type AuthorityRow = Omit<
130
+ UserResourceAuthorityGrant,
131
+ "grantId" | "targetWorkspaceId" | "delegation"
132
+ > & {
133
+ authorityId: string;
134
+ organizationId: string;
135
+ resourceKind: UserResourceKind;
136
+ resourceId: string;
137
+ originWorkspaceId: string | null;
138
+ authorityGeneration: number;
139
+ authorityStatus: UserResourceAuthoritySummary["status"];
140
+ grantId: string | null;
141
+ targetWorkspaceId: string | null;
142
+ };
143
+
144
+ function rfc3339(value: string | null): string | null {
145
+ if (value === null) return null;
146
+ const date = new Date(value);
147
+ if (Number.isNaN(date.getTime())) throw new Error("invalid database timestamptz projection");
148
+ return date.toISOString();
149
+ }
150
+
151
+ async function withOwnerContext<T>(
152
+ db: Database,
153
+ input: { accountId: string; workspaceId: string; subjectId: string },
154
+ fn: (db: Database) => Promise<T>,
155
+ ): Promise<T> {
156
+ return await withRlsContext(db, input, async (scopedDb) => {
157
+ await setSubjectRlsContext(scopedDb, input.subjectId);
158
+ return await fn(scopedDb);
159
+ });
160
+ }
161
+
162
+ export async function listSelfUserResourceAuthorities(
163
+ db: Database,
164
+ input: {
165
+ accountId: string;
166
+ workspaceId: string;
167
+ subjectId: string;
168
+ resourceKind: UserResourceKind;
169
+ cursor?: string | undefined;
170
+ limit: number;
171
+ },
172
+ ): Promise<{ authorities: UserResourceAuthoritySummary[]; nextCursor: string | null }> {
173
+ return await withOwnerContext(db, input, async (scopedDb) => {
174
+ const rows = await rawRows<AuthorityRow>(
175
+ scopedDb,
176
+ sql`
177
+ select authority_id as "authorityId", organization_id as "organizationId",
178
+ resource_kind as "resourceKind", resource_id as "resourceId",
179
+ origin_workspace_id as "originWorkspaceId",
180
+ authority_generation::int as "authorityGeneration",
181
+ authority_status as "authorityStatus", grant_id as "grantId",
182
+ target_workspace_id as "targetWorkspaceId", target_session_id as "targetSessionId",
183
+ action, grant_mode as mode, grant_context as context,
184
+ authority_epoch::int as "authorityEpoch",
185
+ grant_generation::int as generation, grant_status as status,
186
+ expires_at::text as "expiresAt"
187
+ from list_self_user_resource_authorities(
188
+ ${input.accountId}::uuid, ${input.workspaceId}::uuid, ${input.resourceKind},
189
+ ${input.cursor ?? null}::uuid, ${input.limit + 1}
190
+ )
191
+ `,
192
+ );
193
+ const grouped = new Map<string, UserResourceAuthoritySummary>();
194
+ for (const row of rows) {
195
+ const authority = grouped.get(row.authorityId) ?? {
196
+ authorityId: row.authorityId,
197
+ resourceKind: row.resourceKind,
198
+ resourceId: row.resourceId,
199
+ originWorkspaceId: row.originWorkspaceId,
200
+ generation: row.authorityGeneration,
201
+ status: row.authorityStatus,
202
+ grants: [],
203
+ };
204
+ if (row.grantId && row.targetWorkspaceId) {
205
+ authority.grants.push({
206
+ grantId: row.grantId,
207
+ targetWorkspaceId: row.targetWorkspaceId,
208
+ targetSessionId: row.targetSessionId,
209
+ action: row.action,
210
+ mode: row.mode,
211
+ context: row.context,
212
+ authorityEpoch: row.authorityEpoch,
213
+ generation: row.generation,
214
+ status: row.status,
215
+ expiresAt: rfc3339(row.expiresAt),
216
+ delegation: {
217
+ authorityId: row.authorityId,
218
+ grantId: row.grantId,
219
+ organizationId: row.organizationId,
220
+ workspaceId: row.targetWorkspaceId,
221
+ sessionId: row.targetSessionId,
222
+ action: row.action,
223
+ mode: row.mode,
224
+ context: row.context,
225
+ authorityEpoch: row.authorityEpoch,
226
+ authorityGeneration: row.authorityGeneration,
227
+ grantGeneration: row.generation,
228
+ },
229
+ });
230
+ }
231
+ grouped.set(row.authorityId, authority);
232
+ }
233
+ const values = [...grouped.values()];
234
+ const hasMore = values.length > input.limit;
235
+ const authorities = hasMore ? values.slice(0, input.limit) : values;
236
+ return {
237
+ authorities,
238
+ nextCursor: hasMore ? (authorities.at(-1)?.authorityId ?? null) : null,
239
+ };
240
+ });
241
+ }
242
+
243
+ export async function issueSelfUserResourceGrant(
244
+ db: Database,
245
+ input: {
246
+ accountId: string;
247
+ workspaceId: string;
248
+ subjectId: string;
249
+ authorityId: string;
250
+ resourceKind: UserResourceKind;
251
+ mode: "session" | "always";
252
+ context: "user_private" | "workspace_shared";
253
+ sessionId?: string | null;
254
+ expectedAuthorityEpoch?: number | null;
255
+ workspaceSharedAcknowledged: boolean;
256
+ },
257
+ ): Promise<UserResourceAuthorityGrant> {
258
+ return await withOwnerContext(db, input, async (scopedDb) => {
259
+ const [row] = await rawRows<
260
+ Omit<UserResourceAuthorityGrant, "delegation"> & {
261
+ organizationId: string;
262
+ authorityGeneration: number;
263
+ }
264
+ >(
265
+ scopedDb,
266
+ sql`
267
+ select grant_id as "grantId", organization_id as "organizationId",
268
+ authority_generation::int as "authorityGeneration",
269
+ target_workspace_id as "targetWorkspaceId",
270
+ target_session_id as "targetSessionId", action, grant_mode as mode,
271
+ grant_context as context, authority_epoch::int as "authorityEpoch",
272
+ grant_generation::int as generation,
273
+ grant_status as status, expires_at::text as "expiresAt"
274
+ from issue_self_user_resource_grant(
275
+ ${input.accountId}::uuid, ${input.authorityId}::uuid, ${input.workspaceId}::uuid,
276
+ ${input.resourceKind}, ${input.mode}, ${input.context},
277
+ ${input.sessionId ?? null}::uuid, ${input.expectedAuthorityEpoch ?? null}::integer,
278
+ ${input.workspaceSharedAcknowledged}
279
+ )
280
+ `,
281
+ );
282
+ if (!row) throw new Error("user-resource grant was not returned");
283
+ const { organizationId, authorityGeneration, ...grant } = row;
284
+ return {
285
+ ...grant,
286
+ expiresAt: rfc3339(grant.expiresAt),
287
+ delegation: {
288
+ authorityId: input.authorityId,
289
+ grantId: grant.grantId,
290
+ organizationId,
291
+ workspaceId: grant.targetWorkspaceId,
292
+ sessionId: grant.targetSessionId,
293
+ action: grant.action,
294
+ mode: grant.mode,
295
+ context: grant.context,
296
+ authorityEpoch: grant.authorityEpoch,
297
+ authorityGeneration,
298
+ grantGeneration: grant.generation,
299
+ },
300
+ };
301
+ });
302
+ }
303
+
304
+ export async function revokeSelfUserResourceGrant(
305
+ db: Database,
306
+ input: { accountId: string; workspaceId: string; subjectId: string; grantId: string },
307
+ ): Promise<{ grantId: string; generation: number; status: "revoked"; revokedAt: string }> {
308
+ return await withOwnerContext(db, input, async (scopedDb) => {
309
+ const [row] = await rawRows<{
310
+ grantId: string;
311
+ generation: number;
312
+ status: "revoked";
313
+ revokedAt: string;
314
+ }>(
315
+ scopedDb,
316
+ sql`
317
+ select grant_id as "grantId", grant_generation::int as generation,
318
+ grant_status as status, revoked_at::text as "revokedAt"
319
+ from revoke_self_user_resource_grant(
320
+ ${input.accountId}::uuid, ${input.workspaceId}::uuid, ${input.grantId}::uuid
321
+ )
322
+ `,
323
+ );
324
+ if (!row) throw new Error("user-resource grant was not returned");
325
+ return { ...row, revokedAt: rfc3339(row.revokedAt)! };
326
+ });
327
+ }
328
+
329
+ export async function resolveSessionAttemptPersonalResources(
330
+ db: Database,
331
+ input: { accountId: string; workspaceId: string; subjectId: string | null; attemptId: string },
332
+ ): Promise<void> {
333
+ await withRlsContext(db, input, async (scopedDb) => {
334
+ if (input.subjectId) await setSubjectRlsContext(scopedDb, input.subjectId);
335
+ await scopedDb.execute(sql`select authorize_session_attempt_personal_resource_reads(
336
+ ${input.accountId}::uuid, ${input.workspaceId}::uuid, ${input.attemptId}::uuid
337
+ )`);
338
+ });
339
+ }
340
+
341
+ export type PersonalDocumentAuthority = {
342
+ authorityId: string;
343
+ ownerOrganizationMembershipId: string;
344
+ authorityGeneration: number;
345
+ };
346
+
347
+ /**
348
+ * Create the common organization-user authority before inserting a personal
349
+ * Document. Returns null for a configured/local or rolling-legacy subject that
350
+ * has no eligible active organization membership, preserving the existing
351
+ * workspace-anchored personal lane. Call this inside the same database
352
+ * transaction as the Document insert so a failed write cannot leave an orphan
353
+ * authority.
354
+ */
355
+ export async function createPersonalDocumentAuthority(
356
+ db: Database,
357
+ input: {
358
+ accountId: string;
359
+ workspaceId: string;
360
+ subjectId: string;
361
+ documentId: string;
362
+ },
363
+ ): Promise<PersonalDocumentAuthority | null> {
364
+ await setSubjectRlsContext(db, input.subjectId);
365
+ const [row] = await rawRows<PersonalDocumentAuthority>(
366
+ db,
367
+ sql`
368
+ select authority_id as "authorityId",
369
+ owner_organization_membership_id as "ownerOrganizationMembershipId",
370
+ authority_generation::int as "authorityGeneration"
371
+ from create_personal_document_authority(
372
+ ${input.accountId}::uuid, ${input.workspaceId}::uuid, ${input.documentId}::uuid
373
+ )
374
+ `,
375
+ );
376
+ return row ?? null;
377
+ }