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 cc5937a3..935bcea7 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 @@ -8,27 +8,32 @@ CREATE FUNCTION app_jobs.run_scheduled_job (id bigint, job_expiry interval DEFAU RETURNS app_jobs.jobs AS $$ DECLARE + sched app_jobs.scheduled_jobs; j app_jobs.jobs; - last_id bigint; lkd_by text; BEGIN - -- check last scheduled + -- lock the schedule row so concurrent runners serialize here SELECT - last_scheduled_id + * FROM app_jobs.scheduled_jobs s WHERE - s.id = run_scheduled_job.id INTO last_id; + s.id = run_scheduled_job.id + FOR UPDATE INTO sched; + -- schedule deleted: return a null record so the caller unschedules it + IF NOT FOUND THEN + RETURN j; + END IF; -- if it's been scheduled check if it's been run - - IF (last_id IS NOT NULL) THEN + + IF (sched.last_scheduled_id IS NOT NULL) THEN SELECT locked_by FROM app_jobs.jobs js WHERE - js.id = last_id + js.id = sched.last_scheduled_id AND (js.locked_at IS NULL -- never been run OR js.locked_at >= (NOW() - job_expiry) -- still running within a safe interval @@ -38,7 +43,24 @@ BEGIN END IF; END IF; - -- insert new job + -- a job carrying this key that is already in flight covers this tick, and the + -- keyed upsert below cannot refresh a locked row + IF (sched.key IS NOT NULL) THEN + PERFORM + 1 + FROM + app_jobs.jobs jl + WHERE + jl.key = sched.key + AND jl.locked_at IS NOT NULL; + IF (FOUND) THEN + RAISE EXCEPTION 'ALREADY_SCHEDULED'; + END IF; + END IF; + + -- insert new job; key is the dedupe identity, so a pending job carrying the + -- same key is refreshed (same semantics as app_jobs.add_job) instead of + -- violating jobs_key_key INSERT INTO app_jobs.jobs ( database_id, actor_id, @@ -49,23 +71,30 @@ BEGIN priority, max_attempts, key - ) SELECT - database_id, - actor_id, - entity_id, - queue_name, - task_identifier, - payload, - priority, - max_attempts, - key - FROM - app_jobs.scheduled_jobs s - WHERE - s.id = run_scheduled_job.id + ) VALUES ( + sched.database_id, + sched.actor_id, + sched.entity_id, + sched.queue_name, + sched.task_identifier, + sched.payload, + sched.priority, + sched.max_attempts, + sched.key + ) + ON CONFLICT (KEY) + DO UPDATE SET + database_id = excluded.database_id, actor_id = excluded.actor_id, entity_id = excluded.entity_id, + task_identifier = excluded.task_identifier, payload = excluded.payload, queue_name = excluded.queue_name, max_attempts = excluded.max_attempts, priority = excluded.priority, run_at = excluded.run_at, + -- always reset error/retry state + attempts = 0, last_error = NULL + WHERE + jobs.locked_at IS NULL RETURNING * INTO j; - -- update the scheduled job + -- update the scheduled job; j is null when a BEFORE INSERT trigger suppressed + -- the transport row (the fire trigger enqueues through its own ledger and + -- records last_scheduled_id itself), so keep the recorded id in that case UPDATE app_jobs.scheduled_jobs s SET diff --git a/packages/database-jobs/sql/pgpm-database-jobs--0.43.1.bundle.tar.gz b/packages/database-jobs/sql/pgpm-database-jobs--0.43.1.bundle.tar.gz index 8c3fb86a..b2aa9c3e 100644 Binary files a/packages/database-jobs/sql/pgpm-database-jobs--0.43.1.bundle.tar.gz and b/packages/database-jobs/sql/pgpm-database-jobs--0.43.1.bundle.tar.gz differ diff --git a/packages/database-jobs/sql/pgpm-database-jobs--0.43.1.sql b/packages/database-jobs/sql/pgpm-database-jobs--0.43.1.sql index 05d2fae4..89adf8e2 100644 --- a/packages/database-jobs/sql/pgpm-database-jobs--0.43.1.sql +++ b/packages/database-jobs/sql/pgpm-database-jobs--0.43.1.sql @@ -398,27 +398,32 @@ CREATE FUNCTION app_jobs.run_scheduled_job( job_expiry interval DEFAULT '1 hours' ) RETURNS app_jobs.jobs AS $EOFCODE$ DECLARE + sched app_jobs.scheduled_jobs; j app_jobs.jobs; - last_id bigint; lkd_by text; BEGIN - -- check last scheduled + -- lock the schedule row so concurrent runners serialize here SELECT - last_scheduled_id + * FROM app_jobs.scheduled_jobs s WHERE - s.id = run_scheduled_job.id INTO last_id; + s.id = run_scheduled_job.id + FOR UPDATE INTO sched; + -- schedule deleted: return a null record so the caller unschedules it + IF NOT FOUND THEN + RETURN j; + END IF; -- if it's been scheduled check if it's been run - - IF (last_id IS NOT NULL) THEN + + IF (sched.last_scheduled_id IS NOT NULL) THEN SELECT locked_by FROM app_jobs.jobs js WHERE - js.id = last_id + js.id = sched.last_scheduled_id AND (js.locked_at IS NULL -- never been run OR js.locked_at >= (NOW() - job_expiry) -- still running within a safe interval @@ -428,7 +433,24 @@ BEGIN END IF; END IF; - -- insert new job + -- a job carrying this key that is already in flight covers this tick, and the + -- keyed upsert below cannot refresh a locked row + IF (sched.key IS NOT NULL) THEN + PERFORM + 1 + FROM + app_jobs.jobs jl + WHERE + jl.key = sched.key + AND jl.locked_at IS NOT NULL; + IF (FOUND) THEN + RAISE EXCEPTION 'ALREADY_SCHEDULED'; + END IF; + END IF; + + -- insert new job; key is the dedupe identity, so a pending job carrying the + -- same key is refreshed (same semantics as app_jobs.add_job) instead of + -- violating jobs_key_key INSERT INTO app_jobs.jobs ( database_id, actor_id, @@ -439,23 +461,30 @@ BEGIN priority, max_attempts, key - ) SELECT - database_id, - actor_id, - entity_id, - queue_name, - task_identifier, - payload, - priority, - max_attempts, - key - FROM - app_jobs.scheduled_jobs s - WHERE - s.id = run_scheduled_job.id + ) VALUES ( + sched.database_id, + sched.actor_id, + sched.entity_id, + sched.queue_name, + sched.task_identifier, + sched.payload, + sched.priority, + sched.max_attempts, + sched.key + ) + ON CONFLICT (KEY) + DO UPDATE SET + database_id = excluded.database_id, actor_id = excluded.actor_id, entity_id = excluded.entity_id, + task_identifier = excluded.task_identifier, payload = excluded.payload, queue_name = excluded.queue_name, max_attempts = excluded.max_attempts, priority = excluded.priority, run_at = excluded.run_at, + -- always reset error/retry state + attempts = 0, last_error = NULL + WHERE + jobs.locked_at IS NULL RETURNING * INTO j; - -- update the scheduled job + -- update the scheduled job; j is null when a BEFORE INSERT trigger suppressed + -- the transport row (the fire trigger enqueues through its own ledger and + -- records last_scheduled_id itself), so keep the recorded id in that case UPDATE app_jobs.scheduled_jobs s SET diff --git a/packages/jobs/deploy/schemas/app_jobs/procedures/run_scheduled_job.sql b/packages/jobs/deploy/schemas/app_jobs/procedures/run_scheduled_job.sql index 2f4961d5..6640f36d 100644 --- a/packages/jobs/deploy/schemas/app_jobs/procedures/run_scheduled_job.sql +++ b/packages/jobs/deploy/schemas/app_jobs/procedures/run_scheduled_job.sql @@ -8,25 +8,30 @@ CREATE FUNCTION app_jobs.run_scheduled_job (id bigint, job_expiry interval DEFAU RETURNS app_jobs.jobs AS $$ DECLARE + sched app_jobs.scheduled_jobs; j app_jobs.jobs; - last_id bigint; lkd_by text; BEGIN - -- check last scheduled + -- lock the schedule row so concurrent runners serialize here SELECT - last_scheduled_id + * FROM app_jobs.scheduled_jobs s WHERE - s.id = run_scheduled_job.id INTO last_id; + s.id = run_scheduled_job.id + FOR UPDATE INTO sched; + -- schedule deleted: return a null record so the caller unschedules it + IF NOT FOUND THEN + RETURN j; + END IF; -- if it's been scheduled check if it's been run - IF (last_id IS NOT NULL) THEN + IF (sched.last_scheduled_id IS NOT NULL) THEN SELECT locked_by FROM app_jobs.jobs js WHERE - js.id = last_id + js.id = sched.last_scheduled_id AND (js.locked_at IS NULL -- never been run OR js.locked_at >= (NOW() - job_expiry) -- still running within a safe interval @@ -35,27 +40,42 @@ BEGIN RAISE EXCEPTION 'ALREADY_SCHEDULED'; END IF; END IF; - -- insert new job + -- a job carrying this key that is already in flight covers this tick, and the + -- keyed upsert below cannot refresh a locked row + IF (sched.key IS NOT NULL) THEN + PERFORM + 1 + FROM + app_jobs.jobs jl + WHERE + jl.key = sched.key + AND jl.locked_at IS NOT NULL; + IF (FOUND) THEN + RAISE EXCEPTION 'ALREADY_SCHEDULED'; + END IF; + END IF; + -- insert new job; key is the dedupe identity, so a pending job carrying the + -- same key is refreshed (same semantics as app_jobs.add_job) instead of + -- violating jobs_key_key INSERT INTO app_jobs.jobs (queue_name, task_identifier, payload, priority, max_attempts, key) - SELECT - queue_name, - task_identifier, - payload, - priority, - max_attempts, - key - FROM - app_jobs.scheduled_jobs s - WHERE - s.id = run_scheduled_job.id + VALUES (sched.queue_name, sched.task_identifier, sched.payload, sched.priority, sched.max_attempts, sched.key) + ON CONFLICT (KEY) + DO UPDATE SET + task_identifier = excluded.task_identifier, payload = excluded.payload, queue_name = excluded.queue_name, max_attempts = excluded.max_attempts, priority = excluded.priority, run_at = excluded.run_at, + -- always reset error/retry state + attempts = 0, last_error = NULL + WHERE + jobs.locked_at IS NULL RETURNING * INTO j; - -- update the scheduled job + -- update the scheduled job; j is null when a BEFORE INSERT trigger suppressed + -- the transport row (the fire trigger enqueues through its own ledger and + -- records last_scheduled_id itself), so keep the recorded id in that case UPDATE 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; @@ -64,4 +84,3 @@ $$ LANGUAGE 'plpgsql' VOLATILE; COMMIT; - diff --git a/packages/jobs/sql/pgpm-jobs--0.43.1.bundle.tar.gz b/packages/jobs/sql/pgpm-jobs--0.43.1.bundle.tar.gz index 7d2a1a8a..6a07f500 100644 Binary files a/packages/jobs/sql/pgpm-jobs--0.43.1.bundle.tar.gz and b/packages/jobs/sql/pgpm-jobs--0.43.1.bundle.tar.gz differ diff --git a/packages/jobs/sql/pgpm-jobs--0.43.1.sql b/packages/jobs/sql/pgpm-jobs--0.43.1.sql index f32214db..fe611329 100644 --- a/packages/jobs/sql/pgpm-jobs--0.43.1.sql +++ b/packages/jobs/sql/pgpm-jobs--0.43.1.sql @@ -348,25 +348,30 @@ CREATE FUNCTION app_jobs.run_scheduled_job( job_expiry interval DEFAULT '1 hours' ) RETURNS app_jobs.jobs AS $EOFCODE$ DECLARE + sched app_jobs.scheduled_jobs; j app_jobs.jobs; - last_id bigint; lkd_by text; BEGIN - -- check last scheduled + -- lock the schedule row so concurrent runners serialize here SELECT - last_scheduled_id + * FROM app_jobs.scheduled_jobs s WHERE - s.id = run_scheduled_job.id INTO last_id; + s.id = run_scheduled_job.id + FOR UPDATE INTO sched; + -- schedule deleted: return a null record so the caller unschedules it + IF NOT FOUND THEN + RETURN j; + END IF; -- if it's been scheduled check if it's been run - IF (last_id IS NOT NULL) THEN + IF (sched.last_scheduled_id IS NOT NULL) THEN SELECT locked_by FROM app_jobs.jobs js WHERE - js.id = last_id + js.id = sched.last_scheduled_id AND (js.locked_at IS NULL -- never been run OR js.locked_at >= (NOW() - job_expiry) -- still running within a safe interval @@ -375,27 +380,42 @@ BEGIN RAISE EXCEPTION 'ALREADY_SCHEDULED'; END IF; END IF; - -- insert new job + -- a job carrying this key that is already in flight covers this tick, and the + -- keyed upsert below cannot refresh a locked row + IF (sched.key IS NOT NULL) THEN + PERFORM + 1 + FROM + app_jobs.jobs jl + WHERE + jl.key = sched.key + AND jl.locked_at IS NOT NULL; + IF (FOUND) THEN + RAISE EXCEPTION 'ALREADY_SCHEDULED'; + END IF; + END IF; + -- insert new job; key is the dedupe identity, so a pending job carrying the + -- same key is refreshed (same semantics as app_jobs.add_job) instead of + -- violating jobs_key_key INSERT INTO app_jobs.jobs (queue_name, task_identifier, payload, priority, max_attempts, key) - SELECT - queue_name, - task_identifier, - payload, - priority, - max_attempts, - key - FROM - app_jobs.scheduled_jobs s - WHERE - s.id = run_scheduled_job.id + VALUES (sched.queue_name, sched.task_identifier, sched.payload, sched.priority, sched.max_attempts, sched.key) + ON CONFLICT (KEY) + DO UPDATE SET + task_identifier = excluded.task_identifier, payload = excluded.payload, queue_name = excluded.queue_name, max_attempts = excluded.max_attempts, priority = excluded.priority, run_at = excluded.run_at, + -- always reset error/retry state + attempts = 0, last_error = NULL + WHERE + jobs.locked_at IS NULL RETURNING * INTO j; - -- update the scheduled job + -- update the scheduled job; j is null when a BEFORE INSERT trigger suppressed + -- the transport row (the fire trigger enqueues through its own ledger and + -- records last_scheduled_id itself), so keep the recorded id in that case UPDATE 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; diff --git a/packages/metaschema-modules/__tests__/__snapshots__/modules.test.ts.snap b/packages/metaschema-modules/__tests__/__snapshots__/modules.test.ts.snap index a5fdfa19..38ded313 100644 --- a/packages/metaschema-modules/__tests__/__snapshots__/modules.test.ts.snap +++ b/packages/metaschema-modules/__tests__/__snapshots__/modules.test.ts.snap @@ -10,8 +10,8 @@ exports[`db_meta_modules should have all expected module tables 1`] = ` "billing_provider_module", "capabilities_module", "catalog_module", + "cluster_module", "compute_log_module", - "config_secrets_user_module", "connected_accounts_module", "content_preset_module", "crypto_addresses_module", @@ -158,7 +158,7 @@ exports[`db_meta_modules should verify module table structures have database_id exports[`db_meta_modules should verify module tables have proper foreign key relationships 1`] = ` { - "constraintCount": 546, + "constraintCount": 551, "foreignTables": [ "catalog_module", "database", diff --git a/packages/metaschema-modules/__tests__/modules.test.ts b/packages/metaschema-modules/__tests__/modules.test.ts index b1b02098..733773e6 100644 --- a/packages/metaschema-modules/__tests__/modules.test.ts +++ b/packages/metaschema-modules/__tests__/modules.test.ts @@ -30,7 +30,8 @@ describe('db_meta_modules', () => { 'infra_secrets_module', 'infra_config_module', 'internal_secrets_module', - 'config_secrets_user_module', + 'internal_config_module', + 'cluster_module', 'invites_module', 'events_module', 'limits_module', diff --git a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/cluster_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/cluster_module/table.sql new file mode 100644 index 00000000..bd0b06c4 --- /dev/null +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/cluster_module/table.sql @@ -0,0 +1,116 @@ +-- Deploy schemas/metaschema_modules_public/tables/cluster_module/table to pg + +-- requires: schemas/metaschema_modules_public/schema + +BEGIN; + +-- The fleet catalog: Kubernetes clusters, the PostgreSQL servers a cluster owns, +-- the physical databases on those servers, and which logical (metaschema) +-- database is placed in which physical database. +-- +-- Platform scope only. These rows describe fleet capacity, not tenant data, so +-- there is no per-tenant instance of them: the insert trigger refuses any scope +-- outside metaschema_private.constructive_scopes() and steps up to super +-- constructive. A tenant-visible answer to "where does my data live" is a view +-- over one placement row, never a second copy of these tables. +CREATE TABLE metaschema_modules_public.cluster_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). Platform + -- is a global tier, so this stays NULL — recorded as a queryable fact + -- rather than re-derived by consumers. + 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(), + + -- Optional schema name overrides (used when schema IDs are not provided) + public_schema_name text, + private_schema_name text, + + -- Generated table IDs (populated by the generator) + clusters_table_id uuid NOT NULL DEFAULT uuid_nil(), + cluster_events_table_id uuid NOT NULL DEFAULT uuid_nil(), + database_servers_table_id uuid NOT NULL DEFAULT uuid_nil(), + physical_databases_table_id uuid NOT NULL DEFAULT uuid_nil(), + database_placements_table_id uuid NOT NULL DEFAULT uuid_nil(), + + -- Table names (input to the generator) + clusters_table_name text NOT NULL DEFAULT 'clusters', + cluster_events_table_name text NOT NULL DEFAULT 'cluster_events', + database_servers_table_name text NOT NULL DEFAULT 'database_servers', + physical_databases_table_name text NOT NULL DEFAULT 'physical_databases', + database_placements_table_name text NOT NULL DEFAULT 'database_placements', + + -- API routing (get-or-create: if set, schema is added to this API; if NULL, + -- no API is added). The fleet catalog ships on its own `cluster` API so the + -- admin surface is not widened with fleet-only concerns. + api_name text, + private_api_name text, + + -- Scope: determines the security level for this module instance. + -- Resolved to a membership_type integer at trigger time via membership_types. + -- Only the constructive scopes are accepted (see the insert trigger). + scope text NOT NULL DEFAULT 'platform', + + -- Table name prefix. Auto-derived from scope by the trigger when empty and + -- no dedicated API provides namespace isolation. + prefix text NOT NULL DEFAULT '', + + -- Configurable security policies (NULL = use defaults based on scope). + -- When provided, replaces the default policy set in apply_module_security. + policies jsonb NULL, + + -- Per-table provisions overrides from blueprint config. Keys are table keys + -- (clusters, cluster_events, database_servers, physical_databases, + -- database_placements). When a key is present, the module trigger skips + -- default security for that table. + provisions jsonb NULL, + + -- Default capabilities auto-granted to new members. + -- NULL uses the module's built-in defaults; explicit array overrides them. + default_capabilities text[] DEFAULT NULL, + + -- Retention for the partitioned cluster_events tier + partition_interval text NOT NULL DEFAULT '1 month', + retention text NOT NULL DEFAULT '12 months', + premake integer NOT NULL DEFAULT 2, + + -- Constraints + CONSTRAINT cluster_module_db_fkey FOREIGN KEY (database_id) REFERENCES metaschema_public.database (id) ON DELETE CASCADE, + CONSTRAINT cluster_module_schema_fkey FOREIGN KEY (schema_id) REFERENCES metaschema_public.schema (id) ON DELETE CASCADE, + CONSTRAINT cluster_module_private_schema_fkey FOREIGN KEY (private_schema_id) REFERENCES metaschema_public.schema (id) ON DELETE CASCADE, + CONSTRAINT cluster_module_clusters_table_fkey FOREIGN KEY (clusters_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, + CONSTRAINT cluster_module_events_table_fkey FOREIGN KEY (cluster_events_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, + CONSTRAINT cluster_module_servers_table_fkey FOREIGN KEY (database_servers_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, + CONSTRAINT cluster_module_physical_dbs_table_fkey FOREIGN KEY (physical_databases_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE, + CONSTRAINT cluster_module_placements_table_fkey FOREIGN KEY (database_placements_table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE +); + +-- database_id needs no index of its own: cluster_module_unique_scope below +-- leads with it, so it already serves a lookup by database. +CREATE INDEX cluster_module_schema_id_idx ON metaschema_modules_public.cluster_module ( schema_id ); +CREATE INDEX cluster_module_private_schema_id_idx ON metaschema_modules_public.cluster_module ( private_schema_id ); +CREATE INDEX cluster_module_clusters_table_id_idx ON metaschema_modules_public.cluster_module ( clusters_table_id ); +CREATE INDEX cluster_module_cluster_events_table_id_idx ON metaschema_modules_public.cluster_module ( cluster_events_table_id ); +CREATE INDEX cluster_module_database_servers_table_id_idx ON metaschema_modules_public.cluster_module ( database_servers_table_id ); +CREATE INDEX cluster_module_physical_databases_table_id_idx ON metaschema_modules_public.cluster_module ( physical_databases_table_id ); +CREATE INDEX cluster_module_database_placements_table_id_idx ON metaschema_modules_public.cluster_module ( database_placements_table_id ); + +-- One cluster module per database per scope + prefix. +CREATE UNIQUE INDEX cluster_module_unique_scope ON metaschema_modules_public.cluster_module ( database_id, scope, prefix ); + +-- Tables this module generates, as opposed to tables it is handed: the +-- @module_table marker is what +-- metaschema_modules_private.tg_module_install_provenance attributes to this +-- install, keyed by the role name in the column. +COMMENT ON COLUMN metaschema_modules_public.cluster_module.clusters_table_id IS '@module_table'; +COMMENT ON COLUMN metaschema_modules_public.cluster_module.cluster_events_table_id IS '@module_table'; +COMMENT ON COLUMN metaschema_modules_public.cluster_module.database_servers_table_id IS '@module_table'; +COMMENT ON COLUMN metaschema_modules_public.cluster_module.physical_databases_table_id IS '@module_table'; +COMMENT ON COLUMN metaschema_modules_public.cluster_module.database_placements_table_id IS '@module_table'; + +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 deleted file mode 100644 index 4fe6f583..00000000 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/config_secrets_user_module/table.sql +++ /dev/null @@ -1,33 +0,0 @@ --- Deploy schemas/metaschema_modules_public/tables/config_secrets_user_module/table to pg - --- requires: schemas/metaschema_modules_public/schema - -BEGIN; - -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(), - table_name text NOT NULL DEFAULT 'user_secrets', - - -- API routing (configurable per-module) - api_name text DEFAULT 'config', - private_api_name text DEFAULT NULL, - - 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, - CONSTRAINT table_fkey FOREIGN KEY (table_id) REFERENCES metaschema_public.table (id) ON DELETE CASCADE -); - -CREATE UNIQUE INDEX config_secrets_user_module_database_id_idx ON metaschema_modules_public.config_secrets_user_module ( database_id ); -CREATE INDEX config_secrets_user_module_table_id_idx ON metaschema_modules_public.config_secrets_user_module ( table_id ); -CREATE INDEX config_secrets_user_module_schema_id_idx ON metaschema_modules_public.config_secrets_user_module ( schema_id ); - -COMMIT; 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 index 3f1cd659..94bba6c6 100644 --- 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 @@ -13,7 +13,9 @@ CREATE TABLE metaschema_modules_public.infra_config_module ( -- 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 references (resolved by BEFORE INSERT trigger when uuid_nil): + -- schema_id is this module's PUBLIC schema, private_schema_id its PRIVATE + -- schema, the same convention every other module row records. 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/infra_secrets_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/infra_secrets_module/table.sql index f1c10555..d9586f8e 100644 --- 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 @@ -8,7 +8,9 @@ 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 references (resolved by BEFORE INSERT trigger when uuid_nil): + -- schema_id is this module's PUBLIC schema, private_schema_id its PRIVATE + -- schema, the same convention every other module row records. 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/internal_config_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/internal_config_module/table.sql index 58091bf5..e8bf1d87 100644 --- a/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/internal_config_module/table.sql +++ b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/internal_config_module/table.sql @@ -8,7 +8,9 @@ CREATE TABLE metaschema_modules_public.internal_config_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, - -- Schema references (resolved by BEFORE INSERT trigger when uuid_nil) + -- Schema references (resolved by BEFORE INSERT trigger when uuid_nil): + -- schema_id is this module's PUBLIC schema, private_schema_id its PRIVATE + -- schema, the same convention every other module row records. 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/internal_secrets_module/table.sql b/packages/metaschema-modules/deploy/schemas/metaschema_modules_public/tables/internal_secrets_module/table.sql index 02a0a3d9..30cbc4a4 100644 --- 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 @@ -8,7 +8,9 @@ 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 references (resolved by BEFORE INSERT trigger when uuid_nil): + -- schema_id is this module's PUBLIC schema, private_schema_id its PRIVATE + -- schema, the same convention every other module row records. schema_id uuid NOT NULL DEFAULT uuid_nil(), private_schema_id uuid NOT NULL DEFAULT uuid_nil(), diff --git a/packages/metaschema-modules/pgpm.plan b/packages/metaschema-modules/pgpm.plan index dce159b1..99562e22 100644 --- a/packages/metaschema-modules/pgpm.plan +++ b/packages/metaschema-modules/pgpm.plan @@ -9,7 +9,6 @@ schemas/metaschema_modules_public/tables/crypto_auth_module/table [schemas/metas schemas/metaschema_modules_public/tables/default_ids_module/table [schemas/metaschema_modules_public/schema] 2017-08-11T08:11:51Z skitch # add schemas/metaschema_modules_public/tables/default_ids_module/table schemas/metaschema_modules_public/tables/denormalized_table_field/table [schemas/metaschema_modules_public/schema] 2017-08-11T08:11:51Z skitch # add schemas/metaschema_modules_public/tables/denormalized_table_field/table schemas/metaschema_modules_public/tables/emails_module/table [schemas/metaschema_modules_public/schema] 2017-08-11T08:11:51Z skitch # add schemas/metaschema_modules_public/tables/emails_module/table -schemas/metaschema_modules_public/tables/config_secrets_user_module/table [schemas/metaschema_modules_public/schema] 2017-08-11T08:11:51Z skitch # add schemas/metaschema_modules_public/tables/config_secrets_user_module/table schemas/metaschema_modules_public/tables/invites_module/table [schemas/metaschema_modules_public/schema] 2017-08-11T08:11:51Z skitch # add schemas/metaschema_modules_public/tables/invites_module/table schemas/metaschema_modules_public/tables/events_module/table [schemas/metaschema_modules_public/schema] 2017-08-11T08:11:51Z skitch # add schemas/metaschema_modules_public/tables/events_module/table schemas/metaschema_modules_public/tables/limits_module/table [schemas/metaschema_modules_public/schema] 2017-08-11T08:11:51Z skitch # add schemas/metaschema_modules_public/tables/limits_module/table @@ -98,3 +97,4 @@ schemas/metaschema_modules_public/tables/internal_config_module/table [schemas/m schemas/metaschema_modules_public/tables/image_module/table [schemas/metaschema_modules_public/schema] 2026-08-15T00:00:00Z devin # add image_module config table for the central container image catalog + grants schemas/metaschema_modules_public/tables/repository_module/table [schemas/metaschema_modules_public/schema] 2026-08-15T01:00:00Z devin # add repository_module config table (repositories, events, workflows, builds, local change requests) schemas/metaschema_modules_public/tables/machine_module/table [schemas/metaschema_modules_public/schema] 2026-08-15T02:00:00Z devin # add machine_module config table (enrolled machines, sessions, command ledger) +schemas/metaschema_modules_public/tables/cluster_module/table [schemas/metaschema_modules_public/schema] 2026-08-17T00:00:00Z devin # add cluster_module config table (fleet catalog: clusters, database servers, physical databases, placements) diff --git a/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/cluster_module/table.sql b/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/cluster_module/table.sql new file mode 100644 index 00000000..31204dc7 --- /dev/null +++ b/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/cluster_module/table.sql @@ -0,0 +1,7 @@ +-- Revert schemas/metaschema_modules_public/tables/cluster_module/table from pg + +BEGIN; + +DROP TABLE metaschema_modules_public.cluster_module; + +COMMIT; diff --git a/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/config_secrets_user_module/table.sql b/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/config_secrets_user_module/table.sql deleted file mode 100644 index fcc2139c..00000000 --- a/packages/metaschema-modules/revert/schemas/metaschema_modules_public/tables/config_secrets_user_module/table.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Revert schemas/metaschema_modules_public/tables/config_secrets_user_module/table from pg - -BEGIN; - -DROP TABLE metaschema_modules_public.config_secrets_user_module; - -COMMIT; diff --git a/packages/metaschema-modules/sql/metaschema-modules--0.43.1.bundle.tar.gz b/packages/metaschema-modules/sql/metaschema-modules--0.43.1.bundle.tar.gz index 7a7c2974..84ea4497 100644 Binary files a/packages/metaschema-modules/sql/metaschema-modules--0.43.1.bundle.tar.gz and b/packages/metaschema-modules/sql/metaschema-modules--0.43.1.bundle.tar.gz differ diff --git a/packages/metaschema-modules/sql/metaschema-modules--0.43.1.sql b/packages/metaschema-modules/sql/metaschema-modules--0.43.1.sql index 288a3912..23edfdb4 100644 --- a/packages/metaschema-modules/sql/metaschema-modules--0.43.1.sql +++ b/packages/metaschema-modules/sql/metaschema-modules--0.43.1.sql @@ -265,35 +265,6 @@ CREATE INDEX emails_module_schema_id_idx ON metaschema_modules_public.emails_mod COMMENT ON COLUMN metaschema_modules_public.emails_module.table_id IS '@module_table'; -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', - api_name text DEFAULT 'config', - private_api_name text DEFAULT NULL, - 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, - CONSTRAINT table_fkey - FOREIGN KEY(table_id) - REFERENCES metaschema_public.table (id) - ON DELETE CASCADE -); - -CREATE UNIQUE INDEX config_secrets_user_module_database_id_idx ON metaschema_modules_public.config_secrets_user_module (database_id); - -CREATE INDEX config_secrets_user_module_table_id_idx ON metaschema_modules_public.config_secrets_user_module (table_id); - -CREATE INDEX config_secrets_user_module_schema_id_idx ON metaschema_modules_public.config_secrets_user_module (schema_id); - CREATE TABLE metaschema_modules_public.invites_module ( id uuid PRIMARY KEY DEFAULT uuidv7(), database_id uuid NOT NULL, @@ -6399,4 +6370,92 @@ COMMENT ON COLUMN metaschema_modules_public.machine_module.machines_table_id IS COMMENT ON COLUMN metaschema_modules_public.machine_module.machine_sessions_table_id IS '@module_table'; -COMMENT ON COLUMN metaschema_modules_public.machine_module.machine_messages_table_id IS '@module_table'; \ No newline at end of file +COMMENT ON COLUMN metaschema_modules_public.machine_module.machine_messages_table_id IS '@module_table'; + +CREATE TABLE metaschema_modules_public.cluster_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, + clusters_table_id uuid NOT NULL DEFAULT uuid_nil(), + cluster_events_table_id uuid NOT NULL DEFAULT uuid_nil(), + database_servers_table_id uuid NOT NULL DEFAULT uuid_nil(), + physical_databases_table_id uuid NOT NULL DEFAULT uuid_nil(), + database_placements_table_id uuid NOT NULL DEFAULT uuid_nil(), + clusters_table_name text NOT NULL DEFAULT 'clusters', + cluster_events_table_name text NOT NULL DEFAULT 'cluster_events', + database_servers_table_name text NOT NULL DEFAULT 'database_servers', + physical_databases_table_name text NOT NULL DEFAULT 'physical_databases', + database_placements_table_name text NOT NULL DEFAULT 'database_placements', + api_name text, + private_api_name text, + scope text NOT NULL DEFAULT 'platform', + prefix text NOT NULL DEFAULT '', + policies jsonb NULL, + provisions jsonb NULL, + default_capabilities text[] DEFAULT NULL, + partition_interval text NOT NULL DEFAULT '1 month', + retention text NOT NULL DEFAULT '12 months', + premake int NOT NULL DEFAULT 2, + CONSTRAINT cluster_module_db_fkey + FOREIGN KEY(database_id) + REFERENCES metaschema_public.database (id) + ON DELETE CASCADE, + CONSTRAINT cluster_module_schema_fkey + FOREIGN KEY(schema_id) + REFERENCES metaschema_public.schema (id) + ON DELETE CASCADE, + CONSTRAINT cluster_module_private_schema_fkey + FOREIGN KEY(private_schema_id) + REFERENCES metaschema_public.schema (id) + ON DELETE CASCADE, + CONSTRAINT cluster_module_clusters_table_fkey + FOREIGN KEY(clusters_table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE, + CONSTRAINT cluster_module_events_table_fkey + FOREIGN KEY(cluster_events_table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE, + CONSTRAINT cluster_module_servers_table_fkey + FOREIGN KEY(database_servers_table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE, + CONSTRAINT cluster_module_physical_dbs_table_fkey + FOREIGN KEY(physical_databases_table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE, + CONSTRAINT cluster_module_placements_table_fkey + FOREIGN KEY(database_placements_table_id) + REFERENCES metaschema_public.table (id) + ON DELETE CASCADE +); + +CREATE INDEX cluster_module_schema_id_idx ON metaschema_modules_public.cluster_module (schema_id); + +CREATE INDEX cluster_module_private_schema_id_idx ON metaschema_modules_public.cluster_module (private_schema_id); + +CREATE INDEX cluster_module_clusters_table_id_idx ON metaschema_modules_public.cluster_module (clusters_table_id); + +CREATE INDEX cluster_module_cluster_events_table_id_idx ON metaschema_modules_public.cluster_module (cluster_events_table_id); + +CREATE INDEX cluster_module_database_servers_table_id_idx ON metaschema_modules_public.cluster_module (database_servers_table_id); + +CREATE INDEX cluster_module_physical_databases_table_id_idx ON metaschema_modules_public.cluster_module (physical_databases_table_id); + +CREATE INDEX cluster_module_database_placements_table_id_idx ON metaschema_modules_public.cluster_module (database_placements_table_id); + +CREATE UNIQUE INDEX cluster_module_unique_scope ON metaschema_modules_public.cluster_module (database_id, scope, prefix); + +COMMENT ON COLUMN metaschema_modules_public.cluster_module.clusters_table_id IS '@module_table'; + +COMMENT ON COLUMN metaschema_modules_public.cluster_module.cluster_events_table_id IS '@module_table'; + +COMMENT ON COLUMN metaschema_modules_public.cluster_module.database_servers_table_id IS '@module_table'; + +COMMENT ON COLUMN metaschema_modules_public.cluster_module.physical_databases_table_id IS '@module_table'; + +COMMENT ON COLUMN metaschema_modules_public.cluster_module.database_placements_table_id IS '@module_table'; \ No newline at end of file diff --git a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/cluster_module/table.sql b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/cluster_module/table.sql new file mode 100644 index 00000000..e6b9cd0c --- /dev/null +++ b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/cluster_module/table.sql @@ -0,0 +1,14 @@ +-- Verify schemas/metaschema_modules_public/tables/cluster_module/table on pg + +SELECT id, database_id, entity_field, schema_id, private_schema_id, + public_schema_name, private_schema_name, + clusters_table_id, clusters_table_name, + cluster_events_table_id, cluster_events_table_name, + database_servers_table_id, database_servers_table_name, + physical_databases_table_id, physical_databases_table_name, + database_placements_table_id, database_placements_table_name, + api_name, private_api_name, + partition_interval, retention, premake, + scope, prefix, policies, provisions, default_capabilities +FROM metaschema_modules_public.cluster_module +WHERE FALSE; diff --git a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/config_secrets_user_module/table.sql b/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/config_secrets_user_module/table.sql deleted file mode 100644 index 3a3592ff..00000000 --- a/packages/metaschema-modules/verify/schemas/metaschema_modules_public/tables/config_secrets_user_module/table.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Verify schemas/metaschema_modules_public/tables/config_secrets_user_module/table on pg - -BEGIN; - -SELECT assert_table('metaschema_modules_public.config_secrets_user_module'::regclass); - -ROLLBACK;