@opengeni/db 0.7.3 → 0.10.7
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-YFQ7SGE4.js → chunk-BMFDXFPA.js} +23 -1
- package/dist/chunk-BMFDXFPA.js.map +1 -0
- package/dist/chunk-KW526IJA.js +127 -0
- package/dist/chunk-KW526IJA.js.map +1 -0
- package/dist/{chunk-B22X3IEZ.js → chunk-P6PKXY5W.js} +604 -32
- package/dist/chunk-P6PKXY5W.js.map +1 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.js +7408 -2276
- package/dist/index.js.map +1 -1
- package/dist/migrate.d.ts +29 -4
- package/dist/migrate.js +3 -1
- package/dist/provision-roles.d.ts +1122 -73
- package/dist/provision-roles.js +1 -1
- package/dist/{schema-BN5mB9xZ.d.ts → schema-CqkzrBRS.d.ts} +6967 -4457
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +19 -1
- package/drizzle/0053_codex_credential_leases.sql +2 -2
- package/drizzle/0057_durable_queue_control.sql +1 -1
- package/drizzle/0061_session_workflow_wake_outbox.sql +1 -1
- package/drizzle/0062_session_list_snapshot_reaper.sql +1 -1
- package/drizzle/0063_session_control_mega_foundation.sql +1 -1
- package/drizzle/0064_rotation_strategy_sharded_backfill.sql +15 -0
- package/drizzle/0065_codex_subscription_overview.sql +168 -0
- package/drizzle/0065_session_attempt_quiescence.sql +26 -0
- package/drizzle/0065_session_tool_policy.sql +38 -0
- package/drizzle/0066_session_interruption_attempt_lookup.sql +4 -0
- package/drizzle/0067_session_event_payload_bounds.sql +209 -0
- package/drizzle/0068_workspace_control_event_bounds.sql +134 -0
- package/drizzle/0069_session_event_history_backfill.sql +32 -0
- package/drizzle/0070_session_event_type_sequence_lookup.sql +4 -0
- package/drizzle/0071_session_event_monitoring_tail.sql +10 -0
- package/drizzle/0072_sessions_workspace_created_id_idx.sql +4 -0
- package/drizzle/0073_sessions_workspace_updated_id_idx.sql +4 -0
- package/drizzle/0074_session_activity_revisions.sql +72 -0
- package/drizzle/0075_sessions_workspace_activity_revision_idx.sql +4 -0
- package/drizzle/0076_session_workflow_wake_acl.sql +13 -0
- package/drizzle/0077_session_attempt_latest_lookup.sql +4 -0
- package/drizzle/0094_quarantine_credential_bearing_catalog_urls.sql +51 -0
- package/drizzle/0095_github_existing_installations.sql +84 -0
- package/drizzle/0096_session_turn_initiators.sql +97 -0
- package/drizzle/0097_host_export_outbox.sql +1220 -0
- package/drizzle/0098_usage_events_workspace_session_idx.sql +4 -0
- package/drizzle/0099_session_human_input_attempt_owner_index.sql +6 -0
- package/drizzle/0100_session_human_input_requests.sql +89 -0
- package/drizzle/0101_session_mcp_connection_refs.sql +30 -0
- package/drizzle/0102_session_command_receipt_service_actor.sql +16 -0
- package/drizzle/0103_host_export_root_session.sql +166 -0
- package/drizzle/0104_host_export_root_session_backfill.sql +27 -0
- package/drizzle/0105_session_turn_instructions.sql +9 -0
- package/drizzle/0106_session_attempt_mcp_approval_policies.sql +29 -0
- package/drizzle/0107_host_export_lineage_contract.sql +381 -0
- package/drizzle/0108_fence_invalidated_warming_epochs.sql +76 -0
- package/package.json +5 -4
- package/src/codex-token-resolver.ts +175 -14
- package/src/connection-token-resolver.ts +428 -119
- package/src/event-payload-sanitizer.ts +88 -20
- package/src/index.ts +7208 -1273
- package/src/memory-domain.ts +1 -1
- package/src/migrate.ts +86 -23
- package/src/persistence-errors.ts +252 -0
- package/src/provision-roles.ts +42 -0
- package/src/schema.ts +658 -34
- package/src/session-control.ts +520 -38
- package/src/session-queue-commands.ts +385 -61
- package/src/session-tool-call-settlement.ts +58 -7
- package/src/turn-initiator.ts +155 -0
- package/dist/chunk-7LDU7F5P.js +0 -80
- package/dist/chunk-7LDU7F5P.js.map +0 -1
- package/dist/chunk-B22X3IEZ.js.map +0 -1
- package/dist/chunk-YFQ7SGE4.js.map +0 -1
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- tracking-64: workspace-control rows are compact invalidations, not evidence blobs.
|
|
3
|
+
-- Preserve exact source byte counts while bounding the two free-form fields so
|
|
4
|
+
-- old binaries and direct writers cannot create a durable replay poison row.
|
|
5
|
+
|
|
6
|
+
SET lock_timeout = '5s';
|
|
7
|
+
SET statement_timeout = '10min';
|
|
8
|
+
|
|
9
|
+
ALTER TABLE workspace_control_events
|
|
10
|
+
ADD COLUMN reason_original_bytes integer,
|
|
11
|
+
ADD COLUMN actor_original_bytes integer;
|
|
12
|
+
|
|
13
|
+
CREATE OR REPLACE FUNCTION opengeni_private.truncate_workspace_control_text(
|
|
14
|
+
source_text text,
|
|
15
|
+
max_bytes integer
|
|
16
|
+
)
|
|
17
|
+
RETURNS text
|
|
18
|
+
LANGUAGE plpgsql
|
|
19
|
+
IMMUTABLE
|
|
20
|
+
STRICT
|
|
21
|
+
SET search_path = pg_catalog
|
|
22
|
+
AS $function$
|
|
23
|
+
DECLARE
|
|
24
|
+
marker constant text := '…[truncated]';
|
|
25
|
+
prefix_budget integer := greatest(0, max_bytes - octet_length(marker));
|
|
26
|
+
candidate text;
|
|
27
|
+
excess integer;
|
|
28
|
+
BEGIN
|
|
29
|
+
IF octet_length(source_text) <= max_bytes THEN
|
|
30
|
+
RETURN source_text;
|
|
31
|
+
END IF;
|
|
32
|
+
|
|
33
|
+
-- left() is character-safe. Starting at at most prefix_budget characters
|
|
34
|
+
-- caps allocation even for a multi-megabyte legacy value; each pass removes
|
|
35
|
+
-- enough characters for the worst-case four-byte UTF-8 encoding.
|
|
36
|
+
candidate := left(source_text, prefix_budget);
|
|
37
|
+
WHILE octet_length(candidate) > prefix_budget LOOP
|
|
38
|
+
excess := octet_length(candidate) - prefix_budget;
|
|
39
|
+
candidate := left(candidate, greatest(0, char_length(candidate) - ceil(excess / 4.0)::integer));
|
|
40
|
+
END LOOP;
|
|
41
|
+
RETURN candidate || marker;
|
|
42
|
+
END;
|
|
43
|
+
$function$;
|
|
44
|
+
|
|
45
|
+
CREATE OR REPLACE FUNCTION opengeni_private.bound_workspace_control_event()
|
|
46
|
+
RETURNS trigger
|
|
47
|
+
LANGUAGE plpgsql
|
|
48
|
+
SET search_path = pg_catalog
|
|
49
|
+
AS $function$
|
|
50
|
+
DECLARE
|
|
51
|
+
source_bytes integer;
|
|
52
|
+
BEGIN
|
|
53
|
+
IF NEW.reason IS NULL THEN
|
|
54
|
+
NEW.reason_original_bytes := NULL;
|
|
55
|
+
ELSE
|
|
56
|
+
source_bytes := octet_length(NEW.reason);
|
|
57
|
+
IF TG_OP = 'INSERT' THEN
|
|
58
|
+
NEW.reason_original_bytes := greatest(source_bytes, coalesce(NEW.reason_original_bytes, 0));
|
|
59
|
+
ELSIF NEW.reason IS DISTINCT FROM OLD.reason THEN
|
|
60
|
+
-- An update from an old binary carries the old metadata column even
|
|
61
|
+
-- though it knows only `reason`. A genuinely changed raw value starts a
|
|
62
|
+
-- new byte fact instead of inheriting the previous value's larger count.
|
|
63
|
+
NEW.reason_original_bytes := source_bytes;
|
|
64
|
+
ELSE
|
|
65
|
+
NEW.reason_original_bytes := greatest(
|
|
66
|
+
source_bytes,
|
|
67
|
+
coalesce(NEW.reason_original_bytes, OLD.reason_original_bytes, 0)
|
|
68
|
+
);
|
|
69
|
+
END IF;
|
|
70
|
+
NEW.reason := opengeni_private.truncate_workspace_control_text(NEW.reason, 8192);
|
|
71
|
+
END IF;
|
|
72
|
+
|
|
73
|
+
source_bytes := octet_length(NEW.actor);
|
|
74
|
+
IF TG_OP = 'INSERT' THEN
|
|
75
|
+
NEW.actor_original_bytes := greatest(source_bytes, coalesce(NEW.actor_original_bytes, 0));
|
|
76
|
+
ELSIF NEW.actor IS DISTINCT FROM OLD.actor THEN
|
|
77
|
+
NEW.actor_original_bytes := source_bytes;
|
|
78
|
+
ELSE
|
|
79
|
+
NEW.actor_original_bytes := greatest(
|
|
80
|
+
source_bytes,
|
|
81
|
+
coalesce(NEW.actor_original_bytes, OLD.actor_original_bytes, 0)
|
|
82
|
+
);
|
|
83
|
+
END IF;
|
|
84
|
+
NEW.actor := opengeni_private.truncate_workspace_control_text(NEW.actor, 1024);
|
|
85
|
+
RETURN NEW;
|
|
86
|
+
END;
|
|
87
|
+
$function$;
|
|
88
|
+
|
|
89
|
+
CREATE TRIGGER workspace_control_events_bound_fields
|
|
90
|
+
BEFORE INSERT OR UPDATE OF reason, actor, reason_original_bytes, actor_original_bytes
|
|
91
|
+
ON workspace_control_events
|
|
92
|
+
FOR EACH ROW EXECUTE FUNCTION opengeni_private.bound_workspace_control_event();
|
|
93
|
+
|
|
94
|
+
-- Rewrite only historical rows that violate the new durable caps. Already
|
|
95
|
+
-- bounded rows are safe to serve as-is and retain null metadata as the explicit
|
|
96
|
+
-- rolling-upgrade legacy shape; avoiding a blanket UPDATE keeps installation
|
|
97
|
+
-- work proportional to poison rows instead of table cardinality. The original
|
|
98
|
+
-- counts are supplied in the same statement so truncation never erases truth.
|
|
99
|
+
UPDATE workspace_control_events
|
|
100
|
+
SET
|
|
101
|
+
reason_original_bytes = CASE WHEN reason IS NULL THEN NULL ELSE octet_length(reason) END,
|
|
102
|
+
actor_original_bytes = octet_length(actor),
|
|
103
|
+
reason = reason,
|
|
104
|
+
actor = actor
|
|
105
|
+
WHERE octet_length(reason) > 8192
|
|
106
|
+
OR octet_length(actor) > 1024;
|
|
107
|
+
|
|
108
|
+
ALTER TABLE workspace_control_events
|
|
109
|
+
ADD CONSTRAINT workspace_control_events_reason_bytes_check
|
|
110
|
+
CHECK (reason IS NULL OR octet_length(reason) <= 8192) NOT VALID,
|
|
111
|
+
ADD CONSTRAINT workspace_control_events_actor_bytes_check
|
|
112
|
+
CHECK (octet_length(actor) <= 1024) NOT VALID,
|
|
113
|
+
ADD CONSTRAINT workspace_control_events_original_bytes_check
|
|
114
|
+
CHECK (
|
|
115
|
+
(reason IS NULL AND reason_original_bytes IS NULL)
|
|
116
|
+
OR (
|
|
117
|
+
reason IS NOT NULL
|
|
118
|
+
AND (
|
|
119
|
+
reason_original_bytes IS NULL
|
|
120
|
+
OR reason_original_bytes >= octet_length(reason)
|
|
121
|
+
)
|
|
122
|
+
)
|
|
123
|
+
) NOT VALID,
|
|
124
|
+
ADD CONSTRAINT workspace_control_events_actor_original_bytes_check
|
|
125
|
+
CHECK (
|
|
126
|
+
actor_original_bytes IS NULL
|
|
127
|
+
OR actor_original_bytes >= octet_length(actor)
|
|
128
|
+
) NOT VALID;
|
|
129
|
+
|
|
130
|
+
ALTER TABLE workspace_control_events
|
|
131
|
+
VALIDATE CONSTRAINT workspace_control_events_reason_bytes_check,
|
|
132
|
+
VALIDATE CONSTRAINT workspace_control_events_actor_bytes_check,
|
|
133
|
+
VALIDATE CONSTRAINT workspace_control_events_original_bytes_check,
|
|
134
|
+
VALIDATE CONSTRAINT workspace_control_events_actor_original_bytes_check;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- tracking-64: finish the 0067 expand/contract sequence. The trigger has bounded
|
|
3
|
+
-- every new/updated row since 0067; now rewrite only legacy violations through
|
|
4
|
+
-- that trigger and validate the durable storage invariant.
|
|
5
|
+
|
|
6
|
+
SET lock_timeout = '5s';
|
|
7
|
+
SET statement_timeout = '10min';
|
|
8
|
+
|
|
9
|
+
UPDATE session_events
|
|
10
|
+
SET
|
|
11
|
+
payload = payload,
|
|
12
|
+
type = type,
|
|
13
|
+
client_event_id = client_event_id,
|
|
14
|
+
producer_id = producer_id,
|
|
15
|
+
turn_association = turn_association,
|
|
16
|
+
duplicate_reason = duplicate_reason
|
|
17
|
+
WHERE octet_length(payload::text) > 65536
|
|
18
|
+
OR octet_length(type) > 256
|
|
19
|
+
OR position(E'\n' in type) > 0
|
|
20
|
+
OR position(E'\r' in type) > 0
|
|
21
|
+
OR (client_event_id IS NOT NULL AND octet_length(client_event_id) > 1024)
|
|
22
|
+
OR (producer_id IS NOT NULL AND octet_length(producer_id) > 1024)
|
|
23
|
+
OR (turn_association IS NOT NULL AND octet_length(turn_association) > 64)
|
|
24
|
+
OR (duplicate_reason IS NOT NULL AND octet_length(duplicate_reason) > 4096);
|
|
25
|
+
|
|
26
|
+
ALTER TABLE session_events
|
|
27
|
+
VALIDATE CONSTRAINT session_events_payload_bytes_check,
|
|
28
|
+
VALIDATE CONSTRAINT session_events_type_bytes_check,
|
|
29
|
+
VALIDATE CONSTRAINT session_events_client_event_id_bytes_check,
|
|
30
|
+
VALIDATE CONSTRAINT session_events_producer_id_bytes_check,
|
|
31
|
+
VALIDATE CONSTRAINT session_events_turn_association_bytes_check,
|
|
32
|
+
VALIDATE CONSTRAINT session_events_duplicate_reason_bytes_check;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- opengeni:concurrent-index lock-timeout=5s
|
|
3
|
+
CREATE INDEX CONCURRENTLY "session_events_workspace_session_monitoring_tail_idx"
|
|
4
|
+
ON "session_events" ("workspace_id", "session_id", "sequence" DESC)
|
|
5
|
+
WHERE "type" NOT IN (
|
|
6
|
+
'agent.message.delta',
|
|
7
|
+
'agent.reasoning.delta',
|
|
8
|
+
'sandbox.command.output.delta',
|
|
9
|
+
'terminal.pty.output.delta'
|
|
10
|
+
);
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- tracking-65: transactional workspace revisions make updated-order discovery and
|
|
3
|
+
-- its next incremental scan one gap-free handoff independent of application
|
|
4
|
+
-- clocks. Existing sessions remain revision zero until their next activity.
|
|
5
|
+
|
|
6
|
+
SET lock_timeout = '5s';
|
|
7
|
+
SET statement_timeout = '10min';
|
|
8
|
+
|
|
9
|
+
CREATE TABLE "workspace_session_activity_revisions" (
|
|
10
|
+
"workspace_id" uuid PRIMARY KEY,
|
|
11
|
+
"account_id" uuid NOT NULL,
|
|
12
|
+
"revision" bigint NOT NULL DEFAULT 0,
|
|
13
|
+
CONSTRAINT "workspace_session_activity_revisions_workspace_account_fk"
|
|
14
|
+
FOREIGN KEY ("workspace_id", "account_id")
|
|
15
|
+
REFERENCES "workspaces"("id", "account_id") ON DELETE CASCADE,
|
|
16
|
+
CONSTRAINT "workspace_session_activity_revisions_revision_check"
|
|
17
|
+
CHECK ("revision" >= 0)
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
ALTER TABLE "workspace_session_activity_revisions" ENABLE ROW LEVEL SECURITY;
|
|
21
|
+
ALTER TABLE "workspace_session_activity_revisions" FORCE ROW LEVEL SECURITY;
|
|
22
|
+
CREATE POLICY workspace_isolation ON "workspace_session_activity_revisions"
|
|
23
|
+
USING (opengeni_private.workspace_rls_visible(account_id, workspace_id))
|
|
24
|
+
WITH CHECK (opengeni_private.workspace_rls_visible(account_id, workspace_id));
|
|
25
|
+
|
|
26
|
+
-- PostgreSQL installs this constant default without rewriting existing rows.
|
|
27
|
+
-- Revision zero is an intentional legacy bucket ordered by (updated_at, id).
|
|
28
|
+
ALTER TABLE "sessions"
|
|
29
|
+
ADD COLUMN "activity_revision" bigint NOT NULL DEFAULT 0;
|
|
30
|
+
|
|
31
|
+
CREATE OR REPLACE FUNCTION opengeni_private.assign_session_activity_revision()
|
|
32
|
+
RETURNS trigger
|
|
33
|
+
LANGUAGE plpgsql
|
|
34
|
+
SET search_path = pg_catalog
|
|
35
|
+
AS $function$
|
|
36
|
+
DECLARE
|
|
37
|
+
next_revision bigint;
|
|
38
|
+
BEGIN
|
|
39
|
+
EXECUTE format(
|
|
40
|
+
'INSERT INTO %I.workspace_session_activity_revisions AS counter '
|
|
41
|
+
|| '(workspace_id, account_id, revision) VALUES ($1, $2, 1) '
|
|
42
|
+
|| 'ON CONFLICT (workspace_id) DO UPDATE SET '
|
|
43
|
+
|| 'account_id = excluded.account_id, revision = counter.revision + 1 '
|
|
44
|
+
|| 'RETURNING revision',
|
|
45
|
+
TG_TABLE_SCHEMA
|
|
46
|
+
)
|
|
47
|
+
INTO next_revision
|
|
48
|
+
USING NEW.workspace_id, NEW.account_id;
|
|
49
|
+
|
|
50
|
+
NEW.activity_revision := next_revision;
|
|
51
|
+
RETURN NEW;
|
|
52
|
+
END;
|
|
53
|
+
$function$;
|
|
54
|
+
|
|
55
|
+
CREATE TRIGGER sessions_assign_activity_revision
|
|
56
|
+
BEFORE INSERT OR UPDATE OF updated_at, activity_revision
|
|
57
|
+
ON "sessions"
|
|
58
|
+
FOR EACH ROW EXECUTE FUNCTION opengeni_private.assign_session_activity_revision();
|
|
59
|
+
|
|
60
|
+
DO $grants$
|
|
61
|
+
DECLARE target_schema text := current_schema();
|
|
62
|
+
BEGIN
|
|
63
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
|
|
64
|
+
EXECUTE format(
|
|
65
|
+
'GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE %I.workspace_session_activity_revisions TO opengeni_app',
|
|
66
|
+
target_schema
|
|
67
|
+
);
|
|
68
|
+
END IF;
|
|
69
|
+
END $grants$;
|
|
70
|
+
|
|
71
|
+
RESET statement_timeout;
|
|
72
|
+
RESET lock_timeout;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- Migration 0063 recreates this SECURITY DEFINER function and revokes its
|
|
3
|
+
-- default PUBLIC authority. Restore only the runtime dispatcher's exact grant;
|
|
4
|
+
-- production Helm runs migrations without a separate provision-roles pass.
|
|
5
|
+
REVOKE ALL ON FUNCTION opengeni_private.claim_session_workflow_wakes(integer) FROM PUBLIC;
|
|
6
|
+
|
|
7
|
+
DO $$
|
|
8
|
+
BEGIN
|
|
9
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
|
|
10
|
+
GRANT EXECUTE ON FUNCTION opengeni_private.claim_session_workflow_wakes(integer)
|
|
11
|
+
TO opengeni_app;
|
|
12
|
+
END IF;
|
|
13
|
+
END $$;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- Catalog endpoints are public data. Remove opaque query/fragment/userinfo/path URLs
|
|
3
|
+
-- imported before the catalog hygiene gate and prevent their continued use.
|
|
4
|
+
DELETE FROM "capability_installations" AS "installation"
|
|
5
|
+
WHERE EXISTS (
|
|
6
|
+
SELECT 1
|
|
7
|
+
FROM "capability_catalog_items" AS "registry_item"
|
|
8
|
+
WHERE
|
|
9
|
+
"registry_item"."id" = "installation"."capability_id"
|
|
10
|
+
AND "registry_item"."workspace_id" IS NULL
|
|
11
|
+
AND "registry_item"."source" = 'registry'
|
|
12
|
+
AND "registry_item"."metadata" ->> 'registry' = 'integrations.sh'
|
|
13
|
+
AND (
|
|
14
|
+
"registry_item"."endpoint_url" LIKE '%?%'
|
|
15
|
+
OR "registry_item"."endpoint_url" LIKE '%#%'
|
|
16
|
+
OR "registry_item"."endpoint_url" ~* '^https?://[^/?#]*@'
|
|
17
|
+
OR "registry_item"."endpoint_url" ~* '^https?://[^/?#]+/(?:[^/?#]*/)*[^/?#]{24,}(?:/|$)'
|
|
18
|
+
OR "registry_item"."mcp_url" LIKE '%?%'
|
|
19
|
+
OR "registry_item"."mcp_url" LIKE '%#%'
|
|
20
|
+
OR "registry_item"."mcp_url" ~* '^https?://[^/?#]*@'
|
|
21
|
+
OR "registry_item"."mcp_url" ~* '^https?://[^/?#]+/(?:[^/?#]*/)*[^/?#]{24,}(?:/|$)'
|
|
22
|
+
)
|
|
23
|
+
)
|
|
24
|
+
AND NOT EXISTS (
|
|
25
|
+
SELECT 1
|
|
26
|
+
FROM "capability_catalog_items" AS "workspace_item"
|
|
27
|
+
WHERE
|
|
28
|
+
"workspace_item"."workspace_id" = "installation"."workspace_id"
|
|
29
|
+
AND "workspace_item"."id" = "installation"."capability_id"
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
DELETE FROM "capability_catalog_items"
|
|
33
|
+
WHERE
|
|
34
|
+
"source" = 'registry'
|
|
35
|
+
AND "metadata" ->> 'registry' = 'integrations.sh'
|
|
36
|
+
AND (
|
|
37
|
+
"endpoint_url" LIKE '%?%'
|
|
38
|
+
OR "endpoint_url" LIKE '%#%'
|
|
39
|
+
OR "endpoint_url" ~* '^https?://[^/?#]*@'
|
|
40
|
+
OR "endpoint_url" ~* '^https?://[^/?#]+/(?:[^/?#]*/)*[^/?#]{24,}(?:/|$)'
|
|
41
|
+
OR "mcp_url" LIKE '%?%'
|
|
42
|
+
OR "mcp_url" LIKE '%#%'
|
|
43
|
+
OR "mcp_url" ~* '^https?://[^/?#]*@'
|
|
44
|
+
OR "mcp_url" ~* '^https?://[^/?#]+/(?:[^/?#]*/)*[^/?#]{24,}(?:/|$)'
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
-- Earlier import diagnostics may contain the same URLs. Counts remain in their
|
|
48
|
+
-- dedicated columns; the unstructured diagnostic payload is not authoritative.
|
|
49
|
+
UPDATE "import_batches"
|
|
50
|
+
SET "details" = '{}'::jsonb, "updated_at" = NOW()
|
|
51
|
+
WHERE "source" = 'integrations.sh';
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
|
|
3
|
+
ALTER TABLE "github_installations"
|
|
4
|
+
ADD COLUMN IF NOT EXISTS "repository_scope" text NOT NULL DEFAULT 'all';
|
|
5
|
+
ALTER TABLE "github_installations"
|
|
6
|
+
ADD COLUMN IF NOT EXISTS "linked_by_subject_id" text;
|
|
7
|
+
|
|
8
|
+
DO $$
|
|
9
|
+
BEGIN
|
|
10
|
+
IF NOT EXISTS (
|
|
11
|
+
SELECT 1 FROM pg_constraint
|
|
12
|
+
WHERE conname = 'github_installations_repository_scope_check'
|
|
13
|
+
AND conrelid = 'github_installations'::regclass
|
|
14
|
+
) THEN
|
|
15
|
+
ALTER TABLE "github_installations"
|
|
16
|
+
ADD CONSTRAINT "github_installations_repository_scope_check"
|
|
17
|
+
CHECK ("repository_scope" IN ('all', 'selected'));
|
|
18
|
+
END IF;
|
|
19
|
+
END $$;
|
|
20
|
+
|
|
21
|
+
CREATE TABLE IF NOT EXISTS "github_installation_repositories" (
|
|
22
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
|
23
|
+
"account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
|
|
24
|
+
"workspace_id" uuid NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
|
|
25
|
+
"installation_id" integer NOT NULL,
|
|
26
|
+
"repository_id" bigint NOT NULL,
|
|
27
|
+
"created_at" timestamptz NOT NULL DEFAULT now()
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
DO $$
|
|
31
|
+
BEGIN
|
|
32
|
+
IF NOT EXISTS (
|
|
33
|
+
SELECT 1 FROM pg_constraint
|
|
34
|
+
WHERE conname = 'github_installation_repositories_workspace_account_fk'
|
|
35
|
+
AND conrelid = 'github_installation_repositories'::regclass
|
|
36
|
+
) THEN
|
|
37
|
+
ALTER TABLE "github_installation_repositories"
|
|
38
|
+
ADD CONSTRAINT "github_installation_repositories_workspace_account_fk"
|
|
39
|
+
FOREIGN KEY ("workspace_id", "account_id")
|
|
40
|
+
REFERENCES "workspaces"("id", "account_id") ON DELETE CASCADE;
|
|
41
|
+
END IF;
|
|
42
|
+
IF NOT EXISTS (
|
|
43
|
+
SELECT 1 FROM pg_constraint
|
|
44
|
+
WHERE conname = 'github_installation_repositories_installation_fk'
|
|
45
|
+
AND conrelid = 'github_installation_repositories'::regclass
|
|
46
|
+
) THEN
|
|
47
|
+
ALTER TABLE "github_installation_repositories"
|
|
48
|
+
ADD CONSTRAINT "github_installation_repositories_installation_fk"
|
|
49
|
+
FOREIGN KEY ("workspace_id", "installation_id")
|
|
50
|
+
REFERENCES "github_installations"("workspace_id", "installation_id") ON DELETE CASCADE;
|
|
51
|
+
END IF;
|
|
52
|
+
END $$;
|
|
53
|
+
|
|
54
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "github_install_repo_workspace_installation_repo_idx"
|
|
55
|
+
ON "github_installation_repositories" ("workspace_id", "installation_id", "repository_id");
|
|
56
|
+
CREATE INDEX IF NOT EXISTS "github_install_repo_workspace_installation_idx"
|
|
57
|
+
ON "github_installation_repositories" ("workspace_id", "installation_id");
|
|
58
|
+
|
|
59
|
+
ALTER TABLE "github_installation_repositories" ENABLE ROW LEVEL SECURITY;
|
|
60
|
+
ALTER TABLE "github_installation_repositories" FORCE ROW LEVEL SECURITY;
|
|
61
|
+
DO $$
|
|
62
|
+
BEGIN
|
|
63
|
+
IF EXISTS (
|
|
64
|
+
SELECT 1 FROM pg_policies
|
|
65
|
+
WHERE schemaname = current_schema()
|
|
66
|
+
AND tablename = 'github_installation_repositories'
|
|
67
|
+
AND policyname = 'workspace_isolation'
|
|
68
|
+
) THEN
|
|
69
|
+
DROP POLICY workspace_isolation ON "github_installation_repositories";
|
|
70
|
+
END IF;
|
|
71
|
+
END $$;
|
|
72
|
+
CREATE POLICY workspace_isolation ON "github_installation_repositories"
|
|
73
|
+
USING (opengeni_private.workspace_rls_visible(account_id, workspace_id))
|
|
74
|
+
WITH CHECK (opengeni_private.workspace_rls_visible(account_id, workspace_id));
|
|
75
|
+
|
|
76
|
+
DO $$
|
|
77
|
+
BEGIN
|
|
78
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
|
|
79
|
+
EXECUTE format(
|
|
80
|
+
'GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA %I TO opengeni_app',
|
|
81
|
+
current_schema()
|
|
82
|
+
);
|
|
83
|
+
END IF;
|
|
84
|
+
END $$;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- Existing rows and old rolling-deploy writers receive an explicit service
|
|
3
|
+
-- sentinel. Host credential resolvers must reject that sentinel; it is never
|
|
4
|
+
-- inferred back to a session creator.
|
|
5
|
+
|
|
6
|
+
ALTER TABLE "sessions"
|
|
7
|
+
ADD COLUMN IF NOT EXISTS "created_by_kind" text NOT NULL DEFAULT 'service',
|
|
8
|
+
ADD COLUMN IF NOT EXISTS "created_by_subject_id" text NOT NULL DEFAULT 'unattributed-legacy',
|
|
9
|
+
ADD COLUMN IF NOT EXISTS "created_by_context" jsonb NOT NULL DEFAULT '{"backfill":true}'::jsonb;
|
|
10
|
+
|
|
11
|
+
ALTER TABLE "session_turns"
|
|
12
|
+
ADD COLUMN IF NOT EXISTS "initiator_kind" text NOT NULL DEFAULT 'service',
|
|
13
|
+
ADD COLUMN IF NOT EXISTS "initiator_subject_id" text NOT NULL DEFAULT 'unattributed-legacy',
|
|
14
|
+
ADD COLUMN IF NOT EXISTS "initiator_context" jsonb NOT NULL DEFAULT '{"backfill":true}'::jsonb;
|
|
15
|
+
|
|
16
|
+
DO $$
|
|
17
|
+
BEGIN
|
|
18
|
+
IF NOT EXISTS (
|
|
19
|
+
SELECT 1 FROM pg_constraint
|
|
20
|
+
WHERE conname = 'sessions_created_by_kind_check'
|
|
21
|
+
AND conrelid = 'sessions'::regclass
|
|
22
|
+
) THEN
|
|
23
|
+
ALTER TABLE "sessions"
|
|
24
|
+
ADD CONSTRAINT "sessions_created_by_kind_check"
|
|
25
|
+
CHECK ("created_by_kind" IN ('subject', 'service')) NOT VALID;
|
|
26
|
+
END IF;
|
|
27
|
+
IF NOT EXISTS (
|
|
28
|
+
SELECT 1 FROM pg_constraint
|
|
29
|
+
WHERE conname = 'session_turns_initiator_kind_check'
|
|
30
|
+
AND conrelid = 'session_turns'::regclass
|
|
31
|
+
) THEN
|
|
32
|
+
ALTER TABLE "session_turns"
|
|
33
|
+
ADD CONSTRAINT "session_turns_initiator_kind_check"
|
|
34
|
+
CHECK ("initiator_kind" IN ('subject', 'service')) NOT VALID;
|
|
35
|
+
END IF;
|
|
36
|
+
END $$;
|
|
37
|
+
|
|
38
|
+
ALTER TABLE "sessions" VALIDATE CONSTRAINT "sessions_created_by_kind_check";
|
|
39
|
+
ALTER TABLE "session_turns" VALIDATE CONSTRAINT "session_turns_initiator_kind_check";
|
|
40
|
+
|
|
41
|
+
CREATE OR REPLACE FUNCTION opengeni_private.prevent_session_creator_mutation()
|
|
42
|
+
RETURNS trigger
|
|
43
|
+
LANGUAGE plpgsql
|
|
44
|
+
AS $$
|
|
45
|
+
BEGIN
|
|
46
|
+
IF NEW.created_by_kind IS DISTINCT FROM OLD.created_by_kind
|
|
47
|
+
OR NEW.created_by_subject_id IS DISTINCT FROM OLD.created_by_subject_id
|
|
48
|
+
OR NEW.created_by_context IS DISTINCT FROM OLD.created_by_context
|
|
49
|
+
THEN
|
|
50
|
+
RAISE EXCEPTION 'session creator is immutable' USING ERRCODE = '23514';
|
|
51
|
+
END IF;
|
|
52
|
+
RETURN NEW;
|
|
53
|
+
END;
|
|
54
|
+
$$;
|
|
55
|
+
|
|
56
|
+
CREATE OR REPLACE FUNCTION opengeni_private.prevent_session_turn_initiator_mutation()
|
|
57
|
+
RETURNS trigger
|
|
58
|
+
LANGUAGE plpgsql
|
|
59
|
+
AS $$
|
|
60
|
+
BEGIN
|
|
61
|
+
IF NEW.initiator_kind IS DISTINCT FROM OLD.initiator_kind
|
|
62
|
+
OR NEW.initiator_subject_id IS DISTINCT FROM OLD.initiator_subject_id
|
|
63
|
+
OR NEW.initiator_context IS DISTINCT FROM OLD.initiator_context
|
|
64
|
+
THEN
|
|
65
|
+
RAISE EXCEPTION 'session turn initiator is immutable' USING ERRCODE = '23514';
|
|
66
|
+
END IF;
|
|
67
|
+
RETURN NEW;
|
|
68
|
+
END;
|
|
69
|
+
$$;
|
|
70
|
+
|
|
71
|
+
DO $$
|
|
72
|
+
BEGIN
|
|
73
|
+
IF NOT EXISTS (
|
|
74
|
+
SELECT 1 FROM pg_trigger
|
|
75
|
+
WHERE tgname = 'sessions_creator_immutable'
|
|
76
|
+
AND tgrelid = 'sessions'::regclass
|
|
77
|
+
AND NOT tgisinternal
|
|
78
|
+
) THEN
|
|
79
|
+
CREATE TRIGGER sessions_creator_immutable
|
|
80
|
+
BEFORE UPDATE OF created_by_kind, created_by_subject_id, created_by_context
|
|
81
|
+
ON "sessions"
|
|
82
|
+
FOR EACH ROW
|
|
83
|
+
EXECUTE FUNCTION opengeni_private.prevent_session_creator_mutation();
|
|
84
|
+
END IF;
|
|
85
|
+
IF NOT EXISTS (
|
|
86
|
+
SELECT 1 FROM pg_trigger
|
|
87
|
+
WHERE tgname = 'session_turns_initiator_immutable'
|
|
88
|
+
AND tgrelid = 'session_turns'::regclass
|
|
89
|
+
AND NOT tgisinternal
|
|
90
|
+
) THEN
|
|
91
|
+
CREATE TRIGGER session_turns_initiator_immutable
|
|
92
|
+
BEFORE UPDATE OF initiator_kind, initiator_subject_id, initiator_context
|
|
93
|
+
ON "session_turns"
|
|
94
|
+
FOR EACH ROW
|
|
95
|
+
EXECUTE FUNCTION opengeni_private.prevent_session_turn_initiator_mutation();
|
|
96
|
+
END IF;
|
|
97
|
+
END $$;
|