@tangen/db 0.0.0-stage → 0.1.55
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/README.md +73 -2
- package/drizzle/0000_flimsy_shotgun.sql +339 -0
- package/drizzle/0001_app_data.sql +11 -0
- package/drizzle/0002_add_indexes.sql +36 -0
- package/drizzle/0003_extension_data_rename.sql +3 -0
- package/drizzle/0004_extension_data_published_globals.sql +12 -0
- package/drizzle/0005_auth.sql +51 -0
- package/drizzle/0006_auth_identities.sql +13 -0
- package/drizzle/0007_ai.sql +18 -0
- package/drizzle/0008_ai_chat_threads.sql +5 -0
- package/drizzle/0009_forms_source_slug.sql +11 -0
- package/drizzle/0010_rbac_roles_status.sql +14 -0
- package/drizzle/0011_jobs_v2.sql +67 -0
- package/drizzle/0012_search_platform.sql +36 -0
- package/drizzle/0013_search_synonym_label.sql +2 -0
- package/drizzle/0014_object_types_disabled.sql +2 -0
- package/drizzle/0015_playground_collections.sql +25 -0
- package/drizzle/0016_catchup_activity_promotion.sql +36 -0
- package/drizzle/0017_fields_disabled.sql +3 -0
- package/drizzle/0018_object_types_plural.sql +1 -0
- package/drizzle/0019_member_profile.sql +2 -0
- package/drizzle/0020_paused_jobs.sql +7 -0
- package/drizzle/0021_form_submission_notes.sql +2 -0
- package/drizzle/0022_paused_jobs_env_reason.sql +4 -0
- package/drizzle/0023_scale_indexes.sql +3 -0
- package/drizzle/0024_migration_runs.sql +24 -0
- package/drizzle/0025_porter_transfer.sql +30 -0
- package/drizzle/0026_cron_timezone.sql +1 -0
- package/drizzle/0027_rate_limit_counters.sql +6 -0
- package/drizzle/0028_realtime_events.sql +10 -0
- package/drizzle/0030_delivery_artifacts.sql +10 -0
- package/drizzle/0031_realtime_presence.sql +8 -0
- package/drizzle/0032_schema_pushes.sql +15 -0
- package/drizzle/0033_studio_design_capability.sql +37 -0
- package/drizzle/0034_object_types_provided_by.sql +12 -0
- package/drizzle/0035_custom_table_owners.sql +24 -0
- package/drizzle/0036_revision_version_unique.sql +42 -0
- package/drizzle/0037_job_queue_dedupe.sql +20 -0
- package/drizzle/0038_search_documents.sql +87 -0
- package/drizzle/0039_job_prune_indexes.sql +25 -0
- package/drizzle/0040_extension_data_object_idx.sql +10 -0
- package/drizzle/0041_knowledge.sql +79 -0
- package/drizzle/0042_schema_pushes_checksum.sql +7 -0
- package/drizzle/0043_guard_adoption.sql +12 -0
- package/drizzle/0044_retention_indexes.sql +7 -0
- package/drizzle/0045_extension_install_per_env.sql +40 -0
- package/drizzle/0046_extension_migrations.sql +18 -0
- package/drizzle/0047_environment_role.sql +25 -0
- package/drizzle/0048_environment_role_adoption.sql +30 -0
- package/drizzle/0049_release_lifecycle.sql +55 -0
- package/drizzle/0050_member_env_membership.sql +21 -0
- package/drizzle/0051_remove_environments.sql +350 -0
- package/drizzle/0052_release_actor_columns.sql +9 -0
- package/drizzle/0053_kernel_event_record.sql +26 -0
- package/drizzle/0054_kernel_run_identity.sql +13 -0
- package/drizzle/0055_kernel_identities.sql +19 -0
- package/drizzle/0056_forms_leave_core.sql +7 -0
- package/drizzle/0057_auth_leave_core.sql +8 -0
- package/drizzle/0058_member_grants.sql +11 -0
- package/drizzle/0059_extension_granted_grants.sql +7 -0
- package/drizzle/0060_connections.sql +21 -0
- package/drizzle/meta/0000_snapshot.json +1692 -0
- package/drizzle/meta/0002_snapshot.json +1935 -0
- package/drizzle/meta/0003_snapshot.json +1935 -0
- package/drizzle/meta/_journal.json +426 -0
- package/package.json +85 -4
- package/src/cache-keys.ts +43 -0
- package/src/client.ts +136 -0
- package/src/content-ddl.ts +70 -0
- package/src/content-tables.ts +110 -0
- package/src/custom-tables-loader.ts +65 -0
- package/src/ensure-system-schema.ts +195 -0
- package/src/extension-install-state.ts +220 -0
- package/src/extension-migrations.ts +270 -0
- package/src/generated/content-tables.ts +271 -0
- package/src/generated/custom-tables.ts +18 -0
- package/src/index.ts +24 -0
- package/src/instrument.ts +139 -0
- package/src/knowledge-schema.ts +232 -0
- package/src/migrate.ts +67 -0
- package/src/prepare-policy.ts +118 -0
- package/src/reconcile.ts +115 -0
- package/src/schema/ai-chats.ts +36 -0
- package/src/schema/ai-settings.ts +24 -0
- package/src/schema/api-keys.ts +37 -0
- package/src/schema/audit-log.ts +54 -0
- package/src/schema/connections.ts +35 -0
- package/src/schema/custom-table-owners.ts +32 -0
- package/src/schema/delivery-artifacts.ts +33 -0
- package/src/schema/entity-tags.ts +33 -0
- package/src/schema/extension-data.ts +38 -0
- package/src/schema/extension-migrations.ts +42 -0
- package/src/schema/fields.ts +37 -0
- package/src/schema/globals.ts +35 -0
- package/src/schema/identities.ts +27 -0
- package/src/schema/index.ts +51 -0
- package/src/schema/jobs.ts +104 -0
- package/src/schema/knowledge.ts +85 -0
- package/src/schema/media.ts +52 -0
- package/src/schema/member-grants.ts +34 -0
- package/src/schema/members.ts +49 -0
- package/src/schema/migration-runs.ts +46 -0
- package/src/schema/object-types.ts +41 -0
- package/src/schema/objects.ts +38 -0
- package/src/schema/paused-jobs.ts +25 -0
- package/src/schema/playground.ts +43 -0
- package/src/schema/porter-transfer.ts +47 -0
- package/src/schema/realtime.ts +45 -0
- package/src/schema/releases.ts +95 -0
- package/src/schema/roles.ts +27 -0
- package/src/schema/saved-views.ts +33 -0
- package/src/schema/schema-pushes.ts +43 -0
- package/src/schema/search-documents.ts +47 -0
- package/src/schema/search-query-log.ts +24 -0
- package/src/schema/search-recommendations.ts +27 -0
- package/src/schema/search-synonyms.ts +23 -0
- package/src/schema/webhook-deliveries.ts +37 -0
- package/src/schema/webhooks.ts +36 -0
- package/src/schema/workspace-activity.ts +35 -0
- package/src/schema/workspace-domains.ts +24 -0
- package/src/schema/workspace-installed-apps.ts +48 -0
- package/src/schema/workspaces.ts +24 -0
- package/src/schema-kit.ts +64 -0
- package/src/sealed.ts +84 -0
- package/src/seed/skeleton.ts +77 -0
- package/src/sql/apply-gin-indexes.ts +31 -0
- package/src/sql/gin-indexes.sql +11 -0
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
-- Remove first-class Environments (tasks/remove-environments-programme.md, R6 + R7).
|
|
2
|
+
--
|
|
3
|
+
-- One deployment has one database context; a workspace is the content
|
|
4
|
+
-- boundary. This migration keeps ONE environment's rows per workspace and
|
|
5
|
+
-- drops every env_id column, the environment tables and the promotion
|
|
6
|
+
-- bookkeeping. It is generic over information_schema, so content tables,
|
|
7
|
+
-- custom tables and extension tables that carry env_id are handled the same
|
|
8
|
+
-- way as the platform's own tables.
|
|
9
|
+
--
|
|
10
|
+
-- Which environment is kept, per workspace:
|
|
11
|
+
-- 1. TANGEN_KEEP_ENVIRONMENT (run as `SET tangen.keep_environment = '<slug>'`
|
|
12
|
+
-- by the migration runner) names it explicitly.
|
|
13
|
+
-- 2. Otherwise, when exactly one environment holds data rows, that one.
|
|
14
|
+
-- 3. Otherwise, when no environment holds data rows, the production-role one.
|
|
15
|
+
-- 4. Otherwise the migration REFUSES with the per-environment row counts and
|
|
16
|
+
-- the variable to set. Nothing is deleted on refusal.
|
|
17
|
+
-- Data rows are counted in every env-scoped table except bookkeeping and
|
|
18
|
+
-- history (registry, ledgers, queues, caches, logs), which follow the decision.
|
|
19
|
+
--
|
|
20
|
+
-- History tables (audit_log, workspace_activity, job_runs, schema_pushes,
|
|
21
|
+
-- migration_runs, release_history, webhook_deliveries, search_query_log,
|
|
22
|
+
-- knowledge_queries) keep every row and only lose the column.
|
|
23
|
+
|
|
24
|
+
-- ── 1. Decide, and delete the rows of the environments that are not kept ────
|
|
25
|
+
DO $tangen$
|
|
26
|
+
DECLARE
|
|
27
|
+
has_env boolean;
|
|
28
|
+
keep_slug text := nullif(trim(current_setting('tangen.keep_environment', true)), '');
|
|
29
|
+
ws record;
|
|
30
|
+
env record;
|
|
31
|
+
t record;
|
|
32
|
+
k record;
|
|
33
|
+
n bigint;
|
|
34
|
+
counts jsonb;
|
|
35
|
+
data_total bigint;
|
|
36
|
+
with_data int;
|
|
37
|
+
kept text;
|
|
38
|
+
prod text;
|
|
39
|
+
first_env text;
|
|
40
|
+
refusal text := '';
|
|
41
|
+
primary_env text;
|
|
42
|
+
primary_rows bigint := -1;
|
|
43
|
+
bookkeeping text[] := ARRAY[
|
|
44
|
+
'environments', 'object_types', 'fields', 'member_env_roles', 'member_env_membership',
|
|
45
|
+
'workspace_installed_apps', 'custom_table_owners', 'extension_migrations', 'scheduler_state',
|
|
46
|
+
'paused_jobs', 'job_queue', 'cron_jobs', 'delivery_artifacts', 'search_documents',
|
|
47
|
+
'knowledge_chunks', 'knowledge_meta', 'porter_transfer_codes', 'porter_transfer_tokens',
|
|
48
|
+
'auth_sessions', 'playground_collections', 'playground_variables', 'saved_views', 'ai_chats',
|
|
49
|
+
'audit_log', 'workspace_activity', 'job_runs', 'schema_pushes', 'migration_runs',
|
|
50
|
+
'release_history', 'webhook_deliveries', 'search_query_log', 'knowledge_queries', 'promotion_log'
|
|
51
|
+
];
|
|
52
|
+
history text[] := ARRAY[
|
|
53
|
+
'audit_log', 'workspace_activity', 'job_runs', 'schema_pushes', 'migration_runs',
|
|
54
|
+
'release_history', 'webhook_deliveries', 'search_query_log', 'knowledge_queries'
|
|
55
|
+
];
|
|
56
|
+
BEGIN
|
|
57
|
+
-- The decision, per workspace, survives into step 3 (same transaction).
|
|
58
|
+
CREATE TEMP TABLE tangen_kept (workspace_id text, env_id text) ON COMMIT DROP;
|
|
59
|
+
|
|
60
|
+
SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = current_schema() AND table_name = 'environments') INTO has_env;
|
|
61
|
+
|
|
62
|
+
-- Every (workspace, environment) pair that exists: the environments rows,
|
|
63
|
+
-- plus the pairs the data itself names for a workspace the table never
|
|
64
|
+
-- registered (its rows would otherwise survive as several environments in
|
|
65
|
+
-- one workspace and collide once env_id is gone).
|
|
66
|
+
CREATE TEMP TABLE tangen_env_scope (workspace_id text, env_id text, slug text, role text) ON COMMIT DROP;
|
|
67
|
+
IF has_env THEN
|
|
68
|
+
INSERT INTO tangen_env_scope SELECT workspace_id, id, slug, role FROM environments;
|
|
69
|
+
END IF;
|
|
70
|
+
FOR t IN
|
|
71
|
+
SELECT c.table_name FROM information_schema.columns c
|
|
72
|
+
WHERE c.table_schema = current_schema() AND c.column_name = 'env_id' AND c.table_name <> 'environments'
|
|
73
|
+
AND EXISTS (SELECT 1 FROM information_schema.columns w WHERE w.table_schema = c.table_schema AND w.table_name = c.table_name AND w.column_name = 'workspace_id')
|
|
74
|
+
LOOP
|
|
75
|
+
EXECUTE format(
|
|
76
|
+
'INSERT INTO tangen_env_scope SELECT DISTINCT r.workspace_id, r.env_id, regexp_replace(r.env_id, ''^env_'', ''''), NULL FROM %I r ' ||
|
|
77
|
+
'WHERE r.env_id IS NOT NULL AND r.workspace_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM tangen_env_scope s WHERE s.workspace_id = r.workspace_id AND s.env_id = r.env_id)',
|
|
78
|
+
t.table_name);
|
|
79
|
+
END LOOP;
|
|
80
|
+
|
|
81
|
+
IF NOT EXISTS (SELECT 1 FROM tangen_env_scope) THEN
|
|
82
|
+
RAISE NOTICE 'tangen: no environment rows anywhere; dropping env_id columns without selecting rows';
|
|
83
|
+
RETURN;
|
|
84
|
+
END IF;
|
|
85
|
+
|
|
86
|
+
FOR ws IN SELECT DISTINCT workspace_id FROM tangen_env_scope ORDER BY workspace_id LOOP
|
|
87
|
+
counts := '{}'::jsonb;
|
|
88
|
+
data_total := 0;
|
|
89
|
+
with_data := 0;
|
|
90
|
+
prod := NULL;
|
|
91
|
+
first_env := NULL;
|
|
92
|
+
kept := NULL;
|
|
93
|
+
|
|
94
|
+
FOR env IN SELECT env_id AS id, slug, role FROM tangen_env_scope WHERE workspace_id = ws.workspace_id ORDER BY slug LOOP
|
|
95
|
+
n := 0;
|
|
96
|
+
FOR t IN
|
|
97
|
+
SELECT c.table_name
|
|
98
|
+
FROM information_schema.columns c
|
|
99
|
+
WHERE c.table_schema = current_schema() AND c.column_name = 'env_id'
|
|
100
|
+
AND c.table_name <> ALL (bookkeeping)
|
|
101
|
+
AND EXISTS (SELECT 1 FROM information_schema.columns w WHERE w.table_schema = c.table_schema AND w.table_name = c.table_name AND w.column_name = 'workspace_id')
|
|
102
|
+
LOOP
|
|
103
|
+
EXECUTE format('SELECT count(*) FROM %I WHERE env_id = $1 AND workspace_id = $2', t.table_name) INTO n USING env.id, ws.workspace_id;
|
|
104
|
+
counts := counts || jsonb_build_object(env.slug, coalesce((counts ->> env.slug)::bigint, 0) + n);
|
|
105
|
+
END LOOP;
|
|
106
|
+
IF coalesce((counts ->> env.slug)::bigint, 0) > 0 THEN with_data := with_data + 1; END IF;
|
|
107
|
+
data_total := data_total + coalesce((counts ->> env.slug)::bigint, 0);
|
|
108
|
+
IF env.role = 'production' THEN prod := env.id; END IF;
|
|
109
|
+
IF first_env IS NULL THEN first_env := env.id; END IF;
|
|
110
|
+
END LOOP;
|
|
111
|
+
|
|
112
|
+
-- TANGEN_KEEP_ENVIRONMENT decides where it names one of the workspace's
|
|
113
|
+
-- environments; a workspace it does not fit is decided by its data like
|
|
114
|
+
-- any other (one environment with rows, else the production one), and
|
|
115
|
+
-- refused only when that is ambiguous too.
|
|
116
|
+
IF keep_slug IS NOT NULL THEN
|
|
117
|
+
SELECT env_id INTO kept FROM tangen_env_scope WHERE workspace_id = ws.workspace_id AND (slug = keep_slug OR env_id = keep_slug) LIMIT 1;
|
|
118
|
+
END IF;
|
|
119
|
+
IF kept IS NOT NULL THEN
|
|
120
|
+
NULL;
|
|
121
|
+
ELSIF with_data = 1 THEN
|
|
122
|
+
SELECT e.env_id INTO kept FROM tangen_env_scope e WHERE e.workspace_id = ws.workspace_id AND coalesce((counts ->> e.slug)::bigint, 0) > 0 LIMIT 1;
|
|
123
|
+
ELSIF with_data = 0 THEN
|
|
124
|
+
kept := coalesce(prod, first_env);
|
|
125
|
+
ELSE
|
|
126
|
+
refusal := refusal || format(E'\n workspace %s holds data in %s environments: %s. Set TANGEN_KEEP_ENVIRONMENT=<slug> to the one to keep (it has: %s); the others are deleted.',
|
|
127
|
+
ws.workspace_id, with_data,
|
|
128
|
+
(SELECT string_agg(format('%s (%s rows)', key, value), ', ' ORDER BY key) FROM jsonb_each_text(counts)),
|
|
129
|
+
(SELECT string_agg(slug, ', ' ORDER BY slug) FROM tangen_env_scope WHERE workspace_id = ws.workspace_id));
|
|
130
|
+
CONTINUE;
|
|
131
|
+
END IF;
|
|
132
|
+
INSERT INTO tangen_kept VALUES (ws.workspace_id, kept);
|
|
133
|
+
RAISE NOTICE 'tangen: workspace % keeps environment % (% data rows)', ws.workspace_id, kept, data_total;
|
|
134
|
+
-- Tables without a workspace column (the schema registry: object_types,
|
|
135
|
+
-- fields) hold ONE environment's rows afterwards: the named one, else the
|
|
136
|
+
-- one kept by the workspace with the most data.
|
|
137
|
+
IF keep_slug IS NOT NULL AND (kept = keep_slug OR kept = 'env_' || keep_slug) THEN
|
|
138
|
+
primary_env := kept; primary_rows := 9223372036854775807;
|
|
139
|
+
ELSIF data_total > primary_rows THEN
|
|
140
|
+
primary_env := kept; primary_rows := data_total;
|
|
141
|
+
END IF;
|
|
142
|
+
END LOOP;
|
|
143
|
+
|
|
144
|
+
IF refusal <> '' THEN
|
|
145
|
+
RAISE EXCEPTION E'Environment removal refused - the environment to keep is ambiguous:%\nNothing was changed. Run the migration again with TANGEN_KEEP_ENVIRONMENT set (the API boot, or db:migrate in packages/db).', refusal
|
|
146
|
+
USING ERRCODE = 'P0001';
|
|
147
|
+
END IF;
|
|
148
|
+
|
|
149
|
+
INSERT INTO tangen_kept VALUES ('*', primary_env);
|
|
150
|
+
RAISE NOTICE 'tangen: the schema registry and every other workspace-less table keep environment %', primary_env;
|
|
151
|
+
|
|
152
|
+
-- Delete the rows of every other environment: workspace-scoped tables by
|
|
153
|
+
-- their workspace's decision, workspace-less tables by the primary one.
|
|
154
|
+
-- A row with no environment (env_id NULL) predates the model and stays.
|
|
155
|
+
-- Children before parents: a table whose rows reference another
|
|
156
|
+
-- environment-scoped table (fields -> object_types) empties first, so no
|
|
157
|
+
-- foreign key is violated half-way.
|
|
158
|
+
FOR t IN
|
|
159
|
+
WITH RECURSIVE scoped AS (
|
|
160
|
+
SELECT c.table_name::text AS table_name,
|
|
161
|
+
EXISTS (SELECT 1 FROM information_schema.columns w WHERE w.table_schema = c.table_schema AND w.table_name = c.table_name AND w.column_name = 'workspace_id') AS has_ws
|
|
162
|
+
FROM information_schema.columns c
|
|
163
|
+
WHERE c.table_schema = current_schema() AND c.column_name = 'env_id'
|
|
164
|
+
AND c.table_name <> ALL (history) AND c.table_name <> 'environments'
|
|
165
|
+
), refs AS (
|
|
166
|
+
SELECT con.conrelid::regclass::text AS child, con.confrelid::regclass::text AS parent
|
|
167
|
+
FROM pg_constraint con WHERE con.contype = 'f' AND con.conrelid <> con.confrelid
|
|
168
|
+
), depth AS (
|
|
169
|
+
SELECT table_name::text AS table_name, 0 AS d FROM scoped
|
|
170
|
+
UNION ALL
|
|
171
|
+
SELECT r.child::text, depth.d + 1 FROM depth JOIN refs r ON r.parent = depth.table_name JOIN scoped s ON s.table_name = r.child WHERE depth.d < 8
|
|
172
|
+
)
|
|
173
|
+
SELECT s.table_name, s.has_ws FROM scoped s
|
|
174
|
+
ORDER BY (SELECT max(d) FROM depth WHERE depth.table_name = s.table_name) DESC, s.table_name
|
|
175
|
+
LOOP
|
|
176
|
+
IF t.table_name = 'paused_jobs' THEN
|
|
177
|
+
-- '*' pauses every environment; keep it unless the kept environment has its own row.
|
|
178
|
+
EXECUTE 'DELETE FROM paused_jobs p WHERE p.env_id IS NOT NULL AND p.env_id <> ''*'' AND NOT EXISTS (SELECT 1 FROM tangen_kept k WHERE k.workspace_id = p.workspace_id AND k.env_id = p.env_id)';
|
|
179
|
+
EXECUTE 'DELETE FROM paused_jobs p WHERE p.env_id = ''*'' AND EXISTS (SELECT 1 FROM paused_jobs q WHERE q.workspace_id = p.workspace_id AND q.job_id = p.job_id AND q.env_id <> ''*'')';
|
|
180
|
+
ELSIF t.has_ws THEN
|
|
181
|
+
-- A child table that carries env_id but no workspace_id (fields ->
|
|
182
|
+
-- object_types) cannot be scoped by workspace itself: its rows go
|
|
183
|
+
-- with the parent rows they reference.
|
|
184
|
+
FOR k IN
|
|
185
|
+
SELECT con.conrelid::regclass::text AS child, a.attname AS fkcol, pa.attname AS pkcol
|
|
186
|
+
FROM pg_constraint con
|
|
187
|
+
JOIN pg_attribute a ON a.attrelid = con.conrelid AND a.attnum = con.conkey[1]
|
|
188
|
+
JOIN pg_attribute pa ON pa.attrelid = con.confrelid AND pa.attnum = con.confkey[1]
|
|
189
|
+
WHERE con.contype = 'f' AND con.confrelid = quote_ident(t.table_name)::regclass AND con.conrelid <> con.confrelid
|
|
190
|
+
AND array_length(con.conkey, 1) = 1
|
|
191
|
+
AND EXISTS (SELECT 1 FROM information_schema.columns e WHERE e.table_schema = current_schema() AND e.table_name = con.conrelid::regclass::text AND e.column_name = 'env_id')
|
|
192
|
+
LOOP
|
|
193
|
+
EXECUTE format('DELETE FROM %I c USING %I p WHERE p.%I = c.%I AND p.env_id IS NOT NULL AND EXISTS (SELECT 1 FROM tangen_kept k WHERE k.workspace_id = p.workspace_id) AND NOT EXISTS (SELECT 1 FROM tangen_kept k WHERE k.workspace_id = p.workspace_id AND k.env_id = p.env_id)',
|
|
194
|
+
k.child, t.table_name, k.pkcol, k.fkcol);
|
|
195
|
+
END LOOP;
|
|
196
|
+
EXECUTE format('DELETE FROM %I r WHERE r.env_id IS NOT NULL AND EXISTS (SELECT 1 FROM tangen_kept k WHERE k.workspace_id = r.workspace_id) AND NOT EXISTS (SELECT 1 FROM tangen_kept k WHERE k.workspace_id = r.workspace_id AND k.env_id = r.env_id)', t.table_name);
|
|
197
|
+
ELSE
|
|
198
|
+
FOR k IN
|
|
199
|
+
SELECT con.conrelid::regclass::text AS child, a.attname AS fkcol, pa.attname AS pkcol
|
|
200
|
+
FROM pg_constraint con
|
|
201
|
+
JOIN pg_attribute a ON a.attrelid = con.conrelid AND a.attnum = con.conkey[1]
|
|
202
|
+
JOIN pg_attribute pa ON pa.attrelid = con.confrelid AND pa.attnum = con.confkey[1]
|
|
203
|
+
WHERE con.contype = 'f' AND con.confrelid = quote_ident(t.table_name)::regclass AND con.conrelid <> con.confrelid
|
|
204
|
+
AND array_length(con.conkey, 1) = 1
|
|
205
|
+
AND EXISTS (SELECT 1 FROM information_schema.columns e WHERE e.table_schema = current_schema() AND e.table_name = con.conrelid::regclass::text AND e.column_name = 'env_id')
|
|
206
|
+
LOOP
|
|
207
|
+
EXECUTE format('DELETE FROM %I c USING %I p WHERE p.%I = c.%I AND p.env_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM tangen_kept k WHERE k.workspace_id = ''*'' AND k.env_id = p.env_id)',
|
|
208
|
+
k.child, t.table_name, k.pkcol, k.fkcol);
|
|
209
|
+
END LOOP;
|
|
210
|
+
EXECUTE format('DELETE FROM %I r WHERE r.env_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM tangen_kept k WHERE k.workspace_id = ''*'' AND k.env_id = r.env_id)', t.table_name);
|
|
211
|
+
END IF;
|
|
212
|
+
END LOOP;
|
|
213
|
+
END
|
|
214
|
+
$tangen$;
|
|
215
|
+
|
|
216
|
+
-- ── 2. Drop the environment tables and the promotion bookkeeping ────────────
|
|
217
|
+
DROP TABLE IF EXISTS "member_env_roles" CASCADE;
|
|
218
|
+
DROP TABLE IF EXISTS "member_env_membership" CASCADE;
|
|
219
|
+
DROP TABLE IF EXISTS "promotion_log" CASCADE;
|
|
220
|
+
DROP TABLE IF EXISTS "environments" CASCADE;
|
|
221
|
+
|
|
222
|
+
-- ── 3. Drop every env_id column, re-keying what it was part of ──────────────
|
|
223
|
+
DO $tangen$
|
|
224
|
+
DECLARE
|
|
225
|
+
t record;
|
|
226
|
+
k record;
|
|
227
|
+
pk record;
|
|
228
|
+
cols text;
|
|
229
|
+
def text;
|
|
230
|
+
newname text;
|
|
231
|
+
BEGIN
|
|
232
|
+
IF to_regclass('pg_temp.tangen_kept') IS NULL THEN
|
|
233
|
+
CREATE TEMP TABLE tangen_kept (workspace_id text, env_id text) ON COMMIT DROP;
|
|
234
|
+
END IF;
|
|
235
|
+
FOR t IN
|
|
236
|
+
SELECT c.table_name FROM information_schema.columns c
|
|
237
|
+
WHERE c.table_schema = current_schema() AND c.column_name = 'env_id'
|
|
238
|
+
ORDER BY c.table_name
|
|
239
|
+
LOOP
|
|
240
|
+
-- Rows that differ only by env_id would collide on a unique key once the
|
|
241
|
+
-- column is gone: a workspace the environments table never registered, a
|
|
242
|
+
-- pre-model row with no environment beside an adopted one. Keep one per
|
|
243
|
+
-- key - the kept environment's row, else the one with no environment,
|
|
244
|
+
-- else the first by env_id - and say how many went.
|
|
245
|
+
FOR k IN
|
|
246
|
+
SELECT string_agg(quote_ident(a.attname), ', ' ORDER BY a.attnum) AS rest, con.conname AS name
|
|
247
|
+
FROM pg_constraint con JOIN pg_attribute a ON a.attrelid = con.conrelid AND a.attnum = ANY (con.conkey)
|
|
248
|
+
WHERE con.conrelid = quote_ident(t.table_name)::regclass AND con.contype IN ('p', 'u')
|
|
249
|
+
AND EXISTS (SELECT 1 FROM pg_attribute e WHERE e.attrelid = con.conrelid AND e.attnum = ANY (con.conkey) AND e.attname = 'env_id')
|
|
250
|
+
AND a.attname <> 'env_id'
|
|
251
|
+
GROUP BY con.conname
|
|
252
|
+
UNION ALL
|
|
253
|
+
SELECT string_agg(quote_ident(a.attname), ', ' ORDER BY a.attnum), i.indexrelid::regclass::text
|
|
254
|
+
FROM pg_index i JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY (i.indkey)
|
|
255
|
+
WHERE i.indrelid = quote_ident(t.table_name)::regclass AND i.indisunique AND NOT i.indisprimary AND i.indpred IS NULL
|
|
256
|
+
AND NOT EXISTS (SELECT 1 FROM pg_constraint c WHERE c.conindid = i.indexrelid)
|
|
257
|
+
AND EXISTS (SELECT 1 FROM pg_attribute e WHERE e.attrelid = i.indrelid AND e.attnum = ANY (i.indkey) AND e.attname = 'env_id')
|
|
258
|
+
AND a.attname <> 'env_id'
|
|
259
|
+
GROUP BY i.indexrelid
|
|
260
|
+
LOOP
|
|
261
|
+
IF k.rest IS NULL THEN CONTINUE; END IF;
|
|
262
|
+
DROP TABLE IF EXISTS tangen_dupes;
|
|
263
|
+
EXECUTE format(
|
|
264
|
+
'CREATE TEMP TABLE tangen_dupes ON COMMIT DROP AS WITH ranked AS (SELECT ctid AS row_ctid, row_number() OVER (PARTITION BY %s ORDER BY (EXISTS (SELECT 1 FROM tangen_kept kk WHERE kk.env_id = r.env_id)) DESC, (r.env_id IS NULL) DESC, r.env_id) AS rn FROM %I r WHERE %s) SELECT row_ctid FROM ranked WHERE rn > 1',
|
|
265
|
+
k.rest, t.table_name, (SELECT string_agg(x || ' IS NOT NULL', ' AND ') FROM unnest(string_to_array(k.rest, ', ')) AS x));
|
|
266
|
+
-- Rows another table references follow their parent out.
|
|
267
|
+
FOR pk IN
|
|
268
|
+
SELECT con.conrelid::regclass::text AS child, a.attname AS fkcol, pa.attname AS pkcol
|
|
269
|
+
FROM pg_constraint con
|
|
270
|
+
JOIN pg_attribute a ON a.attrelid = con.conrelid AND a.attnum = con.conkey[1]
|
|
271
|
+
JOIN pg_attribute pa ON pa.attrelid = con.confrelid AND pa.attnum = con.confkey[1]
|
|
272
|
+
WHERE con.contype = 'f' AND con.confrelid = quote_ident(t.table_name)::regclass AND con.conrelid <> con.confrelid AND array_length(con.conkey, 1) = 1
|
|
273
|
+
LOOP
|
|
274
|
+
EXECUTE format('DELETE FROM %I c USING %I p, tangen_dupes x WHERE p.ctid = x.row_ctid AND p.%I = c.%I', pk.child, t.table_name, pk.pkcol, pk.fkcol);
|
|
275
|
+
END LOOP;
|
|
276
|
+
EXECUTE format('DELETE FROM %I d USING tangen_dupes x WHERE d.ctid = x.row_ctid', t.table_name);
|
|
277
|
+
GET DIAGNOSTICS cols = ROW_COUNT;
|
|
278
|
+
IF cols::bigint > 0 THEN
|
|
279
|
+
RAISE NOTICE 'tangen: % rows of % differed only by environment on key % and are gone', cols, t.table_name, k.name;
|
|
280
|
+
END IF;
|
|
281
|
+
END LOOP;
|
|
282
|
+
|
|
283
|
+
-- Primary keys and unique constraints that include env_id: remember them without it.
|
|
284
|
+
FOR k IN
|
|
285
|
+
SELECT conname, contype,
|
|
286
|
+
(SELECT string_agg(quote_ident(a.attname), ', ' ORDER BY array_position(con.conkey, a.attnum))
|
|
287
|
+
FROM pg_attribute a WHERE a.attrelid = con.conrelid AND a.attnum = ANY (con.conkey) AND a.attname <> 'env_id') AS rest
|
|
288
|
+
FROM pg_constraint con
|
|
289
|
+
WHERE con.conrelid = quote_ident(t.table_name)::regclass AND con.contype IN ('p', 'u')
|
|
290
|
+
AND EXISTS (SELECT 1 FROM pg_attribute a WHERE a.attrelid = con.conrelid AND a.attnum = ANY (con.conkey) AND a.attname = 'env_id')
|
|
291
|
+
LOOP
|
|
292
|
+
EXECUTE format('ALTER TABLE %I DROP CONSTRAINT %I', t.table_name, k.conname);
|
|
293
|
+
IF k.rest IS NOT NULL THEN
|
|
294
|
+
newname := regexp_replace(regexp_replace(k.conname, '_env_id_', '_'), '_env_', '_');
|
|
295
|
+
IF k.contype = 'p' THEN
|
|
296
|
+
EXECUTE format('ALTER TABLE %I ADD PRIMARY KEY (%s)', t.table_name, k.rest);
|
|
297
|
+
ELSE
|
|
298
|
+
EXECUTE format('ALTER TABLE %I ADD CONSTRAINT %I UNIQUE (%s)', t.table_name, newname, k.rest);
|
|
299
|
+
END IF;
|
|
300
|
+
END IF;
|
|
301
|
+
END LOOP;
|
|
302
|
+
|
|
303
|
+
-- Indexes that include env_id: recreate without it, under the environment-free name.
|
|
304
|
+
FOR k IN
|
|
305
|
+
SELECT indexname, indexdef FROM pg_indexes
|
|
306
|
+
WHERE schemaname = current_schema() AND tablename = t.table_name
|
|
307
|
+
AND indexdef ~ '[(, ]env_id[,) ]'
|
|
308
|
+
AND indexname NOT IN (SELECT conname FROM pg_constraint)
|
|
309
|
+
LOOP
|
|
310
|
+
EXECUTE format('DROP INDEX IF EXISTS %I', k.indexname);
|
|
311
|
+
def := regexp_replace(k.indexdef, 'env_id,\s*', '');
|
|
312
|
+
def := regexp_replace(def, ',\s*env_id(?=[,)])', '');
|
|
313
|
+
newname := regexp_replace(regexp_replace(regexp_replace(k.indexname, '_workspace_env_', '_workspace_'), '_ws_env_', '_ws_'), '_env_', '_');
|
|
314
|
+
newname := regexp_replace(newname, '_env$', '');
|
|
315
|
+
def := replace(def, 'INDEX ' || quote_ident(k.indexname) || ' ON', 'INDEX ' || quote_ident(newname) || ' ON');
|
|
316
|
+
def := replace(def, 'INDEX ' || k.indexname || ' ON', 'INDEX ' || quote_ident(newname) || ' ON');
|
|
317
|
+
IF EXISTS (SELECT 1 FROM pg_indexes WHERE schemaname = current_schema() AND indexname = newname) THEN
|
|
318
|
+
-- An environment-free index of that name already exists (a later
|
|
319
|
+
-- migration or a heal created it beside the environment-keyed one):
|
|
320
|
+
-- it is the one that stays, and the environment-keyed one is gone.
|
|
321
|
+
RAISE NOTICE 'tangen: index % already exists; % dropped', newname, k.indexname;
|
|
322
|
+
ELSIF def !~ '\(\s*\)' AND def !~ 'USING \w+ \(\s*\)' THEN
|
|
323
|
+
EXECUTE replace(replace(def, 'CREATE UNIQUE INDEX', 'CREATE UNIQUE INDEX IF NOT EXISTS'), 'CREATE INDEX', 'CREATE INDEX IF NOT EXISTS');
|
|
324
|
+
END IF;
|
|
325
|
+
END LOOP;
|
|
326
|
+
|
|
327
|
+
EXECUTE format('ALTER TABLE %I DROP COLUMN env_id', t.table_name);
|
|
328
|
+
END LOOP;
|
|
329
|
+
END
|
|
330
|
+
$tangen$;
|
|
331
|
+
|
|
332
|
+
-- ── 4. Releases change in place; runs and history keep their rows ───────────
|
|
333
|
+
DO $tangen$
|
|
334
|
+
DECLARE
|
|
335
|
+
pair record;
|
|
336
|
+
BEGIN
|
|
337
|
+
FOR pair IN
|
|
338
|
+
SELECT * FROM (VALUES
|
|
339
|
+
('releases', 'source_env_id'), ('releases', 'target_env_id'),
|
|
340
|
+
('release_history', 'source_env_id'), ('release_history', 'target_env_id'),
|
|
341
|
+
('release_history', 'source_role'), ('release_history', 'target_role'),
|
|
342
|
+
('migration_runs', 'source_env_id'), ('migration_runs', 'target_env_id')
|
|
343
|
+
) AS v(table_name, column_name)
|
|
344
|
+
LOOP
|
|
345
|
+
IF EXISTS (SELECT 1 FROM information_schema.columns c WHERE c.table_schema = current_schema() AND c.table_name = pair.table_name AND c.column_name = pair.column_name) THEN
|
|
346
|
+
EXECUTE format('ALTER TABLE %I DROP COLUMN %I', pair.table_name, pair.column_name);
|
|
347
|
+
END IF;
|
|
348
|
+
END LOOP;
|
|
349
|
+
END
|
|
350
|
+
$tangen$;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
-- Releases publish in place (tasks/remove-environments-programme.md, R4):
|
|
2
|
+
-- who created and who applied a release, and the review state each item
|
|
3
|
+
-- carries, existed in the schema and on developer databases that drizzle-kit
|
|
4
|
+
-- pushed, but no migration had ever created them. The first fresh database
|
|
5
|
+
-- of the three-deployment proof (scripts/verify-deployments-pg.ts) found out.
|
|
6
|
+
ALTER TABLE "releases" ADD COLUMN IF NOT EXISTS "created_by" text;--> statement-breakpoint
|
|
7
|
+
ALTER TABLE "releases" ADD COLUMN IF NOT EXISTS "applied_by" text;--> statement-breakpoint
|
|
8
|
+
ALTER TABLE "release_items" ADD COLUMN IF NOT EXISTS "comparison_state" text;--> statement-breakpoint
|
|
9
|
+
ALTER TABLE "release_items" ADD COLUMN IF NOT EXISTS "resolved_values" jsonb;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
-- The framework kernel's durable event record and attribution kinds
|
|
2
|
+
-- (tasks/plan-framework-kernel.md K5, §10, §18).
|
|
3
|
+
--
|
|
4
|
+
-- audit_log becomes the event record the domain operations write INSIDE
|
|
5
|
+
-- their transaction: who acted (actor kind and id, on behalf of whom), in
|
|
6
|
+
-- which execution (kind, id, correlation, causation), to which resource, in
|
|
7
|
+
-- which locale, through which transport. The columns are additive; rows the
|
|
8
|
+
-- bus subscriber wrote before this migration keep their shape.
|
|
9
|
+
ALTER TABLE "audit_log" ADD COLUMN IF NOT EXISTS "actor_kind" text;--> statement-breakpoint
|
|
10
|
+
ALTER TABLE "audit_log" ADD COLUMN IF NOT EXISTS "initiator_id" text;--> statement-breakpoint
|
|
11
|
+
ALTER TABLE "audit_log" ADD COLUMN IF NOT EXISTS "initiator_kind" text;--> statement-breakpoint
|
|
12
|
+
ALTER TABLE "audit_log" ADD COLUMN IF NOT EXISTS "execution_kind" text;--> statement-breakpoint
|
|
13
|
+
ALTER TABLE "audit_log" ADD COLUMN IF NOT EXISTS "execution_id" text;--> statement-breakpoint
|
|
14
|
+
ALTER TABLE "audit_log" ADD COLUMN IF NOT EXISTS "correlation_id" text;--> statement-breakpoint
|
|
15
|
+
ALTER TABLE "audit_log" ADD COLUMN IF NOT EXISTS "causation_id" text;--> statement-breakpoint
|
|
16
|
+
ALTER TABLE "audit_log" ADD COLUMN IF NOT EXISTS "resource_kind" text;--> statement-breakpoint
|
|
17
|
+
ALTER TABLE "audit_log" ADD COLUMN IF NOT EXISTS "resource_type" text;--> statement-breakpoint
|
|
18
|
+
ALTER TABLE "audit_log" ADD COLUMN IF NOT EXISTS "resource_id" text;--> statement-breakpoint
|
|
19
|
+
ALTER TABLE "audit_log" ADD COLUMN IF NOT EXISTS "locale" text;--> statement-breakpoint
|
|
20
|
+
ALTER TABLE "audit_log" ADD COLUMN IF NOT EXISTS "source" text;--> statement-breakpoint
|
|
21
|
+
ALTER TABLE "audit_log" ADD COLUMN IF NOT EXISTS "recorded_in_transaction" boolean NOT NULL DEFAULT false;--> statement-breakpoint
|
|
22
|
+
CREATE INDEX IF NOT EXISTS "audit_log_resource" ON "audit_log" ("workspace_id", "resource_id", "created_at");--> statement-breakpoint
|
|
23
|
+
CREATE INDEX IF NOT EXISTS "audit_log_correlation" ON "audit_log" ("correlation_id");--> statement-breakpoint
|
|
24
|
+
-- A revision knows which locale it captured (null: the base row), and the
|
|
25
|
+
-- published revision carries the translations map the snapshot froze.
|
|
26
|
+
ALTER TABLE "_revisions" ADD COLUMN IF NOT EXISTS "locale" text;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
-- Jobs and flows run as someone (tasks/plan-framework-kernel.md K6, §12):
|
|
2
|
+
-- a queued job carries who enqueued it and whose authority it runs with;
|
|
3
|
+
-- a job run records the actor it ran as, the initiator, its execution id and
|
|
4
|
+
-- its correlation, and the attempt. Additive.
|
|
5
|
+
ALTER TABLE "job_queue" ADD COLUMN IF NOT EXISTS "execution" jsonb;--> statement-breakpoint
|
|
6
|
+
ALTER TABLE "job_runs" ADD COLUMN IF NOT EXISTS "actor_kind" text;--> statement-breakpoint
|
|
7
|
+
ALTER TABLE "job_runs" ADD COLUMN IF NOT EXISTS "actor_id" text;--> statement-breakpoint
|
|
8
|
+
ALTER TABLE "job_runs" ADD COLUMN IF NOT EXISTS "initiator_kind" text;--> statement-breakpoint
|
|
9
|
+
ALTER TABLE "job_runs" ADD COLUMN IF NOT EXISTS "initiator_id" text;--> statement-breakpoint
|
|
10
|
+
ALTER TABLE "job_runs" ADD COLUMN IF NOT EXISTS "execution_id" text;--> statement-breakpoint
|
|
11
|
+
ALTER TABLE "job_runs" ADD COLUMN IF NOT EXISTS "correlation_id" text;--> statement-breakpoint
|
|
12
|
+
ALTER TABLE "job_runs" ADD COLUMN IF NOT EXISTS "attempt" integer;--> statement-breakpoint
|
|
13
|
+
CREATE INDEX IF NOT EXISTS "job_runs_correlation" ON "job_runs" ("correlation_id");
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
-- Service and agent identities (tasks/plan-framework-kernel.md K8): a record
|
|
2
|
+
-- that holds authority (grants and capabilities) and owns API keys as its
|
|
3
|
+
-- credentials. A key without an identity keeps acting as itself. Additive.
|
|
4
|
+
CREATE TABLE IF NOT EXISTS "identities" (
|
|
5
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
6
|
+
"workspace_id" text NOT NULL,
|
|
7
|
+
"kind" text NOT NULL,
|
|
8
|
+
"name" text NOT NULL,
|
|
9
|
+
"description" text,
|
|
10
|
+
"status" text DEFAULT 'active' NOT NULL,
|
|
11
|
+
"permissions" jsonb DEFAULT '{}' NOT NULL,
|
|
12
|
+
"capabilities" jsonb DEFAULT '[]' NOT NULL,
|
|
13
|
+
"owner_id" text,
|
|
14
|
+
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
15
|
+
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
|
16
|
+
);--> statement-breakpoint
|
|
17
|
+
CREATE INDEX IF NOT EXISTS "identities_workspace_idx" ON "identities" USING btree ("workspace_id");--> statement-breakpoint
|
|
18
|
+
ALTER TABLE "api_keys" ADD COLUMN IF NOT EXISTS "identity_id" text;--> statement-breakpoint
|
|
19
|
+
CREATE INDEX IF NOT EXISTS "api_keys_identity_idx" ON "api_keys" USING btree ("identity_id");
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
-- Forms leaves core (tasks/plan-core-slimming.md S3, 2026-09-13): the three
|
|
2
|
+
-- tables belong to @tangen/ext-forms, whose migrations create them where the
|
|
3
|
+
-- extension is installed. Nothing outside this repository holds form data yet,
|
|
4
|
+
-- so the core copies are dropped rather than migrated.
|
|
5
|
+
DROP TABLE IF EXISTS "form_submissions" CASCADE;
|
|
6
|
+
DROP TABLE IF EXISTS "form_app_settings" CASCADE;
|
|
7
|
+
DROP TABLE IF EXISTS "forms" CASCADE;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
-- Site users leaves core (tasks/plan-core-slimming.md S4, 2026-09-13): the
|
|
2
|
+
-- four auth tables belong to @tangen/ext-auth, whose migrations create them
|
|
3
|
+
-- where the extension is installed. Nothing outside this repository holds
|
|
4
|
+
-- site user data yet, so the core copies are dropped rather than migrated.
|
|
5
|
+
DROP TABLE IF EXISTS "auth_identities" CASCADE;
|
|
6
|
+
DROP TABLE IF EXISTS "auth_sso_accounts" CASCADE;
|
|
7
|
+
DROP TABLE IF EXISTS "auth_sessions" CASCADE;
|
|
8
|
+
DROP TABLE IF EXISTS "auth_users" CASCADE;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS "member_grants" (
|
|
2
|
+
"workspace_id" text NOT NULL,
|
|
3
|
+
"member_id" text NOT NULL,
|
|
4
|
+
"capabilities" jsonb DEFAULT '[]'::jsonb NOT NULL,
|
|
5
|
+
"extensions" jsonb DEFAULT '[]'::jsonb NOT NULL,
|
|
6
|
+
"granted_by" text,
|
|
7
|
+
"reason" text,
|
|
8
|
+
"expires_at" timestamp with time zone,
|
|
9
|
+
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
10
|
+
CONSTRAINT "member_grants_workspace_id_member_id_pk" PRIMARY KEY("workspace_id","member_id")
|
|
11
|
+
);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
-- Extension Kernel K5.2a: the grants an operator approved for an installation.
|
|
2
|
+
-- NULL = not reviewed (an installation that predates review): the runtime
|
|
3
|
+
-- binds to the manifest's required grants, as before. A JSON array = the
|
|
4
|
+
-- approval; the runtime binds to it intersected with what the current
|
|
5
|
+
-- manifest declares, so a new required grant waits for re-review and a
|
|
6
|
+
-- grant the manifest dropped is revoked without touching the record.
|
|
7
|
+
ALTER TABLE "workspace_installed_apps" ADD COLUMN IF NOT EXISTS "granted_grants" jsonb;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
-- Extension Kernel K6.1: an operator's credentials for a connector, per
|
|
2
|
+
-- workspace, sealed at rest (AES-256-GCM under the deployment's connections
|
|
3
|
+
-- key). `secret` is the envelope, never plaintext; `connector_id` is
|
|
4
|
+
-- `ext:<extension>/<connector>`.
|
|
5
|
+
CREATE TABLE IF NOT EXISTS "connections" (
|
|
6
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
7
|
+
"workspace_id" text NOT NULL,
|
|
8
|
+
"connector_id" text NOT NULL,
|
|
9
|
+
"label" text NOT NULL,
|
|
10
|
+
"kind" text NOT NULL,
|
|
11
|
+
"secret" text NOT NULL,
|
|
12
|
+
"meta" jsonb DEFAULT '{}'::jsonb NOT NULL,
|
|
13
|
+
"status" text DEFAULT 'active' NOT NULL,
|
|
14
|
+
"health" jsonb,
|
|
15
|
+
"created_by" text,
|
|
16
|
+
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
17
|
+
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
18
|
+
"last_checked_at" timestamp with time zone
|
|
19
|
+
);
|
|
20
|
+
--> statement-breakpoint
|
|
21
|
+
CREATE INDEX IF NOT EXISTS "connections_workspace_connector_idx" ON "connections" ("workspace_id", "connector_id");
|