diff --git a/packages/database-jobs/deploy/schemas/app_jobs/procedures/add_scheduled_job.sql b/packages/database-jobs/deploy/schemas/app_jobs/procedures/add_scheduled_job.sql index e9d867771..0921c798b 100644 --- a/packages/database-jobs/deploy/schemas/app_jobs/procedures/add_scheduled_job.sql +++ b/packages/database-jobs/deploy/schemas/app_jobs/procedures/add_scheduled_job.sql @@ -15,7 +15,8 @@ CREATE FUNCTION app_jobs.add_scheduled_job( queue_name text DEFAULT NULL, max_attempts integer DEFAULT 25, priority integer DEFAULT 0, - entity_id uuid DEFAULT NULL + entity_id uuid DEFAULT NULL, + db_id uuid DEFAULT NULL ) RETURNS app_jobs.scheduled_jobs AS $$ @@ -24,7 +25,9 @@ DECLARE v_database_id uuid; v_actor_id uuid; BEGIN - v_database_id := jwt_private.current_database_id(); + -- Callers that run outside a JWT context (e.g. provisioning triggers) pass + -- db_id explicitly; everyone else keeps the JWT-derived default. + v_database_id := coalesce(db_id, jwt_private.current_database_id()); v_actor_id := jwt_public.current_user_id(); IF job_key IS NOT NULL THEN diff --git a/packages/database-jobs/deploy/schemas/app_jobs/procedures/grants/grant_execute_add_scheduled_job_to_authenticated.sql b/packages/database-jobs/deploy/schemas/app_jobs/procedures/grants/grant_execute_add_scheduled_job_to_authenticated.sql new file mode 100644 index 000000000..b0daa37ca --- /dev/null +++ b/packages/database-jobs/deploy/schemas/app_jobs/procedures/grants/grant_execute_add_scheduled_job_to_authenticated.sql @@ -0,0 +1,10 @@ +-- Deploy schemas/app_jobs/procedures/grants/grant_execute_add_scheduled_job_to_authenticated to pg + +-- requires: schemas/app_jobs/schema +-- requires: schemas/app_jobs/procedures/add_scheduled_job + +BEGIN; + +GRANT EXECUTE ON FUNCTION app_jobs.add_scheduled_job(text, json, json, text, text, integer, integer, uuid, uuid) TO authenticated; + +COMMIT; diff --git a/packages/database-jobs/deploy/schemas/app_jobs/procedures/run_scheduled_job.sql b/packages/database-jobs/deploy/schemas/app_jobs/procedures/run_scheduled_job.sql index 7bdb4399c..cc5937a35 100644 --- a/packages/database-jobs/deploy/schemas/app_jobs/procedures/run_scheduled_job.sql +++ b/packages/database-jobs/deploy/schemas/app_jobs/procedures/run_scheduled_job.sql @@ -67,10 +67,10 @@ BEGIN * INTO j; -- update the scheduled job UPDATE - app_jobs.scheduled_jobs s + app_jobs.scheduled_jobs s SET last_scheduled = NOW(), - last_scheduled_id = j.id + last_scheduled_id = COALESCE(j.id, s.last_scheduled_id) WHERE s.id = run_scheduled_job.id; RETURN j; @@ -79,4 +79,3 @@ $$ LANGUAGE 'plpgsql' VOLATILE; COMMIT; - diff --git a/packages/database-jobs/deploy/schemas/app_jobs/procedures/schedule_min_interval_seconds.sql b/packages/database-jobs/deploy/schemas/app_jobs/procedures/schedule_min_interval_seconds.sql new file mode 100644 index 000000000..925391ebe --- /dev/null +++ b/packages/database-jobs/deploy/schemas/app_jobs/procedures/schedule_min_interval_seconds.sql @@ -0,0 +1,95 @@ +-- Deploy schemas/app_jobs/procedures/schedule_min_interval_seconds to pg + +-- requires: schemas/app_jobs/schema + +BEGIN; + +-- Conservative lower-bound estimate (in seconds) of how often a +-- scheduled_jobs.schedule_info spec can fire. Used by generated schedule +-- limit triggers to enforce a minimum schedule interval cap. +-- +-- Supports the node-schedule shapes stored in schedule_info: +-- { "rule": "*/5 * * * *" } 5-field cron (minute resolution) +-- { "rule": "0 */5 * * * *" } 6-field cron (second resolution) +-- { "minute": [...], "hour": [...] } recurrence-object form +-- +-- Returns NULL when no lower bound can be derived (callers should treat +-- NULL as "unknown" and skip enforcement). +CREATE FUNCTION app_jobs.schedule_min_interval_seconds (schedule_info json) + RETURNS integer + AS $$ +DECLARE + v_rule text; + v_fields text[]; + v_seconds_field text; + v_minutes_field text; + v_step int; +BEGIN + IF schedule_info IS NULL THEN + RETURN NULL; + END IF; + + v_rule := schedule_info ->> 'rule'; + + IF v_rule IS NULL THEN + -- Recurrence-object form: second resolution when a "second" key is + -- present, otherwise minute resolution. + IF schedule_info -> 'second' IS NOT NULL THEN + RETURN 1; + ELSIF schedule_info -> 'minute' IS NOT NULL + OR schedule_info -> 'hour' IS NOT NULL + OR schedule_info -> 'dayOfWeek' IS NOT NULL + OR schedule_info -> 'date' IS NOT NULL + OR schedule_info -> 'month' IS NOT NULL THEN + RETURN 60; + END IF; + RETURN NULL; + END IF; + + v_fields := regexp_split_to_array(trim(v_rule), '\s+'); + + IF array_length(v_fields, 1) = 6 THEN + v_seconds_field := v_fields[1]; + v_minutes_field := v_fields[2]; + ELSIF array_length(v_fields, 1) = 5 THEN + v_seconds_field := '0'; + v_minutes_field := v_fields[1]; + ELSE + RETURN NULL; + END IF; + + -- Second-resolution rules + IF v_seconds_field = '*' THEN + RETURN 1; + END IF; + IF v_seconds_field ~ '^\*/\d+$' THEN + v_step := substring(v_seconds_field FROM 3)::int; + RETURN GREATEST(v_step, 1); + END IF; + IF position(',' IN v_seconds_field) > 0 OR position('-' IN v_seconds_field) > 0 THEN + -- Multiple seconds within each matching minute: could fire every second + RETURN 1; + END IF; + + -- Fixed second: at most once per matching minute + IF v_minutes_field = '*' THEN + RETURN 60; + END IF; + IF v_minutes_field ~ '^\*/\d+$' THEN + v_step := substring(v_minutes_field FROM 3)::int; + RETURN GREATEST(v_step, 1) * 60; + END IF; + IF position(',' IN v_minutes_field) > 0 OR position('-' IN v_minutes_field) > 0 THEN + -- Multiple minutes within each matching hour: adjacent values can be + -- one minute apart + RETURN 60; + END IF; + + -- Fixed minute: at most once per hour + RETURN 3600; +END; +$$ +LANGUAGE 'plpgsql' +IMMUTABLE; + +COMMIT; diff --git a/packages/database-jobs/pgpm.plan b/packages/database-jobs/pgpm.plan index b7f961070..d70d89d74 100644 --- a/packages/database-jobs/pgpm.plan +++ b/packages/database-jobs/pgpm.plan @@ -35,6 +35,8 @@ schemas/app_jobs/procedures/fail_job [schemas/app_jobs/schema schemas/app_jobs/t schemas/app_jobs/procedures/complete_jobs [schemas/app_jobs/schema schemas/app_jobs/tables/job_queues/table schemas/app_jobs/tables/jobs/table] 2025-08-26T23:57:41Z pgpm # add schemas/app_jobs/procedures/complete_jobs schemas/app_jobs/procedures/complete_job [schemas/app_jobs/schema schemas/app_jobs/tables/jobs/table schemas/app_jobs/tables/job_queues/table] 2025-08-26T23:57:41Z pgpm # add schemas/app_jobs/procedures/complete_job schemas/app_jobs/procedures/add_scheduled_job [schemas/app_jobs/schema schemas/app_jobs/tables/scheduled_jobs/table pgpm-jwt-claims:schemas/jwt_private/procedures/current_database_id] 2025-08-26T23:57:41Z pgpm # add schemas/app_jobs/procedures/add_scheduled_job +schemas/app_jobs/procedures/grants/grant_execute_add_scheduled_job_to_authenticated [schemas/app_jobs/schema schemas/app_jobs/procedures/add_scheduled_job] 2026-07-15T06:07:00Z pgpm # grant authenticated EXECUTE on add_scheduled_job for INVOKER trigger support +schemas/app_jobs/procedures/schedule_min_interval_seconds [schemas/app_jobs/schema] 2026-07-12T13:30:00Z pgpm # add schedule_min_interval_seconds estimator for schedule interval caps schemas/app_jobs/procedures/add_job [schemas/app_jobs/schema schemas/app_jobs/tables/jobs/table schemas/app_jobs/tables/job_queues/table pgpm-jwt-claims:schemas/jwt_private/procedures/current_database_id pgpm-jwt-claims:schemas/jwt_public/procedures/current_user_id] 2025-08-26T23:57:41Z pgpm # add schemas/app_jobs/procedures/add_job schemas/app_jobs/procedures/grants/grant_execute_add_job_to_authenticated [schemas/app_jobs/schema schemas/app_jobs/procedures/add_job] 2026-06-03T01:15:00Z pgpm # grant authenticated EXECUTE on add_job for INVOKER trigger support schemas/app_jobs/procedures/remove_job [schemas/app_jobs/schema schemas/app_jobs/tables/jobs/table] 2025-08-26T23:57:41Z pgpm # add schemas/app_jobs/procedures/remove_job diff --git a/packages/database-jobs/revert/schemas/app_jobs/procedures/grants/grant_execute_add_scheduled_job_to_authenticated.sql b/packages/database-jobs/revert/schemas/app_jobs/procedures/grants/grant_execute_add_scheduled_job_to_authenticated.sql new file mode 100644 index 000000000..d0fb9a0d1 --- /dev/null +++ b/packages/database-jobs/revert/schemas/app_jobs/procedures/grants/grant_execute_add_scheduled_job_to_authenticated.sql @@ -0,0 +1,7 @@ +-- Revert schemas/app_jobs/procedures/grants/grant_execute_add_scheduled_job_to_authenticated from pg + +BEGIN; + +REVOKE EXECUTE ON FUNCTION app_jobs.add_scheduled_job(text, json, json, text, text, integer, integer, uuid, uuid) FROM authenticated; + +COMMIT; diff --git a/packages/database-jobs/revert/schemas/app_jobs/procedures/schedule_min_interval_seconds.sql b/packages/database-jobs/revert/schemas/app_jobs/procedures/schedule_min_interval_seconds.sql new file mode 100644 index 000000000..c9fc24694 --- /dev/null +++ b/packages/database-jobs/revert/schemas/app_jobs/procedures/schedule_min_interval_seconds.sql @@ -0,0 +1,7 @@ +-- Revert schemas/app_jobs/procedures/schedule_min_interval_seconds from pg + +BEGIN; + +DROP FUNCTION app_jobs.schedule_min_interval_seconds; + +COMMIT; diff --git a/packages/database-jobs/sql/pgpm-database-jobs--0.22.0.sql b/packages/database-jobs/sql/pgpm-database-jobs--0.22.0.sql index fea74b693..0288fb201 100644 --- a/packages/database-jobs/sql/pgpm-database-jobs--0.22.0.sql +++ b/packages/database-jobs/sql/pgpm-database-jobs--0.22.0.sql @@ -449,10 +449,10 @@ BEGIN * INTO j; -- update the scheduled job UPDATE - app_jobs.scheduled_jobs s + app_jobs.scheduled_jobs s SET last_scheduled = NOW(), - last_scheduled_id = j.id + last_scheduled_id = COALESCE(j.id, s.last_scheduled_id) WHERE s.id = run_scheduled_job.id; RETURN j; @@ -735,14 +735,17 @@ CREATE FUNCTION app_jobs.add_scheduled_job( queue_name text DEFAULT NULL, max_attempts int DEFAULT 25, priority int DEFAULT 0, - entity_id uuid DEFAULT NULL + entity_id uuid DEFAULT NULL, + db_id uuid DEFAULT NULL ) RETURNS app_jobs.scheduled_jobs AS $EOFCODE$ DECLARE v_job app_jobs.scheduled_jobs; v_database_id uuid; v_actor_id uuid; BEGIN - v_database_id := jwt_private.current_database_id(); + -- Callers that run outside a JWT context (e.g. provisioning triggers) pass + -- db_id explicitly; everyone else keeps the JWT-derived default. + v_database_id := coalesce(db_id, jwt_private.current_database_id()); v_actor_id := jwt_public.current_user_id(); IF job_key IS NOT NULL THEN @@ -824,6 +827,83 @@ BEGIN END; $EOFCODE$ LANGUAGE plpgsql VOLATILE SECURITY DEFINER; +GRANT EXECUTE ON FUNCTION app_jobs.add_scheduled_job(text, pg_catalog.json, pg_catalog.json, text, text, int, int, uuid, uuid) TO authenticated; + +CREATE FUNCTION app_jobs.schedule_min_interval_seconds( + schedule_info pg_catalog.json +) RETURNS int AS $EOFCODE$ +DECLARE + v_rule text; + v_fields text[]; + v_seconds_field text; + v_minutes_field text; + v_step int; +BEGIN + IF schedule_info IS NULL THEN + RETURN NULL; + END IF; + + v_rule := schedule_info ->> 'rule'; + + IF v_rule IS NULL THEN + -- Recurrence-object form: second resolution when a "second" key is + -- present, otherwise minute resolution. + IF schedule_info -> 'second' IS NOT NULL THEN + RETURN 1; + ELSIF schedule_info -> 'minute' IS NOT NULL + OR schedule_info -> 'hour' IS NOT NULL + OR schedule_info -> 'dayOfWeek' IS NOT NULL + OR schedule_info -> 'date' IS NOT NULL + OR schedule_info -> 'month' IS NOT NULL THEN + RETURN 60; + END IF; + RETURN NULL; + END IF; + + v_fields := regexp_split_to_array(trim(v_rule), '\s+'); + + IF array_length(v_fields, 1) = 6 THEN + v_seconds_field := v_fields[1]; + v_minutes_field := v_fields[2]; + ELSIF array_length(v_fields, 1) = 5 THEN + v_seconds_field := '0'; + v_minutes_field := v_fields[1]; + ELSE + RETURN NULL; + END IF; + + -- Second-resolution rules + IF v_seconds_field = '*' THEN + RETURN 1; + END IF; + IF v_seconds_field ~ '^\*/\d+$' THEN + v_step := substring(v_seconds_field FROM 3)::int; + RETURN GREATEST(v_step, 1); + END IF; + IF position(',' IN v_seconds_field) > 0 OR position('-' IN v_seconds_field) > 0 THEN + -- Multiple seconds within each matching minute: could fire every second + RETURN 1; + END IF; + + -- Fixed second: at most once per matching minute + IF v_minutes_field = '*' THEN + RETURN 60; + END IF; + IF v_minutes_field ~ '^\*/\d+$' THEN + v_step := substring(v_minutes_field FROM 3)::int; + RETURN GREATEST(v_step, 1) * 60; + END IF; + IF position(',' IN v_minutes_field) > 0 OR position('-' IN v_minutes_field) > 0 THEN + -- Multiple minutes within each matching hour: adjacent values can be + -- one minute apart + RETURN 60; + END IF; + + -- Fixed minute: at most once per hour + RETURN 3600; +END; +$EOFCODE$ LANGUAGE plpgsql IMMUTABLE; + CREATE FUNCTION app_jobs.add_job( identifier text, payload pg_catalog.json DEFAULT '{}'::json, diff --git a/packages/database-jobs/verify/schemas/app_jobs/procedures/grants/grant_execute_add_scheduled_job_to_authenticated.sql b/packages/database-jobs/verify/schemas/app_jobs/procedures/grants/grant_execute_add_scheduled_job_to_authenticated.sql new file mode 100644 index 000000000..451d17f40 --- /dev/null +++ b/packages/database-jobs/verify/schemas/app_jobs/procedures/grants/grant_execute_add_scheduled_job_to_authenticated.sql @@ -0,0 +1,7 @@ +-- Verify schemas/app_jobs/procedures/grants/grant_execute_add_scheduled_job_to_authenticated on pg + +BEGIN; + +SELECT has_function_privilege('authenticated', 'app_jobs.add_scheduled_job(text, json, json, text, text, integer, integer, uuid, uuid)', 'EXECUTE'); + +ROLLBACK; diff --git a/packages/database-jobs/verify/schemas/app_jobs/procedures/schedule_min_interval_seconds.sql b/packages/database-jobs/verify/schemas/app_jobs/procedures/schedule_min_interval_seconds.sql new file mode 100644 index 000000000..0d7c0810b --- /dev/null +++ b/packages/database-jobs/verify/schemas/app_jobs/procedures/schedule_min_interval_seconds.sql @@ -0,0 +1,7 @@ +-- Verify schemas/app_jobs/procedures/schedule_min_interval_seconds on pg + +BEGIN; + +SELECT verify_function ('app_jobs.schedule_min_interval_seconds'); + +ROLLBACK; diff --git a/packages/metaschema-modules/__tests__/__snapshots__/modules.test.ts.snap b/packages/metaschema-modules/__tests__/__snapshots__/modules.test.ts.snap index dbb2f2604..5ebbe34aa 100644 --- a/packages/metaschema-modules/__tests__/__snapshots__/modules.test.ts.snap +++ b/packages/metaschema-modules/__tests__/__snapshots__/modules.test.ts.snap @@ -7,11 +7,11 @@ exports[`db_meta_modules should have all expected module tables 1`] = ` "billing_module", "billing_provider_module", "compute_log_module", - "config_secrets_module", "config_secrets_user_module", "connected_accounts_module", "crypto_addresses_module", "crypto_auth_module", + "db_preset_module", "db_usage_module", "default_ids_module", "devices_module", @@ -26,6 +26,10 @@ exports[`db_meta_modules should have all expected module tables 1`] = ` "i18n_module", "identity_providers_module", "inference_log_module", + "infra_config_module", + "infra_secrets_module", + "integration_providers_module", + "internal_secrets_module", "invites_module", "limits_module", "membership_types_module", @@ -55,14 +59,15 @@ exports[`db_meta_modules should have all expected module tables 1`] = ` "users_module", "webauthn_auth_module", "webauthn_credentials_module", + "webhook_module", ], } `; exports[`db_meta_modules should verify all module tables exist in metaschema_modules_public schema 1`] = ` { - "moduleTablesCount": 52, - "totalTables": 59, + "moduleTablesCount": 57, + "totalTables": 64, } `; @@ -129,18 +134,20 @@ exports[`db_meta_modules should verify emails_module table structure 1`] = ` exports[`db_meta_modules should verify module table structures have database_id foreign keys 1`] = ` { - "constraintCount": 310684, + "constraintCount": 325437, } `; exports[`db_meta_modules should verify module tables have proper foreign key relationships 1`] = ` { - "constraintCount": 458387, + "constraintCount": 475451, "foreignTables": [ "database", "field", + "function_invocation_module", "function_module", "graph_module", + "infra_secrets_module", "merkle_store_module", "namespace_module", "schema", @@ -202,19 +209,19 @@ exports[`db_meta_modules should verify sessions_module table structure 1`] = ` }, { "column_default": "'sessions'::text", - "column_name": "sessions_table", + "column_name": "sessions_table_name", "data_type": "text", "is_nullable": "NO", }, { "column_default": "'session_credentials'::text", - "column_name": "session_credentials_table", + "column_name": "session_credentials_table_name", "data_type": "text", "is_nullable": "NO", }, { "column_default": "'app_settings_auth'::text", - "column_name": "auth_settings_table", + "column_name": "auth_settings_table_name", "data_type": "text", "is_nullable": "NO", }, @@ -225,14 +232,14 @@ exports[`db_meta_modules should verify sessions_module table structure 1`] = ` exports[`db_meta_modules should verify specific module table column defaults 1`] = ` { "sessionsDefaults": [ - { - "column_default": "'app_settings_auth'::text", - "column_name": "auth_settings_table", - }, { "column_default": "uuid_nil()", "column_name": "auth_settings_table_id", }, + { + "column_default": "'app_settings_auth'::text", + "column_name": "auth_settings_table_name", + }, { "column_default": "uuidv7()", "column_name": "id", @@ -241,26 +248,26 @@ exports[`db_meta_modules should verify specific module table column defaults 1`] "column_default": "uuid_nil()", "column_name": "schema_id", }, - { - "column_default": "'session_credentials'::text", - "column_name": "session_credentials_table", - }, { "column_default": "uuid_nil()", "column_name": "session_credentials_table_id", }, { - "column_default": "'30 days'::interval", - "column_name": "sessions_default_expiration", + "column_default": "'session_credentials'::text", + "column_name": "session_credentials_table_name", }, { - "column_default": "'sessions'::text", - "column_name": "sessions_table", + "column_default": "'30 days'::interval", + "column_name": "sessions_default_expiration", }, { "column_default": "uuid_nil()", "column_name": "sessions_table_id", }, + { + "column_default": "'sessions'::text", + "column_name": "sessions_table_name", + }, { "column_default": "uuid_nil()", "column_name": "users_table_id", diff --git a/packages/metaschema-modules/__tests__/modules.test.ts b/packages/metaschema-modules/__tests__/modules.test.ts index f44492688..464e4f1a9 100644 --- a/packages/metaschema-modules/__tests__/modules.test.ts +++ b/packages/metaschema-modules/__tests__/modules.test.ts @@ -27,7 +27,9 @@ describe('db_meta_modules', () => { 'crypto_auth_module', 'default_ids_module', 'emails_module', - 'config_secrets_module', + 'infra_secrets_module', + 'infra_config_module', + 'internal_secrets_module', 'config_secrets_user_module', 'invites_module', 'events_module', @@ -106,8 +108,8 @@ describe('db_meta_modules', () => { expect(columnNames).toContain('id'); expect(columnNames).toContain('database_id'); expect(columnNames).toContain('schema_id'); - expect(columnNames).toContain('sessions_table'); - expect(columnNames).toContain('session_credentials_table'); + expect(columnNames).toContain('sessions_table_name'); + expect(columnNames).toContain('session_credentials_table_name'); expect(snapshot({ columns })).toMatchSnapshot(); }); diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/agent_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/agent_module/table.sql index 34c488b34..ea31206ec 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/agent_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/agent_module/table.sql @@ -8,6 +8,11 @@ CREATE TABLE metaschema_modules_public.agent_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + + -- Scope-key column name on the generated table(s), recorded by the insert + -- trigger via metaschema_generators.scope_key_column(scope, key): database -> + -- 'database_id', entity -> the module's key ('entity_id' here), global -> NULL. + entity_field text, -- Schema references (if uuid_nil, resolved from schema name or default) schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/billing_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/billing_module/table.sql index ad014191a..3412729ce 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/billing_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/billing_module/table.sql @@ -35,7 +35,7 @@ CREATE TABLE metaschema_modules_public.billing_module ( meter_credits_table_id uuid NOT NULL DEFAULT uuid_nil(), meter_credits_table_name text NOT NULL DEFAULT '', - -- Meter sources table: maps billing meters to typed daily summary table columns + -- Meter sources table: maps billing meters to typed usage summary table columns meter_sources_table_id uuid NOT NULL DEFAULT uuid_nil(), meter_sources_table_name text NOT NULL DEFAULT '', @@ -45,6 +45,8 @@ CREATE TABLE metaschema_modules_public.billing_module ( -- Generated functions record_usage_function text NOT NULL DEFAULT '', + sweep_expired_subscriptions_function text NOT NULL DEFAULT '', + rollup_usage_summary_function text NOT NULL DEFAULT '', prefix text NULL, diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/compute_log_module/constraints/one_platform_scope.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/compute_log_module/constraints/one_platform_scope.sql new file mode 100644 index 000000000..652128207 --- /dev/null +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/compute_log_module/constraints/one_platform_scope.sql @@ -0,0 +1,12 @@ +-- Deploy schemas/metaschema_modules_public/tables/compute_log_module/constraints/one_platform_scope to pg + +-- requires: schemas/metaschema_modules_public/tables/compute_log_module/table + +BEGIN; + +-- At most one platform-scope compute_log_module per database. +CREATE UNIQUE INDEX compute_log_module_one_platform_scope + ON metaschema_modules_public.compute_log_module (database_id) + WHERE scope = 'platform'; + +COMMIT; diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/compute_log_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/compute_log_module/table.sql index 0961bfe36..07a6f8896 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/compute_log_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/compute_log_module/table.sql @@ -8,6 +8,11 @@ CREATE TABLE metaschema_modules_public.compute_log_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + + -- Scope-key column name on the generated table(s), recorded by the insert + -- trigger via metaschema_generators.scope_key_column(scope, key): database -> + -- 'database_id', entity -> the module's key ('entity_id' here), global -> NULL. + entity_field text, schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), @@ -19,9 +24,9 @@ CREATE TABLE metaschema_modules_public.compute_log_module ( compute_log_table_id uuid NOT NULL DEFAULT uuid_nil(), compute_log_table_name text NOT NULL DEFAULT '', - -- Pre-aggregated daily rollup table - usage_daily_table_id uuid NOT NULL DEFAULT uuid_nil(), - usage_daily_table_name text NOT NULL DEFAULT '', + -- Pre-aggregated usage summary rollup table + usage_summary_table_id uuid NOT NULL DEFAULT uuid_nil(), + usage_summary_table_name text NOT NULL DEFAULT '', -- Partition lifecycle configuration "interval" text NOT NULL DEFAULT '1 month', @@ -44,8 +49,8 @@ CREATE TABLE metaschema_modules_public.compute_log_module ( CONSTRAINT schema_fkey FOREIGN KEY (schema_id) REFERENCES metaschema_public.schema (id) ON DELETE CASCADE, CONSTRAINT private_schema_fkey FOREIGN KEY (private_schema_id) REFERENCES metaschema_public.schema (id) ON DELETE CASCADE, CONSTRAINT compute_log_table_fkey FOREIGN KEY (compute_log_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, - CONSTRAINT usage_daily_table_fkey FOREIGN KEY (usage_daily_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, - CONSTRAINT compute_log_module_database_id_prefix_unique UNIQUE NULLS NOT DISTINCT (database_id, prefix) + CONSTRAINT usage_summary_table_fkey FOREIGN KEY (usage_summary_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, + CONSTRAINT compute_log_module_database_id_scope_unique UNIQUE (database_id, scope) ); CREATE INDEX compute_log_module_database_id_idx ON metaschema_modules_public.compute_log_module ( database_id ); diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/config_secrets_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/config_secrets_module/table.sql deleted file mode 100644 index cd571efc1..000000000 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/config_secrets_module/table.sql +++ /dev/null @@ -1,83 +0,0 @@ --- Deploy schemas/metaschema_modules_public/tables/config_secrets_module/table to pg - --- requires: schemas/metaschema_modules_public/schema - -BEGIN; - -CREATE TABLE metaschema_modules_public.config_secrets_module ( - id uuid PRIMARY KEY DEFAULT uuidv7(), - database_id uuid NOT NULL, - - -- Schema references (resolved by BEFORE INSERT trigger when uuid_nil) - schema_id uuid NOT NULL DEFAULT uuid_nil(), - private_schema_id uuid NOT NULL DEFAULT uuid_nil(), - - -- Schema name overrides: when set, the trigger uses these instead of hardcoded defaults. - public_schema_name text, - private_schema_name text, - - -- Generated table IDs (populated by the generator) - table_id uuid NOT NULL DEFAULT uuid_nil(), - - -- Table name (input — bare name without scope prefix). - -- The trigger prepends the scope prefix automatically. - table_name text NOT NULL DEFAULT 'secrets', - - -- API routing (get-or-create: if set, schema is added to this API) - api_name text DEFAULT 'config', - private_api_name text DEFAULT NULL, - - -- Scope: determines the security level for this module instance. - -- Resolved to a membership_type integer at trigger time via membership_types table. - -- 'app' = app-level (AuthzAppMembership, admin-only secrets + config) - -- 'platform' / 'database' = platform-level infra (database scope carries a database_id) - -- custom entity type names for entity-scoped secrets - -- Note: user-scoped credentials are handled by user_credentials_module (separate module) - scope text NOT NULL DEFAULT 'app', - - -- Table name prefix. Auto-derived from scope by the trigger when empty. - -- Override to create multiple module instances at the same scope. - prefix text NOT NULL DEFAULT '', - - -- Entity table for RLS (NULL for app-level, entity table for entity-scoped) - entity_table_id uuid NULL, - - -- Configurable security policies (NULL = use defaults based on scope). - -- When provided, replaces the default policy set in the security function. - -- Accepts a JSON array of policy objects: - -- {"$type": "AuthzEntityMembership", "privileges": ["select", "update"], "data": {...}} - policies jsonb NULL, - - -- Per-table provisions overrides from blueprint config. - -- Keys are table keys (secrets). - -- When a key is present, the module trigger skips default security for that table; - -- secure_table_provision applies the custom grants/policies instead. - provisions jsonb NULL, - - -- Feature flags - - -- When true, also creates a plaintext config table ({prefix}_config). - -- Only meaningful for app-level scope (scope = 'app'). - has_config boolean NOT NULL DEFAULT false, - - -- Constraints - CONSTRAINT config_secrets_module_db_fkey FOREIGN KEY (database_id) REFERENCES metaschema_public.database (id) ON DELETE CASCADE, - CONSTRAINT config_secrets_module_schema_fkey FOREIGN KEY (schema_id) REFERENCES metaschema_public.schema (id) ON DELETE CASCADE, - CONSTRAINT config_secrets_module_private_schema_fkey FOREIGN KEY (private_schema_id) REFERENCES metaschema_public.schema (id) ON DELETE CASCADE, - CONSTRAINT config_secrets_module_table_fkey FOREIGN KEY (table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, - CONSTRAINT config_secrets_module_entity_table_fkey FOREIGN KEY (entity_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE -); - -CREATE INDEX config_secrets_module_database_id_idx ON metaschema_modules_public.config_secrets_module ( database_id ); -CREATE INDEX config_secrets_module_schema_id_idx ON metaschema_modules_public.config_secrets_module ( schema_id ); -CREATE INDEX config_secrets_module_table_id_idx ON metaschema_modules_public.config_secrets_module ( table_id ); - --- Unique constraint: one config_secrets module per database per scope (K8s infra: scopes never share). -CREATE UNIQUE INDEX config_secrets_module_unique_scope ON metaschema_modules_public.config_secrets_module ( database_id, scope ); - -COMMENT ON TABLE metaschema_modules_public.config_secrets_module IS - 'Entity-aware PGP-encrypted key-value config/secrets module. Supports app-level (admin-only) - and platform/database-scoped infra secrets via the scope column. - User-scoped bcrypt credentials are handled by user_credentials_module.'; - -COMMIT; diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/config_secrets_user_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/config_secrets_user_module/table.sql index a4982746e..9dfe1f388 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/config_secrets_user_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/config_secrets_user_module/table.sql @@ -8,6 +8,10 @@ CREATE TABLE metaschema_modules_public.config_secrets_user_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + + -- Scope-key column name on the generated table ('owner_id' here). + -- Recorded so consumers read it as a stored fact instead of hardcoding. + entity_field text, -- schema_id uuid NOT NULL DEFAULT uuid_nil(), table_id uuid NOT NULL DEFAULT uuid_nil(), @@ -22,6 +26,6 @@ CREATE TABLE metaschema_modules_public.config_secrets_user_module ( CONSTRAINT table_fkey FOREIGN KEY (table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE ); -CREATE INDEX config_secrets_user_module_database_id_idx ON metaschema_modules_public.config_secrets_user_module ( database_id ); +CREATE UNIQUE INDEX config_secrets_user_module_database_id_idx ON metaschema_modules_public.config_secrets_user_module ( database_id ); COMMIT; diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/db_preset_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/db_preset_module/table.sql new file mode 100644 index 000000000..36e8cb522 --- /dev/null +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/db_preset_module/table.sql @@ -0,0 +1,40 @@ +-- Deploy schemas/metaschema_modules_public/tables/db_preset_module/table to pg + +-- requires: schemas/metaschema_modules_public/schema +-- requires: schemas/metaschema_modules_public/tables/merkle_store_module/table + +BEGIN; + +CREATE TABLE metaschema_modules_public.db_preset_module ( + id uuid PRIMARY KEY DEFAULT uuidv7(), + database_id uuid NOT NULL, + public_schema_id uuid NOT NULL DEFAULT uuid_nil(), + private_schema_id uuid NOT NULL DEFAULT uuid_nil(), + public_schema_name text, + private_schema_name text, + scope text NOT NULL, + prefix text NOT NULL, + merkle_store_module_id uuid NOT NULL, + db_presets_table_id uuid NOT NULL DEFAULT uuid_nil(), + + -- Store row name inside the Merkle store tables (one shared infra store) + store_name text NOT NULL, + + api_name text, + private_api_name text, + entity_table_id uuid NULL, + policies jsonb NULL, + provisions jsonb NULL, + created_at timestamptz NOT NULL DEFAULT now(), + CONSTRAINT db_fkey FOREIGN KEY (database_id) REFERENCES metaschema_public.database (id) ON DELETE CASCADE, + CONSTRAINT public_schema_fkey FOREIGN KEY (public_schema_id) REFERENCES metaschema_public.schema (id) ON DELETE CASCADE, + CONSTRAINT private_schema_fkey FOREIGN KEY (private_schema_id) REFERENCES metaschema_public.schema (id) ON DELETE CASCADE, + CONSTRAINT merkle_store_fkey FOREIGN KEY (merkle_store_module_id) REFERENCES metaschema_modules_public.merkle_store_module (id) ON DELETE CASCADE, + CONSTRAINT db_presets_table_fkey FOREIGN KEY (db_presets_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, + CONSTRAINT db_preset_module_entity_table_fkey FOREIGN KEY (entity_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, + CONSTRAINT db_preset_module_database_merkle_unique UNIQUE (database_id, merkle_store_module_id) +); + +CREATE INDEX db_preset_module_database_id_idx ON metaschema_modules_public.db_preset_module ( database_id ); + +COMMIT; diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/db_usage_module/constraints/one_platform_scope.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/db_usage_module/constraints/one_platform_scope.sql new file mode 100644 index 000000000..a9b8b67bc --- /dev/null +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/db_usage_module/constraints/one_platform_scope.sql @@ -0,0 +1,12 @@ +-- Deploy schemas/metaschema_modules_public/tables/db_usage_module/constraints/one_platform_scope to pg + +-- requires: schemas/metaschema_modules_public/tables/db_usage_module/table + +BEGIN; + +-- At most one platform-scope db_usage_module per database. +CREATE UNIQUE INDEX db_usage_module_one_platform_scope + ON metaschema_modules_public.db_usage_module (database_id) + WHERE scope = 'platform'; + +COMMIT; diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/db_usage_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/db_usage_module/table.sql index 5465b033c..e0a3b55eb 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/db_usage_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/db_usage_module/table.sql @@ -8,6 +8,11 @@ CREATE TABLE metaschema_modules_public.db_usage_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + + -- Scope-key column name on the generated table(s), recorded by the insert + -- trigger via metaschema_generators.scope_key_column(scope, key): database -> + -- 'database_id', entity -> the module's key ('entity_id' here), global -> NULL. + entity_field text, schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), @@ -19,17 +24,23 @@ CREATE TABLE metaschema_modules_public.db_usage_module ( table_stats_log_table_id uuid NOT NULL DEFAULT uuid_nil(), table_stats_log_table_name text NOT NULL DEFAULT '', - -- DB table stats daily rollup - table_stats_daily_table_id uuid NOT NULL DEFAULT uuid_nil(), - table_stats_daily_table_name text NOT NULL DEFAULT '', + -- DB table stats usage summary rollup + table_stats_summary_table_id uuid NOT NULL DEFAULT uuid_nil(), + table_stats_summary_table_name text NOT NULL DEFAULT '', -- DB query stats log (partitioned — query execution time from pg_stat_statements) query_stats_log_table_id uuid NOT NULL DEFAULT uuid_nil(), query_stats_log_table_name text NOT NULL DEFAULT '', - -- DB query stats daily rollup - query_stats_daily_table_id uuid NOT NULL DEFAULT uuid_nil(), - query_stats_daily_table_name text NOT NULL DEFAULT '', + -- DB query stats usage summary rollup + query_stats_summary_table_id uuid NOT NULL DEFAULT uuid_nil(), + query_stats_summary_table_name text NOT NULL DEFAULT '', + + -- Generated functions + collect_db_table_stats_function text NOT NULL DEFAULT '', + collect_db_query_stats_function text NOT NULL DEFAULT '', + rollup_db_table_stats_usage_summary_function text NOT NULL DEFAULT '', + rollup_db_query_stats_usage_summary_function text NOT NULL DEFAULT '', -- Partition lifecycle configuration "interval" text NOT NULL DEFAULT '1 month', @@ -54,10 +65,10 @@ CREATE TABLE metaschema_modules_public.db_usage_module ( CONSTRAINT schema_fkey FOREIGN KEY (schema_id) REFERENCES metaschema_public.schema (id) ON DELETE CASCADE, CONSTRAINT private_schema_fkey FOREIGN KEY (private_schema_id) REFERENCES metaschema_public.schema (id) ON DELETE CASCADE, CONSTRAINT table_stats_log_table_fkey FOREIGN KEY (table_stats_log_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, - CONSTRAINT table_stats_daily_table_fkey FOREIGN KEY (table_stats_daily_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, + CONSTRAINT table_stats_summary_table_fkey FOREIGN KEY (table_stats_summary_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, CONSTRAINT query_stats_log_table_fkey FOREIGN KEY (query_stats_log_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, - CONSTRAINT query_stats_daily_table_fkey FOREIGN KEY (query_stats_daily_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, - CONSTRAINT db_usage_module_database_id_prefix_unique UNIQUE NULLS NOT DISTINCT (database_id, prefix) + CONSTRAINT query_stats_summary_table_fkey FOREIGN KEY (query_stats_summary_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, + CONSTRAINT db_usage_module_database_id_scope_unique UNIQUE (database_id, scope) ); CREATE INDEX db_usage_module_database_id_idx ON metaschema_modules_public.db_usage_module ( database_id ); diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/devices_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/devices_module/table.sql index b6c5837ec..159c4a370 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/devices_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/devices_module/table.sql @@ -13,8 +13,8 @@ CREATE TABLE metaschema_modules_public.devices_module ( user_devices_table_id uuid NOT NULL DEFAULT uuid_nil(), device_settings_table_id uuid NOT NULL DEFAULT uuid_nil(), - user_devices_table text NOT NULL DEFAULT 'auth_user_devices', - device_settings_table text NOT NULL DEFAULT 'app_settings_device', + user_devices_table_name text NOT NULL DEFAULT 'auth_user_devices', + device_settings_table_name text NOT NULL DEFAULT 'app_settings_device', -- CONSTRAINT db_fkey FOREIGN KEY (database_id) REFERENCES metaschema_public.database (id) ON DELETE CASCADE, diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/events_module/constraints/one_platform_scope.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/events_module/constraints/one_platform_scope.sql new file mode 100644 index 000000000..be49eae3b --- /dev/null +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/events_module/constraints/one_platform_scope.sql @@ -0,0 +1,12 @@ +-- Deploy schemas/metaschema_modules_public/tables/events_module/constraints/one_platform_scope to pg + +-- requires: schemas/metaschema_modules_public/tables/events_module/table + +BEGIN; + +-- At most one platform-scope events_module per database. +CREATE UNIQUE INDEX events_module_one_platform_scope + ON metaschema_modules_public.events_module (database_id) + WHERE scope = 'platform'; + +COMMIT; diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/events_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/events_module/table.sql index b3a203263..cd27009e9 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/events_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/events_module/table.sql @@ -8,6 +8,11 @@ CREATE TABLE metaschema_modules_public.events_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + + -- Scope-key column name on the generated table(s), recorded by the insert + -- trigger via metaschema_generators.scope_key_column(scope, key): database -> + -- 'database_id', entity -> the module's key ('entity_id' here), global -> NULL. + entity_field text, -- schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), @@ -46,7 +51,6 @@ CREATE TABLE metaschema_modules_public.events_module ( tg_event_bool text NOT NULL DEFAULT '', upsert_aggregate text NOT NULL DEFAULT '', tg_update_aggregates text NOT NULL DEFAULT '', - prune_events text NOT NULL DEFAULT '', steps_required text NOT NULL DEFAULT '', level_achieved text NOT NULL DEFAULT '', tg_check_achievements text NOT NULL DEFAULT '', diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/function_deployment_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/function_deployment_module/table.sql index 2671cd2f5..b810d603c 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/function_deployment_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/function_deployment_module/table.sql @@ -8,6 +8,11 @@ CREATE TABLE metaschema_modules_public.function_deployment_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + + -- Scope-key column name on the generated table(s), recorded by the insert + -- trigger via metaschema_generators.scope_key_column(scope, key): database -> + -- 'database_id', entity -> the module's key ('entity_id' here), global -> NULL. + entity_field text, -- Schema references (if uuid_nil, resolved from schema name or default) schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/function_invocation_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/function_invocation_module/table.sql index 20ff64e28..4e7616b0c 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/function_invocation_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/function_invocation_module/table.sql @@ -8,6 +8,11 @@ CREATE TABLE metaschema_modules_public.function_invocation_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + + -- Scope-key column name on the generated table(s), recorded by the insert + -- trigger via metaschema_generators.scope_key_column(scope, key): database -> + -- 'database_id', entity -> the module's key ('entity_id' here), global -> NULL. + entity_field text, -- Schema references (if uuid_nil, resolved from schema name or default) schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/function_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/function_module/table.sql index 5aa5279bb..d31de9403 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/function_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/function_module/table.sql @@ -8,6 +8,11 @@ CREATE TABLE metaschema_modules_public.function_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + + -- Scope-key column name on the generated table(s), recorded by the insert + -- trigger via metaschema_generators.scope_key_column(scope, key): database -> + -- 'database_id', entity -> the module's key ('entity_id' here), global -> NULL. + entity_field text, -- Schema references (if uuid_nil, resolved from schema name or default) schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), @@ -18,11 +23,19 @@ CREATE TABLE metaschema_modules_public.function_module ( -- Generated table IDs (populated by the generator) definitions_table_id uuid NOT NULL DEFAULT uuid_nil(), + bindings_table_id uuid NOT NULL DEFAULT uuid_nil(), + schedules_table_id uuid, + + -- Optional cron scheduling support. + has_cron boolean NOT NULL DEFAULT false, -- Table names (input to the generator — bare names without scope prefix). -- The trigger prepends the scope prefix automatically. definitions_table_name text NOT NULL DEFAULT 'function_definitions', + -- Actual generated api bindings table name (recorded by the generator) + bindings_table_name text, + -- API routing (get-or-create: if set, schema is added to this API; if NULL, no API is added) api_name text, private_api_name text, @@ -32,7 +45,7 @@ CREATE TABLE metaschema_modules_public.function_module ( scope text NOT NULL DEFAULT 'app', -- Table name prefix. Auto-derived from scope by the trigger when empty. - -- Override to create multiple module instances at the same scope. + -- Naming-only: instances are unique per (database_id, scope). prefix text NOT NULL DEFAULT '', -- Entity table for RLS (NULL for app-level functions, entity table for entity-scoped functions) @@ -59,6 +72,8 @@ CREATE TABLE metaschema_modules_public.function_module ( CONSTRAINT function_module_schema_fkey FOREIGN KEY (schema_id) REFERENCES metaschema_public.schema (id) ON DELETE CASCADE, CONSTRAINT function_module_private_schema_fkey FOREIGN KEY (private_schema_id) REFERENCES metaschema_public.schema (id) ON DELETE CASCADE, CONSTRAINT function_module_definitions_table_fkey FOREIGN KEY (definitions_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, + CONSTRAINT function_module_bindings_table_fkey FOREIGN KEY (bindings_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, + CONSTRAINT function_module_schedules_table_fkey FOREIGN KEY (schedules_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, CONSTRAINT function_module_entity_table_fkey FOREIGN KEY (entity_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE ); diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/graph_execution_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/graph_execution_module/table.sql index 52e75b089..92e1197e0 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/graph_execution_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/graph_execution_module/table.sql @@ -9,6 +9,11 @@ CREATE TABLE metaschema_modules_public.graph_execution_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + + -- Scope-key column name on the generated table(s), recorded by the insert + -- trigger via metaschema_generators.scope_key_column(scope, key): database -> + -- 'database_id', entity -> the module's key ('entity_id' here), global -> NULL. + entity_field text, -- Schema references (if uuid_nil, resolved from schema name or default) schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/graph_module/constraints/one_platform_scope.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/graph_module/constraints/one_platform_scope.sql new file mode 100644 index 000000000..ca94d27a4 --- /dev/null +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/graph_module/constraints/one_platform_scope.sql @@ -0,0 +1,12 @@ +-- Deploy schemas/metaschema_modules_public/tables/graph_module/constraints/one_platform_scope to pg + +-- requires: schemas/metaschema_modules_public/tables/graph_module/table + +BEGIN; + +-- At most one platform-scope graph_module per database. +CREATE UNIQUE INDEX graph_module_one_platform_scope + ON metaschema_modules_public.graph_module (database_id) + WHERE scope = 'platform'; + +COMMIT; diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/graph_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/graph_module/table.sql index 7e60af1e8..1a7ec4b12 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/graph_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/graph_module/table.sql @@ -9,6 +9,11 @@ CREATE TABLE metaschema_modules_public.graph_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + + -- Scope-key column name on the generated table(s), recorded by the insert + -- trigger via metaschema_generators.scope_key_column(scope, key): database -> + -- 'database_id', entity -> the module's key ('entity_id' here), global -> NULL. + entity_field text, -- Schema references (if uuid_nil, resolved from schema name or default) public_schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/hierarchy_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/hierarchy_module/table.sql index 6a35647c9..58654fb64 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/hierarchy_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/hierarchy_module/table.sql @@ -8,6 +8,11 @@ CREATE TABLE metaschema_modules_public.hierarchy_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + + -- Scope-key column name on the generated table(s), recorded by the insert + -- trigger via metaschema_generators.scope_key_column(scope, key): database -> + -- 'database_id', entity -> the module's key ('entity_id' here), global -> NULL. + entity_field text, -- Schema references schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/identity_providers_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/identity_providers_module/table.sql index ee896fe7f..a3af2df6c 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/identity_providers_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/identity_providers_module/table.sql @@ -8,6 +8,11 @@ CREATE TABLE metaschema_modules_public.identity_providers_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + + -- Scope-key column name on the generated table(s), recorded by the insert + -- trigger via metaschema_generators.scope_key_column(scope, key): database -> + -- 'database_id', entity -> the module's key ('entity_id' here), global -> NULL. + entity_field text, schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), @@ -23,7 +28,7 @@ CREATE TABLE metaschema_modules_public.identity_providers_module ( api_name text DEFAULT 'auth', private_api_name text DEFAULT NULL, - -- Entity-aware scope: determines which config_secrets_module table + -- Entity-aware scope: determines which internal_secrets_module table -- the rotate_identity_provider_{prefix}_secret proc targets. -- 'app' = app_secrets (AuthzAppMembership, admin-only) -- 'platform' = platform_secrets (platform-wide infra) @@ -57,7 +62,7 @@ CREATE UNIQUE INDEX identity_providers_module_unique_scope ON metaschema_modules COMMENT ON TABLE metaschema_modules_public.identity_providers_module IS 'Entity-aware config row for the identity_providers_module, which provisions a per-database identity_providers table holding OAuth2 / OIDC (and future SAML) provider definitions. - The scope column determines which config_secrets_module table the rotate proc targets + The scope column determines which internal_secrets_module table the rotate proc targets (app_secrets for app scope, platform_secrets for platform scope). When scope = database, the secrets table gets a database_id column. Scoping matrix: diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/inference_log_module/constraints/one_platform_scope.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/inference_log_module/constraints/one_platform_scope.sql new file mode 100644 index 000000000..4f0812403 --- /dev/null +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/inference_log_module/constraints/one_platform_scope.sql @@ -0,0 +1,12 @@ +-- Deploy schemas/metaschema_modules_public/tables/inference_log_module/constraints/one_platform_scope to pg + +-- requires: schemas/metaschema_modules_public/tables/inference_log_module/table + +BEGIN; + +-- At most one platform-scope inference_log_module per database. +CREATE UNIQUE INDEX inference_log_module_one_platform_scope + ON metaschema_modules_public.inference_log_module (database_id) + WHERE scope = 'platform'; + +COMMIT; diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/inference_log_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/inference_log_module/table.sql index 5f2dc9647..f015ae88c 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/inference_log_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/inference_log_module/table.sql @@ -8,6 +8,11 @@ CREATE TABLE metaschema_modules_public.inference_log_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + + -- Scope-key column name on the generated table(s), recorded by the insert + -- trigger via metaschema_generators.scope_key_column(scope, key): database -> + -- 'database_id', entity -> the module's key ('entity_id' here), global -> NULL. + entity_field text, schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), @@ -19,9 +24,9 @@ CREATE TABLE metaschema_modules_public.inference_log_module ( inference_log_table_id uuid NOT NULL DEFAULT uuid_nil(), inference_log_table_name text NOT NULL DEFAULT '', - -- Pre-aggregated daily rollup table - usage_daily_table_id uuid NOT NULL DEFAULT uuid_nil(), - usage_daily_table_name text NOT NULL DEFAULT '', + -- Pre-aggregated usage summary rollup table + usage_summary_table_id uuid NOT NULL DEFAULT uuid_nil(), + usage_summary_table_name text NOT NULL DEFAULT '', -- Partition lifecycle configuration "interval" text NOT NULL DEFAULT '1 month', @@ -44,7 +49,7 @@ CREATE TABLE metaschema_modules_public.inference_log_module ( CONSTRAINT schema_fkey FOREIGN KEY (schema_id) REFERENCES metaschema_public.schema (id) ON DELETE CASCADE, CONSTRAINT private_schema_fkey FOREIGN KEY (private_schema_id) REFERENCES metaschema_public.schema (id) ON DELETE CASCADE, CONSTRAINT inference_log_table_fkey FOREIGN KEY (inference_log_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, - CONSTRAINT usage_daily_table_fkey FOREIGN KEY (usage_daily_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, + CONSTRAINT usage_summary_table_fkey FOREIGN KEY (usage_summary_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, CONSTRAINT inference_log_module_database_id_prefix_unique UNIQUE NULLS NOT DISTINCT (database_id, prefix) ); diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/infra_config_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/infra_config_module/table.sql new file mode 100644 index 000000000..54360049f --- /dev/null +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/infra_config_module/table.sql @@ -0,0 +1,67 @@ +-- Deploy schemas/metaschema_modules_public/tables/infra_config_module/table to pg + +-- requires: schemas/metaschema_modules_public/schema + +BEGIN; + +CREATE TABLE metaschema_modules_public.infra_config_module ( + id uuid PRIMARY KEY DEFAULT uuidv7(), + database_id uuid NOT NULL, + + + -- Scope-key column name on the generated table(s), recorded by the insert + -- trigger via metaschema_generators.scope_key_column(scope, key): database -> + -- 'database_id', entity -> the module's key ('entity_id' here), global -> NULL. + entity_field text, + -- Schema references (resolved by BEFORE INSERT trigger when uuid_nil) + schema_id uuid NOT NULL DEFAULT uuid_nil(), + private_schema_id uuid NOT NULL DEFAULT uuid_nil(), + + -- Schema name overrides: when set, the trigger uses these instead of hardcoded defaults. + public_schema_name text, + private_schema_name text, + + -- Generated table IDs (populated by the generator) + config_table_id uuid NOT NULL DEFAULT uuid_nil(), + + -- Table name (input -- bare name without scope prefix). + -- The trigger prepends the scope prefix automatically. + config_table_name text NOT NULL DEFAULT 'config', + + -- API routing (get-or-create: if set, schema is added to this API) + api_name text DEFAULT 'config', + private_api_name text DEFAULT NULL, + + -- Scope: determines the security level for this module instance. + scope text NOT NULL DEFAULT 'app', + + -- Table name prefix. Auto-derived from scope by the trigger when empty. + prefix text NOT NULL DEFAULT '', + + -- Entity table for RLS (NULL for app-level, entity table for entity-scoped) + entity_table_id uuid NULL, + + -- Configurable security policies (NULL = use defaults based on scope). + policies jsonb NULL, + + -- Per-table provisions overrides from blueprint config. + provisions jsonb NULL, + + -- Constraints + CONSTRAINT infra_config_module_db_fkey FOREIGN KEY (database_id) REFERENCES metaschema_public.database (id) ON DELETE CASCADE, + CONSTRAINT infra_config_module_schema_fkey FOREIGN KEY (schema_id) REFERENCES metaschema_public.schema (id) ON DELETE CASCADE, + CONSTRAINT infra_config_module_private_schema_fkey FOREIGN KEY (private_schema_id) REFERENCES metaschema_public.schema (id) ON DELETE CASCADE, + CONSTRAINT infra_config_module_config_table_fkey FOREIGN KEY (config_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, + CONSTRAINT infra_config_module_entity_table_fkey FOREIGN KEY (entity_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE +); + +CREATE INDEX infra_config_module_database_id_idx ON metaschema_modules_public.infra_config_module ( database_id ); +CREATE INDEX infra_config_module_schema_id_idx ON metaschema_modules_public.infra_config_module ( schema_id ); +CREATE INDEX infra_config_module_config_table_id_idx ON metaschema_modules_public.infra_config_module ( config_table_id ); + +CREATE UNIQUE INDEX infra_config_module_unique_scope ON metaschema_modules_public.infra_config_module ( database_id, scope ); + +COMMENT ON TABLE metaschema_modules_public.infra_config_module IS + 'Namespace-backed plaintext key-value config module. Requires namespace_module and emits namespace:sync_config job triggers for K8s ConfigMap synchronization.'; + +COMMIT; diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/infra_secrets_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/infra_secrets_module/table.sql new file mode 100644 index 000000000..6eec0c655 --- /dev/null +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/infra_secrets_module/table.sql @@ -0,0 +1,68 @@ +-- Deploy schemas/metaschema_modules_public/tables/infra_secrets_module/table to pg + +-- requires: schemas/metaschema_modules_public/schema + +BEGIN; + +CREATE TABLE metaschema_modules_public.infra_secrets_module ( + id uuid PRIMARY KEY DEFAULT uuidv7(), + database_id uuid NOT NULL, + + -- Schema references (resolved by BEFORE INSERT trigger when uuid_nil) + schema_id uuid NOT NULL DEFAULT uuid_nil(), + private_schema_id uuid NOT NULL DEFAULT uuid_nil(), + + -- Schema name overrides: when set, the trigger uses these instead of hardcoded defaults. + public_schema_name text, + private_schema_name text, + + -- Generated table IDs (populated by the generator) + secrets_table_id uuid NOT NULL DEFAULT uuid_nil(), + + -- Table name (input -- bare name without scope prefix). + -- The trigger prepends the scope prefix automatically. + secrets_table_name text NOT NULL DEFAULT 'secrets', + + -- API routing (get-or-create: if set, schema is added to this API) + api_name text DEFAULT 'config', + private_api_name text DEFAULT NULL, + + -- Scope: determines the security level for this module instance. + scope text NOT NULL DEFAULT 'app', + + -- Table name prefix. Auto-derived from scope by the trigger when empty. + prefix text NOT NULL DEFAULT '', + + -- Entity table for RLS (NULL for app-level, entity table for entity-scoped) + entity_table_id uuid NULL, + + -- Scope-key column name on the generated secrets table, recorded by the + -- insert trigger via metaschema_generators.scope_key_column(scope, key). + -- database → 'database_id', entity → the module's key ('owner_id' here), + -- global (platform/app) → NULL. Consumers read this instead of hardcoding. + entity_field text, + + -- Configurable security policies (NULL = use defaults based on scope). + policies jsonb NULL, + + -- Per-table provisions overrides from blueprint config. + provisions jsonb NULL, + + -- Constraints + CONSTRAINT infra_secrets_module_db_fkey FOREIGN KEY (database_id) REFERENCES metaschema_public.database (id) ON DELETE CASCADE, + CONSTRAINT infra_secrets_module_schema_fkey FOREIGN KEY (schema_id) REFERENCES metaschema_public.schema (id) ON DELETE CASCADE, + CONSTRAINT infra_secrets_module_private_schema_fkey FOREIGN KEY (private_schema_id) REFERENCES metaschema_public.schema (id) ON DELETE CASCADE, + CONSTRAINT infra_secrets_module_secrets_table_fkey FOREIGN KEY (secrets_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, + CONSTRAINT infra_secrets_module_entity_table_fkey FOREIGN KEY (entity_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE +); + +CREATE INDEX infra_secrets_module_database_id_idx ON metaschema_modules_public.infra_secrets_module ( database_id ); +CREATE INDEX infra_secrets_module_schema_id_idx ON metaschema_modules_public.infra_secrets_module ( schema_id ); +CREATE INDEX infra_secrets_module_secrets_table_id_idx ON metaschema_modules_public.infra_secrets_module ( secrets_table_id ); + +CREATE UNIQUE INDEX infra_secrets_module_unique_scope ON metaschema_modules_public.infra_secrets_module ( database_id, scope ); + +COMMENT ON TABLE metaschema_modules_public.infra_secrets_module IS + 'Namespace-backed PGP-encrypted key-value secrets module. Requires namespace_module and emits namespace:sync_secrets job triggers for K8s Secret synchronization.'; + +COMMIT; diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/integration_providers_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/integration_providers_module/table.sql new file mode 100644 index 000000000..7ee85638d --- /dev/null +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/integration_providers_module/table.sql @@ -0,0 +1,63 @@ +-- Deploy schemas/metaschema_modules_public/tables/integration_providers_module/table to pg + +-- requires: schemas/metaschema_modules_public/schema + +BEGIN; + +CREATE TABLE metaschema_modules_public.integration_providers_module ( + id uuid PRIMARY KEY DEFAULT uuidv7(), + database_id uuid NOT NULL, + + -- Scope-key column name on the generated table(s), recorded by the insert + -- trigger via metaschema_generators.scope_key_column(scope, key): database -> + -- 'database_id', entity -> the module's key, global (platform/app) -> NULL. + entity_field text, + schema_id uuid NOT NULL DEFAULT uuid_nil(), + private_schema_id uuid NOT NULL DEFAULT uuid_nil(), + + -- Schema name overrides: when set, the trigger uses these instead of hardcoded defaults. + public_schema_name text, + private_schema_name text, + + -- Generated table IDs (populated by the generator) + table_id uuid NOT NULL DEFAULT uuid_nil(), + + -- Table name (input -- bare name without scope prefix) + table_name text NOT NULL DEFAULT 'integration_providers', + + -- API routing (configurable per-module) + api_name text DEFAULT 'compute', + private_api_name text DEFAULT NULL, + + -- Scope: integration providers are installed once per database as platform scope. + scope text NOT NULL DEFAULT 'platform', + + -- Table name prefix. Auto-derived from scope by the trigger when empty. + prefix text NOT NULL DEFAULT '', + + -- Entity table for RLS (NULL for platform scope) + entity_table_id uuid NULL, + + CONSTRAINT integration_providers_module_db_fkey FOREIGN KEY (database_id) REFERENCES metaschema_public.database (id) ON DELETE CASCADE, + CONSTRAINT integration_providers_module_table_fkey FOREIGN KEY (table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, + CONSTRAINT integration_providers_module_schema_fkey FOREIGN KEY (schema_id) REFERENCES metaschema_public.schema (id) ON DELETE CASCADE, + CONSTRAINT integration_providers_module_private_schema_fkey FOREIGN KEY (private_schema_id) REFERENCES metaschema_public.schema (id) ON DELETE CASCADE, + CONSTRAINT integration_providers_module_entity_table_fkey FOREIGN KEY (entity_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE +); + +CREATE INDEX integration_providers_module_database_id_idx ON metaschema_modules_public.integration_providers_module ( database_id ); +CREATE INDEX integration_providers_module_schema_id_idx ON metaschema_modules_public.integration_providers_module ( schema_id ); +CREATE INDEX integration_providers_module_private_schema_id_idx ON metaschema_modules_public.integration_providers_module ( private_schema_id ); +CREATE INDEX integration_providers_module_table_id_idx ON metaschema_modules_public.integration_providers_module ( table_id ); + +-- One install per database per scope (prefix distinguishes multiple instances) +CREATE UNIQUE INDEX integration_providers_module_unique_scope ON metaschema_modules_public.integration_providers_module ( database_id, scope, prefix ); + +COMMENT ON TABLE metaschema_modules_public.integration_providers_module IS + 'Config row for the integration_providers_module, which provisions a per-database + integration_providers table holding branded, reusable service definitions. + Integration providers act as a catalog of external services (e.g. Mailgun, Postgres) + and list the canonical secret/config names required to use them. + Other modules (function_module, resource_module) match the provider slug as a string.'; + +COMMIT; diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/internal_secrets_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/internal_secrets_module/table.sql new file mode 100644 index 000000000..da057582e --- /dev/null +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/internal_secrets_module/table.sql @@ -0,0 +1,68 @@ +-- Deploy schemas/metaschema_modules_public/tables/internal_secrets_module/table to pg + +-- requires: schemas/metaschema_modules_public/schema + +BEGIN; + +CREATE TABLE metaschema_modules_public.internal_secrets_module ( + id uuid PRIMARY KEY DEFAULT uuidv7(), + database_id uuid NOT NULL, + + -- Schema references (resolved by BEFORE INSERT trigger when uuid_nil) + schema_id uuid NOT NULL DEFAULT uuid_nil(), + private_schema_id uuid NOT NULL DEFAULT uuid_nil(), + + -- Schema name overrides: when set, the trigger uses these instead of hardcoded defaults. + public_schema_name text, + private_schema_name text, + + -- Generated table IDs (populated by the generator) + internal_secrets_table_id uuid NOT NULL DEFAULT uuid_nil(), + + -- Table name (input -- bare name without scope prefix). + -- The trigger prepends the scope prefix automatically. + internal_secrets_table_name text NOT NULL DEFAULT 'internal_secrets', + + -- API routing (get-or-create: if set, schema is added to this API) + api_name text DEFAULT 'config', + private_api_name text DEFAULT NULL, + + -- Scope: determines the security level for this module instance. + scope text NOT NULL DEFAULT 'app', + + -- Table name prefix. Auto-derived from scope by the trigger when empty. + prefix text NOT NULL DEFAULT '', + + -- Entity table for RLS (NULL for app-level, entity table for entity-scoped) + entity_table_id uuid NULL, + + -- Scope-key column name on the generated secrets table, recorded by the + -- insert trigger via metaschema_generators.scope_key_column(scope, key). + -- database → 'database_id', entity → the module's key ('owner_id' here), + -- global (platform/app) → NULL. Consumers read this instead of hardcoding. + entity_field text, + + -- Configurable security policies (NULL = use defaults based on scope). + policies jsonb NULL, + + -- Per-table provisions overrides from blueprint config. + provisions jsonb NULL, + + -- Constraints + CONSTRAINT internal_secrets_module_db_fkey FOREIGN KEY (database_id) REFERENCES metaschema_public.database (id) ON DELETE CASCADE, + CONSTRAINT internal_secrets_module_schema_fkey FOREIGN KEY (schema_id) REFERENCES metaschema_public.schema (id) ON DELETE CASCADE, + CONSTRAINT internal_secrets_module_private_schema_fkey FOREIGN KEY (private_schema_id) REFERENCES metaschema_public.schema (id) ON DELETE CASCADE, + CONSTRAINT internal_secrets_module_internal_secrets_table_fkey FOREIGN KEY (internal_secrets_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, + CONSTRAINT internal_secrets_module_entity_table_fkey FOREIGN KEY (entity_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE +); + +CREATE INDEX internal_secrets_module_database_id_idx ON metaschema_modules_public.internal_secrets_module ( database_id ); +CREATE INDEX internal_secrets_module_schema_id_idx ON metaschema_modules_public.internal_secrets_module ( schema_id ); +CREATE INDEX internal_secrets_module_internal_secrets_table_id_idx ON metaschema_modules_public.internal_secrets_module ( internal_secrets_table_id ); + +CREATE UNIQUE INDEX internal_secrets_module_unique_scope ON metaschema_modules_public.internal_secrets_module ( database_id, scope ); + +COMMENT ON TABLE metaschema_modules_public.internal_secrets_module IS + 'App-scoped PGP-encrypted internal secrets store. No namespace_module dependency and no K8s synchronization. Used by identity_providers_module for OAuth2 client_secret storage.'; + +COMMIT; diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/invites_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/invites_module/table.sql index 973712072..7d93d975b 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/invites_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/invites_module/table.sql @@ -8,6 +8,11 @@ CREATE TABLE metaschema_modules_public.invites_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + + -- Scope-key column name on the generated table(s), recorded by the insert + -- trigger via metaschema_generators.scope_key_column(scope, key): database -> + -- 'database_id', entity -> the module's key ('entity_id' here), global -> NULL. + entity_field text, schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/limits_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/limits_module/table.sql index 30fc89398..5cab0e916 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/limits_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/limits_module/table.sql @@ -7,6 +7,11 @@ BEGIN; CREATE TABLE metaschema_modules_public.limits_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + + -- Scope-key column name on the generated table(s), recorded by the insert + -- trigger via metaschema_generators.scope_key_column(scope, key): database -> + -- 'database_id', entity -> the module's key ('entity_id' here), global -> NULL. + entity_field text, -- schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/memberships_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/memberships_module/table.sql index b77af51ec..28ecd64b6 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/memberships_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/memberships_module/table.sql @@ -7,6 +7,11 @@ BEGIN; CREATE TABLE metaschema_modules_public.memberships_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + + -- Scope-key column name on the generated table(s), recorded by the insert + -- trigger via metaschema_generators.scope_key_column(scope, key): database -> + -- 'database_id', entity -> the module's key ('entity_id' here), global -> NULL. + entity_field text, -- schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/merkle_store_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/merkle_store_module/table.sql index ecfb275be..22effd24d 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/merkle_store_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/merkle_store_module/table.sql @@ -8,6 +8,11 @@ CREATE TABLE metaschema_modules_public.merkle_store_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + + -- Scope-key column name on the generated table(s), recorded by the insert + -- trigger via metaschema_generators.scope_key_column(scope, key): database -> + -- 'database_id', entity -> the module's key ('entity_id' here), global -> NULL. + entity_field text, -- Schema references (if uuid_nil, resolved from schema name or default) schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/namespace_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/namespace_module/table.sql index f8109d134..597b3fe08 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/namespace_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/namespace_module/table.sql @@ -8,6 +8,11 @@ CREATE TABLE metaschema_modules_public.namespace_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + + -- Scope-key column name on the generated table(s), recorded by the insert + -- trigger via metaschema_generators.scope_key_column(scope, key): database -> + -- 'database_id', entity -> the module's key ('entity_id' here), global -> NULL. + entity_field text, -- Schema references (if uuid_nil, resolved from schema name or default) schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/notifications_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/notifications_module/table.sql index 59f7886e0..29a0f3c0f 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/notifications_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/notifications_module/table.sql @@ -8,6 +8,10 @@ CREATE TABLE metaschema_modules_public.notifications_module ( id uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), database_id uuid NOT NULL, + + -- Scope-key column name on the generated table ('scope_entity_id' here). + -- Recorded so consumers read it as a stored fact instead of hardcoding. + entity_field text, schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/permissions_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/permissions_module/table.sql index 3ee65a703..3c162723b 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/permissions_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/permissions_module/table.sql @@ -7,6 +7,11 @@ BEGIN; CREATE TABLE metaschema_modules_public.permissions_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + + -- Scope-key column name on the generated table(s), recorded by the insert + -- trigger via metaschema_generators.scope_key_column(scope, key): database -> + -- 'database_id', entity -> the module's key ('entity_id' here), global -> NULL. + entity_field text, -- schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/profiles_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/profiles_module/table.sql index 2e2fe180c..5ffa05a90 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/profiles_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/profiles_module/table.sql @@ -7,6 +7,11 @@ BEGIN; CREATE TABLE metaschema_modules_public.profiles_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + + -- Scope-key column name on the generated table(s), recorded by the insert + -- trigger via metaschema_generators.scope_key_column(scope, key): database -> + -- 'database_id', entity -> the module's key ('entity_id' here), global -> NULL. + entity_field text, -- schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/rate_limits_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/rate_limits_module/table.sql index b53e8927c..e99b0fe84 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/rate_limits_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/rate_limits_module/table.sql @@ -14,9 +14,9 @@ CREATE TABLE metaschema_modules_public.rate_limits_module ( ip_rate_limits_table_id uuid NOT NULL DEFAULT uuid_nil(), rate_limits_table_id uuid NOT NULL DEFAULT uuid_nil(), - rate_limit_settings_table text NOT NULL DEFAULT 'app_settings_rate_limit', - ip_rate_limits_table text NOT NULL DEFAULT 'auth_ip_rate_limits', - rate_limits_table text NOT NULL DEFAULT 'auth_rate_limits', + rate_limit_settings_table_name text NOT NULL DEFAULT 'app_settings_rate_limit', + ip_rate_limits_table_name text NOT NULL DEFAULT 'auth_ip_rate_limits', + rate_limits_table_name text NOT NULL DEFAULT 'auth_rate_limits', -- CONSTRAINT db_fkey FOREIGN KEY (database_id) REFERENCES metaschema_public.database (id) ON DELETE CASCADE, diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/resource_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/resource_module/table.sql index 064432180..3ad6738ff 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/resource_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/resource_module/table.sql @@ -9,6 +9,11 @@ CREATE TABLE metaschema_modules_public.resource_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + + -- Scope-key column name on the generated table(s), recorded by the insert + -- trigger via metaschema_generators.scope_key_column(scope, key): database -> + -- 'database_id', entity -> the module's key ('entity_id' here), global -> NULL. + entity_field text, -- Schema references (if uuid_nil, resolved from schema name or default) schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), @@ -22,6 +27,9 @@ CREATE TABLE metaschema_modules_public.resource_module ( resource_events_table_id uuid NOT NULL DEFAULT uuid_nil(), resource_status_checks_table_id uuid NOT NULL DEFAULT uuid_nil(), resource_definitions_table_id uuid NOT NULL DEFAULT uuid_nil(), + resource_usage_samples_table_id uuid NOT NULL DEFAULT uuid_nil(), + resource_usage_summary_table_id uuid NOT NULL DEFAULT uuid_nil(), + namespace_usage_summary_table_id uuid NOT NULL DEFAULT uuid_nil(), -- Table names (input to the generator — bare names without scope prefix). -- The trigger prepends the scope prefix automatically. @@ -29,6 +37,21 @@ CREATE TABLE metaschema_modules_public.resource_module ( resource_events_table_name text NOT NULL DEFAULT 'resource_events', resource_status_checks_table_name text NOT NULL DEFAULT 'resource_status_checks', resource_definitions_table_name text NOT NULL DEFAULT 'resource_definitions', + resource_usage_samples_table_name text NOT NULL DEFAULT 'resource_usage_samples', + resource_usage_summary_table_name text NOT NULL DEFAULT 'resource_usage_summaries', + namespace_usage_summary_table_name text NOT NULL DEFAULT 'namespace_usage_summaries', + + -- Generated functions (populated by the generator) + rollup_resource_usage_summary_function text NOT NULL DEFAULT '', + -- Billing bridge: empty when not generated (no entity-keyed namespace + -- module or no billing_module for the database) + resource_billing_rollup_function text NOT NULL DEFAULT '', + + -- Requirement view names (derived by the trigger from resources_table_name). + -- Surfaced to the projection handlers via the module loader so consumers + -- never reconstruct the view name from a naming convention. + resolved_requirements_view_name text, + requirements_state_view_name text, -- API routing (get-or-create: if set, schema is added to this API; if NULL, no API is added) api_name text, @@ -64,6 +87,9 @@ CREATE TABLE metaschema_modules_public.resource_module ( CONSTRAINT resource_module_events_table_fkey FOREIGN KEY (resource_events_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, CONSTRAINT resource_module_status_checks_table_fkey FOREIGN KEY (resource_status_checks_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, CONSTRAINT resource_module_definitions_table_fkey FOREIGN KEY (resource_definitions_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, + CONSTRAINT resource_module_usage_samples_table_fkey FOREIGN KEY (resource_usage_samples_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, + CONSTRAINT resource_module_usage_summary_table_fkey FOREIGN KEY (resource_usage_summary_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, + CONSTRAINT resource_module_ns_usage_summary_table_fkey FOREIGN KEY (namespace_usage_summary_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, CONSTRAINT resource_module_entity_table_fkey FOREIGN KEY (entity_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, CONSTRAINT resource_module_namespace_module_fkey FOREIGN KEY (namespace_module_id) REFERENCES metaschema_modules_public.namespace_module (id) ON DELETE SET NULL ); diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/session_secrets_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/session_secrets_module/table.sql index fac26ebaa..4b8eda28e 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/session_secrets_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/session_secrets_module/table.sql @@ -20,12 +20,12 @@ CREATE TABLE metaschema_modules_public.session_secrets_module ( CONSTRAINT sessions_table_fkey FOREIGN KEY (sessions_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE ); -CREATE INDEX session_secrets_module_database_id_idx ON metaschema_modules_public.session_secrets_module ( database_id ); +CREATE UNIQUE INDEX session_secrets_module_database_id_idx ON metaschema_modules_public.session_secrets_module ( database_id ); CREATE INDEX session_secrets_module_schema_id_idx ON metaschema_modules_public.session_secrets_module ( schema_id ); CREATE INDEX session_secrets_module_table_id_idx ON metaschema_modules_public.session_secrets_module ( table_id ); CREATE INDEX session_secrets_module_sessions_table_id_idx ON metaschema_modules_public.session_secrets_module ( sessions_table_id ); COMMENT ON TABLE metaschema_modules_public.session_secrets_module IS 'Config row for the session_secrets_module, which provisions a DB-private, session-scoped ephemeral key-value store for challenges, nonces, and one-time tokens that must never be readable by end users.'; -COMMENT ON COLUMN metaschema_modules_public.session_secrets_module.sessions_table_id IS 'Resolved reference to sessions_module.sessions_table, used to FK session_secrets.session_id with ON DELETE CASCADE.'; +COMMENT ON COLUMN metaschema_modules_public.session_secrets_module.sessions_table_id IS 'Resolved reference to sessions_module.sessions_table_name, used to FK session_secrets.session_id with ON DELETE CASCADE.'; COMMIT; diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/sessions_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/sessions_module/table.sql index c1ffb7973..8007fda1a 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/sessions_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/sessions_module/table.sql @@ -16,9 +16,9 @@ CREATE TABLE metaschema_modules_public.sessions_module ( users_table_id uuid NOT NULL DEFAULT uuid_nil(), sessions_default_expiration interval NOT NULL DEFAULT '30 days'::interval, - sessions_table text NOT NULL DEFAULT 'sessions', - session_credentials_table text NOT NULL DEFAULT 'session_credentials', - auth_settings_table text NOT NULL DEFAULT 'app_settings_auth', + sessions_table_name text NOT NULL DEFAULT 'sessions', + session_credentials_table_name text NOT NULL DEFAULT 'session_credentials', + auth_settings_table_name text NOT NULL DEFAULT 'app_settings_auth', -- CONSTRAINT db_fkey FOREIGN KEY (database_id) REFERENCES metaschema_public.database (id) ON DELETE CASCADE, diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/storage_log_module/constraints/one_platform_scope.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/storage_log_module/constraints/one_platform_scope.sql new file mode 100644 index 000000000..640b37bbf --- /dev/null +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/storage_log_module/constraints/one_platform_scope.sql @@ -0,0 +1,12 @@ +-- Deploy schemas/metaschema_modules_public/tables/storage_log_module/constraints/one_platform_scope to pg + +-- requires: schemas/metaschema_modules_public/tables/storage_log_module/table + +BEGIN; + +-- At most one platform-scope storage_log_module per database. +CREATE UNIQUE INDEX storage_log_module_one_platform_scope + ON metaschema_modules_public.storage_log_module (database_id) + WHERE scope = 'platform'; + +COMMIT; diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/storage_log_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/storage_log_module/table.sql index c7ac65604..b5e158a4a 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/storage_log_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/storage_log_module/table.sql @@ -8,6 +8,11 @@ CREATE TABLE metaschema_modules_public.storage_log_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + + -- Scope-key column name on the generated table(s), recorded by the insert + -- trigger via metaschema_generators.scope_key_column(scope, key): database -> + -- 'database_id', entity -> the module's key ('entity_id' here), global -> NULL. + entity_field text, schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), @@ -19,9 +24,9 @@ CREATE TABLE metaschema_modules_public.storage_log_module ( storage_log_table_id uuid NOT NULL DEFAULT uuid_nil(), storage_log_table_name text NOT NULL DEFAULT '', - -- Pre-aggregated daily rollup table - usage_daily_table_id uuid NOT NULL DEFAULT uuid_nil(), - usage_daily_table_name text NOT NULL DEFAULT '', + -- Pre-aggregated usage summary rollup table + usage_summary_table_id uuid NOT NULL DEFAULT uuid_nil(), + usage_summary_table_name text NOT NULL DEFAULT '', -- Partition lifecycle configuration "interval" text NOT NULL DEFAULT '1 month', @@ -44,8 +49,8 @@ CREATE TABLE metaschema_modules_public.storage_log_module ( CONSTRAINT schema_fkey FOREIGN KEY (schema_id) REFERENCES metaschema_public.schema (id) ON DELETE CASCADE, CONSTRAINT private_schema_fkey FOREIGN KEY (private_schema_id) REFERENCES metaschema_public.schema (id) ON DELETE CASCADE, CONSTRAINT storage_log_table_fkey FOREIGN KEY (storage_log_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, - CONSTRAINT usage_daily_table_fkey FOREIGN KEY (usage_daily_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, - CONSTRAINT storage_log_module_database_id_prefix_unique UNIQUE NULLS NOT DISTINCT (database_id, prefix) + CONSTRAINT usage_summary_table_fkey FOREIGN KEY (usage_summary_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, + CONSTRAINT storage_log_module_database_id_scope_unique UNIQUE (database_id, scope) ); CREATE INDEX storage_log_module_database_id_idx ON metaschema_modules_public.storage_log_module ( database_id ); diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/storage_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/storage_module/table.sql index 4ae67363c..6d2a2f21a 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/storage_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/storage_module/table.sql @@ -48,6 +48,12 @@ CREATE TABLE metaschema_modules_public.storage_module ( -- Entity table for RLS (NULL for app-level storage, entity table for entity-scoped storage) entity_table_id uuid NULL, + -- Scope-key column name on the generated buckets/files tables, recorded by + -- the insert trigger via metaschema_generators.scope_key_column(scope, key). + -- database → 'database_id', entity → the module's key ('owner_id' here), + -- global (platform/app) → NULL. Consumers read this instead of hardcoding. + entity_field text, + -- S3 connection config (NULL = use global env/plugin defaults) endpoint text NULL, -- S3-compatible API endpoint URL (MinIO, R2, DO Spaces, GCS, etc.) public_url_prefix text NULL, -- Public URL prefix for generating download URLs (e.g., CDN domain) diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/transfer_log_module/constraints/one_platform_scope.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/transfer_log_module/constraints/one_platform_scope.sql new file mode 100644 index 000000000..8b8ba126c --- /dev/null +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/transfer_log_module/constraints/one_platform_scope.sql @@ -0,0 +1,12 @@ +-- Deploy schemas/metaschema_modules_public/tables/transfer_log_module/constraints/one_platform_scope to pg + +-- requires: schemas/metaschema_modules_public/tables/transfer_log_module/table + +BEGIN; + +-- At most one platform-scope transfer_log_module per database. +CREATE UNIQUE INDEX transfer_log_module_one_platform_scope + ON metaschema_modules_public.transfer_log_module (database_id) + WHERE scope = 'platform'; + +COMMIT; diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/transfer_log_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/transfer_log_module/table.sql index c75ec9b89..d9fde8b6e 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/transfer_log_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/transfer_log_module/table.sql @@ -8,6 +8,11 @@ CREATE TABLE metaschema_modules_public.transfer_log_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + + -- Scope-key column name on the generated table(s), recorded by the insert + -- trigger via metaschema_generators.scope_key_column(scope, key): database -> + -- 'database_id', entity -> the module's key ('entity_id' here), global -> NULL. + entity_field text, schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), @@ -19,9 +24,9 @@ CREATE TABLE metaschema_modules_public.transfer_log_module ( transfer_log_table_id uuid NOT NULL DEFAULT uuid_nil(), transfer_log_table_name text NOT NULL DEFAULT '', - -- Pre-aggregated daily rollup table - usage_daily_table_id uuid NOT NULL DEFAULT uuid_nil(), - usage_daily_table_name text NOT NULL DEFAULT '', + -- Pre-aggregated usage summary rollup table + usage_summary_table_id uuid NOT NULL DEFAULT uuid_nil(), + usage_summary_table_name text NOT NULL DEFAULT '', -- Partition lifecycle configuration "interval" text NOT NULL DEFAULT '1 month', @@ -44,7 +49,7 @@ CREATE TABLE metaschema_modules_public.transfer_log_module ( CONSTRAINT schema_fkey FOREIGN KEY (schema_id) REFERENCES metaschema_public.schema (id) ON DELETE CASCADE, CONSTRAINT private_schema_fkey FOREIGN KEY (private_schema_id) REFERENCES metaschema_public.schema (id) ON DELETE CASCADE, CONSTRAINT transfer_log_table_fkey FOREIGN KEY (transfer_log_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, - CONSTRAINT usage_daily_table_fkey FOREIGN KEY (usage_daily_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, + CONSTRAINT usage_summary_table_fkey FOREIGN KEY (usage_summary_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, CONSTRAINT transfer_log_module_database_id_prefix_unique UNIQUE NULLS NOT DISTINCT (database_id, prefix) ); diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/user_credentials_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/user_credentials_module/table.sql index d5bf35330..2194f5f9b 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/user_credentials_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/user_credentials_module/table.sql @@ -8,6 +8,10 @@ CREATE TABLE metaschema_modules_public.user_credentials_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + + -- Scope-key column name on the generated table ('owner_id' here). + -- Recorded so consumers read it as a stored fact instead of hardcoding. + entity_field text, -- Schema references (resolved by BEFORE INSERT trigger when uuid_nil) schema_id uuid NOT NULL DEFAULT uuid_nil(), @@ -17,8 +21,7 @@ CREATE TABLE metaschema_modules_public.user_credentials_module ( -- Table name (input — defaults to 'user_secrets') table_name text NOT NULL DEFAULT 'user_secrets', - -- API routing (get-or-create: if set, schema is added to this API) - api_name text DEFAULT 'config', + -- API routing (private schema only — never routed to a public API) private_api_name text DEFAULT NULL, -- Constraints diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/user_state_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/user_state_module/table.sql index 521f5dce0..569a9a7f0 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/user_state_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/user_state_module/table.sql @@ -7,6 +7,10 @@ BEGIN; CREATE TABLE metaschema_modules_public.user_state_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + + -- Scope-key column name on the generated table ('owner_id' here). + -- Recorded so consumers read it as a stored fact instead of hardcoding. + entity_field text, -- schema_id uuid NOT NULL DEFAULT uuid_nil(), table_id uuid NOT NULL DEFAULT uuid_nil(), diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/webhook_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/webhook_module/table.sql new file mode 100644 index 000000000..22975a650 --- /dev/null +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/webhook_module/table.sql @@ -0,0 +1,100 @@ +-- Deploy schemas/metaschema_modules_public/tables/webhook_module/table to pg + +-- requires: schemas/metaschema_modules_public/schema +-- requires: schemas/metaschema_modules_public/tables/function_module/table +-- requires: schemas/metaschema_modules_public/tables/function_invocation_module/table +-- requires: schemas/metaschema_modules_public/tables/infra_secrets_module/table +-- requires: schemas/metaschema_modules_public/tables/namespace_module/table + +BEGIN; + +CREATE TABLE metaschema_modules_public.webhook_module ( + id uuid PRIMARY KEY DEFAULT uuidv7(), + database_id uuid NOT NULL, + + -- Scope-key column name on the generated tables. The insert trigger records + -- database_id for database scope, entity_id for entity scopes, NULL for + -- global tiers. + entity_field text, + + -- Schema references (uuid_nil is resolved from schema names/defaults). + schema_id uuid NOT NULL DEFAULT uuid_nil(), + private_schema_id uuid NOT NULL DEFAULT uuid_nil(), + public_schema_name text, + private_schema_name text, + + -- Generated table IDs. + webhook_endpoints_table_id uuid NOT NULL DEFAULT uuid_nil(), + webhook_events_table_id uuid NOT NULL DEFAULT uuid_nil(), + + -- Bare table names; the trigger prepends the scope prefix. + webhook_endpoints_table_name text NOT NULL DEFAULT 'webhook_endpoints', + webhook_events_table_name text NOT NULL DEFAULT 'webhook_events', + + -- Paired modules at the exact same scope. + function_module_id uuid, + function_invocation_module_id uuid, + infra_secrets_module_id uuid, + namespace_module_id uuid, + + -- API routing (optional administrative CRUD surface). + api_name text, + private_api_name text, + + scope text NOT NULL DEFAULT 'app', + prefix text NOT NULL DEFAULT '', + entity_table_id uuid NULL, + + policies jsonb NULL, + provisions jsonb NULL, + default_permissions text[] DEFAULT NULL, + + CONSTRAINT webhook_module_db_fkey + FOREIGN KEY (database_id) + REFERENCES metaschema_public.database (id) + ON DELETE CASCADE, + CONSTRAINT webhook_module_schema_fkey + FOREIGN KEY (schema_id) + REFERENCES metaschema_public.schema (id) + ON DELETE CASCADE, + CONSTRAINT webhook_module_private_schema_fkey + FOREIGN KEY (private_schema_id) + REFERENCES metaschema_public.schema (id) + ON DELETE CASCADE, + CONSTRAINT webhook_module_endpoints_table_fkey + FOREIGN KEY (webhook_endpoints_table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE, + CONSTRAINT webhook_module_events_table_fkey + FOREIGN KEY (webhook_events_table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE, + CONSTRAINT webhook_module_function_module_fkey + FOREIGN KEY (function_module_id) + REFERENCES metaschema_modules_public.function_module (id) + ON DELETE CASCADE, + CONSTRAINT webhook_module_invocation_module_fkey + FOREIGN KEY (function_invocation_module_id) + REFERENCES metaschema_modules_public.function_invocation_module (id) + ON DELETE CASCADE, + CONSTRAINT webhook_module_secrets_module_fkey + FOREIGN KEY (infra_secrets_module_id) + REFERENCES metaschema_modules_public.infra_secrets_module (id) + ON DELETE CASCADE, + CONSTRAINT webhook_module_namespace_module_fkey + FOREIGN KEY (namespace_module_id) + REFERENCES metaschema_modules_public.namespace_module (id) + ON DELETE CASCADE, + CONSTRAINT webhook_module_entity_table_fkey + FOREIGN KEY (entity_table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE +); + +CREATE INDEX webhook_module_database_id_idx + ON metaschema_modules_public.webhook_module (database_id); + +CREATE UNIQUE INDEX webhook_module_unique_scope + ON metaschema_modules_public.webhook_module (database_id, scope); + +COMMIT; diff --git a/packages/metaschema-modules/jest.config.js b/packages/metaschema-modules/jest.config.js index 7dac27740..98f3a0e7e 100644 --- a/packages/metaschema-modules/jest.config.js +++ b/packages/metaschema-modules/jest.config.js @@ -3,7 +3,7 @@ module.exports = { forceExit: true, preset: 'ts-jest', testEnvironment: 'node', - testTimeout: 30000, + testTimeout: 60000, // Match both __tests__ and colocated test files testMatch: ['**/?(*.)+(test|spec).{ts,tsx,js,jsx}'], diff --git a/packages/metaschema-modules/pgpm.plan b/packages/metaschema-modules/pgpm.plan index 83ae0abef..88f845fb4 100644 --- a/packages/metaschema-modules/pgpm.plan +++ b/packages/metaschema-modules/pgpm.plan @@ -58,11 +58,14 @@ schemas/metaschema_modules_public/tables/agent_module/table [schemas/metaschema_ schemas/metaschema_modules_public/tables/merkle_store_module/table [schemas/metaschema_modules_public/schema] 2026-05-21T00:00:00Z devin # add merkle_store_module config table for content-addressed Merkle object stores schemas/metaschema_modules_public/tables/graph_module/table [schemas/metaschema_modules_public/schema schemas/metaschema_modules_public/tables/merkle_store_module/table] 2026-05-21T01:00:00Z devin # add graph_module config table for FBP graph utilities on top of merkle store schemas/metaschema_modules_public/tables/graph_execution_module/table [schemas/metaschema_modules_public/schema schemas/metaschema_modules_public/tables/graph_module/table] 2026-06-12T00:00:00Z devin # add graph_execution_module config table for partitioned execution state + merkle tree time-travel debugging +schemas/metaschema_modules_public/tables/db_preset_module/table [schemas/metaschema_modules_public/schema schemas/metaschema_modules_public/tables/merkle_store_module/table] 2026-07-10T00:00:00Z devin # add db_preset_module config table for merkle-versioned db_preset catalog schemas/metaschema_modules_public/tables/namespace_module/table [schemas/metaschema_modules_public/schema] 2026-05-21T02:00:00Z devin # add namespace_module config table for entity-aware namespace provisioning schemas/metaschema_modules_public/tables/function_module/table [schemas/metaschema_modules_public/schema] 2026-05-21T03:00:00Z devin # add function_module config table for entity-aware function definitions schemas/metaschema_modules_public/tables/function_invocation_module/table [schemas/metaschema_modules_public/schema] 2026-06-08T00:00:00Z devin # add function_invocation_module config table for entity-scoped invocations and execution logs -schemas/metaschema_modules_public/tables/config_secrets_module/table [schemas/metaschema_modules_public/schema] 2026-05-29T00:00:00Z devin # add entity-aware config_secrets_module (replaces config_secrets_user_module + config_secrets_org_module) -schemas/metaschema_modules_public/tables/user_credentials_module/table [schemas/metaschema_modules_public/schema] 2026-05-30T00:00:00Z devin # add user_credentials_module for per-user bcrypt credential store (split from config_secrets_module) +schemas/metaschema_modules_public/tables/infra_secrets_module/table [schemas/metaschema_modules_public/schema] 2026-07-09T00:00:00Z devin # add infra_secrets_module for namespace-backed PGP secrets +schemas/metaschema_modules_public/tables/infra_config_module/table [schemas/metaschema_modules_public/schema] 2026-07-09T00:00:00Z devin # add infra_config_module for namespace-backed plaintext config +schemas/metaschema_modules_public/tables/internal_secrets_module/table [schemas/metaschema_modules_public/schema] 2026-07-09T00:00:00Z devin # add internal_secrets_module for app-level PGP secrets with no namespace dependency +schemas/metaschema_modules_public/tables/user_credentials_module/table [schemas/metaschema_modules_public/schema] 2026-05-30T00:00:00Z devin # add user_credentials_module for per-user bcrypt credential store schemas/metaschema_modules_public/tables/user_settings_module/table [schemas/metaschema_modules_public/schema] 2026-05-28T00:00:00Z devin # add user_settings_module for extensible per-user preferences (1:1 with users) schemas/metaschema_modules_public/tables/i18n_module/table [schemas/metaschema_modules_public/schema] 2026-05-28T00:00:00Z devin # add i18n_module config table for internationalization settings @@ -70,3 +73,12 @@ schemas/metaschema_modules_public/tables/function_deployment_module/table [schem schemas/metaschema_modules_public/tables/function_module/constraints/one_platform_database [schemas/metaschema_modules_public/tables/function_module/table] 2026-06-11T08:00:00Z devin # enforce at most one platform-scope function_module (unambiguous resolveDatabaseId) schemas/metaschema_modules_public/tables/principal_auth_module/table [schemas/metaschema_modules_public/schema] 2026-06-24T11:15:00Z devin # add principal_auth_module config table for scoped API keys and agent principals schemas/metaschema_modules_public/tables/resource_module/table [schemas/metaschema_modules_public/schema schemas/metaschema_modules_public/tables/namespace_module/table] 2026-07-02T00:00:00Z devin # add resource_module config table for unified K8s resource management (kind-based dispatch) +schemas/metaschema_modules_public/tables/integration_providers_module/table [schemas/metaschema_modules_public/schema] 2026-07-11T17:30:00Z devin # add integration_providers_module config table for branded service integration definitions +schemas/metaschema_modules_public/tables/graph_module/constraints/one_platform_scope [schemas/metaschema_modules_public/tables/graph_module/table] 2026-07-08T07:30:00Z devin # enforce at most one platform-scope graph_module per database +schemas/metaschema_modules_public/tables/events_module/constraints/one_platform_scope [schemas/metaschema_modules_public/tables/events_module/table] 2026-07-08T07:30:00Z devin # enforce at most one platform-scope events_module per database +schemas/metaschema_modules_public/tables/inference_log_module/constraints/one_platform_scope [schemas/metaschema_modules_public/tables/inference_log_module/table] 2026-07-08T07:30:00Z devin # enforce at most one platform-scope inference_log_module per database +schemas/metaschema_modules_public/tables/compute_log_module/constraints/one_platform_scope [schemas/metaschema_modules_public/tables/compute_log_module/table] 2026-07-08T07:30:00Z devin # enforce at most one platform-scope compute_log_module per database +schemas/metaschema_modules_public/tables/transfer_log_module/constraints/one_platform_scope [schemas/metaschema_modules_public/tables/transfer_log_module/table] 2026-07-08T07:30:00Z devin # enforce at most one platform-scope transfer_log_module per database +schemas/metaschema_modules_public/tables/storage_log_module/constraints/one_platform_scope [schemas/metaschema_modules_public/tables/storage_log_module/table] 2026-07-08T07:30:00Z devin # enforce at most one platform-scope storage_log_module per database +schemas/metaschema_modules_public/tables/db_usage_module/constraints/one_platform_scope [schemas/metaschema_modules_public/tables/db_usage_module/table] 2026-07-08T07:30:00Z devin # enforce at most one platform-scope db_usage_module per database +schemas/metaschema_modules_public/tables/webhook_module/table [schemas/metaschema_modules_public/schema schemas/metaschema_modules_public/tables/function_module/table schemas/metaschema_modules_public/tables/function_invocation_module/table schemas/metaschema_modules_public/tables/infra_secrets_module/table schemas/metaschema_modules_public/tables/namespace_module/table] 2026-07-15T06:30:00Z devin # add scoped webhook ingress module registration diff --git a/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/compute_log_module/constraints/one_platform_scope.sql b/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/compute_log_module/constraints/one_platform_scope.sql new file mode 100644 index 000000000..afb54261a --- /dev/null +++ b/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/compute_log_module/constraints/one_platform_scope.sql @@ -0,0 +1,7 @@ +-- Revert schemas/metaschema_modules_public/tables/compute_log_module/constraints/one_platform_scope from pg + +BEGIN; + +DROP INDEX metaschema_modules_public.compute_log_module_one_platform_scope; + +COMMIT; diff --git a/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/config_secrets_module/table.sql b/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/config_secrets_module/table.sql deleted file mode 100644 index 137e9ced2..000000000 --- a/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/config_secrets_module/table.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Revert schemas/metaschema_modules_public/tables/config_secrets_module/table from pg - -BEGIN; - -DROP TABLE metaschema_modules_public.config_secrets_module; - -COMMIT; diff --git a/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/db_preset_module/table.sql b/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/db_preset_module/table.sql new file mode 100644 index 000000000..3639fd5c6 --- /dev/null +++ b/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/db_preset_module/table.sql @@ -0,0 +1,7 @@ +-- Revert schemas/metaschema_modules_public/tables/db_preset_module/table from pg + +BEGIN; + +DROP TABLE IF EXISTS metaschema_modules_public.db_preset_module CASCADE; + +COMMIT; diff --git a/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/db_usage_module/constraints/one_platform_scope.sql b/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/db_usage_module/constraints/one_platform_scope.sql new file mode 100644 index 000000000..2821ebef9 --- /dev/null +++ b/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/db_usage_module/constraints/one_platform_scope.sql @@ -0,0 +1,7 @@ +-- Revert schemas/metaschema_modules_public/tables/db_usage_module/constraints/one_platform_scope from pg + +BEGIN; + +DROP INDEX metaschema_modules_public.db_usage_module_one_platform_scope; + +COMMIT; diff --git a/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/events_module/constraints/one_platform_scope.sql b/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/events_module/constraints/one_platform_scope.sql new file mode 100644 index 000000000..d6488cea7 --- /dev/null +++ b/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/events_module/constraints/one_platform_scope.sql @@ -0,0 +1,7 @@ +-- Revert schemas/metaschema_modules_public/tables/events_module/constraints/one_platform_scope from pg + +BEGIN; + +DROP INDEX metaschema_modules_public.events_module_one_platform_scope; + +COMMIT; diff --git a/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/graph_module/constraints/one_platform_scope.sql b/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/graph_module/constraints/one_platform_scope.sql new file mode 100644 index 000000000..c9b5b1099 --- /dev/null +++ b/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/graph_module/constraints/one_platform_scope.sql @@ -0,0 +1,7 @@ +-- Revert schemas/metaschema_modules_public/tables/graph_module/constraints/one_platform_scope from pg + +BEGIN; + +DROP INDEX metaschema_modules_public.graph_module_one_platform_scope; + +COMMIT; diff --git a/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/inference_log_module/constraints/one_platform_scope.sql b/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/inference_log_module/constraints/one_platform_scope.sql new file mode 100644 index 000000000..306bd1741 --- /dev/null +++ b/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/inference_log_module/constraints/one_platform_scope.sql @@ -0,0 +1,7 @@ +-- Revert schemas/metaschema_modules_public/tables/inference_log_module/constraints/one_platform_scope from pg + +BEGIN; + +DROP INDEX metaschema_modules_public.inference_log_module_one_platform_scope; + +COMMIT; diff --git a/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/infra_config_module/table.sql b/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/infra_config_module/table.sql new file mode 100644 index 000000000..cd2d93991 --- /dev/null +++ b/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/infra_config_module/table.sql @@ -0,0 +1,7 @@ +-- Revert schemas/metaschema_modules_public/tables/infra_config_module/table from pg + +BEGIN; + +DROP TABLE IF EXISTS metaschema_modules_public.infra_config_module CASCADE; + +COMMIT; diff --git a/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/infra_secrets_module/table.sql b/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/infra_secrets_module/table.sql new file mode 100644 index 000000000..933c1b80a --- /dev/null +++ b/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/infra_secrets_module/table.sql @@ -0,0 +1,7 @@ +-- Revert schemas/metaschema_modules_public/tables/infra_secrets_module/table from pg + +BEGIN; + +DROP TABLE IF EXISTS metaschema_modules_public.infra_secrets_module CASCADE; + +COMMIT; diff --git a/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/integration_providers_module/table.sql b/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/integration_providers_module/table.sql new file mode 100644 index 000000000..ce630d0bd --- /dev/null +++ b/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/integration_providers_module/table.sql @@ -0,0 +1,7 @@ +-- Revert schemas/metaschema_modules_public/tables/integration_providers_module/table from pg + +BEGIN; + +DROP TABLE IF EXISTS metaschema_modules_public.integration_providers_module CASCADE; + +COMMIT; diff --git a/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/internal_secrets_module/table.sql b/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/internal_secrets_module/table.sql new file mode 100644 index 000000000..cde50a920 --- /dev/null +++ b/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/internal_secrets_module/table.sql @@ -0,0 +1,7 @@ +-- Revert schemas/metaschema_modules_public/tables/internal_secrets_module/table from pg + +BEGIN; + +DROP TABLE IF EXISTS metaschema_modules_public.internal_secrets_module CASCADE; + +COMMIT; diff --git a/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/storage_log_module/constraints/one_platform_scope.sql b/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/storage_log_module/constraints/one_platform_scope.sql new file mode 100644 index 000000000..e8e4bd966 --- /dev/null +++ b/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/storage_log_module/constraints/one_platform_scope.sql @@ -0,0 +1,7 @@ +-- Revert schemas/metaschema_modules_public/tables/storage_log_module/constraints/one_platform_scope from pg + +BEGIN; + +DROP INDEX metaschema_modules_public.storage_log_module_one_platform_scope; + +COMMIT; diff --git a/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/transfer_log_module/constraints/one_platform_scope.sql b/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/transfer_log_module/constraints/one_platform_scope.sql new file mode 100644 index 000000000..229fbde4c --- /dev/null +++ b/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/transfer_log_module/constraints/one_platform_scope.sql @@ -0,0 +1,7 @@ +-- Revert schemas/metaschema_modules_public/tables/transfer_log_module/constraints/one_platform_scope from pg + +BEGIN; + +DROP INDEX metaschema_modules_public.transfer_log_module_one_platform_scope; + +COMMIT; diff --git a/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/webhook_module/table.sql b/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/webhook_module/table.sql new file mode 100644 index 000000000..921c49474 --- /dev/null +++ b/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/webhook_module/table.sql @@ -0,0 +1,7 @@ +-- Revert schemas/metaschema_modules_public/tables/webhook_module/table from pg + +BEGIN; + +DROP TABLE metaschema_modules_public.webhook_module; + +COMMIT; diff --git a/packages/metaschema-modules/sql/metaschema-modules--0.15.5.sql b/packages/metaschema-modules/sql/metaschema-modules--0.15.5.sql index c3ad8285f..93cfaf715 100644 --- a/packages/metaschema-modules/sql/metaschema-modules--0.15.5.sql +++ b/packages/metaschema-modules/sql/metaschema-modules--0.15.5.sql @@ -220,6 +220,7 @@ CREATE INDEX emails_module_database_id_idx ON metaschema_modules_public.emails_m CREATE TABLE metaschema_modules_public.config_secrets_user_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + entity_field text, schema_id uuid NOT NULL DEFAULT uuid_nil(), table_id uuid NOT NULL DEFAULT uuid_nil(), table_name text NOT NULL DEFAULT 'user_secrets', @@ -239,11 +240,12 @@ CREATE TABLE metaschema_modules_public.config_secrets_user_module ( ON DELETE CASCADE ); -CREATE INDEX config_secrets_user_module_database_id_idx ON metaschema_modules_public.config_secrets_user_module (database_id); +CREATE UNIQUE INDEX config_secrets_user_module_database_id_idx ON metaschema_modules_public.config_secrets_user_module (database_id); CREATE TABLE metaschema_modules_public.invites_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + entity_field text, schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), public_schema_name text, @@ -301,6 +303,7 @@ CREATE UNIQUE INDEX invites_module_unique_scope ON metaschema_modules_public.inv CREATE TABLE metaschema_modules_public.events_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + entity_field text, schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), public_schema_name text, @@ -327,7 +330,6 @@ CREATE TABLE metaschema_modules_public.events_module ( tg_event_bool text NOT NULL DEFAULT '', upsert_aggregate text NOT NULL DEFAULT '', tg_update_aggregates text NOT NULL DEFAULT '', - prune_events text NOT NULL DEFAULT '', steps_required text NOT NULL DEFAULT '', level_achieved text NOT NULL DEFAULT '', tg_check_achievements text NOT NULL DEFAULT '', @@ -398,6 +400,7 @@ CREATE INDEX events_module_database_id_idx ON metaschema_modules_public.events_m CREATE TABLE metaschema_modules_public.limits_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + entity_field text, schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), public_schema_name text, @@ -529,6 +532,7 @@ CREATE INDEX membership_types_module_database_id_idx ON metaschema_modules_publi CREATE TABLE metaschema_modules_public.memberships_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + entity_field text, schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), public_schema_name text, @@ -641,6 +645,7 @@ CREATE INDEX memberships_module_database_id_idx ON metaschema_modules_public.mem CREATE TABLE metaschema_modules_public.permissions_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + entity_field text, schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), public_schema_name text, @@ -729,6 +734,7 @@ CREATE INDEX phone_numbers_module_database_id_idx ON metaschema_modules_public.p CREATE TABLE metaschema_modules_public.profiles_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + entity_field text, schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), public_schema_name text, @@ -860,6 +866,7 @@ CREATE INDEX rls_module_database_id_idx ON metaschema_modules_public.rls_module CREATE TABLE metaschema_modules_public.user_state_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + entity_field text, schema_id uuid NOT NULL DEFAULT uuid_nil(), table_id uuid NOT NULL DEFAULT uuid_nil(), table_name text NOT NULL DEFAULT 'user_state', @@ -888,9 +895,9 @@ CREATE TABLE metaschema_modules_public.sessions_module ( auth_settings_table_id uuid NOT NULL DEFAULT uuid_nil(), users_table_id uuid NOT NULL DEFAULT uuid_nil(), sessions_default_expiration interval NOT NULL DEFAULT '30 days'::interval, - sessions_table text NOT NULL DEFAULT 'sessions', - session_credentials_table text NOT NULL DEFAULT 'session_credentials', - auth_settings_table text NOT NULL DEFAULT 'app_settings_auth', + sessions_table_name text NOT NULL DEFAULT 'sessions', + session_credentials_table_name text NOT NULL DEFAULT 'session_credentials', + auth_settings_table_name text NOT NULL DEFAULT 'app_settings_auth', CONSTRAINT db_fkey FOREIGN KEY(database_id) REFERENCES metaschema_public.database (id) @@ -1035,6 +1042,7 @@ CREATE INDEX users_module_database_id_idx ON metaschema_modules_public.users_mod CREATE TABLE metaschema_modules_public.hierarchy_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + entity_field text, schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), chart_edges_table_id uuid NOT NULL DEFAULT uuid_nil(), @@ -1511,6 +1519,7 @@ CREATE TABLE metaschema_modules_public.storage_module ( policies jsonb NULL, provisions jsonb NULL, entity_table_id uuid NULL, + entity_field text, endpoint text NULL, public_url_prefix text NULL, provider text NULL, @@ -1831,9 +1840,9 @@ CREATE TABLE metaschema_modules_public.rate_limits_module ( rate_limit_settings_table_id uuid NOT NULL DEFAULT uuid_nil(), ip_rate_limits_table_id uuid NOT NULL DEFAULT uuid_nil(), rate_limits_table_id uuid NOT NULL DEFAULT uuid_nil(), - rate_limit_settings_table text NOT NULL DEFAULT 'app_settings_rate_limit', - ip_rate_limits_table text NOT NULL DEFAULT 'auth_ip_rate_limits', - rate_limits_table text NOT NULL DEFAULT 'auth_rate_limits', + rate_limit_settings_table_name text NOT NULL DEFAULT 'app_settings_rate_limit', + ip_rate_limits_table_name text NOT NULL DEFAULT 'auth_ip_rate_limits', + rate_limits_table_name text NOT NULL DEFAULT 'auth_rate_limits', CONSTRAINT db_fkey FOREIGN KEY(database_id) REFERENCES metaschema_public.database (id) @@ -1872,8 +1881,8 @@ CREATE TABLE metaschema_modules_public.devices_module ( schema_id uuid NOT NULL DEFAULT uuid_nil(), user_devices_table_id uuid NOT NULL DEFAULT uuid_nil(), device_settings_table_id uuid NOT NULL DEFAULT uuid_nil(), - user_devices_table text NOT NULL DEFAULT 'auth_user_devices', - device_settings_table text NOT NULL DEFAULT 'app_settings_device', + user_devices_table_name text NOT NULL DEFAULT 'auth_user_devices', + device_settings_table_name text NOT NULL DEFAULT 'app_settings_device', CONSTRAINT db_fkey FOREIGN KEY(database_id) REFERENCES metaschema_public.database (id) @@ -1925,7 +1934,7 @@ CREATE TABLE metaschema_modules_public.session_secrets_module ( ON DELETE CASCADE ); -CREATE INDEX session_secrets_module_database_id_idx ON metaschema_modules_public.session_secrets_module (database_id); +CREATE UNIQUE INDEX session_secrets_module_database_id_idx ON metaschema_modules_public.session_secrets_module (database_id); CREATE INDEX session_secrets_module_schema_id_idx ON metaschema_modules_public.session_secrets_module (schema_id); @@ -1935,7 +1944,7 @@ CREATE INDEX session_secrets_module_sessions_table_id_idx ON metaschema_modules_ COMMENT ON TABLE metaschema_modules_public.session_secrets_module IS 'Config row for the session_secrets_module, which provisions a DB-private, session-scoped ephemeral key-value store for challenges, nonces, and one-time tokens that must never be readable by end users.'; -COMMENT ON COLUMN metaschema_modules_public.session_secrets_module.sessions_table_id IS 'Resolved reference to sessions_module.sessions_table, used to FK session_secrets.session_id with ON DELETE CASCADE.'; +COMMENT ON COLUMN metaschema_modules_public.session_secrets_module.sessions_table_id IS 'Resolved reference to sessions_module.sessions_table_name, used to FK session_secrets.session_id with ON DELETE CASCADE.'; CREATE TABLE metaschema_modules_public.webauthn_credentials_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), @@ -2031,6 +2040,7 @@ CREATE INDEX webauthn_auth_module_database_id_idx ON metaschema_modules_public.w CREATE TABLE metaschema_modules_public.identity_providers_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + entity_field text, schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), public_schema_name text, @@ -2076,7 +2086,7 @@ CREATE UNIQUE INDEX identity_providers_module_unique_scope ON metaschema_modules COMMENT ON TABLE metaschema_modules_public.identity_providers_module IS 'Entity-aware config row for the identity_providers_module, which provisions a per-database identity_providers table holding OAuth2 / OIDC (and future SAML) provider definitions. - The scope column determines which config_secrets_module table the rotate proc targets + The scope column determines which internal_secrets_module table the rotate proc targets (app_secrets for app scope, platform_secrets for platform scope). When scope = database, the secrets table gets a database_id column. Scoping matrix: @@ -2089,6 +2099,7 @@ COMMENT ON COLUMN metaschema_modules_public.identity_providers_module.private_sc CREATE TABLE metaschema_modules_public.notifications_module ( id uuid PRIMARY KEY DEFAULT uuid_generate_v4(), database_id uuid NOT NULL, + entity_field text, schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), public_schema_name text, @@ -2279,6 +2290,8 @@ CREATE TABLE metaschema_modules_public.billing_module ( meter_defaults_table_id uuid NOT NULL DEFAULT uuid_nil(), meter_defaults_table_name text NOT NULL DEFAULT '', record_usage_function text NOT NULL DEFAULT '', + sweep_expired_subscriptions_function text NOT NULL DEFAULT '', + rollup_usage_summary_function text NOT NULL DEFAULT '', prefix text NULL, default_permissions text[] DEFAULT NULL, api_name text DEFAULT 'usage', @@ -2520,14 +2533,15 @@ COMMENT ON CONSTRAINT rate_window_limits_table_fkey ON metaschema_modules_public CREATE TABLE metaschema_modules_public.inference_log_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + entity_field text, schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), public_schema_name text, private_schema_name text, inference_log_table_id uuid NOT NULL DEFAULT uuid_nil(), inference_log_table_name text NOT NULL DEFAULT '', - usage_daily_table_id uuid NOT NULL DEFAULT uuid_nil(), - usage_daily_table_name text NOT NULL DEFAULT '', + usage_summary_table_id uuid NOT NULL DEFAULT uuid_nil(), + usage_summary_table_name text NOT NULL DEFAULT '', "interval" text NOT NULL DEFAULT '1 month', retention text NOT NULL DEFAULT '12 months', premake int NOT NULL DEFAULT 2, @@ -2553,8 +2567,8 @@ CREATE TABLE metaschema_modules_public.inference_log_module ( FOREIGN KEY(inference_log_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, - CONSTRAINT usage_daily_table_fkey - FOREIGN KEY(usage_daily_table_id) + CONSTRAINT usage_summary_table_fkey + FOREIGN KEY(usage_summary_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, CONSTRAINT inference_log_module_database_id_prefix_unique @@ -2566,14 +2580,15 @@ CREATE INDEX inference_log_module_database_id_idx ON metaschema_modules_public.i CREATE TABLE metaschema_modules_public.compute_log_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + entity_field text, schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), public_schema_name text, private_schema_name text, compute_log_table_id uuid NOT NULL DEFAULT uuid_nil(), compute_log_table_name text NOT NULL DEFAULT '', - usage_daily_table_id uuid NOT NULL DEFAULT uuid_nil(), - usage_daily_table_name text NOT NULL DEFAULT '', + usage_summary_table_id uuid NOT NULL DEFAULT uuid_nil(), + usage_summary_table_name text NOT NULL DEFAULT '', "interval" text NOT NULL DEFAULT '1 month', retention text NOT NULL DEFAULT '12 months', premake int NOT NULL DEFAULT 2, @@ -2599,12 +2614,12 @@ CREATE TABLE metaschema_modules_public.compute_log_module ( FOREIGN KEY(compute_log_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, - CONSTRAINT usage_daily_table_fkey - FOREIGN KEY(usage_daily_table_id) + CONSTRAINT usage_summary_table_fkey + FOREIGN KEY(usage_summary_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, - CONSTRAINT compute_log_module_database_id_prefix_unique - UNIQUE NULLS NOT DISTINCT (database_id, prefix) + CONSTRAINT compute_log_module_database_id_scope_unique + UNIQUE (database_id, scope) ); CREATE INDEX compute_log_module_database_id_idx ON metaschema_modules_public.compute_log_module (database_id); @@ -2612,14 +2627,15 @@ CREATE INDEX compute_log_module_database_id_idx ON metaschema_modules_public.com CREATE TABLE metaschema_modules_public.transfer_log_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + entity_field text, schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), public_schema_name text, private_schema_name text, transfer_log_table_id uuid NOT NULL DEFAULT uuid_nil(), transfer_log_table_name text NOT NULL DEFAULT '', - usage_daily_table_id uuid NOT NULL DEFAULT uuid_nil(), - usage_daily_table_name text NOT NULL DEFAULT '', + usage_summary_table_id uuid NOT NULL DEFAULT uuid_nil(), + usage_summary_table_name text NOT NULL DEFAULT '', "interval" text NOT NULL DEFAULT '1 month', retention text NOT NULL DEFAULT '12 months', premake int NOT NULL DEFAULT 2, @@ -2645,8 +2661,8 @@ CREATE TABLE metaschema_modules_public.transfer_log_module ( FOREIGN KEY(transfer_log_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, - CONSTRAINT usage_daily_table_fkey - FOREIGN KEY(usage_daily_table_id) + CONSTRAINT usage_summary_table_fkey + FOREIGN KEY(usage_summary_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, CONSTRAINT transfer_log_module_database_id_prefix_unique @@ -2658,14 +2674,15 @@ CREATE INDEX transfer_log_module_database_id_idx ON metaschema_modules_public.tr CREATE TABLE metaschema_modules_public.storage_log_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + entity_field text, schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), public_schema_name text, private_schema_name text, storage_log_table_id uuid NOT NULL DEFAULT uuid_nil(), storage_log_table_name text NOT NULL DEFAULT '', - usage_daily_table_id uuid NOT NULL DEFAULT uuid_nil(), - usage_daily_table_name text NOT NULL DEFAULT '', + usage_summary_table_id uuid NOT NULL DEFAULT uuid_nil(), + usage_summary_table_name text NOT NULL DEFAULT '', "interval" text NOT NULL DEFAULT '1 month', retention text NOT NULL DEFAULT '12 months', premake int NOT NULL DEFAULT 2, @@ -2691,12 +2708,12 @@ CREATE TABLE metaschema_modules_public.storage_log_module ( FOREIGN KEY(storage_log_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, - CONSTRAINT usage_daily_table_fkey - FOREIGN KEY(usage_daily_table_id) + CONSTRAINT usage_summary_table_fkey + FOREIGN KEY(usage_summary_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, - CONSTRAINT storage_log_module_database_id_prefix_unique - UNIQUE NULLS NOT DISTINCT (database_id, prefix) + CONSTRAINT storage_log_module_database_id_scope_unique + UNIQUE (database_id, scope) ); CREATE INDEX storage_log_module_database_id_idx ON metaschema_modules_public.storage_log_module (database_id); @@ -2704,18 +2721,23 @@ CREATE INDEX storage_log_module_database_id_idx ON metaschema_modules_public.sto CREATE TABLE metaschema_modules_public.db_usage_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + entity_field text, schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), public_schema_name text, private_schema_name text, table_stats_log_table_id uuid NOT NULL DEFAULT uuid_nil(), table_stats_log_table_name text NOT NULL DEFAULT '', - table_stats_daily_table_id uuid NOT NULL DEFAULT uuid_nil(), - table_stats_daily_table_name text NOT NULL DEFAULT '', + table_stats_summary_table_id uuid NOT NULL DEFAULT uuid_nil(), + table_stats_summary_table_name text NOT NULL DEFAULT '', query_stats_log_table_id uuid NOT NULL DEFAULT uuid_nil(), query_stats_log_table_name text NOT NULL DEFAULT '', - query_stats_daily_table_id uuid NOT NULL DEFAULT uuid_nil(), - query_stats_daily_table_name text NOT NULL DEFAULT '', + query_stats_summary_table_id uuid NOT NULL DEFAULT uuid_nil(), + query_stats_summary_table_name text NOT NULL DEFAULT '', + collect_db_table_stats_function text NOT NULL DEFAULT '', + collect_db_query_stats_function text NOT NULL DEFAULT '', + rollup_db_table_stats_usage_summary_function text NOT NULL DEFAULT '', + rollup_db_query_stats_usage_summary_function text NOT NULL DEFAULT '', "interval" text NOT NULL DEFAULT '1 month', retention text NOT NULL DEFAULT '12 months', premake int NOT NULL DEFAULT 2, @@ -2740,20 +2762,20 @@ CREATE TABLE metaschema_modules_public.db_usage_module ( FOREIGN KEY(table_stats_log_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, - CONSTRAINT table_stats_daily_table_fkey - FOREIGN KEY(table_stats_daily_table_id) + CONSTRAINT table_stats_summary_table_fkey + FOREIGN KEY(table_stats_summary_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, CONSTRAINT query_stats_log_table_fkey FOREIGN KEY(query_stats_log_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, - CONSTRAINT query_stats_daily_table_fkey - FOREIGN KEY(query_stats_daily_table_id) + CONSTRAINT query_stats_summary_table_fkey + FOREIGN KEY(query_stats_summary_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, - CONSTRAINT db_usage_module_database_id_prefix_unique - UNIQUE NULLS NOT DISTINCT (database_id, prefix) + CONSTRAINT db_usage_module_database_id_scope_unique + UNIQUE (database_id, scope) ); CREATE INDEX db_usage_module_database_id_idx ON metaschema_modules_public.db_usage_module (database_id); @@ -2761,6 +2783,7 @@ CREATE INDEX db_usage_module_database_id_idx ON metaschema_modules_public.db_usa CREATE TABLE metaschema_modules_public.agent_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + entity_field text, schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), public_schema_name text, @@ -2851,6 +2874,7 @@ CREATE UNIQUE INDEX agent_module_unique_scope ON metaschema_modules_public.agent CREATE TABLE metaschema_modules_public.merkle_store_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + entity_field text, schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), public_schema_name text, @@ -2905,6 +2929,7 @@ CREATE INDEX merkle_store_module_private_schema_id_idx ON metaschema_modules_pub CREATE TABLE metaschema_modules_public.graph_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + entity_field text, public_schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), public_schema_name text, @@ -2953,6 +2978,7 @@ CREATE INDEX graph_module_database_id_idx ON metaschema_modules_public.graph_mod CREATE TABLE metaschema_modules_public.graph_execution_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + entity_field text, schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), public_schema_name text, @@ -3011,9 +3037,58 @@ CREATE INDEX graph_execution_module_database_id_idx ON metaschema_modules_public CREATE UNIQUE INDEX graph_execution_module_unique_scope ON metaschema_modules_public.graph_execution_module (database_id, scope); +CREATE TABLE metaschema_modules_public.db_preset_module ( + id uuid PRIMARY KEY DEFAULT uuidv7(), + database_id uuid NOT NULL, + public_schema_id uuid NOT NULL DEFAULT uuid_nil(), + private_schema_id uuid NOT NULL DEFAULT uuid_nil(), + public_schema_name text, + private_schema_name text, + scope text NOT NULL, + prefix text NOT NULL, + merkle_store_module_id uuid NOT NULL, + db_presets_table_id uuid NOT NULL DEFAULT uuid_nil(), + store_name text NOT NULL, + api_name text, + private_api_name text, + entity_table_id uuid NULL, + policies jsonb NULL, + provisions jsonb NULL, + created_at timestamptz NOT NULL DEFAULT now(), + CONSTRAINT db_fkey + FOREIGN KEY(database_id) + REFERENCES metaschema_public.database (id) + ON DELETE CASCADE, + CONSTRAINT public_schema_fkey + FOREIGN KEY(public_schema_id) + REFERENCES metaschema_public.schema (id) + ON DELETE CASCADE, + CONSTRAINT private_schema_fkey + FOREIGN KEY(private_schema_id) + REFERENCES metaschema_public.schema (id) + ON DELETE CASCADE, + CONSTRAINT merkle_store_fkey + FOREIGN KEY(merkle_store_module_id) + REFERENCES metaschema_modules_public.merkle_store_module (id) + ON DELETE CASCADE, + CONSTRAINT db_presets_table_fkey + FOREIGN KEY(db_presets_table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE, + CONSTRAINT db_preset_module_entity_table_fkey + FOREIGN KEY(entity_table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE, + CONSTRAINT db_preset_module_database_merkle_unique + UNIQUE (database_id, merkle_store_module_id) +); + +CREATE INDEX db_preset_module_database_id_idx ON metaschema_modules_public.db_preset_module (database_id); + CREATE TABLE metaschema_modules_public.namespace_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + entity_field text, schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), public_schema_name text, @@ -3063,12 +3138,17 @@ CREATE UNIQUE INDEX namespace_module_unique_scope ON metaschema_modules_public.n CREATE TABLE metaschema_modules_public.function_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + entity_field text, schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), public_schema_name text, private_schema_name text, definitions_table_id uuid NOT NULL DEFAULT uuid_nil(), + bindings_table_id uuid NOT NULL DEFAULT uuid_nil(), + schedules_table_id uuid, + has_cron boolean NOT NULL DEFAULT false, definitions_table_name text NOT NULL DEFAULT 'function_definitions', + bindings_table_name text, api_name text, private_api_name text, scope text NOT NULL DEFAULT 'app', @@ -3093,6 +3173,14 @@ CREATE TABLE metaschema_modules_public.function_module ( FOREIGN KEY(definitions_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, + CONSTRAINT function_module_bindings_table_fkey + FOREIGN KEY(bindings_table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE, + CONSTRAINT function_module_schedules_table_fkey + FOREIGN KEY(schedules_table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE, CONSTRAINT function_module_entity_table_fkey FOREIGN KEY(entity_table_id) REFERENCES metaschema_public.table (id) @@ -3106,6 +3194,7 @@ CREATE UNIQUE INDEX function_module_unique_scope ON metaschema_modules_public.fu CREATE TABLE metaschema_modules_public.function_invocation_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + entity_field text, schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), public_schema_name text, @@ -3152,64 +3241,160 @@ CREATE INDEX function_invocation_module_database_id_idx ON metaschema_modules_pu CREATE UNIQUE INDEX function_invocation_module_unique_scope ON metaschema_modules_public.function_invocation_module (database_id, scope); -CREATE TABLE metaschema_modules_public.config_secrets_module ( +CREATE TABLE metaschema_modules_public.infra_secrets_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), public_schema_name text, private_schema_name text, - table_id uuid NOT NULL DEFAULT uuid_nil(), - table_name text NOT NULL DEFAULT 'secrets', + secrets_table_id uuid NOT NULL DEFAULT uuid_nil(), + secrets_table_name text NOT NULL DEFAULT 'secrets', api_name text DEFAULT 'config', private_api_name text DEFAULT NULL, scope text NOT NULL DEFAULT 'app', prefix text NOT NULL DEFAULT '', entity_table_id uuid NULL, + entity_field text, policies jsonb NULL, provisions jsonb NULL, - has_config boolean NOT NULL DEFAULT false, - CONSTRAINT config_secrets_module_db_fkey + CONSTRAINT infra_secrets_module_db_fkey FOREIGN KEY(database_id) REFERENCES metaschema_public.database (id) ON DELETE CASCADE, - CONSTRAINT config_secrets_module_schema_fkey + CONSTRAINT infra_secrets_module_schema_fkey FOREIGN KEY(schema_id) REFERENCES metaschema_public.schema (id) ON DELETE CASCADE, - CONSTRAINT config_secrets_module_private_schema_fkey + CONSTRAINT infra_secrets_module_private_schema_fkey FOREIGN KEY(private_schema_id) REFERENCES metaschema_public.schema (id) ON DELETE CASCADE, - CONSTRAINT config_secrets_module_table_fkey - FOREIGN KEY(table_id) + CONSTRAINT infra_secrets_module_secrets_table_fkey + FOREIGN KEY(secrets_table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE, + CONSTRAINT infra_secrets_module_entity_table_fkey + FOREIGN KEY(entity_table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE +); + +CREATE INDEX infra_secrets_module_database_id_idx ON metaschema_modules_public.infra_secrets_module (database_id); + +CREATE INDEX infra_secrets_module_schema_id_idx ON metaschema_modules_public.infra_secrets_module (schema_id); + +CREATE INDEX infra_secrets_module_secrets_table_id_idx ON metaschema_modules_public.infra_secrets_module (secrets_table_id); + +CREATE UNIQUE INDEX infra_secrets_module_unique_scope ON metaschema_modules_public.infra_secrets_module (database_id, scope); + +COMMENT ON TABLE metaschema_modules_public.infra_secrets_module IS 'Namespace-backed PGP-encrypted key-value secrets module. Requires namespace_module and emits namespace:sync_secrets job triggers for K8s Secret synchronization.'; + +CREATE TABLE metaschema_modules_public.infra_config_module ( + id uuid PRIMARY KEY DEFAULT uuidv7(), + database_id uuid NOT NULL, + entity_field text, + schema_id uuid NOT NULL DEFAULT uuid_nil(), + private_schema_id uuid NOT NULL DEFAULT uuid_nil(), + public_schema_name text, + private_schema_name text, + config_table_id uuid NOT NULL DEFAULT uuid_nil(), + config_table_name text NOT NULL DEFAULT 'config', + api_name text DEFAULT 'config', + private_api_name text DEFAULT NULL, + scope text NOT NULL DEFAULT 'app', + prefix text NOT NULL DEFAULT '', + entity_table_id uuid NULL, + policies jsonb NULL, + provisions jsonb NULL, + CONSTRAINT infra_config_module_db_fkey + FOREIGN KEY(database_id) + REFERENCES metaschema_public.database (id) + ON DELETE CASCADE, + CONSTRAINT infra_config_module_schema_fkey + FOREIGN KEY(schema_id) + REFERENCES metaschema_public.schema (id) + ON DELETE CASCADE, + CONSTRAINT infra_config_module_private_schema_fkey + FOREIGN KEY(private_schema_id) + REFERENCES metaschema_public.schema (id) + ON DELETE CASCADE, + CONSTRAINT infra_config_module_config_table_fkey + FOREIGN KEY(config_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, - CONSTRAINT config_secrets_module_entity_table_fkey + CONSTRAINT infra_config_module_entity_table_fkey FOREIGN KEY(entity_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE ); -CREATE INDEX config_secrets_module_database_id_idx ON metaschema_modules_public.config_secrets_module (database_id); +CREATE INDEX infra_config_module_database_id_idx ON metaschema_modules_public.infra_config_module (database_id); -CREATE INDEX config_secrets_module_schema_id_idx ON metaschema_modules_public.config_secrets_module (schema_id); +CREATE INDEX infra_config_module_schema_id_idx ON metaschema_modules_public.infra_config_module (schema_id); -CREATE INDEX config_secrets_module_table_id_idx ON metaschema_modules_public.config_secrets_module (table_id); +CREATE INDEX infra_config_module_config_table_id_idx ON metaschema_modules_public.infra_config_module (config_table_id); -CREATE UNIQUE INDEX config_secrets_module_unique_scope ON metaschema_modules_public.config_secrets_module (database_id, scope); +CREATE UNIQUE INDEX infra_config_module_unique_scope ON metaschema_modules_public.infra_config_module (database_id, scope); -COMMENT ON TABLE metaschema_modules_public.config_secrets_module IS 'Entity-aware PGP-encrypted key-value config/secrets module. Supports app-level (admin-only) - and platform/database-scoped infra secrets via the scope column. - User-scoped bcrypt credentials are handled by user_credentials_module.'; +COMMENT ON TABLE metaschema_modules_public.infra_config_module IS 'Namespace-backed plaintext key-value config module. Requires namespace_module and emits namespace:sync_config job triggers for K8s ConfigMap synchronization.'; + +CREATE TABLE metaschema_modules_public.internal_secrets_module ( + id uuid PRIMARY KEY DEFAULT uuidv7(), + database_id uuid NOT NULL, + schema_id uuid NOT NULL DEFAULT uuid_nil(), + private_schema_id uuid NOT NULL DEFAULT uuid_nil(), + public_schema_name text, + private_schema_name text, + internal_secrets_table_id uuid NOT NULL DEFAULT uuid_nil(), + internal_secrets_table_name text NOT NULL DEFAULT 'internal_secrets', + api_name text DEFAULT 'config', + private_api_name text DEFAULT NULL, + scope text NOT NULL DEFAULT 'app', + prefix text NOT NULL DEFAULT '', + entity_table_id uuid NULL, + entity_field text, + policies jsonb NULL, + provisions jsonb NULL, + CONSTRAINT internal_secrets_module_db_fkey + FOREIGN KEY(database_id) + REFERENCES metaschema_public.database (id) + ON DELETE CASCADE, + CONSTRAINT internal_secrets_module_schema_fkey + FOREIGN KEY(schema_id) + REFERENCES metaschema_public.schema (id) + ON DELETE CASCADE, + CONSTRAINT internal_secrets_module_private_schema_fkey + FOREIGN KEY(private_schema_id) + REFERENCES metaschema_public.schema (id) + ON DELETE CASCADE, + CONSTRAINT internal_secrets_module_internal_secrets_table_fkey + FOREIGN KEY(internal_secrets_table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE, + CONSTRAINT internal_secrets_module_entity_table_fkey + FOREIGN KEY(entity_table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE +); + +CREATE INDEX internal_secrets_module_database_id_idx ON metaschema_modules_public.internal_secrets_module (database_id); + +CREATE INDEX internal_secrets_module_schema_id_idx ON metaschema_modules_public.internal_secrets_module (schema_id); + +CREATE INDEX internal_secrets_module_internal_secrets_table_id_idx ON metaschema_modules_public.internal_secrets_module (internal_secrets_table_id); + +CREATE UNIQUE INDEX internal_secrets_module_unique_scope ON metaschema_modules_public.internal_secrets_module (database_id, scope); + +COMMENT ON TABLE metaschema_modules_public.internal_secrets_module IS 'App-scoped PGP-encrypted internal secrets store. No namespace_module dependency and no K8s synchronization. Used by identity_providers_module for OAuth2 client_secret storage.'; CREATE TABLE metaschema_modules_public.user_credentials_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + entity_field text, schema_id uuid NOT NULL DEFAULT uuid_nil(), table_id uuid NOT NULL DEFAULT uuid_nil(), table_name text NOT NULL DEFAULT 'user_secrets', - api_name text DEFAULT 'config', private_api_name text DEFAULT NULL, CONSTRAINT user_credentials_module_db_fkey FOREIGN KEY(database_id) @@ -3300,6 +3485,7 @@ CREATE UNIQUE INDEX i18n_module_unique_per_db ON metaschema_modules_public.i18n_ CREATE TABLE metaschema_modules_public.function_deployment_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + entity_field text, schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), public_schema_name text, @@ -3422,6 +3608,7 @@ COMMENT ON TABLE metaschema_modules_public.principal_auth_module IS 'Provisions CREATE TABLE metaschema_modules_public.resource_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, + entity_field text, schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), public_schema_name text, @@ -3430,10 +3617,20 @@ CREATE TABLE metaschema_modules_public.resource_module ( resource_events_table_id uuid NOT NULL DEFAULT uuid_nil(), resource_status_checks_table_id uuid NOT NULL DEFAULT uuid_nil(), resource_definitions_table_id uuid NOT NULL DEFAULT uuid_nil(), + resource_usage_samples_table_id uuid NOT NULL DEFAULT uuid_nil(), + resource_usage_summary_table_id uuid NOT NULL DEFAULT uuid_nil(), + namespace_usage_summary_table_id uuid NOT NULL DEFAULT uuid_nil(), resources_table_name text NOT NULL DEFAULT 'resources', resource_events_table_name text NOT NULL DEFAULT 'resource_events', resource_status_checks_table_name text NOT NULL DEFAULT 'resource_status_checks', resource_definitions_table_name text NOT NULL DEFAULT 'resource_definitions', + resource_usage_samples_table_name text NOT NULL DEFAULT 'resource_usage_samples', + resource_usage_summary_table_name text NOT NULL DEFAULT 'resource_usage_summaries', + namespace_usage_summary_table_name text NOT NULL DEFAULT 'namespace_usage_summaries', + rollup_resource_usage_summary_function text NOT NULL DEFAULT '', + resource_billing_rollup_function text NOT NULL DEFAULT '', + resolved_requirements_view_name text, + requirements_state_view_name text, api_name text, private_api_name text, scope text NOT NULL DEFAULT 'app', @@ -3471,6 +3668,18 @@ CREATE TABLE metaschema_modules_public.resource_module ( FOREIGN KEY(resource_definitions_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, + CONSTRAINT resource_module_usage_samples_table_fkey + FOREIGN KEY(resource_usage_samples_table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE, + CONSTRAINT resource_module_usage_summary_table_fkey + FOREIGN KEY(resource_usage_summary_table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE, + CONSTRAINT resource_module_ns_usage_summary_table_fkey + FOREIGN KEY(namespace_usage_summary_table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE, CONSTRAINT resource_module_entity_table_fkey FOREIGN KEY(entity_table_id) REFERENCES metaschema_public.table (id) @@ -3483,4 +3692,141 @@ CREATE TABLE metaschema_modules_public.resource_module ( CREATE INDEX resource_module_database_id_idx ON metaschema_modules_public.resource_module (database_id); -CREATE UNIQUE INDEX resource_module_unique_scope ON metaschema_modules_public.resource_module (database_id, scope); \ No newline at end of file +CREATE UNIQUE INDEX resource_module_unique_scope ON metaschema_modules_public.resource_module (database_id, scope); + +CREATE TABLE metaschema_modules_public.integration_providers_module ( + id uuid PRIMARY KEY DEFAULT uuidv7(), + database_id uuid NOT NULL, + entity_field text, + schema_id uuid NOT NULL DEFAULT uuid_nil(), + private_schema_id uuid NOT NULL DEFAULT uuid_nil(), + public_schema_name text, + private_schema_name text, + table_id uuid NOT NULL DEFAULT uuid_nil(), + table_name text NOT NULL DEFAULT 'integration_providers', + api_name text DEFAULT 'compute', + private_api_name text DEFAULT NULL, + scope text NOT NULL DEFAULT 'platform', + prefix text NOT NULL DEFAULT '', + entity_table_id uuid NULL, + CONSTRAINT integration_providers_module_db_fkey + FOREIGN KEY(database_id) + REFERENCES metaschema_public.database (id) + ON DELETE CASCADE, + CONSTRAINT integration_providers_module_table_fkey + FOREIGN KEY(table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE, + CONSTRAINT integration_providers_module_schema_fkey + FOREIGN KEY(schema_id) + REFERENCES metaschema_public.schema (id) + ON DELETE CASCADE, + CONSTRAINT integration_providers_module_private_schema_fkey + FOREIGN KEY(private_schema_id) + REFERENCES metaschema_public.schema (id) + ON DELETE CASCADE, + CONSTRAINT integration_providers_module_entity_table_fkey + FOREIGN KEY(entity_table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE +); + +CREATE INDEX integration_providers_module_database_id_idx ON metaschema_modules_public.integration_providers_module (database_id); + +CREATE INDEX integration_providers_module_schema_id_idx ON metaschema_modules_public.integration_providers_module (schema_id); + +CREATE INDEX integration_providers_module_private_schema_id_idx ON metaschema_modules_public.integration_providers_module (private_schema_id); + +CREATE INDEX integration_providers_module_table_id_idx ON metaschema_modules_public.integration_providers_module (table_id); + +CREATE UNIQUE INDEX integration_providers_module_unique_scope ON metaschema_modules_public.integration_providers_module (database_id, scope, prefix); + +COMMENT ON TABLE metaschema_modules_public.integration_providers_module IS 'Config row for the integration_providers_module, which provisions a per-database + integration_providers table holding branded, reusable service definitions. + Integration providers act as a catalog of external services (e.g. Mailgun, Postgres) + and list the canonical secret/config names required to use them. + Other modules (function_module, resource_module) match the provider slug as a string.'; + +CREATE UNIQUE INDEX graph_module_one_platform_scope ON metaschema_modules_public.graph_module (database_id) WHERE scope = 'platform'; + +CREATE UNIQUE INDEX events_module_one_platform_scope ON metaschema_modules_public.events_module (database_id) WHERE scope = 'platform'; + +CREATE UNIQUE INDEX inference_log_module_one_platform_scope ON metaschema_modules_public.inference_log_module (database_id) WHERE scope = 'platform'; + +CREATE UNIQUE INDEX compute_log_module_one_platform_scope ON metaschema_modules_public.compute_log_module (database_id) WHERE scope = 'platform'; + +CREATE UNIQUE INDEX transfer_log_module_one_platform_scope ON metaschema_modules_public.transfer_log_module (database_id) WHERE scope = 'platform'; + +CREATE UNIQUE INDEX storage_log_module_one_platform_scope ON metaschema_modules_public.storage_log_module (database_id) WHERE scope = 'platform'; + +CREATE UNIQUE INDEX db_usage_module_one_platform_scope ON metaschema_modules_public.db_usage_module (database_id) WHERE scope = 'platform'; + +CREATE TABLE metaschema_modules_public.webhook_module ( + id uuid PRIMARY KEY DEFAULT uuidv7(), + database_id uuid NOT NULL, + entity_field text, + schema_id uuid NOT NULL DEFAULT uuid_nil(), + private_schema_id uuid NOT NULL DEFAULT uuid_nil(), + public_schema_name text, + private_schema_name text, + webhook_endpoints_table_id uuid NOT NULL DEFAULT uuid_nil(), + webhook_events_table_id uuid NOT NULL DEFAULT uuid_nil(), + webhook_endpoints_table_name text NOT NULL DEFAULT 'webhook_endpoints', + webhook_events_table_name text NOT NULL DEFAULT 'webhook_events', + function_module_id uuid, + function_invocation_module_id uuid, + infra_secrets_module_id uuid, + namespace_module_id uuid, + api_name text, + private_api_name text, + scope text NOT NULL DEFAULT 'app', + prefix text NOT NULL DEFAULT '', + entity_table_id uuid NULL, + policies jsonb NULL, + provisions jsonb NULL, + default_permissions text[] DEFAULT NULL, + CONSTRAINT webhook_module_db_fkey + FOREIGN KEY(database_id) + REFERENCES metaschema_public.database (id) + ON DELETE CASCADE, + CONSTRAINT webhook_module_schema_fkey + FOREIGN KEY(schema_id) + REFERENCES metaschema_public.schema (id) + ON DELETE CASCADE, + CONSTRAINT webhook_module_private_schema_fkey + FOREIGN KEY(private_schema_id) + REFERENCES metaschema_public.schema (id) + ON DELETE CASCADE, + CONSTRAINT webhook_module_endpoints_table_fkey + FOREIGN KEY(webhook_endpoints_table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE, + CONSTRAINT webhook_module_events_table_fkey + FOREIGN KEY(webhook_events_table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE, + CONSTRAINT webhook_module_function_module_fkey + FOREIGN KEY(function_module_id) + REFERENCES metaschema_modules_public.function_module (id) + ON DELETE CASCADE, + CONSTRAINT webhook_module_invocation_module_fkey + FOREIGN KEY(function_invocation_module_id) + REFERENCES metaschema_modules_public.function_invocation_module (id) + ON DELETE CASCADE, + CONSTRAINT webhook_module_secrets_module_fkey + FOREIGN KEY(infra_secrets_module_id) + REFERENCES metaschema_modules_public.infra_secrets_module (id) + ON DELETE CASCADE, + CONSTRAINT webhook_module_namespace_module_fkey + FOREIGN KEY(namespace_module_id) + REFERENCES metaschema_modules_public.namespace_module (id) + ON DELETE CASCADE, + CONSTRAINT webhook_module_entity_table_fkey + FOREIGN KEY(entity_table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE +); + +CREATE INDEX webhook_module_database_id_idx ON metaschema_modules_public.webhook_module (database_id); + +CREATE UNIQUE INDEX webhook_module_unique_scope ON metaschema_modules_public.webhook_module (database_id, scope); \ No newline at end of file diff --git a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/billing_module/table.sql b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/billing_module/table.sql index aa1c1c914..7b6ad146d 100644 --- a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/billing_module/table.sql +++ b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/billing_module/table.sql @@ -18,6 +18,8 @@ SELECT meter_sources_table_id, meter_sources_table_name, record_usage_function, + sweep_expired_subscriptions_function, + rollup_usage_summary_function, prefix FROM metaschema_modules_public.billing_module WHERE FALSE; diff --git a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/compute_log_module/constraints/one_platform_scope.sql b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/compute_log_module/constraints/one_platform_scope.sql new file mode 100644 index 000000000..f2c90fd17 --- /dev/null +++ b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/compute_log_module/constraints/one_platform_scope.sql @@ -0,0 +1,10 @@ +-- Verify schemas/metaschema_modules_public/tables/compute_log_module/constraints/one_platform_scope on pg + +BEGIN; + +SELECT 1/count(*) +FROM pg_indexes +WHERE schemaname = 'metaschema_modules_public' + AND indexname = 'compute_log_module_one_platform_scope'; + +ROLLBACK; diff --git a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/compute_log_module/table.sql b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/compute_log_module/table.sql index a83925ae6..2b9afc9ce 100644 --- a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/compute_log_module/table.sql +++ b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/compute_log_module/table.sql @@ -2,7 +2,7 @@ SELECT id, database_id, schema_id, private_schema_id, compute_log_table_id, compute_log_table_name, - usage_daily_table_id, usage_daily_table_name, + usage_summary_table_id, usage_summary_table_name, retention, premake, actor_fk_table_id, entity_fk_table_id, prefix diff --git a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/config_secrets_module/table.sql b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/config_secrets_module/table.sql deleted file mode 100644 index 8ecf487d6..000000000 --- a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/config_secrets_module/table.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Verify schemas/metaschema_modules_public/tables/config_secrets_module/table on pg - -BEGIN; - -SELECT verify_table('metaschema_modules_public.config_secrets_module'); - -ROLLBACK; diff --git a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/db_preset_module/table.sql b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/db_preset_module/table.sql new file mode 100644 index 000000000..39387bd8c --- /dev/null +++ b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/db_preset_module/table.sql @@ -0,0 +1,9 @@ +-- Verify schemas/metaschema_modules_public/tables/db_preset_module/table on pg + +BEGIN; + +SELECT id, database_id, public_schema_id, private_schema_id, merkle_store_module_id, store_name, scope, prefix + FROM metaschema_modules_public.db_preset_module + WHERE FALSE; + +ROLLBACK; diff --git a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/db_usage_module/constraints/one_platform_scope.sql b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/db_usage_module/constraints/one_platform_scope.sql new file mode 100644 index 000000000..66b9216a4 --- /dev/null +++ b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/db_usage_module/constraints/one_platform_scope.sql @@ -0,0 +1,10 @@ +-- Verify schemas/metaschema_modules_public/tables/db_usage_module/constraints/one_platform_scope on pg + +BEGIN; + +SELECT 1/count(*) +FROM pg_indexes +WHERE schemaname = 'metaschema_modules_public' + AND indexname = 'db_usage_module_one_platform_scope'; + +ROLLBACK; diff --git a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/db_usage_module/table.sql b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/db_usage_module/table.sql index 0b13ba091..d8720ced2 100644 --- a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/db_usage_module/table.sql +++ b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/db_usage_module/table.sql @@ -2,6 +2,8 @@ SELECT id, database_id, schema_id, private_schema_id, retention, premake, + collect_db_table_stats_function, collect_db_query_stats_function, + rollup_db_table_stats_usage_summary_function, rollup_db_query_stats_usage_summary_function, prefix FROM metaschema_modules_public.db_usage_module WHERE FALSE; diff --git a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/events_module/constraints/one_platform_scope.sql b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/events_module/constraints/one_platform_scope.sql new file mode 100644 index 000000000..6700206da --- /dev/null +++ b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/events_module/constraints/one_platform_scope.sql @@ -0,0 +1,10 @@ +-- Verify schemas/metaschema_modules_public/tables/events_module/constraints/one_platform_scope on pg + +BEGIN; + +SELECT 1/count(*) +FROM pg_indexes +WHERE schemaname = 'metaschema_modules_public' + AND indexname = 'events_module_one_platform_scope'; + +ROLLBACK; diff --git a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/graph_module/constraints/one_platform_scope.sql b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/graph_module/constraints/one_platform_scope.sql new file mode 100644 index 000000000..d7895a524 --- /dev/null +++ b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/graph_module/constraints/one_platform_scope.sql @@ -0,0 +1,10 @@ +-- Verify schemas/metaschema_modules_public/tables/graph_module/constraints/one_platform_scope on pg + +BEGIN; + +SELECT 1/count(*) +FROM pg_indexes +WHERE schemaname = 'metaschema_modules_public' + AND indexname = 'graph_module_one_platform_scope'; + +ROLLBACK; diff --git a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/inference_log_module/constraints/one_platform_scope.sql b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/inference_log_module/constraints/one_platform_scope.sql new file mode 100644 index 000000000..a749206ee --- /dev/null +++ b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/inference_log_module/constraints/one_platform_scope.sql @@ -0,0 +1,10 @@ +-- Verify schemas/metaschema_modules_public/tables/inference_log_module/constraints/one_platform_scope on pg + +BEGIN; + +SELECT 1/count(*) +FROM pg_indexes +WHERE schemaname = 'metaschema_modules_public' + AND indexname = 'inference_log_module_one_platform_scope'; + +ROLLBACK; diff --git a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/inference_log_module/table.sql b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/inference_log_module/table.sql index f30ebac19..f6b4bbe27 100644 --- a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/inference_log_module/table.sql +++ b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/inference_log_module/table.sql @@ -9,8 +9,8 @@ SELECT private_schema_id, inference_log_table_id, inference_log_table_name, - usage_daily_table_id, - usage_daily_table_name, + usage_summary_table_id, + usage_summary_table_name, "interval", retention, premake, diff --git a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/infra_config_module/table.sql b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/infra_config_module/table.sql new file mode 100644 index 000000000..a54ee81a2 --- /dev/null +++ b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/infra_config_module/table.sql @@ -0,0 +1,9 @@ +-- Verify schemas/metaschema_modules_public/tables/infra_config_module/table on pg + +BEGIN; + +SELECT id, database_id, schema_id, config_table_id, scope, prefix + FROM metaschema_modules_public.infra_config_module + WHERE FALSE; + +ROLLBACK; diff --git a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/infra_secrets_module/table.sql b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/infra_secrets_module/table.sql new file mode 100644 index 000000000..1a818b905 --- /dev/null +++ b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/infra_secrets_module/table.sql @@ -0,0 +1,9 @@ +-- Verify schemas/metaschema_modules_public/tables/infra_secrets_module/table on pg + +BEGIN; + +SELECT id, database_id, schema_id, secrets_table_id, scope, prefix + FROM metaschema_modules_public.infra_secrets_module + WHERE FALSE; + +ROLLBACK; diff --git a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/integration_providers_module/table.sql b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/integration_providers_module/table.sql new file mode 100644 index 000000000..e54cd5e69 --- /dev/null +++ b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/integration_providers_module/table.sql @@ -0,0 +1,9 @@ +-- Verify schemas/metaschema_modules_public/tables/integration_providers_module/table on pg + +BEGIN; + +SELECT id, database_id, schema_id, private_schema_id, table_id, table_name, scope, prefix + FROM metaschema_modules_public.integration_providers_module + WHERE FALSE; + +ROLLBACK; diff --git a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/internal_secrets_module/table.sql b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/internal_secrets_module/table.sql new file mode 100644 index 000000000..0605948d6 --- /dev/null +++ b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/internal_secrets_module/table.sql @@ -0,0 +1,9 @@ +-- Verify schemas/metaschema_modules_public/tables/internal_secrets_module/table on pg + +BEGIN; + +SELECT id, database_id, schema_id, internal_secrets_table_id, scope, prefix + FROM metaschema_modules_public.internal_secrets_module + WHERE FALSE; + +ROLLBACK; diff --git a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/resource_module/table.sql b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/resource_module/table.sql index d32d8ea5f..19ab8e405 100644 --- a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/resource_module/table.sql +++ b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/resource_module/table.sql @@ -5,6 +5,7 @@ BEGIN; SELECT id, database_id, schema_id, private_schema_id, resources_table_id, resource_events_table_id, resources_table_name, resource_events_table_name, + resolved_requirements_view_name, requirements_state_view_name, scope, prefix, entity_table_id, namespace_module_id, policies, provisions, default_permissions FROM metaschema_modules_public.resource_module diff --git a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/storage_log_module/constraints/one_platform_scope.sql b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/storage_log_module/constraints/one_platform_scope.sql new file mode 100644 index 000000000..6de14eeef --- /dev/null +++ b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/storage_log_module/constraints/one_platform_scope.sql @@ -0,0 +1,10 @@ +-- Verify schemas/metaschema_modules_public/tables/storage_log_module/constraints/one_platform_scope on pg + +BEGIN; + +SELECT 1/count(*) +FROM pg_indexes +WHERE schemaname = 'metaschema_modules_public' + AND indexname = 'storage_log_module_one_platform_scope'; + +ROLLBACK; diff --git a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/storage_log_module/table.sql b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/storage_log_module/table.sql index c9e95f6c8..506ae1a93 100644 --- a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/storage_log_module/table.sql +++ b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/storage_log_module/table.sql @@ -2,7 +2,7 @@ SELECT id, database_id, schema_id, private_schema_id, storage_log_table_id, storage_log_table_name, - usage_daily_table_id, usage_daily_table_name, + usage_summary_table_id, usage_summary_table_name, "interval", retention, premake, actor_fk_table_id, entity_fk_table_id, prefix diff --git a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/transfer_log_module/constraints/one_platform_scope.sql b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/transfer_log_module/constraints/one_platform_scope.sql new file mode 100644 index 000000000..74c1bd6e1 --- /dev/null +++ b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/transfer_log_module/constraints/one_platform_scope.sql @@ -0,0 +1,10 @@ +-- Verify schemas/metaschema_modules_public/tables/transfer_log_module/constraints/one_platform_scope on pg + +BEGIN; + +SELECT 1/count(*) +FROM pg_indexes +WHERE schemaname = 'metaschema_modules_public' + AND indexname = 'transfer_log_module_one_platform_scope'; + +ROLLBACK; diff --git a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/transfer_log_module/table.sql b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/transfer_log_module/table.sql index d5a0419e9..4760db9f0 100644 --- a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/transfer_log_module/table.sql +++ b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/transfer_log_module/table.sql @@ -2,7 +2,7 @@ SELECT id, database_id, schema_id, private_schema_id, transfer_log_table_id, transfer_log_table_name, - usage_daily_table_id, usage_daily_table_name, + usage_summary_table_id, usage_summary_table_name, "interval", retention, premake, actor_fk_table_id, entity_fk_table_id, prefix diff --git a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/webhook_module/table.sql b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/webhook_module/table.sql new file mode 100644 index 000000000..5bfc40ee1 --- /dev/null +++ b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/webhook_module/table.sql @@ -0,0 +1,9 @@ +-- Verify schemas/metaschema_modules_public/tables/webhook_module/table on pg + +BEGIN; + +SELECT id, database_id, scope +FROM metaschema_modules_public.webhook_module +WHERE false; + +ROLLBACK; diff --git a/packages/metaschema-schema/deploy/schemas/metaschema_private/procedures/is_valid_step_up.sql b/packages/metaschema-schema/deploy/schemas/metaschema_private/procedures/is_valid_step_up.sql new file mode 100644 index 000000000..658a31336 --- /dev/null +++ b/packages/metaschema-schema/deploy/schemas/metaschema_private/procedures/is_valid_step_up.sql @@ -0,0 +1,293 @@ +-- Deploy schemas/metaschema_private/procedures/is_valid_step_up to pg +-- requires: schemas/metaschema_private/schema + +BEGIN; + +-- Validates the declarative step_up field on metaschema_public.table. +-- Expected shape: a non-empty jsonb object mapping DML verbs to a step-up spec: +-- { "DELETE": "mfa", "UPDATE": true } +-- { "DELETE": { "type": "mfa", "min_age": "24 hours" } } +-- Keys must be INSERT, UPDATE, or DELETE. +-- Values must be one of: +-- - true (default step-up type) +-- - a type string: 'password', 'mfa', 'password_or_mfa' +-- - an object with keys from {type, min_age, min_age_lookup, conditions}: +-- type (optional): 'password', 'mfa', 'password_or_mfa' +-- min_age (optional): a positive interval string (e.g. '6 hours'); +-- the guard only fires for rows older than this. +-- Not allowed for INSERT (new rows have no age). +-- min_age_lookup (optional): per-row min_age resolution from a lookup +-- table. Object with exactly {table_id (uuid), +-- fk_field (text), min_age_field (text)}. Requires +-- min_age as the fallback default. UPDATE/DELETE only. +-- conditions (optional): declarative WHEN-clause tree gating the guard +-- (compiled by metaschema_generators.build_condition_expr +-- and validated through the ast_validate framework at +-- apply time). Shape-validated here via +-- is_valid_step_up_conditions. + +-- Shape validator for the conditions tree accepted by the declarative +-- step_up field. Mirrors the grammar of +-- metaschema_generators.build_condition_expr: +-- - array: implicit AND of nodes (must be non-empty) +-- - combinator object: exactly one of {"AND": [...]}, {"OR": [...]}, +-- {"NOT": {...}} +-- - leaf object: {field, op, value?, row?, ref?} where op is one of +-- =, !=, >, <, >=, <=, LIKE, NOT LIKE, IS NULL, IS NOT NULL, +-- IS DISTINCT FROM; row is NEW or OLD; comparison ops require exactly +-- one of value (scalar) or ref ({field, row?}). +-- Field existence and AST safety are enforced at apply time by +-- build_condition_expr + ast_validate.validate_column_expression_ast. +CREATE FUNCTION metaschema_private.is_valid_step_up_conditions(cond jsonb) +RETURNS boolean AS $$ +DECLARE + -- node iteration + v_i int; + + -- leaf validation + v_key text; + v_op text; + + -- ref validation (column-to-column comparison) + v_ref jsonb; + v_ref_key text; +BEGIN + IF cond IS NULL THEN + RETURN false; + END IF; + + -- Array: implicit AND of all elements + IF jsonb_typeof(cond) = 'array' THEN + IF jsonb_array_length(cond) = 0 THEN + RETURN false; + END IF; + FOR v_i IN 0..jsonb_array_length(cond) - 1 LOOP + IF NOT metaschema_private.is_valid_step_up_conditions(cond -> v_i) THEN + RETURN false; + END IF; + END LOOP; + RETURN true; + END IF; + + IF jsonb_typeof(cond) != 'object' OR cond = '{}'::jsonb THEN + RETURN false; + END IF; + + -- Combinator object: exactly one of AND / OR / NOT + IF cond ? 'AND' OR cond ? 'OR' OR cond ? 'NOT' THEN + IF (SELECT count(*) FROM jsonb_object_keys(cond)) != 1 THEN + RETURN false; + END IF; + IF cond ? 'NOT' THEN + RETURN metaschema_private.is_valid_step_up_conditions(cond -> 'NOT'); + END IF; + IF jsonb_typeof(COALESCE(cond -> 'AND', cond -> 'OR')) != 'array' THEN + RETURN false; + END IF; + RETURN metaschema_private.is_valid_step_up_conditions(COALESCE(cond -> 'AND', cond -> 'OR')); + END IF; + + -- Leaf condition: {field, op, value?, row?, ref?} + FOR v_key IN SELECT key FROM jsonb_each(cond) LOOP + IF v_key NOT IN ('field', 'op', 'value', 'row', 'ref') THEN + RETURN false; + END IF; + END LOOP; + + IF jsonb_typeof(cond -> 'field') IS DISTINCT FROM 'string' + OR jsonb_typeof(cond -> 'op') IS DISTINCT FROM 'string' THEN + RETURN false; + END IF; + + v_op := upper(cond ->> 'op'); + IF v_op NOT IN ('=', '!=', '>', '<', '>=', '<=', 'LIKE', 'NOT LIKE', + 'IS NULL', 'IS NOT NULL', 'IS DISTINCT FROM') THEN + RETURN false; + END IF; + + IF cond ? 'row' THEN + IF jsonb_typeof(cond -> 'row') != 'string' + OR upper(cond ->> 'row') NOT IN ('NEW', 'OLD') THEN + RETURN false; + END IF; + END IF; + + -- Operators without a right-hand side + IF v_op IN ('IS NULL', 'IS NOT NULL', 'IS DISTINCT FROM') THEN + IF cond ? 'value' OR cond ? 'ref' THEN + RETURN false; + END IF; + RETURN true; + END IF; + + -- Comparison operators require exactly one of value / ref + IF (cond ? 'value') = (cond ? 'ref') THEN + RETURN false; + END IF; + + IF cond ? 'value' THEN + IF jsonb_typeof(cond -> 'value') NOT IN ('string', 'number', 'boolean') THEN + RETURN false; + END IF; + RETURN true; + END IF; + + v_ref := cond -> 'ref'; + IF jsonb_typeof(v_ref) != 'object' THEN + RETURN false; + END IF; + FOR v_ref_key IN SELECT key FROM jsonb_each(v_ref) LOOP + IF v_ref_key NOT IN ('field', 'row') THEN + RETURN false; + END IF; + END LOOP; + IF jsonb_typeof(v_ref -> 'field') IS DISTINCT FROM 'string' THEN + RETURN false; + END IF; + IF v_ref ? 'row' THEN + IF jsonb_typeof(v_ref -> 'row') != 'string' + OR upper(v_ref ->> 'row') NOT IN ('NEW', 'OLD') THEN + RETURN false; + END IF; + END IF; + + RETURN true; +END; +$$ +LANGUAGE 'plpgsql' IMMUTABLE; + +CREATE FUNCTION metaschema_private.is_valid_step_up(step_up jsonb) +RETURNS boolean AS $$ +DECLARE + -- entry iteration + v_key text; + v_value jsonb; + + -- object value validation + v_obj_key text; + v_type jsonb; + v_min_age jsonb; + v_min_age_interval interval; + + -- min_age_lookup validation (per-row lookup windows) + v_min_age_lookup jsonb; + v_lookup_key text; + v_lookup_table_id uuid; + + -- conditions validation (declarative WHEN-clause tree) + v_conditions jsonb; +BEGIN + IF step_up IS NULL THEN + RETURN false; + END IF; + + IF jsonb_typeof(step_up) != 'object' THEN + RETURN false; + END IF; + + IF step_up = '{}'::jsonb THEN + RETURN false; + END IF; + + FOR v_key, v_value IN SELECT key, value FROM jsonb_each(step_up) LOOP + IF v_key NOT IN ('INSERT', 'UPDATE', 'DELETE') THEN + RETURN false; + END IF; + + IF jsonb_typeof(v_value) = 'boolean' THEN + IF v_value = 'false'::jsonb THEN + RETURN false; + END IF; + ELSIF jsonb_typeof(v_value) = 'string' THEN + IF v_value #>> '{}' NOT IN ('password', 'mfa', 'password_or_mfa') THEN + RETURN false; + END IF; + ELSIF jsonb_typeof(v_value) = 'object' THEN + IF v_value = '{}'::jsonb THEN + RETURN false; + END IF; + + FOR v_obj_key IN SELECT key FROM jsonb_each(v_value) LOOP + IF v_obj_key NOT IN ('type', 'min_age', 'min_age_lookup', 'conditions') THEN + RETURN false; + END IF; + END LOOP; + + v_type := v_value -> 'type'; + IF v_type IS NOT NULL THEN + IF jsonb_typeof(v_type) != 'string' + OR v_type #>> '{}' NOT IN ('password', 'mfa', 'password_or_mfa') THEN + RETURN false; + END IF; + END IF; + + v_min_age := v_value -> 'min_age'; + IF v_min_age IS NOT NULL THEN + -- min_age is meaningless for INSERT: a new row has no age + IF v_key = 'INSERT' THEN + RETURN false; + END IF; + + IF jsonb_typeof(v_min_age) != 'string' THEN + RETURN false; + END IF; + + BEGIN + v_min_age_interval := (v_min_age #>> '{}')::interval; + EXCEPTION WHEN OTHERS THEN + RETURN false; + END; + + IF v_min_age_interval <= interval '0' THEN + RETURN false; + END IF; + END IF; + + v_min_age_lookup := v_value -> 'min_age_lookup'; + IF v_min_age_lookup IS NOT NULL THEN + -- lookup windows are meaningless for INSERT and need min_age + -- as the fallback default + IF v_key = 'INSERT' OR v_min_age IS NULL THEN + RETURN false; + END IF; + + IF jsonb_typeof(v_min_age_lookup) != 'object' THEN + RETURN false; + END IF; + + FOR v_lookup_key IN SELECT key FROM jsonb_each(v_min_age_lookup) LOOP + IF v_lookup_key NOT IN ('table_id', 'fk_field', 'min_age_field') THEN + RETURN false; + END IF; + END LOOP; + + IF jsonb_typeof(v_min_age_lookup -> 'table_id') IS DISTINCT FROM 'string' + OR jsonb_typeof(v_min_age_lookup -> 'fk_field') IS DISTINCT FROM 'string' + OR jsonb_typeof(v_min_age_lookup -> 'min_age_field') IS DISTINCT FROM 'string' THEN + RETURN false; + END IF; + + BEGIN + v_lookup_table_id := (v_min_age_lookup ->> 'table_id')::uuid; + EXCEPTION WHEN OTHERS THEN + RETURN false; + END; + END IF; + + v_conditions := v_value -> 'conditions'; + IF v_conditions IS NOT NULL THEN + IF NOT metaschema_private.is_valid_step_up_conditions(v_conditions) THEN + RETURN false; + END IF; + END IF; + ELSE + RETURN false; + END IF; + END LOOP; + + RETURN true; +END; +$$ +LANGUAGE 'plpgsql' IMMUTABLE; + +COMMIT; diff --git a/packages/metaschema-schema/deploy/schemas/metaschema_public/tables/field/indexes/databases_field_uniq_names_idx.sql b/packages/metaschema-schema/deploy/schemas/metaschema_public/tables/field/indexes/databases_field_uniq_names_idx.sql index 6182d432e..78233d36a 100644 --- a/packages/metaschema-schema/deploy/schemas/metaschema_public/tables/field/indexes/databases_field_uniq_names_idx.sql +++ b/packages/metaschema-schema/deploy/schemas/metaschema_public/tables/field/indexes/databases_field_uniq_names_idx.sql @@ -16,4 +16,12 @@ CREATE UNIQUE INDEX databases_field_uniq_names_idx ON metaschema_public.field ( )), 'hex') ); +COMMENT ON INDEX metaschema_public.databases_field_uniq_names_idx IS +'Guards against PostGraphile/Graphile inflection collisions: a uuid FK field +(e.g. action_id) and a sibling text field (e.g. action) on the same table +inflect toward the same GraphQL property name. For uuid fields the suffix +(_row_id|_id|_uuid|_fk|_pk) is stripped before uniqueness-checking, so any +name-like text field sitting next to a uuid FK must be suffixed explicitly +(convention: use *_name, e.g. action_name, sessions_table_name).'; + COMMIT; diff --git a/packages/metaschema-schema/deploy/schemas/metaschema_public/tables/field/table.sql b/packages/metaschema-schema/deploy/schemas/metaschema_public/tables/field/table.sql index 91346211f..0ef70fa02 100644 --- a/packages/metaschema-schema/deploy/schemas/metaschema_public/tables/field/table.sql +++ b/packages/metaschema-schema/deploy/schemas/metaschema_public/tables/field/table.sql @@ -38,6 +38,9 @@ CREATE TABLE metaschema_public.field ( api_required boolean NOT NULL DEFAULT FALSE, default_value jsonb NULL DEFAULT NULL, + generation_expression jsonb NULL DEFAULT NULL, + generation_type text NULL DEFAULT NULL, + type jsonb NOT NULL, field_order int not null default 0, diff --git a/packages/metaschema-schema/deploy/schemas/metaschema_public/tables/policy/table.sql b/packages/metaschema-schema/deploy/schemas/metaschema_public/tables/policy/table.sql index dd3bbed28..0bc8b2c87 100644 --- a/packages/metaschema-schema/deploy/schemas/metaschema_public/tables/policy/table.sql +++ b/packages/metaschema-schema/deploy/schemas/metaschema_public/tables/policy/table.sql @@ -25,6 +25,8 @@ CREATE TABLE metaschema_public.policy ( policy_type text, data jsonb, + with_check jsonb, + smart_tags jsonb, category metaschema_public.object_category NOT NULL DEFAULT 'app', @@ -37,9 +39,20 @@ CREATE TABLE metaschema_public.policy ( CONSTRAINT db_fkey FOREIGN KEY (database_id) REFERENCES metaschema_public.database (id) ON DELETE CASCADE, CONSTRAINT table_fkey FOREIGN KEY (table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, + CONSTRAINT policy_with_check_shape CHECK ( + with_check IS NULL OR ( + jsonb_typeof(with_check) = 'object' + AND with_check ? '$type' + AND jsonb_typeof(with_check->'$type') = 'string' + ) + ), + UNIQUE (table_id, name) ); +COMMENT ON COLUMN metaschema_public.policy.with_check IS + 'Optional WITH CHECK override node {"$type": "Authz...", "data": {...}}. Only valid for UPDATE policies; NULL inherits the USING expression.'; + CREATE INDEX policy_table_id_idx ON metaschema_public.policy ( table_id ); CREATE INDEX policy_database_id_idx ON metaschema_public.policy ( database_id ); diff --git a/packages/metaschema-schema/deploy/schemas/metaschema_public/tables/table/table.sql b/packages/metaschema-schema/deploy/schemas/metaschema_public/tables/table/table.sql index e1412db2a..179ecb053 100644 --- a/packages/metaschema-schema/deploy/schemas/metaschema_public/tables/table/table.sql +++ b/packages/metaschema-schema/deploy/schemas/metaschema_public/tables/table/table.sql @@ -3,6 +3,7 @@ -- requires: schemas/metaschema_public/tables/database/table -- requires: schemas/metaschema_public/tables/schema/table -- requires: schemas/metaschema_public/types/object_category +-- requires: schemas/metaschema_private/procedures/is_valid_step_up BEGIN; @@ -31,6 +32,8 @@ CREATE TABLE metaschema_public.table ( tags citext[] NOT NULL DEFAULT '{}', + step_up jsonb DEFAULT NULL, + partitioned boolean NOT NULL DEFAULT false, partition_strategy text DEFAULT NULL, partition_key_names text[] DEFAULT NULL, @@ -42,9 +45,16 @@ CREATE TABLE metaschema_public.table ( CONSTRAINT db_fkey FOREIGN KEY (database_id) REFERENCES metaschema_public.database (id) ON DELETE CASCADE, CONSTRAINT schema_fkey FOREIGN KEY (schema_id) REFERENCES metaschema_public.schema (id) ON DELETE CASCADE, - UNIQUE (database_id, schema_id, name) + UNIQUE (database_id, schema_id, name), + + CONSTRAINT table_step_up_check CHECK ( + step_up IS NULL OR metaschema_private.is_valid_step_up(step_up) + ) ); +COMMENT ON COLUMN metaschema_public.table.step_up IS + 'Declarative step-up auth guard: jsonb object mapping DML verbs (INSERT, UPDATE, DELETE) to a step-up spec. Values: true (default password_or_mfa), a type string (password / mfa / password_or_mfa), or an object {type, min_age, min_age_lookup, conditions} where min_age is an interval string (e.g. 6 hours) gating the guard to rows older than that age (UPDATE/DELETE only), min_age_lookup resolves per-row windows from a lookup table, and conditions is a declarative WHEN-clause tree compiled by build_condition_expr.'; + ALTER TABLE metaschema_public.table ADD COLUMN inherits_id uuid NULL REFERENCES metaschema_public.table(id); diff --git a/packages/metaschema-schema/pgpm.plan b/packages/metaschema-schema/pgpm.plan index d5da66f68..16b9661a9 100644 --- a/packages/metaschema-schema/pgpm.plan +++ b/packages/metaschema-schema/pgpm.plan @@ -1,14 +1,15 @@ %syntax-version=1.0.0 %project=metaschema-schema %uri=metaschema-schema - + schemas/metaschema_private/schema [pgpm-inflection:schemas/inflection/tables/inflection_rules/indexes/inflection_rules_type_idx pgpm-database-jobs:schemas/app_jobs/triggers/tg_add_job_with_row pgpm-types:schemas/public/domains/url] 2017-08-11T08:11:51Z skitch # add schemas/metaschema_private/schema schemas/metaschema_public/schema 2017-08-11T08:11:51Z skitch # add schemas/metaschema_public/schema +schemas/metaschema_private/procedures/is_valid_step_up [schemas/metaschema_private/schema] 2026-07-08T00:00:00Z devin # add validator for declarative step_up field on metaschema_public.table schemas/metaschema_public/types/object_category [schemas/metaschema_public/schema] 2017-08-11T08:11:51Z skitch # add schemas/metaschema_public/types/object_category schemas/metaschema_public/types/api_exposure_level [schemas/metaschema_public/schema] 2026-06-18T00:00:00Z devin # add api_exposure_level enum type (exposable, internal_only, never_expose) schemas/metaschema_public/tables/database/table [schemas/metaschema_public/schema] 2017-08-11T08:11:51Z skitch # add schemas/metaschema_public/tables/database/table schemas/metaschema_public/tables/schema/table [schemas/metaschema_public/schema schemas/metaschema_public/tables/database/table schemas/metaschema_public/types/object_category schemas/metaschema_public/types/api_exposure_level] 2017-08-11T08:11:51Z skitch # add schemas/metaschema_public/tables/schema/table -schemas/metaschema_public/tables/table/table [schemas/metaschema_public/schema schemas/metaschema_public/tables/database/table schemas/metaschema_public/tables/schema/table schemas/metaschema_public/types/object_category] 2017-08-11T08:11:51Z skitch # add schemas/metaschema_public/tables/table/table +schemas/metaschema_public/tables/table/table [schemas/metaschema_public/schema schemas/metaschema_public/tables/database/table schemas/metaschema_public/tables/schema/table schemas/metaschema_public/types/object_category schemas/metaschema_private/procedures/is_valid_step_up] 2017-08-11T08:11:51Z skitch # add schemas/metaschema_public/tables/table/table schemas/metaschema_public/tables/check_constraint/table [schemas/metaschema_public/schema schemas/metaschema_public/tables/database/table schemas/metaschema_public/tables/table/table] 2017-08-11T08:11:51Z skitch # add schemas/metaschema_public/tables/check_constraint/table schemas/metaschema_public/tables/database/indexes/databases_database_unique_name_idx [schemas/metaschema_private/schema schemas/metaschema_public/schema schemas/metaschema_public/tables/database/table] 2017-08-11T08:11:51Z skitch # add schemas/metaschema_public/tables/database/indexes/databases_database_unique_name_idx schemas/metaschema_public/tables/field/table [schemas/metaschema_public/schema schemas/metaschema_public/tables/table/table] 2017-08-11T08:11:51Z skitch # add schemas/metaschema_public/tables/field/table @@ -31,7 +32,6 @@ schemas/metaschema_public/tables/view_rule/table [schemas/metaschema_public/sche schemas/metaschema_public/tables/default_privilege/table [schemas/metaschema_public/schema schemas/metaschema_public/tables/schema/table schemas/metaschema_public/tables/database/table] 2026-02-27T00:00:00Z Constructive # add schemas/metaschema_public/tables/default_privilege/table schemas/metaschema_public/tables/enum/table [schemas/metaschema_public/schema schemas/metaschema_public/tables/database/table schemas/metaschema_public/tables/schema/table schemas/metaschema_public/types/object_category] 2026-03-15T00:00:00Z devin # add schemas/metaschema_public/tables/enum/table schemas/metaschema_public/tables/embedding_chunks/table [schemas/metaschema_public/schema schemas/metaschema_public/tables/database/table schemas/metaschema_public/tables/table/table schemas/metaschema_public/tables/field/table] 2026-03-19T00:00:00Z devin # add schemas/metaschema_public/tables/embedding_chunks/table - schemas/metaschema_public/tables/spatial_relation/table [schemas/metaschema_public/schema schemas/metaschema_public/tables/database/table schemas/metaschema_public/tables/table/table schemas/metaschema_public/tables/field/table schemas/metaschema_public/types/object_category] 2026-04-17T00:00:00Z devin # add schemas/metaschema_public/tables/spatial_relation/table schemas/metaschema_public/tables/node_type_registry/table [schemas/metaschema_public/schema] 2026-04-30T00:00:00Z Constructive # add schemas/metaschema_public/tables/node_type_registry/table schemas/metaschema_public/tables/function/table [schemas/metaschema_public/schema schemas/metaschema_public/tables/database/table schemas/metaschema_public/tables/schema/table] 2026-05-09T00:00:00Z devin # add metaschema_public.function table for tracking generated SQL functions diff --git a/packages/metaschema-schema/revert/schemas/metaschema_private/procedures/is_valid_step_up.sql b/packages/metaschema-schema/revert/schemas/metaschema_private/procedures/is_valid_step_up.sql new file mode 100644 index 000000000..95ed5e4be --- /dev/null +++ b/packages/metaschema-schema/revert/schemas/metaschema_private/procedures/is_valid_step_up.sql @@ -0,0 +1,9 @@ +-- Revert schemas/metaschema_private/procedures/is_valid_step_up from pg + +BEGIN; + +DROP FUNCTION metaschema_private.is_valid_step_up; + +DROP FUNCTION metaschema_private.is_valid_step_up_conditions; + +COMMIT; diff --git a/packages/metaschema-schema/sql/metaschema-schema--0.31.0.sql b/packages/metaschema-schema/sql/metaschema-schema--0.31.0.sql index 21d914d13..1f095d846 100644 --- a/packages/metaschema-schema/sql/metaschema-schema--0.31.0.sql +++ b/packages/metaschema-schema/sql/metaschema-schema--0.31.0.sql @@ -25,6 +25,258 @@ ALTER DEFAULT PRIVILEGES IN SCHEMA metaschema_public ALTER DEFAULT PRIVILEGES IN SCHEMA metaschema_public GRANT ALL ON FUNCTIONS TO authenticated; +CREATE FUNCTION metaschema_private.is_valid_step_up_conditions( + cond jsonb +) RETURNS boolean AS $EOFCODE$ +DECLARE + -- node iteration + v_i int; + + -- leaf validation + v_key text; + v_op text; + + -- ref validation (column-to-column comparison) + v_ref jsonb; + v_ref_key text; +BEGIN + IF cond IS NULL THEN + RETURN false; + END IF; + + -- Array: implicit AND of all elements + IF jsonb_typeof(cond) = 'array' THEN + IF jsonb_array_length(cond) = 0 THEN + RETURN false; + END IF; + FOR v_i IN 0..jsonb_array_length(cond) - 1 LOOP + IF NOT metaschema_private.is_valid_step_up_conditions(cond -> v_i) THEN + RETURN false; + END IF; + END LOOP; + RETURN true; + END IF; + + IF jsonb_typeof(cond) != 'object' OR cond = '{}'::jsonb THEN + RETURN false; + END IF; + + -- Combinator object: exactly one of AND / OR / NOT + IF cond ? 'AND' OR cond ? 'OR' OR cond ? 'NOT' THEN + IF (SELECT count(*) FROM jsonb_object_keys(cond)) != 1 THEN + RETURN false; + END IF; + IF cond ? 'NOT' THEN + RETURN metaschema_private.is_valid_step_up_conditions(cond -> 'NOT'); + END IF; + IF jsonb_typeof(COALESCE(cond -> 'AND', cond -> 'OR')) != 'array' THEN + RETURN false; + END IF; + RETURN metaschema_private.is_valid_step_up_conditions(COALESCE(cond -> 'AND', cond -> 'OR')); + END IF; + + -- Leaf condition: {field, op, value?, row?, ref?} + FOR v_key IN SELECT key FROM jsonb_each(cond) LOOP + IF v_key NOT IN ('field', 'op', 'value', 'row', 'ref') THEN + RETURN false; + END IF; + END LOOP; + + IF jsonb_typeof(cond -> 'field') IS DISTINCT FROM 'string' + OR jsonb_typeof(cond -> 'op') IS DISTINCT FROM 'string' THEN + RETURN false; + END IF; + + v_op := upper(cond ->> 'op'); + IF v_op NOT IN ('=', '!=', '>', '<', '>=', '<=', 'LIKE', 'NOT LIKE', + 'IS NULL', 'IS NOT NULL', 'IS DISTINCT FROM') THEN + RETURN false; + END IF; + + IF cond ? 'row' THEN + IF jsonb_typeof(cond -> 'row') != 'string' + OR upper(cond ->> 'row') NOT IN ('NEW', 'OLD') THEN + RETURN false; + END IF; + END IF; + + -- Operators without a right-hand side + IF v_op IN ('IS NULL', 'IS NOT NULL', 'IS DISTINCT FROM') THEN + IF cond ? 'value' OR cond ? 'ref' THEN + RETURN false; + END IF; + RETURN true; + END IF; + + -- Comparison operators require exactly one of value / ref + IF (cond ? 'value') = (cond ? 'ref') THEN + RETURN false; + END IF; + + IF cond ? 'value' THEN + IF jsonb_typeof(cond -> 'value') NOT IN ('string', 'number', 'boolean') THEN + RETURN false; + END IF; + RETURN true; + END IF; + + v_ref := cond -> 'ref'; + IF jsonb_typeof(v_ref) != 'object' THEN + RETURN false; + END IF; + FOR v_ref_key IN SELECT key FROM jsonb_each(v_ref) LOOP + IF v_ref_key NOT IN ('field', 'row') THEN + RETURN false; + END IF; + END LOOP; + IF jsonb_typeof(v_ref -> 'field') IS DISTINCT FROM 'string' THEN + RETURN false; + END IF; + IF v_ref ? 'row' THEN + IF jsonb_typeof(v_ref -> 'row') != 'string' + OR upper(v_ref ->> 'row') NOT IN ('NEW', 'OLD') THEN + RETURN false; + END IF; + END IF; + + RETURN true; +END; +$EOFCODE$ LANGUAGE plpgsql IMMUTABLE; + +CREATE FUNCTION metaschema_private.is_valid_step_up( + step_up jsonb +) RETURNS boolean AS $EOFCODE$ +DECLARE + -- entry iteration + v_key text; + v_value jsonb; + + -- object value validation + v_obj_key text; + v_type jsonb; + v_min_age jsonb; + v_min_age_interval interval; + + -- min_age_lookup validation (per-row lookup windows) + v_min_age_lookup jsonb; + v_lookup_key text; + v_lookup_table_id uuid; + + -- conditions validation (declarative WHEN-clause tree) + v_conditions jsonb; +BEGIN + IF step_up IS NULL THEN + RETURN false; + END IF; + + IF jsonb_typeof(step_up) != 'object' THEN + RETURN false; + END IF; + + IF step_up = '{}'::jsonb THEN + RETURN false; + END IF; + + FOR v_key, v_value IN SELECT key, value FROM jsonb_each(step_up) LOOP + IF v_key NOT IN ('INSERT', 'UPDATE', 'DELETE') THEN + RETURN false; + END IF; + + IF jsonb_typeof(v_value) = 'boolean' THEN + IF v_value = 'false'::jsonb THEN + RETURN false; + END IF; + ELSIF jsonb_typeof(v_value) = 'string' THEN + IF v_value #>> '{}' NOT IN ('password', 'mfa', 'password_or_mfa') THEN + RETURN false; + END IF; + ELSIF jsonb_typeof(v_value) = 'object' THEN + IF v_value = '{}'::jsonb THEN + RETURN false; + END IF; + + FOR v_obj_key IN SELECT key FROM jsonb_each(v_value) LOOP + IF v_obj_key NOT IN ('type', 'min_age', 'min_age_lookup', 'conditions') THEN + RETURN false; + END IF; + END LOOP; + + v_type := v_value -> 'type'; + IF v_type IS NOT NULL THEN + IF jsonb_typeof(v_type) != 'string' + OR v_type #>> '{}' NOT IN ('password', 'mfa', 'password_or_mfa') THEN + RETURN false; + END IF; + END IF; + + v_min_age := v_value -> 'min_age'; + IF v_min_age IS NOT NULL THEN + -- min_age is meaningless for INSERT: a new row has no age + IF v_key = 'INSERT' THEN + RETURN false; + END IF; + + IF jsonb_typeof(v_min_age) != 'string' THEN + RETURN false; + END IF; + + BEGIN + v_min_age_interval := (v_min_age #>> '{}')::interval; + EXCEPTION WHEN OTHERS THEN + RETURN false; + END; + + IF v_min_age_interval <= interval '0' THEN + RETURN false; + END IF; + END IF; + + v_min_age_lookup := v_value -> 'min_age_lookup'; + IF v_min_age_lookup IS NOT NULL THEN + -- lookup windows are meaningless for INSERT and need min_age + -- as the fallback default + IF v_key = 'INSERT' OR v_min_age IS NULL THEN + RETURN false; + END IF; + + IF jsonb_typeof(v_min_age_lookup) != 'object' THEN + RETURN false; + END IF; + + FOR v_lookup_key IN SELECT key FROM jsonb_each(v_min_age_lookup) LOOP + IF v_lookup_key NOT IN ('table_id', 'fk_field', 'min_age_field') THEN + RETURN false; + END IF; + END LOOP; + + IF jsonb_typeof(v_min_age_lookup -> 'table_id') IS DISTINCT FROM 'string' + OR jsonb_typeof(v_min_age_lookup -> 'fk_field') IS DISTINCT FROM 'string' + OR jsonb_typeof(v_min_age_lookup -> 'min_age_field') IS DISTINCT FROM 'string' THEN + RETURN false; + END IF; + + BEGIN + v_lookup_table_id := (v_min_age_lookup ->> 'table_id')::uuid; + EXCEPTION WHEN OTHERS THEN + RETURN false; + END; + END IF; + + v_conditions := v_value -> 'conditions'; + IF v_conditions IS NOT NULL THEN + IF NOT metaschema_private.is_valid_step_up_conditions(v_conditions) THEN + RETURN false; + END IF; + END IF; + ELSE + RETURN false; + END IF; + END LOOP; + + RETURN true; +END; +$EOFCODE$ LANGUAGE plpgsql IMMUTABLE; + CREATE TYPE metaschema_public.object_category AS ENUM ('core', 'module', 'permissions', 'auth', 'memberships', 'app'); CREATE TYPE metaschema_public.api_exposure_level AS ENUM ('exposable', 'internal_only', 'never_expose'); @@ -93,6 +345,7 @@ CREATE TABLE metaschema_public.table ( plural_name text, singular_name text, tags citext[] NOT NULL DEFAULT '{}', + step_up jsonb DEFAULT NULL, partitioned boolean NOT NULL DEFAULT false, partition_strategy text DEFAULT NULL, partition_key_names text[] DEFAULT NULL, @@ -107,9 +360,16 @@ CREATE TABLE metaschema_public.table ( FOREIGN KEY(schema_id) REFERENCES metaschema_public.schema (id) ON DELETE CASCADE, - UNIQUE (database_id, schema_id, name) + UNIQUE (database_id, schema_id, name), + CONSTRAINT table_step_up_check + CHECK ( + step_up IS NULL + OR metaschema_private.is_valid_step_up(step_up) + ) ); +COMMENT ON COLUMN metaschema_public."table".step_up IS 'Declarative step-up auth guard: jsonb object mapping DML verbs (INSERT, UPDATE, DELETE) to a step-up spec. Values: true (default password_or_mfa), a type string (password / mfa / password_or_mfa), or an object {type, min_age, min_age_lookup, conditions} where min_age is an interval string (e.g. 6 hours) gating the guard to rows older than that age (UPDATE/DELETE only), min_age_lookup resolves per-row windows from a lookup table, and conditions is a declarative WHEN-clause tree compiled by build_condition_expr.'; + ALTER TABLE metaschema_public.table ADD COLUMN inherits_id uuid NULL @@ -168,6 +428,8 @@ CREATE TABLE metaschema_public.field ( is_required boolean NOT NULL DEFAULT false, api_required boolean NOT NULL DEFAULT false, default_value jsonb NULL DEFAULT NULL, + generation_expression jsonb NULL DEFAULT NULL, + generation_type text NULL DEFAULT NULL, type jsonb NOT NULL, field_order int NOT NULL DEFAULT 0, regexp text DEFAULT NULL, @@ -199,6 +461,13 @@ CREATE UNIQUE INDEX databases_field_uniq_names_idx ON metaschema_public.field (t ELSE name END)), 'hex'))); +COMMENT ON INDEX metaschema_public.databases_field_uniq_names_idx IS 'Guards against PostGraphile/Graphile inflection collisions: a uuid FK field +(e.g. action_id) and a sibling text field (e.g. action) on the same table +inflect toward the same GraphQL property name. For uuid fields the suffix +(_row_id|_id|_uuid|_fk|_pk) is stripped before uniqueness-checking, so any +name-like text field sitting next to a uuid FK must be suffixed explicitly +(convention: use *_name, e.g. action_name, sessions_table_name).'; + CREATE TABLE metaschema_public.foreign_key_constraint ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL DEFAULT uuid_nil(), @@ -307,6 +576,7 @@ CREATE TABLE metaschema_public.policy ( disabled boolean DEFAULT false, policy_type text, data jsonb, + with_check jsonb, smart_tags jsonb, category metaschema_public.object_category NOT NULL DEFAULT 'app', tags citext[] NOT NULL DEFAULT '{}', @@ -320,9 +590,18 @@ CREATE TABLE metaschema_public.policy ( FOREIGN KEY(table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, + CONSTRAINT policy_with_check_shape + CHECK ( + with_check IS NULL + OR (jsonb_typeof(with_check) = 'object' + AND with_check ? '$type' + AND jsonb_typeof(with_check -> '$type') = 'string') + ), UNIQUE (table_id, name) ); +COMMENT ON COLUMN metaschema_public.policy.with_check IS 'Optional WITH CHECK override node {"$type": "Authz...", "data": {...}}. Only valid for UPDATE policies; NULL inherits the USING expression.'; + CREATE INDEX policy_table_id_idx ON metaschema_public.policy (table_id); CREATE INDEX policy_database_id_idx ON metaschema_public.policy (database_id); diff --git a/packages/metaschema-schema/verify/schemas/metaschema_private/procedures/is_valid_step_up.sql b/packages/metaschema-schema/verify/schemas/metaschema_private/procedures/is_valid_step_up.sql new file mode 100644 index 000000000..cf56d543b --- /dev/null +++ b/packages/metaschema-schema/verify/schemas/metaschema_private/procedures/is_valid_step_up.sql @@ -0,0 +1,9 @@ +-- Verify schemas/metaschema_private/procedures/is_valid_step_up on pg + +BEGIN; + +SELECT verify_function ('metaschema_private.is_valid_step_up'); + +SELECT verify_function ('metaschema_private.is_valid_step_up_conditions'); + +ROLLBACK; diff --git a/packages/metaschema-schema/verify/schemas/metaschema_public/tables/database/table.sql b/packages/metaschema-schema/verify/schemas/metaschema_public/tables/database/table.sql index 387734c86..2644ae259 100644 --- a/packages/metaschema-schema/verify/schemas/metaschema_public/tables/database/table.sql +++ b/packages/metaschema-schema/verify/schemas/metaschema_public/tables/database/table.sql @@ -2,6 +2,5 @@ BEGIN; SELECT verify_table ('metaschema_public.database'); -SELECT verify_index ('metaschema_public.database', 'databases_database_platform_singleton_idx'); ROLLBACK; diff --git a/packages/utils/deploy/schemas/utils/procedures/enforce_identity_providers_quota.sql b/packages/utils/deploy/schemas/utils/procedures/enforce_identity_providers_quota.sql index 3f225be4f..d70b1dc58 100644 --- a/packages/utils/deploy/schemas/utils/procedures/enforce_identity_providers_quota.sql +++ b/packages/utils/deploy/schemas/utils/procedures/enforce_identity_providers_quota.sql @@ -5,14 +5,13 @@ BEGIN; -- BEFORE INSERT trigger function enforcing the per-database cap on --- non-built-in identity_providers rows. Built-in providers (is_built_in = TRUE) --- are exempt from the quota. The quota value is read from the singleton +-- identity_providers rows. The quota value is read from the singleton -- app_settings_auth.identity_providers_max column. -- -- TG_ARGV[0] : app_settings_auth schema name -- TG_ARGV[1] : app_settings_auth table name -- --- Raises IDENTITY_PROVIDER_QUOTA_EXCEEDED when the non-built-in row count on +-- Raises IDENTITY_PROVIDER_QUOTA_EXCEEDED when the row count on -- the triggering table is already at or above the configured maximum. CREATE FUNCTION utils.enforce_identity_providers_quota() RETURNS TRIGGER @@ -24,11 +23,6 @@ DECLARE v_count int; BEGIN - -- Built-in providers are exempt from the quota. - IF NEW.is_built_in THEN - RETURN NEW; - END IF; - v_settings_schema = TG_ARGV[0]; v_settings_table = TG_ARGV[1]; @@ -40,7 +34,7 @@ BEGIN INTO v_max; EXECUTE format( - 'SELECT count(1) FROM %1$I.%2$I WHERE NOT is_built_in', + 'SELECT count(1) FROM %1$I.%2$I', TG_TABLE_SCHEMA, TG_TABLE_NAME ) diff --git a/packages/utils/sql/pgpm-utils--0.15.5.sql b/packages/utils/sql/pgpm-utils--0.15.5.sql index 55b48cf17..33b71f231 100644 --- a/packages/utils/sql/pgpm-utils--0.15.5.sql +++ b/packages/utils/sql/pgpm-utils--0.15.5.sql @@ -6,7 +6,11 @@ GRANT USAGE ON SCHEMA utils TO PUBLIC; ALTER DEFAULT PRIVILEGES IN SCHEMA utils GRANT EXECUTE ON FUNCTIONS TO PUBLIC; -CREATE FUNCTION utils.mask_pad(bitstr text, bitlen int, pad text DEFAULT '0') RETURNS text AS $EOFCODE$ +CREATE FUNCTION utils.mask_pad( + bitstr text, + bitlen int, + pad text DEFAULT '0' +) RETURNS text AS $EOFCODE$ SELECT ( CASE WHEN length(bitstr) > bitlen THEN @@ -17,7 +21,11 @@ CREATE FUNCTION utils.mask_pad(bitstr text, bitlen int, pad text DEFAULT '0') RE END) $EOFCODE$ LANGUAGE sql; -CREATE FUNCTION utils.bitmask_pad(bitstr pg_catalog.varbit, bitlen int, pad text DEFAULT '0') RETURNS pg_catalog.varbit AS $EOFCODE$ +CREATE FUNCTION utils.bitmask_pad( + bitstr pg_catalog.varbit, + bitlen int, + pad text DEFAULT '0' +) RETURNS pg_catalog.varbit AS $EOFCODE$ SELECT ( CASE WHEN length(bitstr) > bitlen THEN @@ -71,11 +79,6 @@ DECLARE v_count int; BEGIN - -- Built-in providers are exempt from the quota. - IF NEW.is_built_in THEN - RETURN NEW; - END IF; - v_settings_schema = TG_ARGV[0]; v_settings_table = TG_ARGV[1]; @@ -87,7 +90,7 @@ BEGIN INTO v_max; EXECUTE format( - 'SELECT count(1) FROM %1$I.%2$I WHERE NOT is_built_in', + 'SELECT count(1) FROM %1$I.%2$I', TG_TABLE_SCHEMA, TG_TABLE_NAME ) diff --git a/packages/uuid/LICENSE b/packages/uuid/LICENSE new file mode 100644 index 000000000..7b18c9183 --- /dev/null +++ b/packages/uuid/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2025 Dan Lynch +Copyright (c) 2025 Constructive + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/uuid/Makefile b/packages/uuid/Makefile new file mode 100644 index 000000000..f4db01043 --- /dev/null +++ b/packages/uuid/Makefile @@ -0,0 +1,6 @@ +EXTENSION = pgpm-uuid +DATA = sql/pgpm-uuid--0.15.4.sql + +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) diff --git a/packages/uuid/README.md b/packages/uuid/README.md new file mode 100644 index 000000000..004946d6a --- /dev/null +++ b/packages/uuid/README.md @@ -0,0 +1,288 @@ +# @pgpm/uuid + +

+ +

+ +

+ + + + + +

+ +UUID utilities and extensions for PostgreSQL. + +## Overview + +`@pgpm/uuid` provides PostgreSQL functions for generating pseudo-ordered UUIDs that maintain some temporal ordering while preserving UUID randomness. This package is particularly useful for multi-tenant applications where you want UUIDs that cluster by tenant or time period for better database performance. + +## Features + +- **pseudo_order_uuid()**: Generates time-influenced UUIDs for better index performance +- **pseudo_order_seed_uuid(seed)**: Generates UUIDs with a seed prefix for multi-tenant scenarios +- **trigger_set_uuid_seed**: Trigger function to automatically set UUID from a seed column +- **trigger_set_uuid_related_field**: Trigger function to set UUID based on another column value +- Version 4 UUID format compliance + +## Installation + +If you have `pgpm` installed: + +```bash +pgpm install @pgpm/uuid +pgpm deploy +``` + +This is a quick way to get started. The sections below provide more detailed installation options. + +### Prerequisites + +```bash +# Install pgpm CLI +npm install -g pgpm + +# Start local Postgres (via Docker) and export env vars +pgpm docker start +eval "$(pgpm env)" +``` + +> **Tip:** Already running Postgres? Skip the Docker step and just export your `PG*` environment variables. + +### **Add to an Existing Package** + +```bash +# 1. Install the package +pgpm install @pgpm/uuid + +# 2. Deploy locally +pgpm deploy +``` + +### **Add to a New Project** + +```bash +# 1. Create a workspace +pgpm init workspace + +# 2. Create your first module +cd my-workspace +pgpm init + +# 3. Install a package +cd packages/my-module +pgpm install @pgpm/uuid + +# 4. Deploy everything +pgpm deploy --createdb --database mydb1 +``` + +## Usage + +### Basic UUID Generation + +```sql +-- Generate a pseudo-ordered UUID +SELECT uuids.pseudo_order_uuid(); +-- Returns: e.g., '3a7f-8b2c-4d1e-9f3a-1b2c3d4e5f6a' + +-- Use as default value in a table +CREATE TABLE items ( + id UUID DEFAULT uuids.pseudo_order_uuid() PRIMARY KEY, + name TEXT +); +``` + +### Seeded UUID Generation (Multi-Tenant) + +```sql +-- Generate UUID with tenant seed +SELECT uuids.pseudo_order_seed_uuid('tenant-123'); +-- UUIDs for the same tenant will share a common prefix + +-- Useful for multi-tenant applications +CREATE TABLE tenant_data ( + id UUID DEFAULT uuids.pseudo_order_seed_uuid('default-tenant'), + tenant_id TEXT, + data JSONB +); +``` + +### Automatic UUID Setting with Triggers + +#### Set UUID from Seed Column + +```sql +CREATE TABLE items ( + id UUID, + name TEXT, + tenant_id TEXT, + custom_uuid UUID +); + +-- Automatically set custom_uuid based on tenant_id +CREATE TRIGGER set_custom_uuid +BEFORE INSERT ON items +FOR EACH ROW +EXECUTE FUNCTION uuids.trigger_set_uuid_seed('custom_uuid', 'tenant_id'); + +-- Insert will automatically generate custom_uuid from tenant_id +INSERT INTO items (name, tenant_id) VALUES ('Item 1', 'tenant-abc'); +``` + +#### Set UUID from Related Field + +```sql +CREATE TABLE tenant_records ( + id UUID, + tenant TEXT +); + +-- Automatically set id based on tenant column +CREATE TRIGGER set_id_from_tenant +BEFORE INSERT ON tenant_records +FOR EACH ROW +EXECUTE FUNCTION uuids.trigger_set_uuid_related_field('id', 'tenant'); + +-- Insert will automatically generate id from tenant value +INSERT INTO tenant_records (tenant) VALUES ('tenant-xyz'); +``` + +## Functions + +### uuids.pseudo_order_uuid() + +Generates a pseudo-ordered UUID that incorporates temporal information for better index performance. + +**Returns**: `uuid` + +**Algorithm**: +- First 4 characters: MD5 hash of current year + week number (provides weekly clustering) +- Remaining characters: Random values with timestamp influence +- Format: Version 4 UUID compliant + +**Benefits**: +- Better B-tree index performance compared to random UUIDs +- Reduces index fragmentation +- Maintains UUID uniqueness guarantees + +### uuids.pseudo_order_seed_uuid(seed text) + +Generates a pseudo-ordered UUID with a seed prefix for multi-tenant scenarios. + +**Parameters**: +- `seed` (text): Seed value (typically tenant ID or organization ID) + +**Returns**: `uuid` + +**Algorithm**: +- First 2 characters: MD5 hash of seed (provides tenant clustering) +- Next 2 characters: MD5 hash of year + week +- Remaining characters: Random values with timestamp influence +- Format: Version 4 UUID compliant + +**Benefits**: +- UUIDs for same tenant cluster together in indexes +- Improves query performance for tenant-specific queries +- Maintains uniqueness across tenants + +### uuids.trigger_set_uuid_seed(uuid_column, seed_column) + +Trigger function that automatically sets a UUID column based on a seed column value. + +**Parameters**: +- `uuid_column` (text): Name of the UUID column to set +- `seed_column` (text): Name of the column containing the seed value + +**Usage**: +```sql +CREATE TRIGGER trigger_name +BEFORE INSERT ON table_name +FOR EACH ROW +EXECUTE FUNCTION uuids.trigger_set_uuid_seed('id_column', 'seed_column'); +``` + +### uuids.trigger_set_uuid_related_field(uuid_column, related_column) + +Trigger function that automatically sets a UUID column based on another column's value. + +**Parameters**: +- `uuid_column` (text): Name of the UUID column to set +- `related_column` (text): Name of the column to use as seed + +**Usage**: +```sql +CREATE TRIGGER trigger_name +BEFORE INSERT ON table_name +FOR EACH ROW +EXECUTE FUNCTION uuids.trigger_set_uuid_related_field('id_column', 'related_column'); +``` + +## Use Cases + +### Multi-Tenant Applications + +```sql +CREATE TABLE tenant_users ( + id UUID, + tenant_id TEXT NOT NULL, + email TEXT NOT NULL, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE TRIGGER set_user_id +BEFORE INSERT ON tenant_users +FOR EACH ROW +EXECUTE FUNCTION uuids.trigger_set_uuid_seed('id', 'tenant_id'); + +-- All users for tenant 'acme' will have UUIDs with same prefix +INSERT INTO tenant_users (tenant_id, email) VALUES + ('acme', 'user1@acme.com'), + ('acme', 'user2@acme.com'); +``` + +### Time-Series Data with Better Index Performance + +```sql +CREATE TABLE events ( + id UUID DEFAULT uuids.pseudo_order_uuid() PRIMARY KEY, + event_type TEXT, + payload JSONB, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +-- UUIDs will cluster by week, improving range query performance +``` + +## Dependencies + +- `@pgpm/verify`: Verification utilities for database objects + +## Testing + +```bash +pnpm test +``` + +The test suite validates: +- UUID format compliance (version 4) +- Pseudo-ordered UUID generation +- Seeded UUID generation with consistent prefixes +- Trigger functions for automatic UUID setting + +## Related Tooling + +* [pgpm](https://github.com/constructive-io/constructive/tree/main/packages/pgpm): **🖥️ PostgreSQL Package Manager** for modular Postgres development. Works with database workspaces, scaffolding, migrations, seeding, and installing database packages. +* [pgsql-test](https://github.com/constructive-io/constructive/tree/main/packages/pgsql-test): **📊 Isolated testing environments** with per-test transaction rollbacks—ideal for integration tests, complex migrations, and RLS simulation. +* [supabase-test](https://github.com/constructive-io/constructive/tree/main/packages/supabase-test): **🧪 Supabase-native test harness** preconfigured for the local Supabase stack—per-test rollbacks, JWT/role context helpers, and CI/GitHub Actions ready. +* [graphile-test](https://github.com/constructive-io/constructive/tree/main/packages/graphile-test): **🔐 Authentication mocking** for Graphile-focused test helpers and emulating row-level security contexts. +* [pgsql-parser](https://github.com/constructive-io/pgsql-parser): **🔄 SQL conversion engine** that interprets and converts PostgreSQL syntax. +* [libpg-query-node](https://github.com/constructive-io/libpg-query-node): **🌉 Node.js bindings** for `libpg_query`, converting SQL into parse trees. +* [pg-proto-parser](https://github.com/constructive-io/pg-proto-parser): **📦 Protobuf parser** for parsing PostgreSQL Protocol Buffers definitions to generate TypeScript interfaces, utility functions, and JSON mappings for enums. + +## Disclaimer + +AS DESCRIBED IN THE LICENSES, THE SOFTWARE IS PROVIDED "AS IS", AT YOUR OWN RISK, AND WITHOUT WARRANTIES OF ANY KIND. + +No developer or entity involved in creating this software will be liable for any claims or damages whatsoever associated with your use, inability to use, or your interaction with other users of the code, including any direct, indirect, incidental, special, exemplary, punitive or consequential damages, or loss of profits, cryptocurrencies, tokens, or anything else of value. diff --git a/packages/uuid/deploy/extension/defaults.sql b/packages/uuid/deploy/extension/defaults.sql new file mode 100644 index 000000000..342d0fae5 --- /dev/null +++ b/packages/uuid/deploy/extension/defaults.sql @@ -0,0 +1,8 @@ +-- Deploy extension/defaults to pg + +BEGIN; + +-- hstore +GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public to public; + +COMMIT; diff --git a/packages/uuid/deploy/schemas/uuids/procedures/pseudo_order_seed_uuid.sql b/packages/uuid/deploy/schemas/uuids/procedures/pseudo_order_seed_uuid.sql new file mode 100644 index 000000000..2d8be89fc --- /dev/null +++ b/packages/uuid/deploy/schemas/uuids/procedures/pseudo_order_seed_uuid.sql @@ -0,0 +1,50 @@ +-- Deploy schemas/uuids/procedures/pseudo_order_seed_uuid to pg + +-- requires: schemas/uuids/schema + +-- The first 2 characters of the UUID value comes from +-- the MD5 hash of the uuid_seed. The next +-- 2 characters are the MD5 hash of the concatenation +-- of the current year and week number. This value is, +-- of course, static over a week. The remaining of the +-- UUID value comes from the MD5 of a random value and +-- the current time at a precision of 1us. The third field +-- is prefixed with a “4” to indicate it is a version 4 UUID type. + +BEGIN; + +CREATE FUNCTION uuids.pseudo_order_seed_uuid( + seed text +) + RETURNS uuid +AS $$ +DECLARE + new_uuid char(36); + md5_str char(32); + md5_str2 char(32); + uid text; +BEGIN + md5_str := md5(concat(random(), now())); + md5_str2 := md5(concat(random(), now())); + + new_uuid := concat( + LEFT (md5(seed), 2), + LEFT (md5(concat(extract(year FROM now()), extract(week FROM now()))), 2), + substring(md5_str, 1, 4), + '-', + substring(md5_str, 5, 4), + '-4', + substring(md5_str2, 9, 3), + '-', + substring(md5_str, 13, 4), + '-', + substring(md5_str2, 17, 12) + ); + RETURN new_uuid; +END; +$$ +LANGUAGE plpgsql VOLATILE; + +COMMENT ON FUNCTION uuids.pseudo_order_seed_uuid IS 'Pseudo Ordered UUID with a seed. Good for multi-tenant scenarios, other wise use non-seed.'; + +COMMIT; diff --git a/packages/uuid/deploy/schemas/uuids/procedures/pseudo_order_uuid.sql b/packages/uuid/deploy/schemas/uuids/procedures/pseudo_order_uuid.sql new file mode 100644 index 000000000..920711f21 --- /dev/null +++ b/packages/uuid/deploy/schemas/uuids/procedures/pseudo_order_uuid.sql @@ -0,0 +1,27 @@ +-- Deploy schemas/uuids/procedures/pseudo_order_uuid to pg + +-- requires: schemas/uuids/schema + +BEGIN; + +CREATE FUNCTION uuids.pseudo_order_uuid() + RETURNS uuid +AS $$ +DECLARE + new_uuid char(36); + md5_str char(32); + md5_str2 char(32); +BEGIN + md5_str := md5(concat(random(), now())); + md5_str2 := md5(concat(random(), now())); + new_uuid := concat( + LEFT (md5(concat(extract(year FROM now()), extract(week FROM now()))), 4), + LEFT (md5_str, 4), '-', substring(md5_str, 5, 4), '-4', substring(md5_str2, 9, 3), '-', substring(md5_str, 13, 4), '-', substring(md5_str2, 17, 12)); + RETURN new_uuid; +END; +$$ +LANGUAGE plpgsql VOLATILE; + +COMMENT ON FUNCTION uuids.pseudo_order_seed_uuid IS 'Pseudo Ordered UUID'; + +COMMIT; diff --git a/packages/uuid/deploy/schemas/uuids/procedures/trigger_set_uuid_related_field.sql b/packages/uuid/deploy/schemas/uuids/procedures/trigger_set_uuid_related_field.sql new file mode 100644 index 000000000..f49e335e4 --- /dev/null +++ b/packages/uuid/deploy/schemas/uuids/procedures/trigger_set_uuid_related_field.sql @@ -0,0 +1,29 @@ +-- Deploy schemas/uuids/procedures/trigger_set_uuid_related_field to pg + +-- requires: schemas/uuids/schema +-- requires: schemas/uuids/procedures/pseudo_order_seed_uuid + +BEGIN; + +-- https://dba.stackexchange.com/questions/61271/how-to-access-new-or-old-field-given-only-the-fields-name +-- https://dba.stackexchange.com/questions/82039/assign-to-new-by-key-in-a-postgres-trigger/82044#82044 +-- https://stackoverflow.com/questions/7711432/how-to-set-value-of-composite-variable-field-using-dynamic-sql + +-- usage CREATE TRIGGER ... +-- EXECUTE PROCEDURE uuids.trigger_set_uuid_seed ('id', 'database_id'); + +CREATE FUNCTION uuids.trigger_set_uuid_related_field() +RETURNS TRIGGER AS $$ +DECLARE + _seed_column text := to_json(NEW) ->> TG_ARGV[1]; +BEGIN + IF _seed_column IS NULL THEN + RAISE EXCEPTION 'UUID seed is NULL on table %', TG_TABLE_NAME; + END IF; + NEW := NEW #= (TG_ARGV[0] || '=>' || uuids.pseudo_order_seed_uuid(_seed_column))::hstore; + RETURN NEW; +END; +$$ +LANGUAGE 'plpgsql' VOLATILE; + +COMMIT; diff --git a/packages/uuid/deploy/schemas/uuids/procedures/trigger_set_uuid_seed.sql b/packages/uuid/deploy/schemas/uuids/procedures/trigger_set_uuid_seed.sql new file mode 100644 index 000000000..85b5741d3 --- /dev/null +++ b/packages/uuid/deploy/schemas/uuids/procedures/trigger_set_uuid_seed.sql @@ -0,0 +1,24 @@ +-- Deploy schemas/uuids/procedures/trigger_set_uuid_seed to pg + +-- requires: schemas/uuids/schema +-- requires: schemas/uuids/procedures/pseudo_order_seed_uuid + +BEGIN; + +-- https://dba.stackexchange.com/questions/61271/how-to-access-new-or-old-field-given-only-the-fields-name +-- https://dba.stackexchange.com/questions/82039/assign-to-new-by-key-in-a-postgres-trigger/82044#82044 +-- https://stackoverflow.com/questions/7711432/how-to-set-value-of-composite-variable-field-using-dynamic-sql + +-- usage CREATE TRIGGER ... +-- EXECUTE PROCEDURE uuids.trigger_set_uuid_seed ('id', '41c33217-a5e5-46d1-c8db-62a563cba3af'); + +CREATE FUNCTION uuids.trigger_set_uuid_seed() +RETURNS TRIGGER AS $$ +BEGIN + NEW := NEW #= (TG_ARGV[0] || '=>' || uuids.pseudo_order_seed_uuid(TG_ARGV[1]))::hstore; + RETURN NEW; +END; +$$ +LANGUAGE 'plpgsql' VOLATILE; + +COMMIT; diff --git a/packages/uuid/deploy/schemas/uuids/schema.sql b/packages/uuid/deploy/schemas/uuids/schema.sql new file mode 100644 index 000000000..992cc782d --- /dev/null +++ b/packages/uuid/deploy/schemas/uuids/schema.sql @@ -0,0 +1,16 @@ +-- Deploy schemas/uuids/schema to pg + + +BEGIN; + +CREATE SCHEMA uuids; + +GRANT USAGE ON SCHEMA uuids +TO public; + +ALTER DEFAULT PRIVILEGES +IN SCHEMA uuids +GRANT EXECUTE ON FUNCTIONS +TO public; + +COMMIT; diff --git a/packages/uuid/jest.config.js b/packages/uuid/jest.config.js new file mode 100644 index 000000000..e20e7efb5 --- /dev/null +++ b/packages/uuid/jest.config.js @@ -0,0 +1,15 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + + // Match both __tests__ and colocated test files + testMatch: ['**/?(*.)+(test|spec).{ts,tsx,js,jsx}'], + + // Ignore build artifacts and type declarations + testPathIgnorePatterns: ['/dist/', '\\.d\\.ts$'], + modulePathIgnorePatterns: ['/dist/'], + watchPathIgnorePatterns: ['/dist/'], + + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], +}; diff --git a/packages/uuid/package.json b/packages/uuid/package.json new file mode 100644 index 000000000..91166706c --- /dev/null +++ b/packages/uuid/package.json @@ -0,0 +1,37 @@ +{ + "name": "@pgpm/uuid", + "version": "0.32.1", + "description": "UUID utilities and extensions for PostgreSQL", + "author": "Dan Lynch ", + "contributors": [ + "Constructive " + ], + "keywords": [ + "postgresql", + "pgpm", + "uuid", + "identifiers" + ], + "publishConfig": { + "access": "public" + }, + "scripts": { + "bundle": "pgpm package", + "test": "jest", + "test:watch": "jest --watch" + }, + "dependencies": { + "@pgpm/verify": "workspace:*" + }, + "devDependencies": { + "pgpm": "^4.28.7" + }, + "repository": { + "type": "git", + "url": "https://github.com/constructive-io/pgpm-modules" + }, + "homepage": "https://github.com/constructive-io/pgpm-modules", + "bugs": { + "url": "https://github.com/constructive-io/pgpm-modules/issues" + } +} diff --git a/packages/uuid/pgpm-uuid.control b/packages/uuid/pgpm-uuid.control new file mode 100644 index 000000000..7527344ee --- /dev/null +++ b/packages/uuid/pgpm-uuid.control @@ -0,0 +1,8 @@ +# pgpm-uuid extension +comment = 'pgpm-uuid extension' +default_version = '0.15.4' +module_pathname = '$libdir/pgpm-uuid' +requires = 'pgcrypto,plpgsql,uuid-ossp,hstore,pgpm-verify' +relocatable = false +superuser = false + \ No newline at end of file diff --git a/packages/uuid/pgpm.plan b/packages/uuid/pgpm.plan new file mode 100644 index 000000000..398203767 --- /dev/null +++ b/packages/uuid/pgpm.plan @@ -0,0 +1,9 @@ +%syntax-version=1.0.0 +%project=pgpm-uuid +%uri=pgpm-uuid + +schemas/uuids/schema 2017-08-11T08:11:51Z skitch # add schemas/uuids/schema +schemas/uuids/procedures/pseudo_order_seed_uuid [schemas/uuids/schema] 2017-08-11T08:11:51Z skitch # add schemas/uuids/procedures/pseudo_order_seed_uuid +schemas/uuids/procedures/pseudo_order_uuid [schemas/uuids/schema] 2017-08-11T08:11:51Z skitch # add schemas/uuids/procedures/pseudo_order_uuid +schemas/uuids/procedures/trigger_set_uuid_related_field [schemas/uuids/schema schemas/uuids/procedures/pseudo_order_seed_uuid] 2017-08-11T08:11:51Z skitch # add schemas/uuids/procedures/trigger_set_uuid_related_field +schemas/uuids/procedures/trigger_set_uuid_seed [schemas/uuids/schema schemas/uuids/procedures/pseudo_order_seed_uuid] 2017-08-11T08:11:51Z skitch # add schemas/uuids/procedures/trigger_set_uuid_seed \ No newline at end of file diff --git a/packages/uuid/revert/extension/defaults.sql b/packages/uuid/revert/extension/defaults.sql new file mode 100644 index 000000000..a5f303fe8 --- /dev/null +++ b/packages/uuid/revert/extension/defaults.sql @@ -0,0 +1,5 @@ +-- Revert extension/defaults from pg + +BEGIN; + +COMMIT; diff --git a/packages/uuid/revert/schemas/uuids/procedures/pseudo_order_seed_uuid.sql b/packages/uuid/revert/schemas/uuids/procedures/pseudo_order_seed_uuid.sql new file mode 100644 index 000000000..dfb77b5c7 --- /dev/null +++ b/packages/uuid/revert/schemas/uuids/procedures/pseudo_order_seed_uuid.sql @@ -0,0 +1,7 @@ +-- Revert schemas/uuids/procedures/pseudo_order_seed_uuid from pg + +BEGIN; + +DROP FUNCTION uuids.pseudo_order_seed_uuid; + +COMMIT; diff --git a/packages/uuid/revert/schemas/uuids/procedures/pseudo_order_uuid.sql b/packages/uuid/revert/schemas/uuids/procedures/pseudo_order_uuid.sql new file mode 100644 index 000000000..79d1c887f --- /dev/null +++ b/packages/uuid/revert/schemas/uuids/procedures/pseudo_order_uuid.sql @@ -0,0 +1,7 @@ +-- Revert schemas/uuids/procedures/pseudo_order_uuid from pg + +BEGIN; + +DROP FUNCTION uuids.pseudo_order_uuid; + +COMMIT; diff --git a/packages/uuid/revert/schemas/uuids/procedures/trigger_set_uuid_related_field.sql b/packages/uuid/revert/schemas/uuids/procedures/trigger_set_uuid_related_field.sql new file mode 100644 index 000000000..40b46093b --- /dev/null +++ b/packages/uuid/revert/schemas/uuids/procedures/trigger_set_uuid_related_field.sql @@ -0,0 +1,7 @@ +-- Revert schemas/uuids/procedures/trigger_set_uuid_related_field from pg + +BEGIN; + +DROP FUNCTION uuids.trigger_set_uuid_related_field; + +COMMIT; diff --git a/packages/uuid/revert/schemas/uuids/procedures/trigger_set_uuid_seed.sql b/packages/uuid/revert/schemas/uuids/procedures/trigger_set_uuid_seed.sql new file mode 100644 index 000000000..4d3b956d9 --- /dev/null +++ b/packages/uuid/revert/schemas/uuids/procedures/trigger_set_uuid_seed.sql @@ -0,0 +1,7 @@ +-- Revert schemas/uuids/procedures/trigger_set_uuid_seed from pg + +BEGIN; + +DROP FUNCTION uuids.trigger_set_uuid_seed; + +COMMIT; diff --git a/packages/uuid/revert/schemas/uuids/schema.sql b/packages/uuid/revert/schemas/uuids/schema.sql new file mode 100644 index 000000000..2dee222e3 --- /dev/null +++ b/packages/uuid/revert/schemas/uuids/schema.sql @@ -0,0 +1,7 @@ +-- Revert schemas/uuids/schema from pg + +BEGIN; + +DROP SCHEMA uuids; + +COMMIT; diff --git a/packages/uuid/sql/pgpm-uuid--0.15.4.sql b/packages/uuid/sql/pgpm-uuid--0.15.4.sql new file mode 100644 index 000000000..27853fc92 --- /dev/null +++ b/packages/uuid/sql/pgpm-uuid--0.15.4.sql @@ -0,0 +1,74 @@ +\echo Use "CREATE EXTENSION pgpm-uuid" to load this file. \quit +CREATE SCHEMA uuids; + +GRANT USAGE ON SCHEMA uuids TO PUBLIC; + +ALTER DEFAULT PRIVILEGES IN SCHEMA uuids + GRANT EXECUTE ON FUNCTIONS TO PUBLIC; + +CREATE FUNCTION uuids.pseudo_order_seed_uuid( + seed text +) RETURNS uuid AS $EOFCODE$ +DECLARE + new_uuid char(36); + md5_str char(32); + md5_str2 char(32); + uid text; +BEGIN + md5_str := md5(concat(random(), now())); + md5_str2 := md5(concat(random(), now())); + + new_uuid := concat( + LEFT (md5(seed), 2), + LEFT (md5(concat(extract(year FROM now()), extract(week FROM now()))), 2), + substring(md5_str, 1, 4), + '-', + substring(md5_str, 5, 4), + '-4', + substring(md5_str2, 9, 3), + '-', + substring(md5_str, 13, 4), + '-', + substring(md5_str2, 17, 12) + ); + RETURN new_uuid; +END; +$EOFCODE$ LANGUAGE plpgsql VOLATILE; + +COMMENT ON FUNCTION uuids.pseudo_order_seed_uuid IS 'Pseudo Ordered UUID with a seed. Good for multi-tenant scenarios, other wise use non-seed.'; + +CREATE FUNCTION uuids.pseudo_order_uuid() RETURNS uuid AS $EOFCODE$ +DECLARE + new_uuid char(36); + md5_str char(32); + md5_str2 char(32); +BEGIN + md5_str := md5(concat(random(), now())); + md5_str2 := md5(concat(random(), now())); + new_uuid := concat( + LEFT (md5(concat(extract(year FROM now()), extract(week FROM now()))), 4), + LEFT (md5_str, 4), '-', substring(md5_str, 5, 4), '-4', substring(md5_str2, 9, 3), '-', substring(md5_str, 13, 4), '-', substring(md5_str2, 17, 12)); + RETURN new_uuid; +END; +$EOFCODE$ LANGUAGE plpgsql VOLATILE; + +COMMENT ON FUNCTION uuids.pseudo_order_seed_uuid IS 'Pseudo Ordered UUID'; + +CREATE FUNCTION uuids.trigger_set_uuid_related_field() RETURNS trigger AS $EOFCODE$ +DECLARE + _seed_column text := to_json(NEW) ->> TG_ARGV[1]; +BEGIN + IF _seed_column IS NULL THEN + RAISE EXCEPTION 'UUID seed is NULL on table %', TG_TABLE_NAME; + END IF; + NEW := NEW #= (TG_ARGV[0] || '=>' || uuids.pseudo_order_seed_uuid(_seed_column))::hstore; + RETURN NEW; +END; +$EOFCODE$ LANGUAGE plpgsql VOLATILE; + +CREATE FUNCTION uuids.trigger_set_uuid_seed() RETURNS trigger AS $EOFCODE$ +BEGIN + NEW := NEW #= (TG_ARGV[0] || '=>' || uuids.pseudo_order_seed_uuid(TG_ARGV[1]))::hstore; + RETURN NEW; +END; +$EOFCODE$ LANGUAGE plpgsql VOLATILE; \ No newline at end of file diff --git a/packages/uuid/verify/extension/defaults.sql b/packages/uuid/verify/extension/defaults.sql new file mode 100644 index 000000000..87245abb4 --- /dev/null +++ b/packages/uuid/verify/extension/defaults.sql @@ -0,0 +1,5 @@ +-- Verify extension/defaults on pg + +BEGIN; + +ROLLBACK; diff --git a/packages/uuid/verify/schemas/uuids/procedures/pseudo_order_seed_uuid.sql b/packages/uuid/verify/schemas/uuids/procedures/pseudo_order_seed_uuid.sql new file mode 100644 index 000000000..10d5635fe --- /dev/null +++ b/packages/uuid/verify/schemas/uuids/procedures/pseudo_order_seed_uuid.sql @@ -0,0 +1,7 @@ +-- Verify schemas/uuids/procedures/pseudo_order_seed_uuid on pg + +BEGIN; + +SELECT verify_function ('uuids.pseudo_order_seed_uuid'); + +ROLLBACK; diff --git a/packages/uuid/verify/schemas/uuids/procedures/pseudo_order_uuid.sql b/packages/uuid/verify/schemas/uuids/procedures/pseudo_order_uuid.sql new file mode 100644 index 000000000..4bf9e5512 --- /dev/null +++ b/packages/uuid/verify/schemas/uuids/procedures/pseudo_order_uuid.sql @@ -0,0 +1,7 @@ +-- Verify schemas/uuids/procedures/pseudo_order_uuid on pg + +BEGIN; + +SELECT verify_function ('uuids.pseudo_order_uuid'); + +ROLLBACK; diff --git a/packages/uuid/verify/schemas/uuids/procedures/trigger_set_uuid_related_field.sql b/packages/uuid/verify/schemas/uuids/procedures/trigger_set_uuid_related_field.sql new file mode 100644 index 000000000..0b4b02c42 --- /dev/null +++ b/packages/uuid/verify/schemas/uuids/procedures/trigger_set_uuid_related_field.sql @@ -0,0 +1,7 @@ +-- Verify schemas/uuids/procedures/trigger_set_uuid_related_field on pg + +BEGIN; + +SELECT verify_function ('uuids.trigger_set_uuid_related_field'); + +ROLLBACK; diff --git a/packages/uuid/verify/schemas/uuids/procedures/trigger_set_uuid_seed.sql b/packages/uuid/verify/schemas/uuids/procedures/trigger_set_uuid_seed.sql new file mode 100644 index 000000000..72342b832 --- /dev/null +++ b/packages/uuid/verify/schemas/uuids/procedures/trigger_set_uuid_seed.sql @@ -0,0 +1,7 @@ +-- Verify schemas/uuids/procedures/trigger_set_uuid_seed on pg + +BEGIN; + +SELECT verify_function ('uuids.trigger_set_uuid_seed'); + +ROLLBACK; diff --git a/packages/uuid/verify/schemas/uuids/schema.sql b/packages/uuid/verify/schemas/uuids/schema.sql new file mode 100644 index 000000000..62f1e2f9f --- /dev/null +++ b/packages/uuid/verify/schemas/uuids/schema.sql @@ -0,0 +1,7 @@ +-- Verify schemas/uuids/schema on pg + +BEGIN; + +SELECT verify_schema ('uuids'); + +ROLLBACK; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 59b7857c4..1a57b3636 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -346,6 +346,16 @@ importers: specifier: ^4.28.7 version: 4.28.8(@dataplan/json@1.0.0(grafast@1.0.2(graphql@16.13.0)))(@dataplan/pg@1.0.3(@dataplan/json@1.0.0(grafast@1.0.2(graphql@16.13.0)))(grafast@1.0.2(graphql@16.13.0))(graphile-config@1.0.1)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.22.0))(@types/node@22.19.17)(grafserv@1.0.0(@types/node@22.19.17)(grafast@1.0.2(graphql@16.13.0))(graphile-config@1.0.1)(graphql@16.13.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5))(ws@8.20.0))(graphile-build@5.0.2(grafast@1.0.2(graphql@16.13.0))(graphile-config@1.0.1)(graphql@16.13.0))(pg-sql2@5.0.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(tamedevil@0.1.1)(use-sync-external-store@1.6.0(react@19.2.5))(ws@8.20.0) + packages/uuid: + dependencies: + "@pgpm/verify": + specifier: workspace:* + version: link:../verify + devDependencies: + pgpm: + specifier: ^4.28.7 + version: 4.28.8(@dataplan/json@1.0.0(grafast@1.0.2(graphql@16.13.0)))(@dataplan/pg@1.0.3(@dataplan/json@1.0.0(grafast@1.0.2(graphql@16.13.0)))(grafast@1.0.2(graphql@16.13.0))(graphile-config@1.0.1)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.22.0))(@types/node@22.19.17)(grafserv@1.0.0(@types/node@22.19.17)(grafast@1.0.2(graphql@16.13.0))(graphile-config@1.0.1)(graphql@16.13.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5))(ws@8.20.0))(graphile-build@5.0.2(grafast@1.0.2(graphql@16.13.0))(graphile-config@1.0.1)(graphql@16.13.0))(pg-sql2@5.0.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(tamedevil@0.1.1)(use-sync-external-store@1.6.0(react@19.2.5))(ws@8.20.0) + packages/verify: devDependencies: pgpm: