@opengeni/db 0.31.1 → 0.36.1

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 (125) hide show
  1. package/dist/browser-auth.d.ts +209 -1
  2. package/dist/browser-downloads.d.ts +51 -0
  3. package/dist/browser-identities.d.ts +11 -2
  4. package/dist/browser-sessions.d.ts +83 -1
  5. package/dist/browser-state-artifacts.d.ts +75 -1
  6. package/dist/canonical-human-identities.d.ts +48 -0
  7. package/dist/canonical-human-identities.js +28 -0
  8. package/dist/canonical-human-identities.js.map +1 -0
  9. package/dist/capability-integrations.d.ts +8 -6
  10. package/dist/{chunk-MWDVXQOL.js → chunk-5KTIEDX7.js} +5773 -4212
  11. package/dist/chunk-5KTIEDX7.js.map +1 -0
  12. package/dist/chunk-FPVAD4CO.js +117 -0
  13. package/dist/chunk-FPVAD4CO.js.map +1 -0
  14. package/dist/chunk-GYO7ED2Q.js +259 -0
  15. package/dist/chunk-GYO7ED2Q.js.map +1 -0
  16. package/dist/{chunk-F5FT7BEZ.js → chunk-HSOWSYOX.js} +2 -2
  17. package/dist/{chunk-YHZXQ7HP.js → chunk-VKLB2ZGI.js} +446 -19
  18. package/dist/chunk-VKLB2ZGI.js.map +1 -0
  19. package/dist/{chunk-NBE6CHRI.js → chunk-VTPRIXKG.js} +49 -9
  20. package/dist/chunk-VTPRIXKG.js.map +1 -0
  21. package/dist/{chunk-ISI2TIUG.js → chunk-Z7VYMNPZ.js} +76 -4
  22. package/dist/chunk-Z7VYMNPZ.js.map +1 -0
  23. package/dist/database.d.ts +5 -0
  24. package/dist/editable-artifact-durable-export.js +2 -2
  25. package/dist/editable-artifacts.js +3 -3
  26. package/dist/index.d.ts +467 -29
  27. package/dist/index.js +23247 -16232
  28. package/dist/index.js.map +1 -1
  29. package/dist/insights.d.ts +23 -7
  30. package/dist/integration-bindings.d.ts +24 -24
  31. package/dist/integration-facets.d.ts +99 -0
  32. package/dist/interaction-revisions.d.ts +11 -0
  33. package/dist/interaction-schema.d.ts +698 -34
  34. package/dist/persistence-errors.d.ts +8 -0
  35. package/dist/provision-roles.js +1 -1
  36. package/dist/runtime-posture.d.ts +8 -7
  37. package/dist/schema.d.ts +9464 -5412
  38. package/dist/schema.js +51 -9
  39. package/dist/session-control.d.ts +29 -3
  40. package/dist/session-tenancy.d.ts +44 -0
  41. package/dist/session-tenancy.js +16 -0
  42. package/dist/session-tenancy.js.map +1 -0
  43. package/dist/slack-task-policy-schema.d.ts +847 -0
  44. package/dist/slack-task-policy.d.ts +65 -0
  45. package/dist/task-notes-schema.d.ts +924 -0
  46. package/dist/task-notes.d.ts +85 -0
  47. package/dist/video-generation.d.ts +9 -0
  48. package/dist/video-generation.js +5 -3
  49. package/dist/xai-subscription.d.ts +326 -0
  50. package/drizzle/0212_browser_state_transfer_hardening.sql +560 -0
  51. package/drizzle/0213_browser_interaction_authority.sql +67 -0
  52. package/drizzle/0214_browser_download_saves.sql +34 -0
  53. package/drizzle/0215_browser_controller_host.sql +385 -0
  54. package/drizzle/0216_browser_auth_health_evidence.sql +137 -0
  55. package/drizzle/0217_external_browser_auth_operations.sql +22 -0
  56. package/drizzle/0218_organization_tenancy_foundation.sql +299 -0
  57. package/drizzle/0219_organization_tenancy_managed_human_provisioning.sql +317 -0
  58. package/drizzle/0219_site_auth_maintenance_sessions.sql +234 -0
  59. package/drizzle/0220_memory_slack_append_only_cascade.sql +38 -0
  60. package/drizzle/0220_session_channels.sql +54 -0
  61. package/drizzle/0221_sessions_channel_index.sql +5 -0
  62. package/drizzle/0222_session_visibility_authority_epochs.sql +158 -0
  63. package/drizzle/0222_sessions_channel_fk.sql +89 -0
  64. package/drizzle/0223_pending_tool_event_output.sql +55 -0
  65. package/drizzle/0223_sessions_channel_fk_validate.sql +118 -0
  66. package/drizzle/0224_slack_post_outcome_reconciliation.sql +123 -0
  67. package/drizzle/0225_session_visibility_fork_activation.sql +1363 -0
  68. package/drizzle/0226_personal_codex_authority_foundation.sql +365 -0
  69. package/drizzle/0227_slack_native_actions.sql +145 -0
  70. package/drizzle/0228_interaction_controller_data_plane.sql +14 -0
  71. package/drizzle/0228_slack_task_policy.sql +400 -0
  72. package/drizzle/0229_slack_inbox_file_fact.sql +10 -0
  73. package/drizzle/0230_user_scoped_variable_sets_rigs.sql +84 -0
  74. package/drizzle/0231_integration_definition_identity_cutover.sql +271 -0
  75. package/drizzle/0232_integration_facet_authority_cutover.sql +610 -0
  76. package/drizzle/0233_skill_and_integration_authority_cutover.sql +1162 -0
  77. package/drizzle/0234_xai_subscription_authority.sql +1139 -0
  78. package/drizzle/0235_canonical_human_login_bindings.sql +920 -0
  79. package/drizzle/0236_browser_identity_lifecycle.sql +39 -0
  80. package/drizzle/0236_session_visibility_slack_policy.sql +72 -0
  81. package/drizzle/0237_interaction_transition_reaper.sql +263 -0
  82. package/drizzle/0238_goal_persistence_policy.sql +471 -0
  83. package/drizzle/0238_recover_unclaimed_session_turns.sql +307 -0
  84. package/drizzle/0238_supergrok_realtime_model.sql +29 -0
  85. package/drizzle/0239_supergrok_video_funding.sql +50 -0
  86. package/drizzle/0239_task_tree_notes.sql +990 -0
  87. package/package.json +13 -5
  88. package/src/browser-auth.ts +2600 -123
  89. package/src/browser-downloads.ts +517 -0
  90. package/src/browser-identities.ts +220 -3
  91. package/src/browser-sessions.ts +334 -45
  92. package/src/browser-state-artifacts.ts +402 -12
  93. package/src/canonical-human-identities.ts +317 -0
  94. package/src/capability-integrations.ts +121 -213
  95. package/src/database.ts +41 -1
  96. package/src/index.ts +4625 -411
  97. package/src/insights.ts +109 -28
  98. package/src/integration-bindings.ts +101 -101
  99. package/src/{integration-features.ts → integration-facets.ts} +187 -184
  100. package/src/interaction-revisions.ts +33 -3
  101. package/src/interaction-schema.ts +311 -8
  102. package/src/pack-components.ts +43 -43
  103. package/src/pack-installations.ts +0 -43
  104. package/src/persistence-errors.ts +60 -1
  105. package/src/plugin-packages.ts +9 -9
  106. package/src/provision-roles.ts +232 -0
  107. package/src/runtime-posture.ts +227 -14
  108. package/src/schema.ts +1188 -75
  109. package/src/session-control.ts +122 -37
  110. package/src/session-queue-commands.ts +78 -1
  111. package/src/session-tenancy.ts +188 -0
  112. package/src/session-tool-call-settlement.ts +9 -3
  113. package/src/slack-task-policy-schema.ts +130 -0
  114. package/src/slack-task-policy.ts +341 -0
  115. package/src/task-notes-schema.ts +135 -0
  116. package/src/task-notes.ts +178 -0
  117. package/src/video-generation.ts +90 -12
  118. package/src/workspace-artifacts.ts +1 -1
  119. package/src/xai-subscription.ts +1887 -0
  120. package/dist/chunk-ISI2TIUG.js.map +0 -1
  121. package/dist/chunk-MWDVXQOL.js.map +0 -1
  122. package/dist/chunk-NBE6CHRI.js.map +0 -1
  123. package/dist/chunk-YHZXQ7HP.js.map +0 -1
  124. package/dist/integration-features.d.ts +0 -99
  125. /package/dist/{chunk-F5FT7BEZ.js.map → chunk-HSOWSYOX.js.map} +0 -0
