diff --git a/docker/migrations/forward/_ledger/application-migrations.tsv b/docker/migrations/forward/_ledger/application-migrations.tsv index fb74fa11cc..962f092ff0 100644 --- a/docker/migrations/forward/_ledger/application-migrations.tsv +++ b/docker/migrations/forward/_ledger/application-migrations.tsv @@ -80,7 +80,7 @@ nodes/node_projection_registration/0001_add_heartbeat_columns.sql node:node_proj nodes/node_projection_registration/0002_node_service_registry_tenant_rls.sql node:node_projection_registration node:node_projection_registration omninode_internal node:node_projection_registration:0002_node_service_registry_tenant_rls.sql 3e8a3c12dbefc4432262a1c2533667f4a5f342c39149915bd80b45241c182e98 nodes/node_projection_registration/0003_reconcile_heartbeat_observability.sql node:node_projection_registration node:node_projection_registration omninode_internal node:node_projection_registration:0003_reconcile_heartbeat_observability.sql 0bc9286ec9b0f7ad36e1d29e54d34bc11fe2be64cf5b874d7432384869ade69e nodes/node_projection_registration/0004_node_service_registry_no_force_rls.sql node:node_projection_registration node:node_projection_registration omninode_internal node:node_projection_registration:0004_node_service_registry_no_force_rls.sql adb21c75ec2503b50a7272857e660ff07cfda16374698cc245af93047e3da6f2 -nodes/node_projection_registration/0005_create_projection_watermarks.sql node:node_projection_registration node:node_projection_registration omninode_internal node:node_projection_registration:0005_create_projection_watermarks.sql 67aecf3cb8359f45edf4b5f636ab22fcc25595f43621dd2ab1b8bf6b07d7c75b +nodes/node_projection_registration/0005_create_projection_watermarks.sql node:node_projection_registration node:node_projection_registration omninode_internal node:node_projection_registration:0005_create_projection_watermarks.sql 61102cd194178531b874221f7226e04bebcc9b224380145aa6a88b25879d9593 nodes/node_projection_routing_decision/0021_create_agent_routing_decisions.sql node:node_projection_routing_decision node:node_projection_routing_decision omninode_internal node:node_projection_routing_decision:0021_create_agent_routing_decisions.sql 46d169e595f65ad89e7f524efffedb59eb0e1a37f9527e67f1ff2e8eaaa7711e nodes/node_projection_routing_decision/0022_agent_routing_decisions_tenant_id_and_rls.sql node:node_projection_routing_decision node:node_projection_routing_decision tenant node:node_projection_routing_decision:0022_agent_routing_decisions_tenant_id_and_rls.sql bac8be9bbe6167601ca805d35a78f469a6bce9ca9fc22e88a363c50dac0b7f83 nodes/node_projection_sandbox_decisions/0001_create_sandbox_decisions.sql node:node_projection_sandbox_decisions node:node_projection_sandbox_decisions omninode_internal node:node_projection_sandbox_decisions:0001_create_sandbox_decisions.sql dcf92194d262bb957dd5537a2d9e16083b89d2d1c259f08f869460b76145b96c diff --git a/docker/migrations/forward/nodes/node_projection_registration/0005_create_projection_watermarks.sql b/docker/migrations/forward/nodes/node_projection_registration/0005_create_projection_watermarks.sql index 03515b0094..4762f8f9f2 100644 --- a/docker/migrations/forward/nodes/node_projection_registration/0005_create_projection_watermarks.sql +++ b/docker/migrations/forward/nodes/node_projection_registration/0005_create_projection_watermarks.sql @@ -75,11 +75,48 @@ -- defaults rule, rather than swallowing that gap silently. -- -- Idempotency: CREATE TABLE / INDEX are guarded so the migration is safe on --- a DB where the table already exists and on a fresh omnidash_analytics. --- The GRANT below is idempotent by Postgres's own semantics (re-granting an --- already-held privilege is a no-op, not an error). +-- a DB where the table already exists. The GRANT below is idempotent by +-- Postgres's own semantics (re-granting an already-held privilege is a no-op, +-- not an error). +-- +-- NOT safe on a database where the omninode_internal schema is absent: this file +-- asserts that schema rather than creating it (see the precondition below for +-- why), so schema provisioning is a prerequisite, not something this migration +-- performs. An earlier revision of this comment claimed the file was safe "on a +-- fresh omnidash_analytics"; that was true only while it issued CREATE SCHEMA, +-- which is precisely the statement the deployed role has no privilege to run. -CREATE SCHEMA IF NOT EXISTS omninode_internal; +-- ----------------------------------------------------------------------------- +-- Precondition: the schema must ALREADY exist. This asserts; it does not create. +-- +-- This file previously ran `CREATE SCHEMA IF NOT EXISTS omninode_internal`, which +-- fails on the deployed onex-dev lane with: +-- +-- ERROR: permission denied for database omnidash_analytics +-- +-- CREATE SCHEMA requires CREATE on the DATABASE, which the migration role +-- (role_omnidash / NODE_DB_USER on the managed RDS lane) does not hold -- and +-- `IF NOT EXISTS` does not help, because Postgres checks the privilege before it +-- checks existence. The statement therefore failed even though the schema was +-- already present, taking the whole migrate Job past its backoff limit and, with +-- it, the entire staging deploy (every post-migration step, including the runtime +-- image pin, is skipped when this Job fails). +-- +-- The sibling file node_projection_live_events/0002_create_omninode_internal_live_events.sql +-- documents this exact hazard under the heading "THE SCHEMA TRAP THIS FILE +-- ASSERTS, NOT WORKS AROUND" and uses the pattern reproduced below: read +-- pg_catalog.pg_namespace, which needs no schema-level privilege and is therefore +-- safe under any connecting role, and let integer division by zero fail the +-- migration loudly when the schema is genuinely absent. Statically provable, so it +-- needs no DO/RAISE block (which the repo's dynamic-SQL rejection would refuse). +-- +-- Safe on this lane: 0002 above is recorded as already applied against +-- omnidash_analytics, and it carries this same divide-by-zero assert -- it could +-- not have applied if omninode_internal did not exist there. +-- ----------------------------------------------------------------------------- +SELECT 1 / count(*) AS omninode_internal_schema_exists_precondition + FROM pg_catalog.pg_namespace + WHERE nspname = 'omninode_internal'; CREATE TABLE IF NOT EXISTS omninode_internal.projection_watermarks ( projection_name TEXT PRIMARY KEY,