@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
@@ -30,6 +30,14 @@ export type SlackSharedTaskOrigin = {
30
30
  sourceThreadTs: string;
31
31
  initiatingSlackUserId: string;
32
32
  policyRevisionId: string;
33
+ /**
34
+ * Home tenancy of the frozen policy revision. The row's own
35
+ * `accountId`/`workspaceId` are the routed task's, which is a different
36
+ * workspace once Slack workspace routing is on. Null on rows written before
37
+ * routing existed, where the two were always equal.
38
+ */
39
+ policyAccountId: string | null;
40
+ policyWorkspaceId: string | null;
33
41
  policyHash: string;
34
42
  policyActivationVersion: number;
35
43
  publicationMode: "never" | "approval_required" | "allow";
@@ -310,6 +318,16 @@ export async function saveSlackSharedTaskOrigin(
310
318
  .limit(1);
311
319
  if (!existing) throw new Error("Slack shared-task origin conflict could not be resolved");
312
320
  for (const [key, value] of Object.entries(input)) {
321
+ // A row written before Slack workspace routing carries no policy tenancy
322
+ // and implied its own, because the two could not differ then. A replay
323
+ // from a new image supplies it, which is not a change of evidence and
324
+ // must not be reported as one.
325
+ if (
326
+ (key === "policyAccountId" || key === "policyWorkspaceId") &&
327
+ existing[key as keyof OriginRow] === null
328
+ ) {
329
+ continue;
330
+ }
313
331
  if (existing[key as keyof OriginRow] !== value) {
314
332
  throw new SlackTaskPolicyInvalidOperationError(
315
333
  "Slack shared-task origin identity conflicted with immutable evidence",
@@ -9,8 +9,10 @@ import {
9
9
  import { and, asc, eq, gt, inArray, ne } from "drizzle-orm";
10
10
 
11
11
  import { type Database, withWorkspaceRls } from "./database";
12
+ import { namedSubjectPersonalWorkspaceId } from "./slack-routing-personal-workspace";
12
13
  import { withLosslessContentWriteVersion } from "./lossless-json";
13
14
  import * as schema from "./schema";
15
+ import { normalizeWorkspaceMembershipPermissions } from "./workspace-membership-permissions";
14
16
 
15
17
  type RequestRow = typeof schema.slackUserLinkAccessRequests.$inferSelect;
16
18
  type OperationKind = "request" | "cancel" | "approve" | "deny";
@@ -563,7 +565,21 @@ async function membershipAllowsSlackLink(database: Database, row: RequestRow) {
563
565
  ),
564
566
  )
565
567
  .limit(1);
566
- const permissions = membership?.permissions ?? [];
568
+ if (workspaceMembershipPermissionsAllowSlackLink(membership?.permissions)) return true;
569
+ // A managed human's own personal workspace deliberately has no
570
+ // `workspace_memberships` row, so a bare membership join denies its owner.
571
+ // Once a Slack direct message can land there, that would tell people to
572
+ // request access to their own workspace. The pointer is DERIVED from an
573
+ // active organization membership; it is never accepted from the request row.
574
+ const personalWorkspaceId = await namedSubjectPersonalWorkspaceId(database, {
575
+ accountId: row.accountId,
576
+ subjectId: row.subjectId,
577
+ });
578
+ return personalWorkspaceId === row.workspaceId;
579
+ }
580
+
581
+ export function workspaceMembershipPermissionsAllowSlackLink(value: unknown): boolean {
582
+ const permissions = normalizeWorkspaceMembershipPermissions(value);
567
583
  return permissions.includes("sessions:create") || permissions.includes("workspace:admin");
568
584
  }
569
585
 
@@ -133,3 +133,77 @@ export const taskNoteWriteCapabilities = pgTable(
133
133
  ),
134
134
  }),
135
135
  );
136
+
137
+ // Transaction-local, owner-only admission for the single Task-note evidence
138
+ // insert materialized by a governed promotion. Migration 0260 owns its RLS,
139
+ // one-shot trigger consumption, and runtime-role revocation.
140
+ export const taskNoteKnowledgePromotionCapabilities = pgTable(
141
+ "task_note_knowledge_promotion_capabilities",
142
+ {
143
+ backendPid: integer("backend_pid").notNull(),
144
+ transactionId: xid8("transaction_id").notNull(),
145
+ capabilityId: uuid("capability_id").notNull(),
146
+ accountId: uuid("account_id").notNull(),
147
+ workspaceId: uuid("workspace_id").notNull(),
148
+ noteId: uuid("note_id").notNull(),
149
+ rootSessionId: uuid("root_session_id").notNull(),
150
+ noteVersion: integer("note_version").notNull(),
151
+ noteTextHash: text("note_text_hash").notNull(),
152
+ evidenceOperationId: text("evidence_operation_id").notNull(),
153
+ claimOperationId: text("claim_operation_id").notNull(),
154
+ actorSubjectId: text("actor_subject_id").notNull(),
155
+ initiatingHumanSubjectId: text("initiating_human_subject_id").notNull(),
156
+ learningPolicySnapshotId: uuid("learning_policy_snapshot_id").notNull(),
157
+ },
158
+ (table) => ({
159
+ identity: uniqueIndex("task_note_knowledge_promotion_capabilities_identity_uq").on(
160
+ table.backendPid,
161
+ table.transactionId,
162
+ table.capabilityId,
163
+ ),
164
+ }),
165
+ );
166
+
167
+ // Immutable, content-free lineage for one atomic archive + replacement create.
168
+ // Migration 0260 owns exact constraints, FORCE RLS, and lifecycle-only writes.
169
+ export const taskNoteReplacementReceipts = pgTable(
170
+ "task_note_replacement_receipts",
171
+ {
172
+ id: uuid("id").primaryKey().defaultRandom(),
173
+ accountId: uuid("account_id").notNull(),
174
+ workspaceId: uuid("workspace_id").notNull(),
175
+ rootSessionId: uuid("root_session_id").notNull(),
176
+ operationId: uuid("operation_id").notNull(),
177
+ inputHash: text("input_hash").notNull(),
178
+ replacedNoteId: uuid("replaced_note_id").notNull(),
179
+ replacedNoteVersion: integer("replaced_note_version").notNull(),
180
+ replacementNoteId: uuid("replacement_note_id").notNull(),
181
+ replacementNoteVersion: integer("replacement_note_version").notNull(),
182
+ archiveOperationId: uuid("archive_operation_id").notNull(),
183
+ createOperationId: uuid("create_operation_id").notNull(),
184
+ actorKind: text("actor_kind").notNull(),
185
+ actorSubjectId: text("actor_subject_id").notNull(),
186
+ initiatingHumanSubjectId: text("initiating_human_subject_id"),
187
+ actorSessionId: uuid("actor_session_id").notNull(),
188
+ actorTurnId: uuid("actor_turn_id").notNull(),
189
+ actorAttemptId: uuid("actor_attempt_id").notNull(),
190
+ actorExecutionGeneration: integer("actor_execution_generation").notNull(),
191
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
192
+ },
193
+ (table) => ({
194
+ workspaceOperation: uniqueIndex("task_note_replacement_receipts_workspace_operation_uq").on(
195
+ table.workspaceId,
196
+ table.operationId,
197
+ ),
198
+ workspaceReplacement: uniqueIndex("task_note_replacement_receipts_workspace_replacement_uq").on(
199
+ table.workspaceId,
200
+ table.replacementNoteId,
201
+ ),
202
+ rootTimeline: index("task_note_replacement_receipts_root_timeline_idx").on(
203
+ table.workspaceId,
204
+ table.rootSessionId,
205
+ table.createdAt,
206
+ table.id,
207
+ ),
208
+ }),
209
+ );
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
  }