@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.
- package/dist/{chunk-OGCE6O2X.js → chunk-7LDU7F5P.js} +31 -3
- package/dist/chunk-7LDU7F5P.js.map +1 -0
- package/dist/chunk-B22X3IEZ.js +2634 -0
- package/dist/chunk-B22X3IEZ.js.map +1 -0
- package/dist/{chunk-57MLICFR.js → chunk-YFQ7SGE4.js} +18 -6
- package/dist/chunk-YFQ7SGE4.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +16571 -5018
- package/dist/index.js.map +1 -1
- package/dist/migrate.js +1 -1
- package/dist/provision-roles.d.ts +2121 -248
- package/dist/provision-roles.js +1 -1
- package/dist/{schema-BUbuMteO.d.ts → schema-BN5mB9xZ.d.ts} +8716 -3432
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +43 -5
- package/drizzle/0044_reap_dead_turn_holders.sql +104 -0
- package/drizzle/0045_workspace_captures.sql +83 -0
- package/drizzle/0045_workspace_memory_v1.sql +71 -0
- package/drizzle/0046_variable_sets_rename.sql +56 -0
- package/drizzle/0047_rigs.sql +151 -0
- package/drizzle/0048_rig_runtime.sql +9 -0
- package/drizzle/0049_enrollment_went_offline.sql +28 -0
- package/drizzle/0050_enrollment_op_stream.sql +1 -0
- package/drizzle/0051_codex_pin_source.sql +48 -0
- package/drizzle/0052_file_upload_cleanup.sql +91 -0
- package/drizzle/0053_codex_credential_leases.sql +230 -0
- package/drizzle/0054_session_pins.sql +85 -0
- package/drizzle/0055_session_list_snapshots.sql +73 -0
- package/drizzle/0056_workspace_model_policies.sql +48 -0
- package/drizzle/0057_durable_queue_control.sql +536 -0
- package/drizzle/0058_turn_admission_usage_enrollment.sql +158 -0
- package/drizzle/0059_workspace_pause_control_kind.sql +12 -0
- package/drizzle/0060_session_system_update_deferral.sql +10 -0
- package/drizzle/0061_session_workflow_wake_outbox.sql +157 -0
- package/drizzle/0062_session_list_snapshot_reaper.sql +48 -0
- package/drizzle/0063_session_control_mega_foundation.sql +1324 -0
- package/package.json +13 -13
- package/src/codex-token-resolver.ts +58 -23
- package/src/connection-token-resolver.ts +146 -57
- package/src/environment-crypto.ts +5 -1
- package/src/event-payload-sanitizer.ts +29 -1
- package/src/index.ts +20990 -6465
- package/src/memory-domain.ts +218 -0
- package/src/migrate.ts +58 -3
- package/src/provision-roles.ts +46 -17
- package/src/schema.ts +2888 -1121
- package/src/session-control.ts +1759 -0
- package/src/session-queue-commands.ts +1753 -0
- package/src/session-tool-call-settlement.ts +269 -0
- package/dist/chunk-57MLICFR.js.map +0 -1
- package/dist/chunk-OGCE6O2X.js.map +0 -1
- package/dist/chunk-ZIUCA2IO.js +0 -1268
- 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 $$;
|