@opengeni/db 0.6.1 → 0.7.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 (53) hide show
  1. package/dist/{chunk-OGCE6O2X.js → chunk-7LDU7F5P.js} +31 -3
  2. package/dist/chunk-7LDU7F5P.js.map +1 -0
  3. package/dist/chunk-B22X3IEZ.js +2634 -0
  4. package/dist/chunk-B22X3IEZ.js.map +1 -0
  5. package/dist/{chunk-57MLICFR.js → chunk-YFQ7SGE4.js} +18 -6
  6. package/dist/chunk-YFQ7SGE4.js.map +1 -0
  7. package/dist/index.d.ts +2 -2
  8. package/dist/index.js +16571 -5018
  9. package/dist/index.js.map +1 -1
  10. package/dist/migrate.js +1 -1
  11. package/dist/provision-roles.d.ts +2121 -248
  12. package/dist/provision-roles.js +1 -1
  13. package/dist/{schema-BUbuMteO.d.ts → schema-BN5mB9xZ.d.ts} +8716 -3432
  14. package/dist/schema.d.ts +1 -1
  15. package/dist/schema.js +43 -5
  16. package/drizzle/0044_reap_dead_turn_holders.sql +104 -0
  17. package/drizzle/0045_workspace_captures.sql +83 -0
  18. package/drizzle/0045_workspace_memory_v1.sql +71 -0
  19. package/drizzle/0046_variable_sets_rename.sql +56 -0
  20. package/drizzle/0047_rigs.sql +151 -0
  21. package/drizzle/0048_rig_runtime.sql +9 -0
  22. package/drizzle/0049_enrollment_went_offline.sql +28 -0
  23. package/drizzle/0050_enrollment_op_stream.sql +1 -0
  24. package/drizzle/0051_codex_pin_source.sql +48 -0
  25. package/drizzle/0052_file_upload_cleanup.sql +91 -0
  26. package/drizzle/0053_codex_credential_leases.sql +230 -0
  27. package/drizzle/0054_session_pins.sql +85 -0
  28. package/drizzle/0055_session_list_snapshots.sql +73 -0
  29. package/drizzle/0056_workspace_model_policies.sql +48 -0
  30. package/drizzle/0057_durable_queue_control.sql +536 -0
  31. package/drizzle/0058_turn_admission_usage_enrollment.sql +158 -0
  32. package/drizzle/0059_workspace_pause_control_kind.sql +12 -0
  33. package/drizzle/0060_session_system_update_deferral.sql +10 -0
  34. package/drizzle/0061_session_workflow_wake_outbox.sql +157 -0
  35. package/drizzle/0062_session_list_snapshot_reaper.sql +48 -0
  36. package/drizzle/0063_session_control_mega_foundation.sql +1324 -0
  37. package/package.json +13 -13
  38. package/src/codex-token-resolver.ts +58 -23
  39. package/src/connection-token-resolver.ts +146 -57
  40. package/src/environment-crypto.ts +5 -1
  41. package/src/event-payload-sanitizer.ts +29 -1
  42. package/src/index.ts +20990 -6465
  43. package/src/memory-domain.ts +218 -0
  44. package/src/migrate.ts +58 -3
  45. package/src/provision-roles.ts +46 -17
  46. package/src/schema.ts +2888 -1121
  47. package/src/session-control.ts +1759 -0
  48. package/src/session-queue-commands.ts +1753 -0
  49. package/src/session-tool-call-settlement.ts +269 -0
  50. package/dist/chunk-57MLICFR.js.map +0 -1
  51. package/dist/chunk-OGCE6O2X.js.map +0 -1
  52. package/dist/chunk-ZIUCA2IO.js +0 -1268
  53. package/dist/chunk-ZIUCA2IO.js.map +0 -1
@@ -0,0 +1,48 @@
1
+ -- 0051_codex_pin_source.sql
2
+ -- Sharded codex rotation (AM-2): a SOURCE discriminator for the per-session codex pin.
3
+ -- `codex_pin_source` is 'manual' (the user's in-session account switcher — SACRED, no
4
+ -- policy path ever moves it) or 'policy' (the sharded strategy's deterministic home
5
+ -- assignment — MAY be re-sharded when its account caps). NULL means no pin. Plaintext
6
+ -- metadata, NEVER a token. Additive only; RLS is row-level so the existing sessions
7
+ -- workspace_isolation policy already covers the column — NO policy change. Runs under the
8
+ -- runner's advisory lock (no app traffic sees the ALTER/backfill window).
9
+
10
+ ALTER TABLE "sessions" ADD COLUMN IF NOT EXISTS "codex_pin_source" text;
11
+
12
+ -- Backfill: every PRE-EXISTING pin is a MANUAL pin. INVARIANT — before this migration the
13
+ -- ONLY writer of sessions.codex_pinned_credential_id was setSessionCodexPin via the manual
14
+ -- in-session account-switcher API route (no rotation/policy path wrote a pin). So a
15
+ -- currently-pinned session is, by construction, a user's manual pin and must be stamped
16
+ -- 'manual' — otherwise a NULL source would strip it of its sacredness the moment the
17
+ -- sharded strategy is enabled (a policy path could re-shard the user's chosen account).
18
+ -- Migrations run as the table owner; FORCE RLS would subject this UPDATE to the
19
+ -- workspace-scoped policy (matching zero rows, no GUCs set), so drop FORCE only for the
20
+ -- owner around the backfill (precedent 0009/0028) — the app role stays policy-bound and
21
+ -- FORCE is restored in the same implicit transaction.
22
+ ALTER TABLE "sessions" NO FORCE ROW LEVEL SECURITY;
23
+ UPDATE "sessions"
24
+ SET "codex_pin_source" = 'manual'
25
+ WHERE "codex_pinned_credential_id" IS NOT NULL AND "codex_pin_source" IS NULL;
26
+ ALTER TABLE "sessions" FORCE ROW LEVEL SECURITY;
27
+
28
+ -- Constrain to the two known sources (or NULL). IF NOT EXISTS guard so a re-run is a no-op.
29
+ DO $$
30
+ BEGIN
31
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'sessions_codex_pin_source_check') THEN
32
+ ALTER TABLE "sessions"
33
+ ADD CONSTRAINT "sessions_codex_pin_source_check"
34
+ CHECK ("codex_pin_source" IS NULL OR "codex_pin_source" IN ('manual', 'policy'));
35
+ END IF;
36
+ END $$;
37
+
38
+ -- Re-grant (schema-agnostic, matching 0049/0050) so opengeni_app keeps DML on the altered
39
+ -- table under a dedicated (non-public) schema too.
40
+ DO $$
41
+ BEGIN
42
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
43
+ EXECUTE format(
44
+ 'GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA %I TO opengeni_app',
45
+ current_schema()
46
+ );
47
+ END IF;
48
+ END $$;
@@ -0,0 +1,91 @@
1
+ -- Reclaim direct-upload objects whose signed PUT completed but whose client
2
+ -- disappeared before finalize. The global worker cannot enumerate FORCE-RLS
3
+ -- workspaces directly, so this tightly-scoped SECURITY DEFINER function is the
4
+ -- sanctioned cross-workspace claim seam. It returns only storage routing ids;
5
+ -- no user content or signed URL is exposed.
6
+ --
7
+ -- Claims are leased, not terminal: a provider delete failure or worker crash
8
+ -- leaves cleanup_pending rows reclaimable after p_claim_timeout_ms. The caller
9
+ -- settles a successful idempotent delete through the ordinary workspace-RLS DB
10
+ -- helper. Pending rows are not eligible until their signed URL has expired plus
11
+ -- p_grace_ms, so cleanup never races a live draft/upload.
12
+
13
+ CREATE INDEX IF NOT EXISTS "file_uploads_pending_expiry_cleanup_idx"
14
+ ON "file_uploads" ("expires_at", "id")
15
+ WHERE "status" = 'pending';
16
+
17
+ CREATE INDEX IF NOT EXISTS "file_uploads_claim_recovery_idx"
18
+ ON "file_uploads" ("updated_at", "id")
19
+ WHERE "status" = 'cleanup_pending';
20
+
21
+ CREATE OR REPLACE FUNCTION opengeni_private.claim_expired_file_upload_cleanup(
22
+ p_grace_ms bigint,
23
+ p_claim_timeout_ms bigint,
24
+ p_limit integer
25
+ )
26
+ RETURNS TABLE (
27
+ upload_id uuid,
28
+ account_id uuid,
29
+ workspace_id uuid,
30
+ file_id uuid,
31
+ object_key text
32
+ )
33
+ LANGUAGE sql
34
+ SECURITY DEFINER
35
+ -- EMBED-SAFE: the data tables live in the caller-selected public or dedicated
36
+ -- schema. Match the repository's existing global-reaper convention by
37
+ -- inheriting that schema-aware search_path; opengeni_private is absolute.
38
+ AS $$
39
+ WITH candidates AS (
40
+ SELECT U.id
41
+ FROM file_uploads U
42
+ WHERE
43
+ (
44
+ U.status = 'pending'
45
+ AND U.expires_at <= clock_timestamp() - (
46
+ greatest(p_grace_ms, 0)::double precision * interval '1 millisecond'
47
+ )
48
+ )
49
+ OR
50
+ (
51
+ U.status = 'cleanup_pending'
52
+ AND U.updated_at <= clock_timestamp() - (
53
+ greatest(p_claim_timeout_ms, 0)::double precision * interval '1 millisecond'
54
+ )
55
+ )
56
+ ORDER BY U.expires_at, U.id
57
+ -- Defense in depth: the worker asks for 100, but the privileged function
58
+ -- itself stays bounded even if an application caller passes a huge limit.
59
+ LIMIT least(greatest(p_limit, 0), 1000)
60
+ FOR UPDATE SKIP LOCKED
61
+ ), claimed AS (
62
+ UPDATE file_uploads U
63
+ SET status = 'cleanup_pending', updated_at = clock_timestamp()
64
+ FROM candidates C
65
+ WHERE U.id = C.id
66
+ RETURNING U.id, U.account_id, U.workspace_id, U.file_id
67
+ ), failed_files AS (
68
+ UPDATE files F
69
+ SET status = 'failed', updated_at = clock_timestamp()
70
+ FROM claimed C
71
+ WHERE F.id = C.file_id AND F.workspace_id = C.workspace_id
72
+ RETURNING F.id, F.object_key
73
+ )
74
+ SELECT C.id, C.account_id, C.workspace_id, C.file_id, F.object_key
75
+ FROM claimed C
76
+ JOIN failed_files F ON F.id = C.file_id;
77
+ $$;
78
+
79
+ -- PostgreSQL grants new functions to PUBLIC by default. This function bypasses
80
+ -- workspace RLS, so only the migration owner and the explicit application role
81
+ -- may execute it.
82
+ REVOKE ALL ON FUNCTION opengeni_private.claim_expired_file_upload_cleanup(bigint, bigint, integer)
83
+ FROM PUBLIC;
84
+
85
+ DO $$
86
+ BEGIN
87
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
88
+ GRANT EXECUTE ON FUNCTION opengeni_private.claim_expired_file_upload_cleanup(bigint, bigint, integer)
89
+ TO opengeni_app;
90
+ END IF;
91
+ END $$;
@@ -0,0 +1,230 @@
1
+ -- 0053_codex_credential_leases.sql
2
+ -- OPE-21: fair, atomic Codex subscription selection across worker replicas.
3
+ --
4
+ -- Every field in this migration is workspace-local. In particular, the same
5
+ -- external ChatGPT subscription connected in two workspaces is intentionally
6
+ -- usable concurrently: no provider identity hash, lease, cooldown, usage row, or
7
+ -- cursor crosses the workspace boundary.
8
+
9
+ -- Server-held fairness metadata. Provider usage headers remain useful capacity
10
+ -- hints, but are never the allocator's only state: live DB leases are ranked
11
+ -- first and this cursor breaks equal-load/equal-capacity ties.
12
+ ALTER TABLE "codex_subscription_credentials"
13
+ ADD COLUMN IF NOT EXISTS "allocator_enabled" boolean NOT NULL DEFAULT true,
14
+ ADD COLUMN IF NOT EXISTS "selection_count" integer NOT NULL DEFAULT 0,
15
+ ADD COLUMN IF NOT EXISTS "last_selected_at" timestamptz;
16
+
17
+ CREATE UNIQUE INDEX IF NOT EXISTS "codex_subscription_credentials_workspace_id_idx"
18
+ ON "codex_subscription_credentials" ("workspace_id", "id");
19
+
20
+ -- Never flip the legacy bit's default. Old workers only understand
21
+ -- rotation_enabled; keeping it false makes schema-first rollout and binary
22
+ -- rollback safe. New compatible code may explicitly set the separate allocator
23
+ -- bit only after every replica understands leases.
24
+ ALTER TABLE "codex_rotation_settings"
25
+ ALTER COLUMN "rotation_enabled" SET DEFAULT false,
26
+ ADD COLUMN IF NOT EXISTS "lease_rotation_enabled" boolean NOT NULL DEFAULT false;
27
+
28
+ -- Composite target for the lease turn FK. The id is already globally unique,
29
+ -- but including workspace_id makes tenant integrity independently enforceable.
30
+ CREATE UNIQUE INDEX IF NOT EXISTS "session_turns_workspace_id_idx"
31
+ ON "session_turns" ("workspace_id", "id");
32
+
33
+ CREATE TABLE IF NOT EXISTS "codex_credential_leases" (
34
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
35
+ "account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
36
+ "workspace_id" uuid NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
37
+ "credential_id" uuid NOT NULL,
38
+ "turn_id" uuid NOT NULL,
39
+ "holder_id" text NOT NULL,
40
+ "generation" integer NOT NULL DEFAULT 1,
41
+ "leased_until" timestamptz NOT NULL,
42
+ "created_at" timestamptz NOT NULL DEFAULT now(),
43
+ "updated_at" timestamptz NOT NULL DEFAULT now(),
44
+ CONSTRAINT "codex_credential_leases_workspace_account_fk"
45
+ FOREIGN KEY ("workspace_id", "account_id")
46
+ REFERENCES "workspaces"("id", "account_id")
47
+ ON DELETE CASCADE,
48
+ CONSTRAINT "codex_credential_leases_workspace_credential_fk"
49
+ FOREIGN KEY ("workspace_id", "credential_id")
50
+ REFERENCES "codex_subscription_credentials"("workspace_id", "id")
51
+ ON DELETE CASCADE,
52
+ CONSTRAINT "codex_credential_leases_workspace_turn_fk"
53
+ FOREIGN KEY ("workspace_id", "turn_id")
54
+ REFERENCES "session_turns"("workspace_id", "id")
55
+ ON DELETE CASCADE
56
+ );
57
+
58
+ CREATE UNIQUE INDEX IF NOT EXISTS "codex_credential_leases_workspace_turn_idx"
59
+ ON "codex_credential_leases" ("workspace_id", "turn_id");
60
+ CREATE INDEX IF NOT EXISTS "codex_credential_leases_active_credential_idx"
61
+ ON "codex_credential_leases" ("workspace_id", "credential_id", "leased_until");
62
+ CREATE INDEX IF NOT EXISTS "codex_credential_leases_expiry_idx"
63
+ ON "codex_credential_leases" ("leased_until");
64
+
65
+ ALTER TABLE "codex_credential_leases" ENABLE ROW LEVEL SECURITY;
66
+ ALTER TABLE "codex_credential_leases" FORCE ROW LEVEL SECURITY;
67
+ DO $$
68
+ BEGIN
69
+ IF EXISTS (
70
+ SELECT 1 FROM pg_policies
71
+ WHERE schemaname = current_schema()
72
+ AND tablename = 'codex_credential_leases'
73
+ AND policyname = 'workspace_isolation'
74
+ ) THEN
75
+ DROP POLICY workspace_isolation ON "codex_credential_leases";
76
+ END IF;
77
+ END $$;
78
+
79
+ CREATE POLICY workspace_isolation ON "codex_credential_leases"
80
+ USING (opengeni_private.workspace_rls_visible(account_id, workspace_id))
81
+ WITH CHECK (opengeni_private.workspace_rls_visible(account_id, workspace_id));
82
+
83
+ -- One durable capacity wait per session. This row is also the coalescing
84
+ -- commit->signal outbox: an eligibility-affecting mutation increments
85
+ -- wake_revision in the same transaction; the session workflow advances
86
+ -- observed_wake_revision only after allocator re-evaluation. A lost/duplicate
87
+ -- Temporal signal therefore cannot lose or duplicate the continuation.
88
+ CREATE UNIQUE INDEX IF NOT EXISTS "session_goals_workspace_id_idx"
89
+ ON "session_goals" ("workspace_id", "id");
90
+ CREATE UNIQUE INDEX IF NOT EXISTS "sessions_workspace_id_idx"
91
+ ON "sessions" ("workspace_id", "id");
92
+
93
+ CREATE TABLE IF NOT EXISTS "codex_capacity_waiters" (
94
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
95
+ "account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
96
+ "workspace_id" uuid NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
97
+ "session_id" uuid NOT NULL,
98
+ "goal_id" uuid NOT NULL,
99
+ "blocked_turn_id" uuid NOT NULL,
100
+ "workflow_id" text NOT NULL,
101
+ "generation" integer NOT NULL DEFAULT 1,
102
+ "status" text NOT NULL DEFAULT 'waiting',
103
+ "goal_version" integer NOT NULL,
104
+ "control_generation" integer NOT NULL DEFAULT 0,
105
+ "policy_hash" text,
106
+ "earliest_reset_at" timestamptz,
107
+ "next_check_at" timestamptz NOT NULL,
108
+ "reset_kind" text NOT NULL,
109
+ "refresh_attempt" integer NOT NULL DEFAULT 0,
110
+ "wake_revision" integer NOT NULL DEFAULT 1,
111
+ "observed_wake_revision" integer NOT NULL DEFAULT 0,
112
+ "last_wake_reason" text NOT NULL DEFAULT 'capacity_wait_armed',
113
+ "resumed_turn_id" uuid,
114
+ "created_at" timestamptz NOT NULL DEFAULT now(),
115
+ "updated_at" timestamptz NOT NULL DEFAULT now(),
116
+ CONSTRAINT "codex_capacity_waiters_status_check"
117
+ CHECK (status IN ('waiting', 'resumed', 'superseded')),
118
+ CONSTRAINT "codex_capacity_waiters_reset_kind_check"
119
+ CHECK (reset_kind IN ('authoritative', 'bounded_refresh')),
120
+ CONSTRAINT "codex_capacity_waiters_generation_check"
121
+ CHECK (generation > 0 AND goal_version > 0 AND control_generation >= 0),
122
+ CONSTRAINT "codex_capacity_waiters_revision_check"
123
+ CHECK (wake_revision > 0 AND observed_wake_revision >= 0
124
+ AND observed_wake_revision <= wake_revision),
125
+ CONSTRAINT "codex_capacity_waiters_refresh_attempt_check"
126
+ CHECK (refresh_attempt >= 0),
127
+ CONSTRAINT "codex_capacity_waiters_workspace_account_fk"
128
+ FOREIGN KEY ("workspace_id", "account_id")
129
+ REFERENCES "workspaces"("id", "account_id") ON DELETE CASCADE,
130
+ CONSTRAINT "codex_capacity_waiters_workspace_session_fk"
131
+ FOREIGN KEY ("workspace_id", "session_id")
132
+ REFERENCES "sessions"("workspace_id", "id") ON DELETE CASCADE,
133
+ CONSTRAINT "codex_capacity_waiters_workspace_goal_fk"
134
+ FOREIGN KEY ("workspace_id", "goal_id")
135
+ REFERENCES "session_goals"("workspace_id", "id") ON DELETE CASCADE,
136
+ CONSTRAINT "codex_capacity_waiters_workspace_blocked_turn_fk"
137
+ FOREIGN KEY ("workspace_id", "blocked_turn_id")
138
+ REFERENCES "session_turns"("workspace_id", "id") ON DELETE CASCADE,
139
+ CONSTRAINT "codex_capacity_waiters_workspace_resumed_turn_fk"
140
+ FOREIGN KEY ("workspace_id", "resumed_turn_id")
141
+ REFERENCES "session_turns"("workspace_id", "id") ON DELETE SET NULL
142
+ );
143
+
144
+ CREATE UNIQUE INDEX IF NOT EXISTS "codex_capacity_waiters_workspace_session_idx"
145
+ ON "codex_capacity_waiters" ("workspace_id", "session_id");
146
+ CREATE UNIQUE INDEX IF NOT EXISTS "codex_capacity_waiters_workspace_id_idx"
147
+ ON "codex_capacity_waiters" ("workspace_id", "id");
148
+ CREATE INDEX IF NOT EXISTS "codex_capacity_waiters_pending_idx"
149
+ ON "codex_capacity_waiters" ("workspace_id", "status", "next_check_at");
150
+ CREATE INDEX IF NOT EXISTS "codex_capacity_waiters_wake_repair_idx"
151
+ ON "codex_capacity_waiters" ("status", "wake_revision", "observed_wake_revision");
152
+
153
+ ALTER TABLE "codex_capacity_waiters" ENABLE ROW LEVEL SECURITY;
154
+ ALTER TABLE "codex_capacity_waiters" FORCE ROW LEVEL SECURITY;
155
+ DO $$
156
+ BEGIN
157
+ IF EXISTS (
158
+ SELECT 1 FROM pg_policies
159
+ WHERE schemaname = current_schema()
160
+ AND tablename = 'codex_capacity_waiters'
161
+ AND policyname = 'workspace_isolation'
162
+ ) THEN
163
+ DROP POLICY workspace_isolation ON "codex_capacity_waiters";
164
+ END IF;
165
+ END $$;
166
+
167
+ CREATE POLICY workspace_isolation ON "codex_capacity_waiters"
168
+ USING (opengeni_private.workspace_rls_visible(account_id, workspace_id))
169
+ WITH CHECK (opengeni_private.workspace_rls_visible(account_id, workspace_id));
170
+
171
+ -- Defense in depth for legacy id-only pin/last/active FKs. Normal API/worker
172
+ -- accessors already validate workspace ownership; these triggers reject a
173
+ -- malformed internal/maintenance write too.
174
+ CREATE OR REPLACE FUNCTION opengeni_private.enforce_codex_credential_workspace()
175
+ RETURNS trigger
176
+ LANGUAGE plpgsql
177
+ SECURITY DEFINER
178
+ SET search_path = pg_catalog
179
+ AS $$
180
+ DECLARE
181
+ referenced_workspace uuid;
182
+ candidate uuid;
183
+ BEGIN
184
+ IF TG_TABLE_NAME = 'codex_rotation_settings' THEN
185
+ candidate := NEW.active_credential_id;
186
+ ELSIF TG_ARGV[0] = 'pinned' THEN
187
+ candidate := NEW.codex_pinned_credential_id;
188
+ ELSE
189
+ candidate := NEW.codex_last_credential_id;
190
+ END IF;
191
+ IF candidate IS NULL THEN
192
+ RETURN NEW;
193
+ END IF;
194
+ EXECUTE format(
195
+ 'SELECT workspace_id FROM %I.codex_subscription_credentials WHERE id = $1',
196
+ TG_TABLE_SCHEMA
197
+ ) INTO referenced_workspace USING candidate;
198
+ IF referenced_workspace IS DISTINCT FROM NEW.workspace_id THEN
199
+ RAISE EXCEPTION 'Codex credential reference must remain in the row workspace'
200
+ USING ERRCODE = '23514';
201
+ END IF;
202
+ RETURN NEW;
203
+ END $$;
204
+
205
+ DROP TRIGGER IF EXISTS codex_rotation_settings_workspace_guard ON "codex_rotation_settings";
206
+ CREATE TRIGGER codex_rotation_settings_workspace_guard
207
+ BEFORE INSERT OR UPDATE OF active_credential_id, workspace_id ON "codex_rotation_settings"
208
+ FOR EACH ROW EXECUTE FUNCTION opengeni_private.enforce_codex_credential_workspace();
209
+ DROP TRIGGER IF EXISTS sessions_codex_pinned_workspace_guard ON "sessions";
210
+ CREATE TRIGGER sessions_codex_pinned_workspace_guard
211
+ BEFORE INSERT OR UPDATE OF codex_pinned_credential_id, workspace_id ON "sessions"
212
+ FOR EACH ROW EXECUTE FUNCTION opengeni_private.enforce_codex_credential_workspace('pinned');
213
+ DROP TRIGGER IF EXISTS sessions_codex_last_workspace_guard ON "sessions";
214
+ CREATE TRIGGER sessions_codex_last_workspace_guard
215
+ BEFORE INSERT OR UPDATE OF codex_last_credential_id, workspace_id ON "sessions"
216
+ FOR EACH ROW EXECUTE FUNCTION opengeni_private.enforce_codex_credential_workspace('last');
217
+
218
+ DO $$
219
+ DECLARE
220
+ target_schema text := current_schema();
221
+ app_role text := 'opengeni_app';
222
+ BEGIN
223
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = app_role) THEN
224
+ EXECUTE format(
225
+ 'GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA %I TO %I',
226
+ target_schema,
227
+ app_role
228
+ );
229
+ END IF;
230
+ END $$;
@@ -0,0 +1,85 @@
1
+ -- Personal, server-authoritative session pins. A pin belongs to the authenticated
2
+ -- member (`subject_id`), never to the session itself: shared workspace members
3
+ -- independently organize the same durable session rows. Deleting a session or
4
+ -- workspace cleans pins through FKs; no session activity/history field changes.
5
+
6
+ -- Bound the brief catalog/table locks needed for the additive table and foreign keys
7
+ -- so production migration contention fails closed and retries rather than blocking
8
+ -- API writes indefinitely. The composite sessions parent index is owned by the
9
+ -- preceding 0053 migration on current main.
10
+ SET lock_timeout = '5s';
11
+ SET statement_timeout = '10min';
12
+
13
+ CREATE TABLE IF NOT EXISTS "session_pins" (
14
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
15
+ "account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
16
+ "workspace_id" uuid NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
17
+ "subject_id" text NOT NULL,
18
+ "session_id" uuid NOT NULL REFERENCES "sessions"("id") ON DELETE CASCADE,
19
+ "pinned" boolean NOT NULL DEFAULT true,
20
+ "pinned_at" timestamptz DEFAULT now(),
21
+ "version" integer NOT NULL DEFAULT 1,
22
+ CONSTRAINT "session_pins_subject_nonempty" CHECK (length(btrim("subject_id")) > 0),
23
+ CONSTRAINT "session_pins_version_positive" CHECK ("version" >= 1),
24
+ CONSTRAINT "session_pins_state_consistent" CHECK (
25
+ ("pinned" AND "pinned_at" IS NOT NULL)
26
+ OR (NOT "pinned" AND "pinned_at" IS NULL)
27
+ ),
28
+ CONSTRAINT "session_pins_workspace_account_fk"
29
+ FOREIGN KEY ("workspace_id", "account_id")
30
+ REFERENCES "workspaces"("id", "account_id")
31
+ ON DELETE CASCADE,
32
+ CONSTRAINT "session_pins_workspace_session_fk"
33
+ FOREIGN KEY ("workspace_id", "session_id")
34
+ REFERENCES "sessions"("workspace_id", "id")
35
+ ON DELETE CASCADE
36
+ );
37
+
38
+ CREATE UNIQUE INDEX IF NOT EXISTS "session_pins_subject_workspace_session_idx"
39
+ ON "session_pins" ("subject_id", "workspace_id", "session_id");
40
+ CREATE INDEX IF NOT EXISTS "session_pins_workspace_subject_pinned_idx"
41
+ ON "session_pins" ("workspace_id", "subject_id", "pinned", "pinned_at" DESC, "session_id" DESC);
42
+
43
+ ALTER TABLE "session_pins" ENABLE ROW LEVEL SECURITY;
44
+ ALTER TABLE "session_pins" FORCE ROW LEVEL SECURITY;
45
+
46
+ CREATE OR REPLACE FUNCTION opengeni_private.current_subject_id()
47
+ RETURNS text
48
+ LANGUAGE sql
49
+ STABLE
50
+ AS $$
51
+ SELECT nullif(current_setting('opengeni.subject_id', true), '');
52
+ $$;
53
+
54
+ DO $$
55
+ BEGIN
56
+ IF EXISTS (
57
+ SELECT 1 FROM pg_policies
58
+ WHERE schemaname = current_schema() AND tablename = 'session_pins' AND policyname = 'workspace_isolation'
59
+ ) THEN
60
+ DROP POLICY workspace_isolation ON "session_pins";
61
+ END IF;
62
+ END $$;
63
+
64
+ CREATE POLICY workspace_isolation ON "session_pins"
65
+ USING (
66
+ opengeni_private.workspace_rls_visible(account_id, workspace_id)
67
+ AND subject_id = opengeni_private.current_subject_id()
68
+ )
69
+ WITH CHECK (
70
+ opengeni_private.workspace_rls_visible(account_id, workspace_id)
71
+ AND subject_id = opengeni_private.current_subject_id()
72
+ );
73
+
74
+ DO $$
75
+ BEGIN
76
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
77
+ EXECUTE format(
78
+ 'GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA %I TO opengeni_app',
79
+ current_schema()
80
+ );
81
+ END IF;
82
+ END $$;
83
+
84
+ RESET statement_timeout;
85
+ RESET lock_timeout;
@@ -0,0 +1,73 @@
1
+ -- Short-lived server-owned activity-order snapshots for stable session cursors.
2
+ -- The snapshot is subject-scoped as well as workspace-scoped: one member's
3
+ -- continuation chain must never disclose or affect another member's view.
4
+
5
+ SET lock_timeout = '5s';
6
+ SET statement_timeout = '10min';
7
+
8
+ CREATE TABLE IF NOT EXISTS "session_list_snapshots" (
9
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
10
+ "account_id" uuid NOT NULL,
11
+ "workspace_id" uuid NOT NULL,
12
+ "subject_id" text NOT NULL,
13
+ "parent_session_filter" text NOT NULL DEFAULT 'all',
14
+ "search" text,
15
+ "ordinary_session_ids" uuid[] NOT NULL DEFAULT '{}'::uuid[],
16
+ "expires_at" timestamptz NOT NULL,
17
+ "created_at" timestamptz NOT NULL DEFAULT now(),
18
+ CONSTRAINT "session_list_snapshots_workspace_account_fk"
19
+ FOREIGN KEY ("workspace_id", "account_id")
20
+ REFERENCES "workspaces"("id", "account_id")
21
+ ON DELETE CASCADE,
22
+ CONSTRAINT "session_list_snapshots_subject_nonempty"
23
+ CHECK (length(btrim("subject_id")) > 0),
24
+ CONSTRAINT "session_list_snapshots_parent_filter_valid"
25
+ CHECK (
26
+ "parent_session_filter" = 'all'
27
+ OR "parent_session_filter" = 'null'
28
+ OR "parent_session_filter" ~ '^[0-9a-fA-F-]{36}$'
29
+ ),
30
+ CONSTRAINT "session_list_snapshots_search_length"
31
+ CHECK ("search" IS NULL OR length("search") <= 200)
32
+ );
33
+
34
+ CREATE INDEX IF NOT EXISTS "session_list_snapshots_workspace_expiry_idx"
35
+ ON "session_list_snapshots" ("workspace_id", "subject_id", "expires_at");
36
+
37
+ ALTER TABLE "session_list_snapshots" ENABLE ROW LEVEL SECURITY;
38
+ ALTER TABLE "session_list_snapshots" FORCE ROW LEVEL SECURITY;
39
+
40
+ DO $$
41
+ BEGIN
42
+ IF EXISTS (
43
+ SELECT 1 FROM pg_policies
44
+ WHERE schemaname = current_schema()
45
+ AND tablename = 'session_list_snapshots'
46
+ AND policyname = 'workspace_isolation'
47
+ ) THEN
48
+ DROP POLICY workspace_isolation ON "session_list_snapshots";
49
+ END IF;
50
+ END $$;
51
+
52
+ CREATE POLICY workspace_isolation ON "session_list_snapshots"
53
+ USING (
54
+ opengeni_private.workspace_rls_visible(account_id, workspace_id)
55
+ AND subject_id = opengeni_private.current_subject_id()
56
+ )
57
+ WITH CHECK (
58
+ opengeni_private.workspace_rls_visible(account_id, workspace_id)
59
+ AND subject_id = opengeni_private.current_subject_id()
60
+ );
61
+
62
+ DO $$
63
+ BEGIN
64
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
65
+ EXECUTE format(
66
+ 'GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA %I TO opengeni_app',
67
+ current_schema()
68
+ );
69
+ END IF;
70
+ END $$;
71
+
72
+ RESET statement_timeout;
73
+ RESET lock_timeout;
@@ -0,0 +1,48 @@
1
+ -- Per-workspace model/provider availability policy: the HARD blocker deciding
2
+ -- which providers/models may serve a turn in a workspace at all. NULL columns =
3
+ -- unrestricted (exactly today's behavior; no row is ever required). A non-null
4
+ -- allowed_providers is a strict allowlist over resolved provider identities
5
+ -- (the built-in OpenAI/Azure client — including the legacy null-resolution
6
+ -- fallback — maps to one well-known id); allowed_models is an additional exact
7
+ -- model-id allowlist. Enforced at the API model choke points (422) and
8
+ -- authoritatively in the worker after turn model resolution, so a
9
+ -- codex-subscription workspace can be fail-closed to codex: a blocked turn
10
+ -- waits or fails loud, it never falls through to the paid built-in provider.
11
+
12
+ SET lock_timeout = '5s';
13
+ SET statement_timeout = '10min';
14
+
15
+ CREATE TABLE IF NOT EXISTS "workspace_model_policies" (
16
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
17
+ "account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
18
+ "workspace_id" uuid NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
19
+ "allowed_providers" text[],
20
+ "allowed_models" text[],
21
+ "created_at" timestamptz NOT NULL DEFAULT now(),
22
+ "updated_at" timestamptz NOT NULL DEFAULT now()
23
+ );
24
+
25
+ CREATE UNIQUE INDEX IF NOT EXISTS "workspace_model_policies_workspace_idx"
26
+ ON "workspace_model_policies" ("workspace_id");
27
+
28
+ -- RLS — verbatim pattern from 0028_codex_multi_account.sql (codex_rotation_settings).
29
+ ALTER TABLE "workspace_model_policies" ENABLE ROW LEVEL SECURITY;
30
+ ALTER TABLE "workspace_model_policies" FORCE ROW LEVEL SECURITY;
31
+ DO $$
32
+ BEGIN
33
+ IF EXISTS (SELECT 1 FROM pg_policies
34
+ WHERE schemaname = current_schema() AND tablename = 'workspace_model_policies' AND policyname = 'workspace_isolation') THEN
35
+ DROP POLICY workspace_isolation ON "workspace_model_policies";
36
+ END IF;
37
+ END $$;
38
+ CREATE POLICY workspace_isolation ON "workspace_model_policies"
39
+ USING (opengeni_private.workspace_rls_visible(account_id, workspace_id))
40
+ WITH CHECK (opengeni_private.workspace_rls_visible(account_id, workspace_id));
41
+
42
+ -- Re-grant (verbatim from 0028/0024): new tables are not covered by prior grants.
43
+ DO $$
44
+ BEGIN
45
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
46
+ GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO opengeni_app;
47
+ END IF;
48
+ END $$;