@opengeni/db 0.28.9 → 0.36.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 (225) hide show
  1. package/dist/attached-browser-devices.d.ts +42 -0
  2. package/dist/attempt-tool-catalogs.d.ts +16 -0
  3. package/dist/browser-auth.d.ts +299 -0
  4. package/dist/browser-downloads.d.ts +51 -0
  5. package/dist/browser-identities.d.ts +137 -0
  6. package/dist/browser-sessions.d.ts +354 -0
  7. package/dist/browser-state-artifacts.d.ts +91 -0
  8. package/dist/canonical-human-identities.d.ts +48 -0
  9. package/dist/canonical-human-identities.js +28 -0
  10. package/dist/canonical-human-identities.js.map +1 -0
  11. package/dist/capability-components.d.ts +37 -0
  12. package/dist/capability-integrations.d.ts +194 -0
  13. package/dist/chunk-5KTIEDX7.js +13200 -0
  14. package/dist/chunk-5KTIEDX7.js.map +1 -0
  15. package/dist/chunk-CQZ4QSP5.js +995 -0
  16. package/dist/chunk-CQZ4QSP5.js.map +1 -0
  17. package/dist/chunk-F32YT7MP.js +117 -0
  18. package/dist/chunk-F32YT7MP.js.map +1 -0
  19. package/dist/chunk-JC5QPLUM.js +666 -0
  20. package/dist/chunk-JC5QPLUM.js.map +1 -0
  21. package/dist/chunk-P3RE4D2B.js +3535 -0
  22. package/dist/chunk-P3RE4D2B.js.map +1 -0
  23. package/dist/chunk-TAUTWOS5.js +259 -0
  24. package/dist/chunk-TAUTWOS5.js.map +1 -0
  25. package/dist/chunk-VKLB2ZGI.js +2389 -0
  26. package/dist/chunk-VKLB2ZGI.js.map +1 -0
  27. package/dist/codemode-operations.d.ts +117 -0
  28. package/dist/codex-token-resolver.d.ts +2 -2
  29. package/dist/company-profile-schema.d.ts +798 -0
  30. package/dist/company-profile.d.ts +268 -0
  31. package/dist/computer-sessions.d.ts +109 -0
  32. package/dist/connection-token-resolver.d.ts +9 -4
  33. package/dist/database.d.ts +57 -3
  34. package/dist/editable-artifact-durable-export.d.ts +153 -0
  35. package/dist/editable-artifact-durable-export.js +788 -0
  36. package/dist/editable-artifact-durable-export.js.map +1 -0
  37. package/dist/editable-artifact-materialization.d.ts +123 -0
  38. package/dist/editable-artifacts-schema.d.ts +4152 -0
  39. package/dist/editable-artifacts.d.ts +610 -0
  40. package/dist/editable-artifacts.js +26 -0
  41. package/dist/editable-artifacts.js.map +1 -0
  42. package/dist/generated-images.d.ts +139 -0
  43. package/dist/index.d.ts +1122 -126
  44. package/dist/index.js +42329 -18407
  45. package/dist/index.js.map +1 -1
  46. package/dist/insights.d.ts +73 -10
  47. package/dist/integration-bindings.d.ts +70 -0
  48. package/dist/integration-facets.d.ts +99 -0
  49. package/dist/interaction-revisions.d.ts +14 -0
  50. package/dist/interaction-schema.d.ts +6036 -0
  51. package/dist/knowledge-source-sync-schema.d.ts +1679 -0
  52. package/dist/knowledge-source-sync.d.ts +274 -0
  53. package/dist/memory-governance.d.ts +2 -2
  54. package/dist/memory-slack-delivery.d.ts +101 -0
  55. package/dist/new-session-drafts.d.ts +2 -2
  56. package/dist/pack-components.d.ts +76 -0
  57. package/dist/pack-installations.d.ts +86 -0
  58. package/dist/persistence-errors.d.ts +3 -3
  59. package/dist/plugin-packages.d.ts +126 -0
  60. package/dist/preference-registry.d.ts +1 -1
  61. package/dist/provision-roles.d.ts +9 -1
  62. package/dist/provision-roles.js +1 -1
  63. package/dist/runtime-posture.d.ts +19 -6
  64. package/dist/schema.d.ts +22654 -9359
  65. package/dist/schema.js +209 -3
  66. package/dist/scoped-knowledge.d.ts +97 -1
  67. package/dist/session-control.d.ts +33 -7
  68. package/dist/session-queue-commands.d.ts +19 -11
  69. package/dist/session-realtime-context.d.ts +2 -2
  70. package/dist/session-realtime-ledger.d.ts +4 -4
  71. package/dist/session-realtime-mirror.d.ts +2 -2
  72. package/dist/session-realtime-state.d.ts +1 -1
  73. package/dist/session-realtime-terminal.d.ts +1 -1
  74. package/dist/session-realtime.d.ts +7 -7
  75. package/dist/session-tenancy.d.ts +44 -0
  76. package/dist/session-tenancy.js +16 -0
  77. package/dist/session-tenancy.js.map +1 -0
  78. package/dist/session-tool-call-settlement.d.ts +1 -1
  79. package/dist/slack-task-policy-schema.d.ts +847 -0
  80. package/dist/slack-task-policy.d.ts +65 -0
  81. package/dist/slack-user-link-access.d.ts +54 -0
  82. package/dist/task-notes-schema.d.ts +924 -0
  83. package/dist/task-notes.d.ts +85 -0
  84. package/dist/transcription-recordings.d.ts +2 -2
  85. package/dist/turn-initiator.d.ts +2 -2
  86. package/dist/video-generation.d.ts +225 -0
  87. package/dist/video-generation.js +68 -0
  88. package/dist/video-generation.js.map +1 -0
  89. package/dist/workspace-artifacts.d.ts +1 -1
  90. package/dist/workspace-instruction-policies.d.ts +1 -1
  91. package/dist/workspace-learning-policy-schema.d.ts +932 -0
  92. package/dist/workspace-learning-policy.d.ts +161 -0
  93. package/dist/xai-subscription.d.ts +326 -0
  94. package/drizzle/0183_model_call_provider_cost_estimates.sql +39 -0
  95. package/drizzle/0184_sandbox_drain_teardown_fence.sql +682 -0
  96. package/drizzle/0185_temporal_schedule_cleanup_outbox.sql +168 -0
  97. package/drizzle/0186_sandbox_capture_provider_contract.sql +400 -0
  98. package/drizzle/0187_generated_image_artifacts.sql +173 -0
  99. package/drizzle/0188_image_generation_retention_failure.sql +25 -0
  100. package/drizzle/0189_retained_session_image_formats.sql +13 -0
  101. package/drizzle/0190_timeline_annotations.sql +31 -0
  102. package/drizzle/0191_editable_artifact_engine.sql +3853 -0
  103. package/drizzle/0192_editable_artifact_live_tickets.sql +338 -0
  104. package/drizzle/0193_editable_artifact_authorization.sql +190 -0
  105. package/drizzle/0194_editable_artifact_durable_exports.sql +160 -0
  106. package/drizzle/0195_editable_artifact_import_authorization.sql +203 -0
  107. package/drizzle/0196_rig_provider_images.sql +152 -0
  108. package/drizzle/0197_knowledge_source_sync_schedules.sql +523 -0
  109. package/drizzle/0198_memory_slack_publication_delivery.sql +393 -0
  110. package/drizzle/0199_workspace_learning_policy.sql +980 -0
  111. package/drizzle/0201_company_profile_authority.sql +849 -0
  112. package/drizzle/0202_document_index_checkpoints.sql +85 -0
  113. package/drizzle/0203_durable_video_generation.sql +353 -0
  114. package/drizzle/0204_video_generation_funding.sql +44 -0
  115. package/drizzle/0205_attempt_tool_catalogs.sql +230 -0
  116. package/drizzle/0206_browser_sessions.sql +646 -0
  117. package/drizzle/0207_browser_identities.sql +379 -0
  118. package/drizzle/0208_attached_browser_devices.sql +165 -0
  119. package/drizzle/0209_computer_sessions.sql +575 -0
  120. package/drizzle/0210_browser_auth_network_interventions.sql +441 -0
  121. package/drizzle/0211_editable_artifact_session_links.sql +58 -0
  122. package/drizzle/0212_browser_state_transfer_hardening.sql +560 -0
  123. package/drizzle/0212_slack_installation_bindings.sql +348 -0
  124. package/drizzle/0213_browser_interaction_authority.sql +67 -0
  125. package/drizzle/0213_slack_user_link_access_requests.sql +177 -0
  126. package/drizzle/0214_browser_download_saves.sql +34 -0
  127. package/drizzle/0214_session_activity_commit_gate.sql +262 -0
  128. package/drizzle/0215_browser_controller_host.sql +385 -0
  129. package/drizzle/0215_capabilities_platform.sql +980 -0
  130. package/drizzle/0216_browser_auth_health_evidence.sql +137 -0
  131. package/drizzle/0216_pack_component_ownership.sql +235 -0
  132. package/drizzle/0217_capability_definition_delete_authority.sql +32 -0
  133. package/drizzle/0217_external_browser_auth_operations.sql +22 -0
  134. package/drizzle/0218_organization_tenancy_foundation.sql +299 -0
  135. package/drizzle/0219_organization_tenancy_managed_human_provisioning.sql +317 -0
  136. package/drizzle/0219_site_auth_maintenance_sessions.sql +234 -0
  137. package/drizzle/0220_memory_slack_append_only_cascade.sql +38 -0
  138. package/drizzle/0220_session_channels.sql +54 -0
  139. package/drizzle/0221_sessions_channel_index.sql +5 -0
  140. package/drizzle/0222_session_visibility_authority_epochs.sql +158 -0
  141. package/drizzle/0222_sessions_channel_fk.sql +89 -0
  142. package/drizzle/0223_pending_tool_event_output.sql +55 -0
  143. package/drizzle/0223_sessions_channel_fk_validate.sql +118 -0
  144. package/drizzle/0224_slack_post_outcome_reconciliation.sql +123 -0
  145. package/drizzle/0225_session_visibility_fork_activation.sql +1363 -0
  146. package/drizzle/0226_personal_codex_authority_foundation.sql +365 -0
  147. package/drizzle/0227_slack_native_actions.sql +145 -0
  148. package/drizzle/0228_interaction_controller_data_plane.sql +14 -0
  149. package/drizzle/0228_slack_task_policy.sql +400 -0
  150. package/drizzle/0229_slack_inbox_file_fact.sql +10 -0
  151. package/drizzle/0230_user_scoped_variable_sets_rigs.sql +84 -0
  152. package/drizzle/0231_integration_definition_identity_cutover.sql +271 -0
  153. package/drizzle/0232_integration_facet_authority_cutover.sql +610 -0
  154. package/drizzle/0233_skill_and_integration_authority_cutover.sql +1162 -0
  155. package/drizzle/0234_xai_subscription_authority.sql +1139 -0
  156. package/drizzle/0235_canonical_human_login_bindings.sql +920 -0
  157. package/drizzle/0236_browser_identity_lifecycle.sql +39 -0
  158. package/drizzle/0236_session_visibility_slack_policy.sql +72 -0
  159. package/drizzle/0237_interaction_transition_reaper.sql +263 -0
  160. package/drizzle/0238_goal_persistence_policy.sql +471 -0
  161. package/drizzle/0238_supergrok_realtime_model.sql +29 -0
  162. package/drizzle/0239_supergrok_video_funding.sql +50 -0
  163. package/drizzle/0239_task_tree_notes.sql +990 -0
  164. package/package.json +26 -5
  165. package/src/attached-browser-devices.ts +371 -0
  166. package/src/attempt-tool-catalogs.ts +131 -0
  167. package/src/browser-auth.ts +4373 -0
  168. package/src/browser-downloads.ts +517 -0
  169. package/src/browser-identities.ts +1385 -0
  170. package/src/browser-sessions.ts +2294 -0
  171. package/src/browser-state-artifacts.ts +449 -0
  172. package/src/canonical-human-identities.ts +317 -0
  173. package/src/capability-components.ts +166 -0
  174. package/src/capability-integrations.ts +1571 -0
  175. package/src/codemode-operations.ts +674 -0
  176. package/src/company-profile-schema.ts +143 -0
  177. package/src/company-profile.ts +968 -0
  178. package/src/computer-sessions.ts +1130 -0
  179. package/src/connection-token-resolver.ts +270 -25
  180. package/src/database.ts +468 -24
  181. package/src/editable-artifact-durable-export.ts +1201 -0
  182. package/src/editable-artifact-materialization.ts +893 -0
  183. package/src/editable-artifacts-schema.ts +879 -0
  184. package/src/editable-artifacts.ts +5404 -0
  185. package/src/generated-images.ts +740 -0
  186. package/src/index.ts +9963 -1465
  187. package/src/insights.ts +251 -23
  188. package/src/integration-bindings.ts +455 -0
  189. package/src/integration-facets.ts +862 -0
  190. package/src/interaction-revisions.ts +70 -0
  191. package/src/interaction-schema.ts +1606 -0
  192. package/src/knowledge-source-sync-schema.ts +192 -0
  193. package/src/knowledge-source-sync.ts +1922 -0
  194. package/src/memory-slack-delivery.ts +801 -0
  195. package/src/pack-components.ts +954 -0
  196. package/src/pack-installations.ts +886 -0
  197. package/src/persistence-errors.ts +4 -7
  198. package/src/plugin-packages.ts +1186 -0
  199. package/src/provision-roles.ts +534 -1
  200. package/src/runtime-posture-cli.ts +9 -0
  201. package/src/runtime-posture.ts +721 -5
  202. package/src/schema.ts +6270 -3159
  203. package/src/scoped-knowledge-schema.ts +1 -1
  204. package/src/scoped-knowledge.ts +655 -3
  205. package/src/session-control.ts +130 -42
  206. package/src/session-queue-commands.ts +265 -15
  207. package/src/session-realtime-context.ts +2 -2
  208. package/src/session-realtime-ledger.ts +3 -3
  209. package/src/session-realtime.ts +7 -7
  210. package/src/session-tenancy.ts +188 -0
  211. package/src/session-tool-call-settlement.ts +18 -3
  212. package/src/slack-task-policy-schema.ts +130 -0
  213. package/src/slack-task-policy.ts +341 -0
  214. package/src/slack-user-link-access.ts +702 -0
  215. package/src/task-notes-schema.ts +135 -0
  216. package/src/task-notes.ts +178 -0
  217. package/src/video-generation.ts +1530 -0
  218. package/src/workspace-artifacts.ts +1 -1
  219. package/src/workspace-learning-policy-schema.ts +139 -0
  220. package/src/workspace-learning-policy.ts +595 -0
  221. package/src/xai-subscription.ts +1887 -0
  222. package/dist/chunk-P6CUHXQZ.js +0 -7248
  223. package/dist/chunk-P6CUHXQZ.js.map +0 -1
  224. package/dist/chunk-VHBFHMPC.js +0 -1219
  225. package/dist/chunk-VHBFHMPC.js.map +0 -1
