@opengeni/db 0.31.1 → 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.
- package/dist/browser-auth.d.ts +209 -1
- package/dist/browser-downloads.d.ts +51 -0
- package/dist/browser-identities.d.ts +11 -2
- package/dist/browser-sessions.d.ts +83 -1
- package/dist/browser-state-artifacts.d.ts +75 -1
- package/dist/canonical-human-identities.d.ts +48 -0
- package/dist/canonical-human-identities.js +28 -0
- package/dist/canonical-human-identities.js.map +1 -0
- package/dist/capability-integrations.d.ts +8 -6
- package/dist/{chunk-MWDVXQOL.js → chunk-5KTIEDX7.js} +5773 -4212
- package/dist/chunk-5KTIEDX7.js.map +1 -0
- package/dist/{chunk-NBE6CHRI.js → chunk-CQZ4QSP5.js} +49 -9
- package/dist/chunk-CQZ4QSP5.js.map +1 -0
- package/dist/chunk-F32YT7MP.js +117 -0
- package/dist/chunk-F32YT7MP.js.map +1 -0
- package/dist/{chunk-ISI2TIUG.js → chunk-JC5QPLUM.js} +30 -3
- package/dist/chunk-JC5QPLUM.js.map +1 -0
- package/dist/{chunk-F5FT7BEZ.js → chunk-P3RE4D2B.js} +2 -2
- package/dist/chunk-TAUTWOS5.js +259 -0
- package/dist/chunk-TAUTWOS5.js.map +1 -0
- package/dist/{chunk-YHZXQ7HP.js → chunk-VKLB2ZGI.js} +446 -19
- package/dist/chunk-VKLB2ZGI.js.map +1 -0
- package/dist/database.d.ts +5 -0
- package/dist/editable-artifact-durable-export.js +2 -2
- package/dist/editable-artifacts.js +3 -3
- package/dist/index.d.ts +438 -29
- package/dist/index.js +35640 -28969
- package/dist/index.js.map +1 -1
- package/dist/insights.d.ts +23 -7
- package/dist/integration-bindings.d.ts +24 -24
- package/dist/integration-facets.d.ts +99 -0
- package/dist/interaction-revisions.d.ts +11 -0
- package/dist/interaction-schema.d.ts +698 -34
- package/dist/provision-roles.js +1 -1
- package/dist/runtime-posture.d.ts +8 -7
- package/dist/schema.d.ts +9464 -5412
- package/dist/schema.js +51 -9
- package/dist/session-control.d.ts +29 -3
- package/dist/session-tenancy.d.ts +44 -0
- package/dist/session-tenancy.js +16 -0
- package/dist/session-tenancy.js.map +1 -0
- package/dist/slack-task-policy-schema.d.ts +847 -0
- package/dist/slack-task-policy.d.ts +65 -0
- package/dist/task-notes-schema.d.ts +924 -0
- package/dist/task-notes.d.ts +85 -0
- package/dist/video-generation.d.ts +9 -0
- package/dist/video-generation.js +5 -3
- package/dist/xai-subscription.d.ts +326 -0
- package/drizzle/0212_browser_state_transfer_hardening.sql +560 -0
- package/drizzle/0213_browser_interaction_authority.sql +67 -0
- package/drizzle/0214_browser_download_saves.sql +34 -0
- package/drizzle/0215_browser_controller_host.sql +385 -0
- package/drizzle/0216_browser_auth_health_evidence.sql +137 -0
- package/drizzle/0217_external_browser_auth_operations.sql +22 -0
- package/drizzle/0218_organization_tenancy_foundation.sql +299 -0
- package/drizzle/0219_organization_tenancy_managed_human_provisioning.sql +317 -0
- package/drizzle/0219_site_auth_maintenance_sessions.sql +234 -0
- package/drizzle/0220_memory_slack_append_only_cascade.sql +38 -0
- package/drizzle/0220_session_channels.sql +54 -0
- package/drizzle/0221_sessions_channel_index.sql +5 -0
- package/drizzle/0222_session_visibility_authority_epochs.sql +158 -0
- package/drizzle/0222_sessions_channel_fk.sql +89 -0
- package/drizzle/0223_pending_tool_event_output.sql +55 -0
- package/drizzle/0223_sessions_channel_fk_validate.sql +118 -0
- package/drizzle/0224_slack_post_outcome_reconciliation.sql +123 -0
- package/drizzle/0225_session_visibility_fork_activation.sql +1363 -0
- package/drizzle/0226_personal_codex_authority_foundation.sql +365 -0
- package/drizzle/0227_slack_native_actions.sql +145 -0
- package/drizzle/0228_interaction_controller_data_plane.sql +14 -0
- package/drizzle/0228_slack_task_policy.sql +400 -0
- package/drizzle/0229_slack_inbox_file_fact.sql +10 -0
- package/drizzle/0230_user_scoped_variable_sets_rigs.sql +84 -0
- package/drizzle/0231_integration_definition_identity_cutover.sql +271 -0
- package/drizzle/0232_integration_facet_authority_cutover.sql +610 -0
- package/drizzle/0233_skill_and_integration_authority_cutover.sql +1162 -0
- package/drizzle/0234_xai_subscription_authority.sql +1139 -0
- package/drizzle/0235_canonical_human_login_bindings.sql +920 -0
- package/drizzle/0236_browser_identity_lifecycle.sql +39 -0
- package/drizzle/0236_session_visibility_slack_policy.sql +72 -0
- package/drizzle/0237_interaction_transition_reaper.sql +263 -0
- package/drizzle/0238_goal_persistence_policy.sql +471 -0
- package/drizzle/0238_supergrok_realtime_model.sql +29 -0
- package/drizzle/0239_supergrok_video_funding.sql +50 -0
- package/drizzle/0239_task_tree_notes.sql +990 -0
- package/package.json +13 -5
- package/src/browser-auth.ts +2600 -123
- package/src/browser-downloads.ts +517 -0
- package/src/browser-identities.ts +220 -3
- package/src/browser-sessions.ts +334 -45
- package/src/browser-state-artifacts.ts +402 -12
- package/src/canonical-human-identities.ts +317 -0
- package/src/capability-integrations.ts +121 -213
- package/src/database.ts +41 -1
- package/src/index.ts +4157 -394
- package/src/insights.ts +109 -28
- package/src/integration-bindings.ts +101 -101
- package/src/{integration-features.ts → integration-facets.ts} +187 -184
- package/src/interaction-revisions.ts +33 -3
- package/src/interaction-schema.ts +311 -8
- package/src/pack-components.ts +43 -43
- package/src/pack-installations.ts +0 -43
- package/src/plugin-packages.ts +9 -9
- package/src/provision-roles.ts +232 -0
- package/src/runtime-posture.ts +227 -14
- package/src/schema.ts +1188 -75
- package/src/session-control.ts +122 -37
- package/src/session-queue-commands.ts +78 -1
- package/src/session-tenancy.ts +188 -0
- package/src/session-tool-call-settlement.ts +9 -3
- package/src/slack-task-policy-schema.ts +130 -0
- package/src/slack-task-policy.ts +341 -0
- package/src/task-notes-schema.ts +135 -0
- package/src/task-notes.ts +178 -0
- package/src/video-generation.ts +90 -12
- package/src/workspace-artifacts.ts +1 -1
- package/src/xai-subscription.ts +1887 -0
- package/dist/chunk-ISI2TIUG.js.map +0 -1
- package/dist/chunk-MWDVXQOL.js.map +0 -1
- package/dist/chunk-NBE6CHRI.js.map +0 -1
- package/dist/chunk-YHZXQ7HP.js.map +0 -1
- package/dist/integration-features.d.ts +0 -99
- /package/dist/{chunk-F5FT7BEZ.js.map → chunk-P3RE4D2B.js.map} +0 -0
|
@@ -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.';
|