@@ -0,0 +1,471 @@
1
+ -- deployment-mode: rolling
2
+ -- Freeze the exact goal projection on each accepted logical turn and
3
+ -- separate semantic objective revisions from the established continuation
4
+ -- lifecycle version/wake ledger.
5
+
6
+ SET LOCAL lock_timeout = '5s';
7
+ SET LOCAL statement_timeout = '10min';
8
+
9
+ ALTER TABLE "session_goals"
10
+ ADD COLUMN IF NOT EXISTS "objective_revision" integer NOT NULL DEFAULT 1,
11
+ ADD COLUMN IF NOT EXISTS "mutation_policy" text NOT NULL DEFAULT 'preserve_intent';
12
+
13
+ ALTER TABLE "session_goals"
14
+ ADD CONSTRAINT "session_goals_objective_revision_chk"
15
+ CHECK ("objective_revision" > 0) NOT VALID,
16
+ ADD CONSTRAINT "session_goals_mutation_policy_chk"
17
+ CHECK ("mutation_policy" IN (
18
+ 'review_changes', 'preserve_intent', 'autonomous_adaptation'
19
+ )) NOT VALID;
20
+
21
+ ALTER TABLE "session_goals"
22
+ VALIDATE CONSTRAINT "session_goals_objective_revision_chk",
23
+ VALIDATE CONSTRAINT "session_goals_mutation_policy_chk";
24
+
25
+ ALTER TABLE "session_turns"
26
+ ADD COLUMN IF NOT EXISTS "goal_snapshot" jsonb;
27
+
28
+ ALTER TABLE "session_turns"
29
+ ADD CONSTRAINT "session_turns_goal_snapshot_chk" CHECK (
30
+ "goal_snapshot" IS NULL
31
+ OR (
32
+ jsonb_typeof("goal_snapshot") = 'object'
33
+ AND "goal_snapshot" ->> 'state' IN ('none', 'active', 'paused', 'completed')
34
+ AND jsonb_typeof("goal_snapshot" -> 'capturedAt') = 'string'
35
+ AND (
36
+ ("goal_snapshot" ->> 'state' = 'none'
37
+ AND "goal_snapshot" - 'state' - 'capturedAt' = '{}'::jsonb)
38
+ OR
39
+ ("goal_snapshot" ->> 'state' IN ('active', 'paused', 'completed')
40
+ AND jsonb_typeof("goal_snapshot" -> 'goalId') = 'string'
41
+ AND jsonb_typeof("goal_snapshot" -> 'objectiveRevision') = 'number'
42
+ AND ("goal_snapshot" ->> 'objectiveRevision') ~ '^[1-9][0-9]*$'
43
+ AND jsonb_typeof("goal_snapshot" -> 'text') = 'string'
44
+ AND length("goal_snapshot" ->> 'text') > 0
45
+ AND octet_length("goal_snapshot" ->> 'text') <= 8192
46
+ AND (
47
+ "goal_snapshot" -> 'successCriteria' = 'null'::jsonb
48
+ OR (
49
+ jsonb_typeof("goal_snapshot" -> 'successCriteria') = 'string'
50
+ AND octet_length("goal_snapshot" ->> 'successCriteria') <= 8192
51
+ )
52
+ )
53
+ AND "goal_snapshot" ->> 'mutationPolicy' IN (
54
+ 'review_changes', 'preserve_intent', 'autonomous_adaptation'
55
+ ))
56
+ )
57
+ )
58
+ ) NOT VALID;
59
+
60
+ ALTER TABLE "session_turns"
61
+ VALIDATE CONSTRAINT "session_turns_goal_snapshot_chk";
62
+
63
+ CREATE TABLE "session_goal_revisions" (
64
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
65
+ "account_id" uuid NOT NULL,
66
+ "workspace_id" uuid NOT NULL,
67
+ "session_id" uuid NOT NULL,
68
+ "goal_id" uuid NOT NULL,
69
+ "disposition" text NOT NULL,
70
+ "change_kind" text NOT NULL,
71
+ "base_objective_revision" integer NOT NULL,
72
+ "result_objective_revision" integer,
73
+ "text" text NOT NULL,
74
+ "success_criteria" text,
75
+ "mutation_policy" text NOT NULL,
76
+ "rationale" text NOT NULL,
77
+ "actor" text NOT NULL,
78
+ "actor_turn_id" uuid,
79
+ "actor_attempt_id" uuid,
80
+ "proposal_id" uuid,
81
+ "created_at" timestamptz NOT NULL DEFAULT now(),
82
+ CONSTRAINT "session_goal_revisions_workspace_account_fk"
83
+ FOREIGN KEY ("workspace_id", "account_id")
84
+ REFERENCES "workspaces"("id", "account_id") ON DELETE CASCADE,
85
+ CONSTRAINT "session_goal_revisions_workspace_session_fk"
86
+ FOREIGN KEY ("workspace_id", "session_id")
87
+ REFERENCES "sessions"("workspace_id", "id") ON DELETE CASCADE,
88
+ CONSTRAINT "session_goal_revisions_actor_turn_fk"
89
+ FOREIGN KEY ("workspace_id", "actor_turn_id")
90
+ REFERENCES "session_turns"("workspace_id", "id") ON DELETE RESTRICT,
91
+ CONSTRAINT "session_goal_revisions_actor_attempt_fk"
92
+ FOREIGN KEY ("workspace_id", "actor_attempt_id")
93
+ REFERENCES "session_turn_attempts"("workspace_id", "id") ON DELETE RESTRICT,
94
+ CONSTRAINT "session_goal_revisions_disposition_chk"
95
+ CHECK ("disposition" IN ('applied', 'proposed', 'rejected')),
96
+ CONSTRAINT "session_goal_revisions_change_kind_chk"
97
+ CHECK ("change_kind" IN ('refinement', 'adaptation', 'replacement')),
98
+ CONSTRAINT "session_goal_revisions_policy_chk"
99
+ CHECK ("mutation_policy" IN (
100
+ 'review_changes', 'preserve_intent', 'autonomous_adaptation'
101
+ )),
102
+ CONSTRAINT "session_goal_revisions_actor_chk"
103
+ CHECK ("actor" IN ('agent', 'api', 'scheduled_task')),
104
+ CONSTRAINT "session_goal_revisions_revision_shape_chk" CHECK (
105
+ ("disposition" = 'applied'
106
+ AND "result_objective_revision" = "base_objective_revision" + 1)
107
+ OR
108
+ ("disposition" IN ('proposed', 'rejected')
109
+ AND "result_objective_revision" IS NULL)
110
+ )
111
+ );
112
+
113
+ CREATE UNIQUE INDEX "session_goal_revisions_workspace_id_uq"
114
+ ON "session_goal_revisions" ("workspace_id", "id");
115
+ CREATE UNIQUE INDEX "session_goal_revisions_applied_revision_uq"
116
+ ON "session_goal_revisions" (
117
+ "workspace_id", "goal_id", "result_objective_revision"
118
+ ) WHERE "disposition" = 'applied';
119
+ CREATE INDEX "session_goal_revisions_goal_timeline_idx"
120
+ ON "session_goal_revisions" ("workspace_id", "goal_id", "created_at", "id");
121
+ ALTER TABLE "session_goal_revisions"
122
+ ADD CONSTRAINT "session_goal_revisions_proposal_fk"
123
+ FOREIGN KEY ("workspace_id", "proposal_id")
124
+ REFERENCES "session_goal_revisions"("workspace_id", "id") ON DELETE RESTRICT;
125
+
126
+ ALTER TABLE "session_goal_revisions" ENABLE ROW LEVEL SECURITY;
127
+ ALTER TABLE "session_goal_revisions" FORCE ROW LEVEL SECURITY;
128
+ CREATE POLICY workspace_isolation ON "session_goal_revisions"
129
+ USING (opengeni_private.workspace_rls_visible("account_id", "workspace_id"))
130
+ WITH CHECK (opengeni_private.workspace_rls_visible("account_id", "workspace_id"));
131
+ CREATE POLICY session_visibility_isolation ON "session_goal_revisions"
132
+ AS RESTRICTIVE
133
+ USING (session_reference_visible(
134
+ "account_id", "workspace_id", "session_id"
135
+ ))
136
+ WITH CHECK (session_reference_visible(
137
+ "account_id", "workspace_id", "session_id"
138
+ ));
139
+
140
+ CREATE OR REPLACE FUNCTION session_goal_revision_reject_mutation()
141
+ RETURNS trigger
142
+ LANGUAGE plpgsql
143
+ AS $$
144
+ BEGIN
145
+ -- Parent lifecycle deletion is the sole mutation exception. PostgreSQL's
146
+ -- cascading constraint trigger runs only after its parent row is absent;
147
+ -- require both nested trigger context and one missing ownership edge so
148
+ -- direct or unrelated-trigger deletes remain fail-closed.
149
+ IF TG_OP = 'DELETE'
150
+ AND pg_trigger_depth() > 1
151
+ AND (
152
+ NOT EXISTS (SELECT 1 FROM "managed_accounts" WHERE "id" = OLD."account_id")
153
+ OR NOT EXISTS (SELECT 1 FROM "workspaces" WHERE "id" = OLD."workspace_id")
154
+ OR NOT EXISTS (SELECT 1 FROM "sessions" WHERE "id" = OLD."session_id")
155
+ )
156
+ THEN
157
+ RETURN OLD;
158
+ END IF;
159
+ RAISE EXCEPTION 'session goal revisions are immutable'
160
+ USING ERRCODE = '55000';
161
+ END;
162
+ $$;
163
+
164
+ DROP TRIGGER IF EXISTS session_goal_revisions_immutable
165
+ ON "session_goal_revisions";
166
+ CREATE TRIGGER session_goal_revisions_immutable
167
+ BEFORE UPDATE OR DELETE ON "session_goal_revisions"
168
+ FOR EACH ROW EXECUTE FUNCTION session_goal_revision_reject_mutation();
169
+
170
+ CREATE OR REPLACE FUNCTION session_goal_revision_bound_insert()
171
+ RETURNS trigger
172
+ LANGUAGE plpgsql
173
+ AS $$
174
+ BEGIN
175
+ IF octet_length(NEW."text") > 8192 THEN
176
+ RAISE EXCEPTION 'goal text exceeds 8192 UTF-8 bytes'
177
+ USING ERRCODE = '22001';
178
+ END IF;
179
+ IF NEW."success_criteria" IS NOT NULL
180
+ AND octet_length(NEW."success_criteria") > 8192 THEN
181
+ RAISE EXCEPTION 'goal success criteria exceeds 8192 UTF-8 bytes'
182
+ USING ERRCODE = '22001';
183
+ END IF;
184
+ IF octet_length(NEW."rationale") > 2048 THEN
185
+ RAISE EXCEPTION 'goal rationale exceeds 2048 UTF-8 bytes'
186
+ USING ERRCODE = '22001';
187
+ END IF;
188
+ RETURN NEW;
189
+ END;
190
+ $$;
191
+
192
+ INSERT INTO "session_goal_revisions" (
193
+ "account_id", "workspace_id", "session_id", "goal_id", "disposition",
194
+ "change_kind", "base_objective_revision", "result_objective_revision",
195
+ "text", "success_criteria", "mutation_policy", "rationale", "actor",
196
+ "created_at"
197
+ )
198
+ SELECT
199
+ goal."account_id", goal."workspace_id", goal."session_id", goal."id",
200
+ 'applied', 'replacement', 0, 1, goal."text", goal."success_criteria",
201
+ goal."mutation_policy", 'Existing goal captured during policy activation',
202
+ CASE
203
+ WHEN goal."created_by" = 'scheduled_task' THEN 'scheduled_task'
204
+ WHEN goal."created_by" = 'agent' THEN 'agent'
205
+ ELSE 'api'
206
+ END,
207
+ goal."created_at"
208
+ FROM "session_goals" goal
209
+ ON CONFLICT ("workspace_id", "goal_id", "result_objective_revision")
210
+ WHERE "disposition" = 'applied'
211
+ DO NOTHING;
212
+
213
+ DROP TRIGGER IF EXISTS session_goal_revisions_bound_insert
214
+ ON "session_goal_revisions";
215
+ CREATE TRIGGER session_goal_revisions_bound_insert
216
+ BEFORE INSERT ON "session_goal_revisions"
217
+ FOR EACH ROW EXECUTE FUNCTION session_goal_revision_bound_insert();
218
+
219
+ CREATE OR REPLACE FUNCTION session_goal_revision_before_write()
220
+ RETURNS trigger
221
+ LANGUAGE plpgsql
222
+ AS $$
223
+ DECLARE
224
+ semantic_change boolean;
225
+ BEGIN
226
+ IF TG_OP = 'INSERT' THEN
227
+ IF octet_length(NEW."text") > 8192 THEN
228
+ RAISE EXCEPTION 'goal text exceeds 8192 UTF-8 bytes'
229
+ USING ERRCODE = '22001';
230
+ END IF;
231
+ IF NEW."success_criteria" IS NOT NULL
232
+ AND octet_length(NEW."success_criteria") > 8192 THEN
233
+ RAISE EXCEPTION 'goal success criteria exceeds 8192 UTF-8 bytes'
234
+ USING ERRCODE = '22001';
235
+ END IF;
236
+ IF NEW."objective_revision" <> 1 THEN
237
+ RAISE EXCEPTION 'a new goal must start at objective revision 1'
238
+ USING ERRCODE = '23514';
239
+ END IF;
240
+ RETURN NEW;
241
+ END IF;
242
+
243
+ semantic_change :=
244
+ NEW."text" IS DISTINCT FROM OLD."text"
245
+ OR NEW."success_criteria" IS DISTINCT FROM OLD."success_criteria"
246
+ OR NEW."mutation_policy" IS DISTINCT FROM OLD."mutation_policy";
247
+
248
+ IF semantic_change THEN
249
+ IF octet_length(NEW."text") > 8192 THEN
250
+ RAISE EXCEPTION 'goal text exceeds 8192 UTF-8 bytes'
251
+ USING ERRCODE = '22001';
252
+ END IF;
253
+ IF NEW."success_criteria" IS NOT NULL
254
+ AND octet_length(NEW."success_criteria") > 8192 THEN
255
+ RAISE EXCEPTION 'goal success criteria exceeds 8192 UTF-8 bytes'
256
+ USING ERRCODE = '22001';
257
+ END IF;
258
+ IF NEW."objective_revision" = OLD."objective_revision" THEN
259
+ -- Rolling-old writers know only the lifecycle version. Preserve their
260
+ -- semantic mutation while still advancing the new exact objective fence.
261
+ NEW."objective_revision" := OLD."objective_revision" + 1;
262
+ ELSIF NEW."objective_revision" <> OLD."objective_revision" + 1 THEN
263
+ RAISE EXCEPTION 'goal objective revision must advance exactly once'
264
+ USING ERRCODE = '40001';
265
+ END IF;
266
+ ELSIF NEW."objective_revision" <> OLD."objective_revision" THEN
267
+ RAISE EXCEPTION 'goal objective revision changed without semantic content'
268
+ USING ERRCODE = '23514';
269
+ END IF;
270
+ RETURN NEW;
271
+ END;
272
+ $$;
273
+
274
+ CREATE OR REPLACE FUNCTION session_goal_revision_after_write()
275
+ RETURNS trigger
276
+ LANGUAGE plpgsql
277
+ AS $$
278
+ DECLARE
279
+ semantic_change boolean := TG_OP = 'INSERT';
280
+ base_revision integer := 0;
281
+ change_kind text := 'replacement';
282
+ change_rationale text := 'Goal created';
283
+ change_actor text;
284
+ change_turn_id uuid;
285
+ change_attempt_id uuid;
286
+ source_proposal_id uuid;
287
+ BEGIN
288
+ IF TG_OP = 'UPDATE' THEN
289
+ semantic_change :=
290
+ NEW."text" IS DISTINCT FROM OLD."text"
291
+ OR NEW."success_criteria" IS DISTINCT FROM OLD."success_criteria"
292
+ OR NEW."mutation_policy" IS DISTINCT FROM OLD."mutation_policy";
293
+ base_revision := OLD."objective_revision";
294
+ change_rationale := 'Goal changed by a rolling-compatible writer';
295
+ END IF;
296
+ IF NOT semantic_change THEN
297
+ RETURN NEW;
298
+ END IF;
299
+
300
+ change_kind := COALESCE(
301
+ NULLIF(current_setting('opengeni.goal_change_kind', true), ''),
302
+ change_kind
303
+ );
304
+ change_rationale := COALESCE(
305
+ NULLIF(current_setting('opengeni.goal_change_rationale', true), ''),
306
+ change_rationale
307
+ );
308
+ change_actor := COALESCE(
309
+ NULLIF(current_setting('opengeni.goal_change_actor', true), ''),
310
+ CASE
311
+ WHEN NEW."created_by" = 'scheduled_task' THEN 'scheduled_task'
312
+ WHEN NEW."created_by" = 'agent' THEN 'agent'
313
+ ELSE 'api'
314
+ END
315
+ );
316
+ change_turn_id := NULLIF(
317
+ current_setting('opengeni.goal_change_turn_id', true), ''
318
+ )::uuid;
319
+ change_attempt_id := NULLIF(
320
+ current_setting('opengeni.goal_change_attempt_id', true), ''
321
+ )::uuid;
322
+ source_proposal_id := NULLIF(
323
+ current_setting('opengeni.goal_change_proposal_id', true), ''
324
+ )::uuid;
325
+
326
+ INSERT INTO "session_goal_revisions" (
327
+ "account_id", "workspace_id", "session_id", "goal_id", "disposition",
328
+ "change_kind", "base_objective_revision", "result_objective_revision",
329
+ "text", "success_criteria", "mutation_policy", "rationale", "actor",
330
+ "actor_turn_id", "actor_attempt_id", "proposal_id", "created_at"
331
+ ) VALUES (
332
+ NEW."account_id", NEW."workspace_id", NEW."session_id", NEW."id",
333
+ 'applied', change_kind, base_revision, NEW."objective_revision", NEW."text",
334
+ NEW."success_criteria", NEW."mutation_policy", change_rationale,
335
+ change_actor, change_turn_id, change_attempt_id, source_proposal_id, now()
336
+ )
337
+ ON CONFLICT ("workspace_id", "goal_id", "result_objective_revision")
338
+ WHERE "disposition" = 'applied'
339
+ DO NOTHING;
340
+ RETURN NEW;
341
+ END;
342
+ $$;
343
+
344
+ DROP TRIGGER IF EXISTS session_goal_revision_before_write_trigger ON "session_goals";
345
+ CREATE TRIGGER session_goal_revision_before_write_trigger
346
+ BEFORE INSERT OR UPDATE OF "text", "success_criteria", "mutation_policy", "objective_revision"
347
+ ON "session_goals"
348
+ FOR EACH ROW EXECUTE FUNCTION session_goal_revision_before_write();
349
+
350
+ DROP TRIGGER IF EXISTS session_goal_revision_after_write_trigger ON "session_goals";
351
+ CREATE TRIGGER session_goal_revision_after_write_trigger
352
+ AFTER INSERT OR UPDATE OF "text", "success_criteria", "mutation_policy"
353
+ ON "session_goals"
354
+ FOR EACH ROW EXECUTE FUNCTION session_goal_revision_after_write();
355
+
356
+ CREATE OR REPLACE FUNCTION session_goal_prompt_projection(
357
+ value text,
358
+ max_bytes integer
359
+ )
360
+ RETURNS text
361
+ LANGUAGE plpgsql
362
+ IMMUTABLE
363
+ STRICT
364
+ AS $$
365
+ DECLARE
366
+ original_bytes integer := octet_length(value);
367
+ marker text;
368
+ budget integer;
369
+ lower_chars integer := 0;
370
+ upper_chars integer := length(value);
371
+ midpoint integer;
372
+ BEGIN
373
+ IF original_bytes <= max_bytes THEN
374
+ RETURN value;
375
+ END IF;
376
+ marker := E'\n[truncated; original UTF-8 bytes=' || original_bytes::text || ']';
377
+ budget := greatest(0, max_bytes - octet_length(marker));
378
+ WHILE lower_chars < upper_chars LOOP
379
+ midpoint := (lower_chars + upper_chars + 1) / 2;
380
+ IF octet_length(left(value, midpoint)) <= budget THEN
381
+ lower_chars := midpoint;
382
+ ELSE
383
+ upper_chars := midpoint - 1;
384
+ END IF;
385
+ END LOOP;
386
+ RETURN left(value, lower_chars) || marker;
387
+ END;
388
+ $$;
389
+
390
+ CREATE OR REPLACE FUNCTION session_goal_freeze_turn_snapshot()
391
+ RETURNS trigger
392
+ LANGUAGE plpgsql
393
+ AS $$
394
+ DECLARE
395
+ goal_row "session_goals"%ROWTYPE;
396
+ BEGIN
397
+ SELECT * INTO goal_row
398
+ FROM "session_goals"
399
+ WHERE "workspace_id" = NEW."workspace_id"
400
+ AND "session_id" = NEW."session_id";
401
+
402
+ IF FOUND THEN
403
+ NEW."goal_snapshot" := jsonb_build_object(
404
+ 'state', goal_row."status",
405
+ 'goalId', goal_row."id"::text,
406
+ 'objectiveRevision', goal_row."objective_revision",
407
+ 'text', session_goal_prompt_projection(goal_row."text", 8192),
408
+ 'successCriteria', to_jsonb(
409
+ session_goal_prompt_projection(goal_row."success_criteria", 8192)
410
+ ),
411
+ 'mutationPolicy', goal_row."mutation_policy",
412
+ 'capturedAt', to_char(
413
+ NEW."created_at" AT TIME ZONE 'UTC',
414
+ 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'
415
+ )
416
+ );
417
+ ELSE
418
+ NEW."goal_snapshot" := jsonb_build_object(
419
+ 'state', 'none',
420
+ 'capturedAt', to_char(
421
+ NEW."created_at" AT TIME ZONE 'UTC',
422
+ 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'
423
+ )
424
+ );
425
+ END IF;
426
+ RETURN NEW;
427
+ END;
428
+ $$;
429
+
430
+ CREATE OR REPLACE FUNCTION session_goal_reject_turn_snapshot_mutation()
431
+ RETURNS trigger
432
+ LANGUAGE plpgsql
433
+ AS $$
434
+ BEGIN
435
+ IF OLD."goal_snapshot" IS NOT NULL
436
+ AND NEW."goal_snapshot" IS DISTINCT FROM OLD."goal_snapshot" THEN
437
+ RAISE EXCEPTION 'turn goal snapshot is immutable after acceptance'
438
+ USING ERRCODE = '55000';
439
+ END IF;
440
+ RETURN NEW;
441
+ END;
442
+ $$;
443
+
444
+ DROP TRIGGER IF EXISTS session_turns_goal_snapshot_insert ON "session_turns";
445
+ CREATE TRIGGER session_turns_goal_snapshot_insert
446
+ BEFORE INSERT ON "session_turns"
447
+ FOR EACH ROW EXECUTE FUNCTION session_goal_freeze_turn_snapshot();
448
+
449
+ DROP TRIGGER IF EXISTS session_turns_goal_snapshot_immutable ON "session_turns";
450
+ CREATE TRIGGER session_turns_goal_snapshot_immutable
451
+ BEFORE UPDATE OF "goal_snapshot" ON "session_turns"
452
+ FOR EACH ROW EXECUTE FUNCTION session_goal_reject_turn_snapshot_mutation();
453
+
454
+ DROP INDEX IF EXISTS "session_command_receipts_goal_progress_operation_uq";
455
+ CREATE UNIQUE INDEX "session_command_receipts_goal_progress_operation_uq"
456
+ ON "session_command_receipts" (
457
+ "workspace_id", "action", "target_session_id", "operation_key"
458
+ ) WHERE "action" = 'goal.progress';
459
+
460
+ UPDATE "sessions"
461
+ SET "first_party_mcp_tools" = "first_party_mcp_tools" || '["goal_progress"]'::jsonb
462
+ WHERE "first_party_mcp_tools" ? 'goal_update'
463
+ AND NOT "first_party_mcp_tools" ? 'goal_progress';
464
+
465
+ DO $$
466
+ BEGIN
467
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
468
+ GRANT SELECT, INSERT ON "session_goal_revisions" TO opengeni_app;
469
+ REVOKE UPDATE, DELETE ON "session_goal_revisions" FROM opengeni_app;
470
+ END IF;
471
+ END $$;