@@ -0,0 +1,990 @@
1
+ -- deployment-mode: rolling
2
+ -- Root-session task notes are an explicit, bounded coordination surface for
3
+ -- agents in one session tree. They are not prompt memory, company knowledge,
4
+ -- policy, preferences, or authority. All reads and writes pass through the
5
+ -- exact-attempt lifecycle functions below.
6
+
7
+ SET LOCAL lock_timeout = '5s';
8
+ SET LOCAL statement_timeout = '10min';
9
+
10
+ CREATE TABLE "task_notes" (
11
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
12
+ "account_id" uuid NOT NULL,
13
+ "workspace_id" uuid NOT NULL,
14
+ "root_session_id" uuid NOT NULL,
15
+ "kind" text NOT NULL,
16
+ "text" text NOT NULL,
17
+ "text_hash" text NOT NULL,
18
+ "status" text DEFAULT 'active' NOT NULL,
19
+ "version" integer DEFAULT 1 NOT NULL,
20
+ "expires_at" timestamptz NOT NULL,
21
+ "create_operation_id" uuid NOT NULL,
22
+ "create_input_hash" text NOT NULL,
23
+ "created_by_actor_kind" text NOT NULL,
24
+ "created_by_actor_subject_id" text NOT NULL,
25
+ "created_by_initiating_human_subject_id" text,
26
+ "created_by_session_id" uuid NOT NULL,
27
+ "created_by_turn_id" uuid NOT NULL,
28
+ "created_by_attempt_id" uuid NOT NULL,
29
+ "created_by_execution_generation" integer NOT NULL,
30
+ "archive_operation_id" uuid,
31
+ "archive_input_hash" text,
32
+ "archived_by_actor_kind" text,
33
+ "archived_by_actor_subject_id" text,
34
+ "archived_by_initiating_human_subject_id" text,
35
+ "archived_by_session_id" uuid,
36
+ "archived_by_turn_id" uuid,
37
+ "archived_by_attempt_id" uuid,
38
+ "archived_by_execution_generation" integer,
39
+ "archived_at" timestamptz,
40
+ "created_at" timestamptz DEFAULT now() NOT NULL,
41
+ "updated_at" timestamptz DEFAULT now() NOT NULL,
42
+ CONSTRAINT "task_notes_workspace_account_fk"
43
+ FOREIGN KEY ("workspace_id", "account_id")
44
+ REFERENCES "workspaces" ("id", "account_id") ON DELETE CASCADE,
45
+ CONSTRAINT "task_notes_root_session_fk"
46
+ FOREIGN KEY ("workspace_id", "root_session_id")
47
+ REFERENCES "sessions" ("workspace_id", "id") ON DELETE CASCADE,
48
+ CONSTRAINT "task_notes_created_by_session_fk"
49
+ FOREIGN KEY ("workspace_id", "created_by_session_id")
50
+ REFERENCES "sessions" ("workspace_id", "id") ON DELETE CASCADE,
51
+ CONSTRAINT "task_notes_created_by_turn_fk"
52
+ FOREIGN KEY ("workspace_id", "created_by_turn_id")
53
+ REFERENCES "session_turns" ("workspace_id", "id") ON DELETE RESTRICT,
54
+ CONSTRAINT "task_notes_created_by_attempt_fk"
55
+ FOREIGN KEY ("workspace_id", "created_by_attempt_id")
56
+ REFERENCES "session_turn_attempts" ("workspace_id", "id") ON DELETE RESTRICT,
57
+ CONSTRAINT "task_notes_kind_check"
58
+ CHECK ("kind" IN ('finding','decision','blocker','ownership','artifact','handoff')),
59
+ CONSTRAINT "task_notes_text_check"
60
+ CHECK (
61
+ octet_length("text") BETWEEN 1 AND 4096
62
+ AND "text" = btrim("text")
63
+ ),
64
+ CONSTRAINT "task_notes_text_hash_check"
65
+ CHECK ("text_hash" ~ '^[0-9a-f]{64}$'),
66
+ CONSTRAINT "task_notes_status_check"
67
+ CHECK ("status" IN ('active','archived')),
68
+ CONSTRAINT "task_notes_version_check"
69
+ CHECK ("version" BETWEEN 1 AND 2),
70
+ CONSTRAINT "task_notes_expiry_check"
71
+ CHECK ("expires_at" > "created_at" AND "expires_at" <= "created_at" + interval '30 days'),
72
+ CONSTRAINT "task_notes_create_hash_check"
73
+ CHECK ("create_input_hash" ~ '^[0-9a-f]{64}$'),
74
+ CONSTRAINT "task_notes_create_actor_check"
75
+ CHECK (
76
+ "created_by_actor_kind" IN ('human','service')
77
+ AND length("created_by_actor_subject_id") BETWEEN 1 AND 1024
78
+ AND octet_length("created_by_actor_subject_id") <= 4096
79
+ AND ("created_by_initiating_human_subject_id" IS NULL OR (
80
+ length("created_by_initiating_human_subject_id") BETWEEN 1 AND 1024
81
+ AND octet_length("created_by_initiating_human_subject_id") <= 4096
82
+ ))
83
+ AND "created_by_execution_generation" > 0
84
+ ),
85
+ CONSTRAINT "task_notes_archive_shape_check"
86
+ CHECK (
87
+ ("status" = 'active' AND "version" = 1
88
+ AND "archive_operation_id" IS NULL
89
+ AND "archive_input_hash" IS NULL
90
+ AND "archived_by_actor_kind" IS NULL
91
+ AND "archived_by_actor_subject_id" IS NULL
92
+ AND "archived_by_initiating_human_subject_id" IS NULL
93
+ AND "archived_by_session_id" IS NULL
94
+ AND "archived_by_turn_id" IS NULL
95
+ AND "archived_by_attempt_id" IS NULL
96
+ AND "archived_by_execution_generation" IS NULL
97
+ AND "archived_at" IS NULL)
98
+ OR
99
+ ("status" = 'archived' AND "version" = 2
100
+ AND "archive_operation_id" IS NOT NULL
101
+ AND "archive_input_hash" ~ '^[0-9a-f]{64}$'
102
+ AND "archived_by_actor_kind" IN ('human','service')
103
+ AND length("archived_by_actor_subject_id") BETWEEN 1 AND 1024
104
+ AND octet_length("archived_by_actor_subject_id") <= 4096
105
+ AND ("archived_by_initiating_human_subject_id" IS NULL OR (
106
+ length("archived_by_initiating_human_subject_id") BETWEEN 1 AND 1024
107
+ AND octet_length("archived_by_initiating_human_subject_id") <= 4096
108
+ ))
109
+ AND "archived_by_session_id" IS NOT NULL
110
+ AND "archived_by_turn_id" IS NOT NULL
111
+ AND "archived_by_attempt_id" IS NOT NULL
112
+ AND "archived_by_execution_generation" > 0
113
+ AND "archived_at" IS NOT NULL
114
+ AND "archived_at" >= "created_at")
115
+ )
116
+ );
117
+
118
+ CREATE UNIQUE INDEX "task_notes_workspace_id_uq"
119
+ ON "task_notes" ("workspace_id", "id");
120
+ CREATE UNIQUE INDEX "task_notes_workspace_root_id_uq"
121
+ ON "task_notes" ("workspace_id", "root_session_id", "id");
122
+ CREATE UNIQUE INDEX "task_notes_workspace_create_operation_uq"
123
+ ON "task_notes" ("workspace_id", "create_operation_id");
124
+ CREATE UNIQUE INDEX "task_notes_workspace_archive_operation_uq"
125
+ ON "task_notes" ("workspace_id", "archive_operation_id")
126
+ WHERE "archive_operation_id" IS NOT NULL;
127
+ CREATE INDEX "task_notes_root_active_idx"
128
+ ON "task_notes" ("workspace_id", "root_session_id", "status", "expires_at", "updated_at" DESC, "id" DESC);
129
+
130
+ CREATE TABLE "task_note_events" (
131
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
132
+ "account_id" uuid NOT NULL,
133
+ "workspace_id" uuid NOT NULL,
134
+ "root_session_id" uuid NOT NULL,
135
+ "note_id" uuid NOT NULL,
136
+ "event_type" text NOT NULL,
137
+ "note_version" integer NOT NULL,
138
+ "operation_id" uuid NOT NULL,
139
+ "input_hash" text NOT NULL,
140
+ "text_hash" text NOT NULL,
141
+ "reason" text,
142
+ "actor_kind" text NOT NULL,
143
+ "actor_subject_id" text NOT NULL,
144
+ "initiating_human_subject_id" text,
145
+ "actor_session_id" uuid NOT NULL,
146
+ "actor_turn_id" uuid NOT NULL,
147
+ "actor_attempt_id" uuid NOT NULL,
148
+ "actor_execution_generation" integer NOT NULL,
149
+ "created_at" timestamptz DEFAULT now() NOT NULL,
150
+ CONSTRAINT "task_note_events_workspace_account_fk"
151
+ FOREIGN KEY ("workspace_id", "account_id")
152
+ REFERENCES "workspaces" ("id", "account_id") ON DELETE CASCADE,
153
+ CONSTRAINT "task_note_events_root_session_fk"
154
+ FOREIGN KEY ("workspace_id", "root_session_id")
155
+ REFERENCES "sessions" ("workspace_id", "id") ON DELETE CASCADE,
156
+ CONSTRAINT "task_note_events_note_fk"
157
+ FOREIGN KEY ("workspace_id", "note_id")
158
+ REFERENCES "task_notes" ("workspace_id", "id") ON DELETE CASCADE,
159
+ CONSTRAINT "task_note_events_actor_session_fk"
160
+ FOREIGN KEY ("workspace_id", "actor_session_id")
161
+ REFERENCES "sessions" ("workspace_id", "id") ON DELETE RESTRICT,
162
+ CONSTRAINT "task_note_events_actor_turn_fk"
163
+ FOREIGN KEY ("workspace_id", "actor_turn_id")
164
+ REFERENCES "session_turns" ("workspace_id", "id") ON DELETE RESTRICT,
165
+ CONSTRAINT "task_note_events_actor_attempt_fk"
166
+ FOREIGN KEY ("workspace_id", "actor_attempt_id")
167
+ REFERENCES "session_turn_attempts" ("workspace_id", "id") ON DELETE RESTRICT,
168
+ CONSTRAINT "task_note_events_type_check"
169
+ CHECK (("event_type" = 'created' AND "note_version" = 1 AND "reason" IS NULL)
170
+ OR ("event_type" = 'archived' AND "note_version" = 2)),
171
+ CONSTRAINT "task_note_events_hash_check"
172
+ CHECK ("input_hash" ~ '^[0-9a-f]{64}$' AND "text_hash" ~ '^[0-9a-f]{64}$'),
173
+ CONSTRAINT "task_note_events_reason_check"
174
+ CHECK ("reason" IS NULL OR (
175
+ octet_length("reason") BETWEEN 1 AND 2048
176
+ )),
177
+ CONSTRAINT "task_note_events_actor_check"
178
+ CHECK (
179
+ "actor_kind" IN ('human','service')
180
+ AND length("actor_subject_id") BETWEEN 1 AND 1024
181
+ AND octet_length("actor_subject_id") <= 4096
182
+ AND ("initiating_human_subject_id" IS NULL OR (
183
+ length("initiating_human_subject_id") BETWEEN 1 AND 1024
184
+ AND octet_length("initiating_human_subject_id") <= 4096
185
+ ))
186
+ AND "actor_execution_generation" > 0
187
+ )
188
+ );
189
+
190
+ CREATE UNIQUE INDEX "task_note_events_workspace_operation_uq"
191
+ ON "task_note_events" ("workspace_id", "operation_id");
192
+ CREATE UNIQUE INDEX "task_note_events_note_version_uq"
193
+ ON "task_note_events" ("workspace_id", "note_id", "note_version");
194
+ CREATE INDEX "task_note_events_root_timeline_idx"
195
+ ON "task_note_events" ("workspace_id", "root_session_id", "created_at", "id");
196
+
197
+ CREATE TABLE "task_note_write_capabilities" (
198
+ "backend_pid" integer NOT NULL,
199
+ "transaction_id" xid8 NOT NULL,
200
+ "capability_id" uuid NOT NULL,
201
+ PRIMARY KEY ("backend_pid", "transaction_id", "capability_id")
202
+ );
203
+
204
+ ALTER TABLE "task_notes" ENABLE ROW LEVEL SECURITY;
205
+ ALTER TABLE "task_notes" FORCE ROW LEVEL SECURITY;
206
+ ALTER TABLE "task_note_events" ENABLE ROW LEVEL SECURITY;
207
+ ALTER TABLE "task_note_events" FORCE ROW LEVEL SECURITY;
208
+ ALTER TABLE "task_note_write_capabilities" ENABLE ROW LEVEL SECURITY;
209
+ ALTER TABLE "task_note_write_capabilities" FORCE ROW LEVEL SECURITY;
210
+
211
+ DO $task_note_policies$
212
+ DECLARE
213
+ data_schema text := current_schema();
214
+ migration_owner text := current_user;
215
+ BEGIN
216
+ EXECUTE format(
217
+ 'CREATE POLICY task_note_capability_owner ON %I.task_note_write_capabilities '
218
+ || 'FOR ALL USING (current_user = %L) WITH CHECK (current_user = %L)',
219
+ data_schema, migration_owner, migration_owner
220
+ );
221
+ END
222
+ $task_note_policies$;
223
+
224
+ CREATE POLICY task_notes_tenant ON "task_notes"
225
+ USING (
226
+ "account_id" = opengeni_private.current_account_id()
227
+ AND "workspace_id" = opengeni_private.current_workspace_id()
228
+ )
229
+ WITH CHECK (
230
+ "account_id" = opengeni_private.current_account_id()
231
+ AND "workspace_id" = opengeni_private.current_workspace_id()
232
+ );
233
+ CREATE POLICY session_visibility_isolation ON "task_notes" AS RESTRICTIVE
234
+ USING (session_reference_visible("account_id", "workspace_id", "root_session_id"))
235
+ WITH CHECK (session_reference_visible("account_id", "workspace_id", "root_session_id"));
236
+ CREATE POLICY task_notes_lifecycle_only ON "task_notes" AS RESTRICTIVE
237
+ USING (EXISTS (
238
+ SELECT 1 FROM "task_note_write_capabilities" capability
239
+ WHERE capability."backend_pid" = pg_backend_pid()
240
+ AND capability."transaction_id" = pg_current_xact_id()
241
+ AND capability."capability_id" = nullif(
242
+ current_setting('opengeni.task_note_write_capability', true), ''
243
+ )::uuid
244
+ ))
245
+ WITH CHECK (EXISTS (
246
+ SELECT 1 FROM "task_note_write_capabilities" capability
247
+ WHERE capability."backend_pid" = pg_backend_pid()
248
+ AND capability."transaction_id" = pg_current_xact_id()
249
+ AND capability."capability_id" = nullif(
250
+ current_setting('opengeni.task_note_write_capability', true), ''
251
+ )::uuid
252
+ ));
253
+
254
+ CREATE POLICY task_note_events_tenant ON "task_note_events"
255
+ USING (
256
+ "account_id" = opengeni_private.current_account_id()
257
+ AND "workspace_id" = opengeni_private.current_workspace_id()
258
+ )
259
+ WITH CHECK (
260
+ "account_id" = opengeni_private.current_account_id()
261
+ AND "workspace_id" = opengeni_private.current_workspace_id()
262
+ );
263
+ CREATE POLICY session_visibility_isolation ON "task_note_events" AS RESTRICTIVE
264
+ USING (session_reference_visible("account_id", "workspace_id", "root_session_id"))
265
+ WITH CHECK (session_reference_visible("account_id", "workspace_id", "root_session_id"));
266
+ CREATE POLICY task_note_events_lifecycle_only ON "task_note_events" AS RESTRICTIVE
267
+ USING (EXISTS (
268
+ SELECT 1 FROM "task_note_write_capabilities" capability
269
+ WHERE capability."backend_pid" = pg_backend_pid()
270
+ AND capability."transaction_id" = pg_current_xact_id()
271
+ AND capability."capability_id" = nullif(
272
+ current_setting('opengeni.task_note_write_capability', true), ''
273
+ )::uuid
274
+ ))
275
+ WITH CHECK (EXISTS (
276
+ SELECT 1 FROM "task_note_write_capabilities" capability
277
+ WHERE capability."backend_pid" = pg_backend_pid()
278
+ AND capability."transaction_id" = pg_current_xact_id()
279
+ AND capability."capability_id" = nullif(
280
+ current_setting('opengeni.task_note_write_capability', true), ''
281
+ )::uuid
282
+ ));
283
+
284
+ CREATE OR REPLACE FUNCTION guard_task_note_mutation()
285
+ RETURNS trigger
286
+ LANGUAGE plpgsql
287
+ SECURITY DEFINER
288
+ SET search_path FROM CURRENT
289
+ AS $$
290
+ DECLARE
291
+ has_capability boolean;
292
+ BEGIN
293
+ IF TG_OP = 'DELETE'
294
+ AND pg_trigger_depth() > 1
295
+ AND (
296
+ NOT EXISTS (SELECT 1 FROM workspaces WHERE id = OLD.workspace_id)
297
+ OR NOT EXISTS (SELECT 1 FROM sessions WHERE id = OLD.root_session_id)
298
+ OR NOT EXISTS (SELECT 1 FROM sessions WHERE id = OLD.created_by_session_id)
299
+ )
300
+ THEN
301
+ RETURN OLD;
302
+ END IF;
303
+ SELECT EXISTS (
304
+ SELECT 1 FROM task_note_write_capabilities capability
305
+ WHERE capability.backend_pid = pg_backend_pid()
306
+ AND capability.transaction_id = pg_current_xact_id()
307
+ AND capability.capability_id = nullif(
308
+ current_setting('opengeni.task_note_write_capability', true), ''
309
+ )::uuid
310
+ ) INTO has_capability;
311
+ IF NOT has_capability THEN
312
+ RAISE EXCEPTION 'task note mutation requires lifecycle authority'
313
+ USING ERRCODE = '42501';
314
+ END IF;
315
+ IF TG_OP = 'DELETE' THEN
316
+ RAISE EXCEPTION 'task notes are archived, never deleted'
317
+ USING ERRCODE = '42501';
318
+ END IF;
319
+ IF TG_OP = 'INSERT' THEN
320
+ IF NEW.text_hash <> encode(sha256(convert_to(NEW.text, 'UTF8')), 'hex')
321
+ OR NEW.status <> 'active'
322
+ OR NEW.version <> 1
323
+ THEN
324
+ RAISE EXCEPTION 'task note create projection is invalid'
325
+ USING ERRCODE = '23514';
326
+ END IF;
327
+ RETURN NEW;
328
+ END IF;
329
+ IF NEW.account_id IS DISTINCT FROM OLD.account_id
330
+ OR NEW.workspace_id IS DISTINCT FROM OLD.workspace_id
331
+ OR NEW.root_session_id IS DISTINCT FROM OLD.root_session_id
332
+ OR NEW.kind IS DISTINCT FROM OLD.kind
333
+ OR NEW.text IS DISTINCT FROM OLD.text
334
+ OR NEW.text_hash IS DISTINCT FROM OLD.text_hash
335
+ OR NEW.expires_at IS DISTINCT FROM OLD.expires_at
336
+ OR NEW.create_operation_id IS DISTINCT FROM OLD.create_operation_id
337
+ OR NEW.create_input_hash IS DISTINCT FROM OLD.create_input_hash
338
+ OR NEW.created_by_actor_kind IS DISTINCT FROM OLD.created_by_actor_kind
339
+ OR NEW.created_by_actor_subject_id IS DISTINCT FROM OLD.created_by_actor_subject_id
340
+ OR NEW.created_by_initiating_human_subject_id IS DISTINCT FROM OLD.created_by_initiating_human_subject_id
341
+ OR NEW.created_by_session_id IS DISTINCT FROM OLD.created_by_session_id
342
+ OR NEW.created_by_turn_id IS DISTINCT FROM OLD.created_by_turn_id
343
+ OR NEW.created_by_attempt_id IS DISTINCT FROM OLD.created_by_attempt_id
344
+ OR NEW.created_by_execution_generation IS DISTINCT FROM OLD.created_by_execution_generation
345
+ OR NEW.created_at IS DISTINCT FROM OLD.created_at
346
+ OR OLD.status <> 'active'
347
+ OR NEW.status <> 'archived'
348
+ OR NEW.version <> OLD.version + 1
349
+ OR NEW.updated_at <= OLD.updated_at
350
+ THEN
351
+ RAISE EXCEPTION 'task note update must be one active-to-archived transition'
352
+ USING ERRCODE = '23514';
353
+ END IF;
354
+ RETURN NEW;
355
+ END;
356
+ $$;
357
+
358
+ CREATE TRIGGER task_notes_guard_mutation
359
+ BEFORE INSERT OR UPDATE OR DELETE ON "task_notes"
360
+ FOR EACH ROW EXECUTE FUNCTION guard_task_note_mutation();
361
+
362
+ CREATE OR REPLACE FUNCTION guard_task_note_event_mutation()
363
+ RETURNS trigger
364
+ LANGUAGE plpgsql
365
+ SECURITY DEFINER
366
+ SET search_path FROM CURRENT
367
+ AS $$
368
+ DECLARE
369
+ note_row task_notes%ROWTYPE;
370
+ BEGIN
371
+ IF TG_OP = 'DELETE'
372
+ AND pg_trigger_depth() > 1
373
+ AND (
374
+ NOT EXISTS (SELECT 1 FROM workspaces WHERE id = OLD.workspace_id)
375
+ OR NOT EXISTS (SELECT 1 FROM sessions WHERE id = OLD.root_session_id)
376
+ OR NOT EXISTS (SELECT 1 FROM sessions WHERE id = OLD.actor_session_id)
377
+ OR NOT EXISTS (
378
+ SELECT 1 FROM task_notes
379
+ WHERE workspace_id = OLD.workspace_id AND id = OLD.note_id
380
+ )
381
+ )
382
+ THEN
383
+ RETURN OLD;
384
+ END IF;
385
+ IF TG_OP <> 'INSERT' THEN
386
+ RAISE EXCEPTION 'task note events are immutable'
387
+ USING ERRCODE = '42501';
388
+ END IF;
389
+ IF NOT EXISTS (
390
+ SELECT 1 FROM task_note_write_capabilities capability
391
+ WHERE capability.backend_pid = pg_backend_pid()
392
+ AND capability.transaction_id = pg_current_xact_id()
393
+ AND capability.capability_id = nullif(
394
+ current_setting('opengeni.task_note_write_capability', true), ''
395
+ )::uuid
396
+ ) THEN
397
+ RAISE EXCEPTION 'task note event requires lifecycle authority'
398
+ USING ERRCODE = '42501';
399
+ END IF;
400
+ SELECT * INTO STRICT note_row FROM task_notes note
401
+ WHERE note.workspace_id = NEW.workspace_id AND note.id = NEW.note_id;
402
+ IF NEW.account_id IS DISTINCT FROM note_row.account_id
403
+ OR NEW.root_session_id IS DISTINCT FROM note_row.root_session_id
404
+ OR NEW.text_hash IS DISTINCT FROM note_row.text_hash
405
+ OR NEW.note_version IS DISTINCT FROM note_row.version
406
+ OR (NEW.event_type = 'created' AND (
407
+ NEW.operation_id IS DISTINCT FROM note_row.create_operation_id
408
+ OR NEW.input_hash IS DISTINCT FROM note_row.create_input_hash
409
+ OR NEW.actor_kind IS DISTINCT FROM note_row.created_by_actor_kind
410
+ OR NEW.actor_subject_id IS DISTINCT FROM note_row.created_by_actor_subject_id
411
+ OR NEW.initiating_human_subject_id IS DISTINCT FROM note_row.created_by_initiating_human_subject_id
412
+ OR NEW.actor_session_id IS DISTINCT FROM note_row.created_by_session_id
413
+ OR NEW.actor_turn_id IS DISTINCT FROM note_row.created_by_turn_id
414
+ OR NEW.actor_attempt_id IS DISTINCT FROM note_row.created_by_attempt_id
415
+ OR NEW.actor_execution_generation IS DISTINCT FROM note_row.created_by_execution_generation
416
+ ))
417
+ OR (NEW.event_type = 'archived' AND (
418
+ NEW.operation_id IS DISTINCT FROM note_row.archive_operation_id
419
+ OR NEW.input_hash IS DISTINCT FROM note_row.archive_input_hash
420
+ OR NEW.actor_kind IS DISTINCT FROM note_row.archived_by_actor_kind
421
+ OR NEW.actor_subject_id IS DISTINCT FROM note_row.archived_by_actor_subject_id
422
+ OR NEW.initiating_human_subject_id IS DISTINCT FROM note_row.archived_by_initiating_human_subject_id
423
+ OR NEW.actor_session_id IS DISTINCT FROM note_row.archived_by_session_id
424
+ OR NEW.actor_turn_id IS DISTINCT FROM note_row.archived_by_turn_id
425
+ OR NEW.actor_attempt_id IS DISTINCT FROM note_row.archived_by_attempt_id
426
+ OR NEW.actor_execution_generation IS DISTINCT FROM note_row.archived_by_execution_generation
427
+ ))
428
+ THEN
429
+ RAISE EXCEPTION 'task note event does not match its durable note receipt'
430
+ USING ERRCODE = '23514';
431
+ END IF;
432
+ RETURN NEW;
433
+ END;
434
+ $$;
435
+
436
+ CREATE TRIGGER task_note_events_guard_mutation
437
+ BEFORE INSERT OR UPDATE OR DELETE ON "task_note_events"
438
+ FOR EACH ROW EXECUTE FUNCTION guard_task_note_event_mutation();
439
+
440
+ CREATE OR REPLACE FUNCTION resolve_task_note_attempt_authority(
441
+ p_account_id uuid,
442
+ p_workspace_id uuid,
443
+ p_session_id uuid,
444
+ p_turn_id uuid,
445
+ p_attempt_id uuid,
446
+ p_execution_generation integer
447
+ ) RETURNS TABLE (
448
+ root_session_id uuid,
449
+ actor_kind text,
450
+ actor_subject_id text,
451
+ initiating_human_subject_id text
452
+ )
453
+ LANGUAGE plpgsql
454
+ SECURITY DEFINER
455
+ SET search_path FROM CURRENT
456
+ AS $$
457
+ DECLARE
458
+ resolved_root_id uuid;
459
+ turn_row session_turns%ROWTYPE;
460
+ previous_subject_id text := pg_catalog.current_setting('opengeni.subject_id', true);
461
+ previous_initiating_human_subject_id text := pg_catalog.current_setting(
462
+ 'opengeni.initiating_human_subject_id', true
463
+ );
464
+ visibility_context_set boolean := false;
465
+ BEGIN
466
+ IF p_account_id IS NULL OR p_workspace_id IS NULL OR p_session_id IS NULL
467
+ OR p_turn_id IS NULL OR p_attempt_id IS NULL
468
+ OR p_execution_generation IS NULL OR p_execution_generation < 1
469
+ OR p_account_id IS DISTINCT FROM opengeni_private.current_account_id()
470
+ OR p_workspace_id IS DISTINCT FROM opengeni_private.current_workspace_id()
471
+ THEN
472
+ RAISE EXCEPTION 'task notes require exact tenant and attempt authority'
473
+ USING ERRCODE = '42501';
474
+ END IF;
475
+
476
+ PERFORM 1 FROM workspaces workspace
477
+ WHERE workspace.account_id = p_account_id AND workspace.id = p_workspace_id
478
+ FOR KEY SHARE;
479
+ IF NOT FOUND THEN
480
+ RAISE EXCEPTION 'task note workspace is unavailable' USING ERRCODE = '42501';
481
+ END IF;
482
+
483
+ SELECT session.root_session_id INTO resolved_root_id
484
+ FROM sessions session
485
+ WHERE session.account_id = p_account_id
486
+ AND session.workspace_id = p_workspace_id
487
+ AND session.id = p_session_id;
488
+ IF resolved_root_id IS NULL THEN
489
+ RAISE EXCEPTION 'task note session is unavailable' USING ERRCODE = '42501';
490
+ END IF;
491
+
492
+ PERFORM 1 FROM sessions session
493
+ WHERE session.account_id = p_account_id
494
+ AND session.workspace_id = p_workspace_id
495
+ AND session.id IN (p_session_id, resolved_root_id)
496
+ ORDER BY session.id
497
+ -- Root authority serializes sibling mutations, including the active-record
498
+ -- cap check. UUID order preserves the canonical multi-session lock order.
499
+ FOR UPDATE;
500
+ IF (SELECT count(*) FROM sessions session
501
+ WHERE session.account_id = p_account_id
502
+ AND session.workspace_id = p_workspace_id
503
+ AND session.id IN (p_session_id, resolved_root_id))
504
+ <> (CASE WHEN p_session_id = resolved_root_id THEN 1 ELSE 2 END)
505
+ THEN
506
+ RAISE EXCEPTION 'task note root session is unavailable' USING ERRCODE = '42501';
507
+ END IF;
508
+ SELECT turn.* INTO turn_row
509
+ FROM session_turns turn
510
+ JOIN sessions session ON session.account_id = turn.account_id
511
+ AND session.workspace_id = turn.workspace_id
512
+ AND session.id = turn.session_id
513
+ WHERE turn.account_id = p_account_id
514
+ AND turn.workspace_id = p_workspace_id
515
+ AND turn.session_id = p_session_id
516
+ AND turn.id = p_turn_id
517
+ AND turn.active_attempt_id = p_attempt_id
518
+ AND session.active_turn_id = p_turn_id
519
+ AND turn.execution_generation = p_execution_generation
520
+ AND turn.status IN ('running','requires_action','recovering','waiting_capacity')
521
+ FOR SHARE;
522
+ IF NOT FOUND THEN
523
+ RAISE EXCEPTION 'task notes require the exact current turn' USING ERRCODE = '42501';
524
+ END IF;
525
+
526
+ PERFORM 1 FROM session_turn_attempts attempt
527
+ WHERE attempt.account_id = p_account_id
528
+ AND attempt.workspace_id = p_workspace_id
529
+ AND attempt.session_id = p_session_id
530
+ AND attempt.turn_id = p_turn_id
531
+ AND attempt.id = p_attempt_id
532
+ AND attempt.execution_generation = p_execution_generation
533
+ AND attempt.state IN ('claimed','running')
534
+ AND NOT EXISTS (
535
+ SELECT 1 FROM session_attempt_interruptions interruption
536
+ WHERE interruption.workspace_id = attempt.workspace_id
537
+ AND interruption.attempt_id = attempt.id
538
+ AND interruption.state IN ('pending','delivered','acknowledged')
539
+ )
540
+ FOR UPDATE;
541
+ IF NOT FOUND THEN
542
+ RAISE EXCEPTION 'task notes require the exact current attempt' USING ERRCODE = '42501';
543
+ END IF;
544
+
545
+ -- A worker connection is transport, not session-visibility authority. Recheck
546
+ -- the private root and addressed session under the immutable human authority
547
+ -- frozen on the accepted logical turn, then restore the caller's GUCs. Pure
548
+ -- service work deliberately supplies no manufactured human identity.
549
+ PERFORM pg_catalog.set_config(
550
+ 'opengeni.subject_id',
551
+ CASE WHEN turn_row.initiator_kind = 'subject'
552
+ THEN turn_row.initiator_subject_id ELSE '' END,
553
+ true
554
+ );
555
+ PERFORM pg_catalog.set_config(
556
+ 'opengeni.initiating_human_subject_id',
557
+ COALESCE(turn_row.initiating_human_subject_id, ''),
558
+ true
559
+ );
560
+ visibility_context_set := true;
561
+ IF (SELECT count(*) FROM sessions session
562
+ WHERE session.account_id = p_account_id
563
+ AND session.workspace_id = p_workspace_id
564
+ AND session.id IN (p_session_id, resolved_root_id)
565
+ AND (
566
+ session.visibility = 'workspace_shared'
567
+ OR session_private_actor_visible(
568
+ session.account_id,
569
+ session.workspace_id,
570
+ session.owner_organization_membership_id,
571
+ session.owner_subject_id
572
+ )
573
+ )) <> (CASE WHEN p_session_id = resolved_root_id THEN 1 ELSE 2 END)
574
+ THEN
575
+ RAISE EXCEPTION 'task note session tree is not visible to this actor'
576
+ USING ERRCODE = '42501';
577
+ END IF;
578
+ PERFORM pg_catalog.set_config(
579
+ 'opengeni.subject_id', COALESCE(previous_subject_id, ''), true
580
+ );
581
+ PERFORM pg_catalog.set_config(
582
+ 'opengeni.initiating_human_subject_id',
583
+ COALESCE(previous_initiating_human_subject_id, ''),
584
+ true
585
+ );
586
+ visibility_context_set := false;
587
+
588
+ root_session_id := resolved_root_id;
589
+ actor_kind := CASE turn_row.initiator_kind WHEN 'subject' THEN 'human' ELSE 'service' END;
590
+ actor_subject_id := turn_row.initiator_subject_id;
591
+ initiating_human_subject_id := turn_row.initiating_human_subject_id;
592
+ RETURN NEXT;
593
+ EXCEPTION WHEN OTHERS THEN
594
+ IF visibility_context_set THEN
595
+ PERFORM pg_catalog.set_config(
596
+ 'opengeni.subject_id', COALESCE(previous_subject_id, ''), true
597
+ );
598
+ PERFORM pg_catalog.set_config(
599
+ 'opengeni.initiating_human_subject_id',
600
+ COALESCE(previous_initiating_human_subject_id, ''),
601
+ true
602
+ );
603
+ END IF;
604
+ RAISE;
605
+ END;
606
+ $$;
607
+
608
+ CREATE OR REPLACE FUNCTION create_task_note_for_attempt(
609
+ p_account_id uuid,
610
+ p_workspace_id uuid,
611
+ p_session_id uuid,
612
+ p_turn_id uuid,
613
+ p_attempt_id uuid,
614
+ p_execution_generation integer,
615
+ p_operation_id uuid,
616
+ p_kind text,
617
+ p_text text,
618
+ p_expires_in_days integer
619
+ ) RETURNS TABLE (
620
+ note_id uuid, root_session_id uuid, kind text, note_text text, status text,
621
+ version integer, expires_at timestamptz, created_at timestamptz,
622
+ updated_at timestamptz, archived_at timestamptz, actor_kind text,
623
+ source_session_id uuid, source_turn_id uuid, replayed boolean
624
+ )
625
+ LANGUAGE plpgsql
626
+ SECURITY DEFINER
627
+ SET search_path FROM CURRENT
628
+ AS $$
629
+ DECLARE
630
+ authority record;
631
+ existing_event task_note_events%ROWTYPE;
632
+ note_row task_notes%ROWTYPE;
633
+ calculated_text_hash text;
634
+ calculated_input_hash text;
635
+ calculated_expires_at timestamptz;
636
+ write_capability_id uuid := gen_random_uuid();
637
+ previous_capability text := current_setting('opengeni.task_note_write_capability', true);
638
+ BEGIN
639
+ SELECT * INTO STRICT authority FROM resolve_task_note_attempt_authority(
640
+ p_account_id, p_workspace_id, p_session_id, p_turn_id,
641
+ p_attempt_id, p_execution_generation
642
+ );
643
+ IF p_operation_id IS NULL
644
+ OR p_kind NOT IN ('finding','decision','blocker','ownership','artifact','handoff')
645
+ OR p_text IS NULL OR octet_length(p_text) NOT BETWEEN 1 AND 4096
646
+ OR p_text <> btrim(p_text)
647
+ OR p_expires_in_days IS NULL OR p_expires_in_days NOT BETWEEN 1 AND 30
648
+ THEN
649
+ RAISE EXCEPTION 'task note input is invalid' USING ERRCODE = '22023';
650
+ END IF;
651
+ calculated_expires_at := transaction_timestamp()
652
+ + pg_catalog.make_interval(days => p_expires_in_days);
653
+ calculated_text_hash := encode(sha256(convert_to(p_text, 'UTF8')), 'hex');
654
+ calculated_input_hash := encode(sha256(convert_to(jsonb_build_object(
655
+ 'accountId', p_account_id, 'workspaceId', p_workspace_id,
656
+ 'rootSessionId', authority.root_session_id, 'sessionId', p_session_id,
657
+ 'turnId', p_turn_id, 'attemptId', p_attempt_id,
658
+ 'executionGeneration', p_execution_generation, 'kind', p_kind,
659
+ 'textHash', calculated_text_hash, 'expiresInDays', p_expires_in_days
660
+ )::text, 'UTF8')), 'hex');
661
+
662
+ INSERT INTO task_note_write_capabilities (backend_pid, transaction_id, capability_id)
663
+ VALUES (pg_backend_pid(), pg_current_xact_id(), write_capability_id);
664
+ PERFORM set_config('opengeni.task_note_write_capability', write_capability_id::text, true);
665
+
666
+ SELECT * INTO existing_event FROM task_note_events event
667
+ WHERE event.workspace_id = p_workspace_id AND event.operation_id = p_operation_id
668
+ FOR SHARE;
669
+ IF FOUND THEN
670
+ IF existing_event.event_type <> 'created'
671
+ OR existing_event.input_hash <> calculated_input_hash
672
+ OR existing_event.root_session_id <> authority.root_session_id
673
+ OR existing_event.actor_session_id <> p_session_id
674
+ OR existing_event.actor_turn_id <> p_turn_id
675
+ OR existing_event.actor_attempt_id <> p_attempt_id
676
+ OR existing_event.actor_execution_generation <> p_execution_generation
677
+ THEN
678
+ RAISE EXCEPTION 'task note operation conflicts with another input or attempt'
679
+ USING ERRCODE = '23505';
680
+ END IF;
681
+ SELECT * INTO STRICT note_row FROM task_notes note
682
+ WHERE note.workspace_id = p_workspace_id AND note.id = existing_event.note_id;
683
+ replayed := true;
684
+ ELSE
685
+ IF (SELECT count(*) FROM task_notes note
686
+ WHERE note.workspace_id = p_workspace_id
687
+ AND note.root_session_id = authority.root_session_id
688
+ AND note.status = 'active' AND note.expires_at > transaction_timestamp()) >= 500
689
+ THEN
690
+ RAISE EXCEPTION 'task note active-record limit reached' USING ERRCODE = '54000';
691
+ END IF;
692
+ INSERT INTO task_notes (
693
+ account_id, workspace_id, root_session_id, kind, text, text_hash,
694
+ expires_at, create_operation_id, create_input_hash,
695
+ created_by_actor_kind, created_by_actor_subject_id,
696
+ created_by_initiating_human_subject_id, created_by_session_id,
697
+ created_by_turn_id, created_by_attempt_id, created_by_execution_generation
698
+ ) VALUES (
699
+ p_account_id, p_workspace_id, authority.root_session_id, p_kind, p_text,
700
+ calculated_text_hash, calculated_expires_at, p_operation_id, calculated_input_hash,
701
+ authority.actor_kind, authority.actor_subject_id,
702
+ authority.initiating_human_subject_id, p_session_id, p_turn_id,
703
+ p_attempt_id, p_execution_generation
704
+ ) RETURNING * INTO note_row;
705
+ INSERT INTO task_note_events (
706
+ account_id, workspace_id, root_session_id, note_id, event_type,
707
+ note_version, operation_id, input_hash, text_hash, actor_kind,
708
+ actor_subject_id, initiating_human_subject_id, actor_session_id,
709
+ actor_turn_id, actor_attempt_id, actor_execution_generation
710
+ ) VALUES (
711
+ p_account_id, p_workspace_id, authority.root_session_id, note_row.id,
712
+ 'created', 1, p_operation_id, calculated_input_hash,
713
+ calculated_text_hash, authority.actor_kind, authority.actor_subject_id,
714
+ authority.initiating_human_subject_id, p_session_id, p_turn_id,
715
+ p_attempt_id, p_execution_generation
716
+ );
717
+ replayed := false;
718
+ END IF;
719
+
720
+ note_id := note_row.id; root_session_id := note_row.root_session_id;
721
+ kind := note_row.kind; note_text := note_row.text; status := note_row.status;
722
+ version := note_row.version; expires_at := note_row.expires_at;
723
+ created_at := note_row.created_at; updated_at := note_row.updated_at;
724
+ archived_at := note_row.archived_at; actor_kind := note_row.created_by_actor_kind;
725
+ source_session_id := note_row.created_by_session_id;
726
+ source_turn_id := note_row.created_by_turn_id;
727
+ DELETE FROM task_note_write_capabilities capability
728
+ WHERE capability.backend_pid = pg_backend_pid()
729
+ AND capability.transaction_id = pg_current_xact_id()
730
+ AND capability.capability_id = write_capability_id;
731
+ PERFORM set_config('opengeni.task_note_write_capability', coalesce(previous_capability, ''), true);
732
+ RETURN NEXT;
733
+ EXCEPTION WHEN OTHERS THEN
734
+ PERFORM set_config('opengeni.task_note_write_capability', coalesce(previous_capability, ''), true);
735
+ RAISE;
736
+ END;
737
+ $$;
738
+
739
+ CREATE OR REPLACE FUNCTION archive_task_note_for_attempt(
740
+ p_account_id uuid,
741
+ p_workspace_id uuid,
742
+ p_session_id uuid,
743
+ p_turn_id uuid,
744
+ p_attempt_id uuid,
745
+ p_execution_generation integer,
746
+ p_operation_id uuid,
747
+ p_note_id uuid,
748
+ p_expected_version integer,
749
+ p_reason text
750
+ ) RETURNS TABLE (
751
+ note_id uuid, root_session_id uuid, kind text, note_text text, status text,
752
+ version integer, expires_at timestamptz, created_at timestamptz,
753
+ updated_at timestamptz, archived_at timestamptz, actor_kind text,
754
+ source_session_id uuid, source_turn_id uuid, replayed boolean
755
+ )
756
+ LANGUAGE plpgsql
757
+ SECURITY DEFINER
758
+ SET search_path FROM CURRENT
759
+ AS $$
760
+ DECLARE
761
+ authority record;
762
+ existing_event task_note_events%ROWTYPE;
763
+ note_row task_notes%ROWTYPE;
764
+ calculated_input_hash text;
765
+ write_capability_id uuid := gen_random_uuid();
766
+ previous_capability text := current_setting('opengeni.task_note_write_capability', true);
767
+ BEGIN
768
+ SELECT * INTO STRICT authority FROM resolve_task_note_attempt_authority(
769
+ p_account_id, p_workspace_id, p_session_id, p_turn_id,
770
+ p_attempt_id, p_execution_generation
771
+ );
772
+ IF p_operation_id IS NULL OR p_note_id IS NULL OR p_expected_version <> 1
773
+ OR p_reason IS NULL OR octet_length(p_reason) NOT BETWEEN 1 AND 2048
774
+ OR p_reason <> btrim(p_reason)
775
+ THEN
776
+ RAISE EXCEPTION 'task note archive input is invalid' USING ERRCODE = '22023';
777
+ END IF;
778
+ calculated_input_hash := encode(sha256(convert_to(jsonb_build_object(
779
+ 'accountId', p_account_id, 'workspaceId', p_workspace_id,
780
+ 'rootSessionId', authority.root_session_id, 'sessionId', p_session_id,
781
+ 'turnId', p_turn_id, 'attemptId', p_attempt_id,
782
+ 'executionGeneration', p_execution_generation, 'noteId', p_note_id,
783
+ 'expectedVersion', p_expected_version, 'reason', p_reason
784
+ )::text, 'UTF8')), 'hex');
785
+
786
+ INSERT INTO task_note_write_capabilities (backend_pid, transaction_id, capability_id)
787
+ VALUES (pg_backend_pid(), pg_current_xact_id(), write_capability_id);
788
+ PERFORM set_config('opengeni.task_note_write_capability', write_capability_id::text, true);
789
+
790
+ SELECT * INTO existing_event FROM task_note_events event
791
+ WHERE event.workspace_id = p_workspace_id AND event.operation_id = p_operation_id
792
+ FOR SHARE;
793
+ IF FOUND THEN
794
+ IF existing_event.event_type <> 'archived'
795
+ OR existing_event.input_hash <> calculated_input_hash
796
+ OR existing_event.root_session_id <> authority.root_session_id
797
+ OR existing_event.actor_session_id <> p_session_id
798
+ OR existing_event.actor_turn_id <> p_turn_id
799
+ OR existing_event.actor_attempt_id <> p_attempt_id
800
+ OR existing_event.actor_execution_generation <> p_execution_generation
801
+ THEN
802
+ RAISE EXCEPTION 'task note archive operation conflicts with another input or attempt'
803
+ USING ERRCODE = '23505';
804
+ END IF;
805
+ SELECT * INTO STRICT note_row FROM task_notes note
806
+ WHERE note.workspace_id = p_workspace_id AND note.id = existing_event.note_id;
807
+ replayed := true;
808
+ ELSE
809
+ SELECT * INTO note_row FROM task_notes note
810
+ WHERE note.workspace_id = p_workspace_id
811
+ AND note.root_session_id = authority.root_session_id
812
+ AND note.id = p_note_id
813
+ FOR UPDATE;
814
+ IF NOT FOUND THEN
815
+ RAISE EXCEPTION 'task note is unavailable' USING ERRCODE = 'P0002';
816
+ END IF;
817
+ IF note_row.status <> 'active' OR note_row.version <> p_expected_version THEN
818
+ RAISE EXCEPTION 'task note archive version conflict' USING ERRCODE = '40001';
819
+ END IF;
820
+ UPDATE task_notes note SET
821
+ status = 'archived', version = note.version + 1,
822
+ archive_operation_id = p_operation_id,
823
+ archive_input_hash = calculated_input_hash,
824
+ archived_by_actor_kind = authority.actor_kind,
825
+ archived_by_actor_subject_id = authority.actor_subject_id,
826
+ archived_by_initiating_human_subject_id = authority.initiating_human_subject_id,
827
+ archived_by_session_id = p_session_id,
828
+ archived_by_turn_id = p_turn_id,
829
+ archived_by_attempt_id = p_attempt_id,
830
+ archived_by_execution_generation = p_execution_generation,
831
+ archived_at = clock_timestamp(), updated_at = clock_timestamp()
832
+ WHERE note.workspace_id = p_workspace_id AND note.id = p_note_id
833
+ AND note.status = 'active' AND note.version = p_expected_version
834
+ RETURNING * INTO STRICT note_row;
835
+ INSERT INTO task_note_events (
836
+ account_id, workspace_id, root_session_id, note_id, event_type,
837
+ note_version, operation_id, input_hash, text_hash, reason, actor_kind,
838
+ actor_subject_id, initiating_human_subject_id, actor_session_id,
839
+ actor_turn_id, actor_attempt_id, actor_execution_generation
840
+ ) VALUES (
841
+ p_account_id, p_workspace_id, authority.root_session_id, note_row.id,
842
+ 'archived', note_row.version, p_operation_id, calculated_input_hash,
843
+ note_row.text_hash, p_reason, authority.actor_kind, authority.actor_subject_id,
844
+ authority.initiating_human_subject_id, p_session_id, p_turn_id,
845
+ p_attempt_id, p_execution_generation
846
+ );
847
+ replayed := false;
848
+ END IF;
849
+
850
+ note_id := note_row.id; root_session_id := note_row.root_session_id;
851
+ kind := note_row.kind; note_text := note_row.text; status := note_row.status;
852
+ version := note_row.version; expires_at := note_row.expires_at;
853
+ created_at := note_row.created_at; updated_at := note_row.updated_at;
854
+ archived_at := note_row.archived_at; actor_kind := note_row.created_by_actor_kind;
855
+ source_session_id := note_row.created_by_session_id;
856
+ source_turn_id := note_row.created_by_turn_id;
857
+ DELETE FROM task_note_write_capabilities capability
858
+ WHERE capability.backend_pid = pg_backend_pid()
859
+ AND capability.transaction_id = pg_current_xact_id()
860
+ AND capability.capability_id = write_capability_id;
861
+ PERFORM set_config('opengeni.task_note_write_capability', coalesce(previous_capability, ''), true);
862
+ RETURN NEXT;
863
+ EXCEPTION WHEN OTHERS THEN
864
+ PERFORM set_config('opengeni.task_note_write_capability', coalesce(previous_capability, ''), true);
865
+ RAISE;
866
+ END;
867
+ $$;
868
+
869
+ CREATE OR REPLACE FUNCTION list_task_notes_for_attempt(
870
+ p_account_id uuid,
871
+ p_workspace_id uuid,
872
+ p_session_id uuid,
873
+ p_turn_id uuid,
874
+ p_attempt_id uuid,
875
+ p_execution_generation integer,
876
+ p_include_archived boolean DEFAULT false,
877
+ p_limit integer DEFAULT 10
878
+ ) RETURNS TABLE (
879
+ note_id uuid, root_session_id uuid, kind text, note_text text, status text,
880
+ version integer, expires_at timestamptz, created_at timestamptz,
881
+ updated_at timestamptz, archived_at timestamptz, actor_kind text,
882
+ source_session_id uuid, source_turn_id uuid
883
+ )
884
+ LANGUAGE plpgsql
885
+ SECURITY DEFINER
886
+ SET search_path FROM CURRENT
887
+ AS $$
888
+ DECLARE
889
+ authority record;
890
+ write_capability_id uuid := gen_random_uuid();
891
+ previous_capability text := current_setting('opengeni.task_note_write_capability', true);
892
+ BEGIN
893
+ SELECT * INTO STRICT authority FROM resolve_task_note_attempt_authority(
894
+ p_account_id, p_workspace_id, p_session_id, p_turn_id,
895
+ p_attempt_id, p_execution_generation
896
+ );
897
+ IF p_include_archived IS NULL OR p_limit IS NULL OR p_limit NOT BETWEEN 1 AND 20 THEN
898
+ RAISE EXCEPTION 'task note list input is invalid' USING ERRCODE = '22023';
899
+ END IF;
900
+ INSERT INTO task_note_write_capabilities (backend_pid, transaction_id, capability_id)
901
+ VALUES (pg_backend_pid(), pg_current_xact_id(), write_capability_id);
902
+ PERFORM set_config('opengeni.task_note_write_capability', write_capability_id::text, true);
903
+ RETURN QUERY
904
+ SELECT note.id, note.root_session_id, note.kind, note.text, note.status,
905
+ note.version, note.expires_at, note.created_at, note.updated_at,
906
+ note.archived_at, note.created_by_actor_kind, note.created_by_session_id,
907
+ note.created_by_turn_id
908
+ FROM task_notes note
909
+ WHERE note.account_id = p_account_id
910
+ AND note.workspace_id = p_workspace_id
911
+ AND note.root_session_id = authority.root_session_id
912
+ AND note.expires_at > transaction_timestamp()
913
+ AND (p_include_archived OR note.status = 'active')
914
+ ORDER BY note.updated_at DESC, note.id DESC
915
+ LIMIT p_limit;
916
+ DELETE FROM task_note_write_capabilities capability
917
+ WHERE capability.backend_pid = pg_backend_pid()
918
+ AND capability.transaction_id = pg_current_xact_id()
919
+ AND capability.capability_id = write_capability_id;
920
+ PERFORM set_config('opengeni.task_note_write_capability', coalesce(previous_capability, ''), true);
921
+ EXCEPTION WHEN OTHERS THEN
922
+ PERFORM set_config('opengeni.task_note_write_capability', coalesce(previous_capability, ''), true);
923
+ RAISE;
924
+ END;
925
+ $$;
926
+
927
+ DO $task_note_search_paths$
928
+ DECLARE
929
+ data_schema text := current_schema();
930
+ BEGIN
931
+ EXECUTE format(
932
+ 'ALTER FUNCTION %1$I.guard_task_note_mutation() SET search_path = %1$I, pg_catalog',
933
+ data_schema
934
+ );
935
+ EXECUTE format(
936
+ 'ALTER FUNCTION %1$I.guard_task_note_event_mutation() SET search_path = %1$I, pg_catalog',
937
+ data_schema
938
+ );
939
+ EXECUTE format(
940
+ 'ALTER FUNCTION %1$I.resolve_task_note_attempt_authority(uuid,uuid,uuid,uuid,uuid,integer) SET search_path = %1$I, pg_catalog',
941
+ data_schema
942
+ );
943
+ EXECUTE format(
944
+ 'ALTER FUNCTION %1$I.create_task_note_for_attempt(uuid,uuid,uuid,uuid,uuid,integer,uuid,text,text,integer) SET search_path = %1$I, pg_catalog',
945
+ data_schema
946
+ );
947
+ EXECUTE format(
948
+ 'ALTER FUNCTION %1$I.archive_task_note_for_attempt(uuid,uuid,uuid,uuid,uuid,integer,uuid,uuid,integer,text) SET search_path = %1$I, pg_catalog',
949
+ data_schema
950
+ );
951
+ EXECUTE format(
952
+ 'ALTER FUNCTION %1$I.list_task_notes_for_attempt(uuid,uuid,uuid,uuid,uuid,integer,boolean,integer) SET search_path = %1$I, pg_catalog',
953
+ data_schema
954
+ );
955
+ END
956
+ $task_note_search_paths$;
957
+
958
+ REVOKE ALL ON "task_notes" FROM PUBLIC;
959
+ REVOKE ALL ON "task_note_events" FROM PUBLIC;
960
+ REVOKE ALL ON "task_note_write_capabilities" FROM PUBLIC;
961
+ REVOKE ALL ON FUNCTION guard_task_note_mutation() FROM PUBLIC;
962
+ REVOKE ALL ON FUNCTION guard_task_note_event_mutation() FROM PUBLIC;
963
+ REVOKE ALL ON FUNCTION resolve_task_note_attempt_authority(uuid,uuid,uuid,uuid,uuid,integer) FROM PUBLIC;
964
+ REVOKE ALL ON FUNCTION create_task_note_for_attempt(uuid,uuid,uuid,uuid,uuid,integer,uuid,text,text,integer) FROM PUBLIC;
965
+ REVOKE ALL ON FUNCTION archive_task_note_for_attempt(uuid,uuid,uuid,uuid,uuid,integer,uuid,uuid,integer,text) FROM PUBLIC;
966
+ REVOKE ALL ON FUNCTION list_task_notes_for_attempt(uuid,uuid,uuid,uuid,uuid,integer,boolean,integer) FROM PUBLIC;
967
+
968
+ DO $task_note_runtime_grants$
969
+ BEGIN
970
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
971
+ REVOKE ALL ON "task_notes" FROM opengeni_app;
972
+ REVOKE ALL ON "task_note_events" FROM opengeni_app;
973
+ REVOKE ALL ON "task_note_write_capabilities" FROM opengeni_app;
974
+ GRANT EXECUTE ON FUNCTION create_task_note_for_attempt(uuid,uuid,uuid,uuid,uuid,integer,uuid,text,text,integer) TO opengeni_app;
975
+ GRANT EXECUTE ON FUNCTION archive_task_note_for_attempt(uuid,uuid,uuid,uuid,uuid,integer,uuid,uuid,integer,text) TO opengeni_app;
976
+ GRANT EXECUTE ON FUNCTION list_task_notes_for_attempt(uuid,uuid,uuid,uuid,uuid,integer,boolean,integer) TO opengeni_app;
977
+ END IF;
978
+ END
979
+ $task_note_runtime_grants$;
980
+
981
+ COMMENT ON TABLE "task_notes" IS
982
+ 'Bounded, expiring, non-authoritative root-session coordination notes. Never auto-composed into prompts.';
983
+ COMMENT ON TABLE "task_note_events" IS
984
+ 'Immutable exact-attempt create/archive receipts for task notes; values never confer authority.';
985
+ COMMENT ON FUNCTION create_task_note_for_attempt(uuid,uuid,uuid,uuid,uuid,integer,uuid,text,text,integer) IS
986
+ 'Creates one task-tree note under exact current-attempt authority; operation replay is bound to root, attempt, and canonical input hash.';
987
+ COMMENT ON FUNCTION archive_task_note_for_attempt(uuid,uuid,uuid,uuid,uuid,integer,uuid,uuid,integer,text) IS
988
+ 'Archives one task-tree note with immutable separate archive provenance; the create receipt remains unchanged.';
989
+ COMMENT ON FUNCTION list_task_notes_for_attempt(uuid,uuid,uuid,uuid,uuid,integer,boolean,integer) IS
990
+ 'Explicit bounded task-tree note retrieval under exact current-attempt and root-session visibility authority.';