diff --git a/.github/omnimarket-contract-pin.yaml b/.github/omnimarket-contract-pin.yaml index aa88173220..dcd49dc1dc 100644 --- a/.github/omnimarket-contract-pin.yaml +++ b/.github/omnimarket-contract-pin.yaml @@ -32,4 +32,4 @@ # An `Omnimarket-Source-Ref: ` PR-body trailer still overrides this pin for # cross-repo co-development (OMN-15703's escape hatch, unchanged). repository: OmniNode-ai/omnimarket -omnimarket_contract_ref: a0941e08eb80dc4d6f1baefac4f6582b12ac3e34 +omnimarket_contract_ref: 8863472ce4730652d5074cdaad0cdf7f63215554 diff --git a/docker/migrations/forward/_ledger/application-migrations.tsv b/docker/migrations/forward/_ledger/application-migrations.tsv index 3421c9faa3..84f7fe1b70 100644 --- a/docker/migrations/forward/_ledger/application-migrations.tsv +++ b/docker/migrations/forward/_ledger/application-migrations.tsv @@ -148,3 +148,24 @@ nodes/node_projection_receipt_gate/0002_grant_omninode_runtime_receipt_gate_sequ nodes/node_canary_score_reducer/0004_grant_tenant_projection_writer_capability_scores_sequence.sql node:node_canary_score_reducer node:node_canary_score_reducer omninode_internal node:node_canary_score_reducer:0004_grant_tenant_projection_writer_capability_scores_sequence.sql e88e31a208ea0e1f17dfc46edbe002f2932dfcdb51f7cf4396bd7780de6f9530 nodes/node_delegation_routing_reducer/0003_grant_tenant_projection_writer_overlay_sequence.sql node:node_delegation_routing_reducer node:node_delegation_routing_reducer omninode_internal node:node_delegation_routing_reducer:0003_grant_tenant_projection_writer_overlay_sequence.sql 9b65f5d17d4dd7a4288160ee5832b09e7b66c7ae12533726f72b7d6e822fe4ff nodes/node_projection_dep_health/003_grant_tenant_projection_writer_dep_health_sequence.sql node:node_projection_dep_health node:node_projection_dep_health omninode_internal node:node_projection_dep_health:003_grant_tenant_projection_writer_dep_health_sequence.sql d776dced7888832011d1086b378693dc4f06b9241a9cc978e92fb948d0180d6a +nodes/node_deployment_evidence_reducer/0002_grant_omninode_runtime_deployment_evidence_tables.sql node:node_deployment_evidence_reducer node:node_deployment_evidence_reducer omninode_internal node:node_deployment_evidence_reducer:0002_grant_omninode_runtime_deployment_evidence_tables.sql dbe97c896cacd985977a22ee2997eb26ae3c4bfe019d2907165269878343e5ae +nodes/node_evidence_dashboard_reducer/0002_grant_omninode_runtime_evidence_dashboard_tables.sql node:node_evidence_dashboard_reducer node:node_evidence_dashboard_reducer omninode_internal node:node_evidence_dashboard_reducer:0002_grant_omninode_runtime_evidence_dashboard_tables.sql fd619046af813abb7a3e0c9441635277a1fe31040a207ede035409b33e91a46c +nodes/node_llm_delegation_projection/0002_grant_omninode_runtime_llm_delegation_daily_projection.sql node:node_llm_delegation_projection node:node_llm_delegation_projection omninode_internal node:node_llm_delegation_projection:0002_grant_omninode_runtime_llm_delegation_daily_projection.sql 971d02041f07744e26158fe7533b80cd82ec4c85d578ce81042fbede5fef4f93 +nodes/node_nightly_loop_controller/002_grant_omninode_runtime_nightly_loop_tables.sql node:node_nightly_loop_controller node:node_nightly_loop_controller omninode_internal node:node_nightly_loop_controller:002_grant_omninode_runtime_nightly_loop_tables.sql 1f3811924f38915d81002881c6396a3b216107c3e7869b30c0c895ecf88c47c8 +nodes/node_projection_baselines_quality/003_grant_omninode_runtime_baselines_quality_snapshots.sql node:node_projection_baselines_quality node:node_projection_baselines_quality omninode_internal node:node_projection_baselines_quality:003_grant_omninode_runtime_baselines_quality_snapshots.sql 9e02c9bfea641951898cf244208a95b622fad6470aae57e6cd1a2fc88321e56b +nodes/node_projection_baselines_roi/003_grant_omninode_runtime_baselines_roi_snapshots.sql node:node_projection_baselines_roi node:node_projection_baselines_roi omninode_internal node:node_projection_baselines_roi:003_grant_omninode_runtime_baselines_roi_snapshots.sql 20cf763840a6bf37f657a0688b50dc8cf739e184d03533c5e37fa74af8f4a995 +nodes/node_projection_capsule_store/080_grant_omninode_runtime_capsule_store.sql node:node_projection_capsule_store node:node_projection_capsule_store omninode_internal node:node_projection_capsule_store:080_grant_omninode_runtime_capsule_store.sql 029a9607b0b72576de70e9411c673d3816a03f2f446038bbc06cdad611b24540 +nodes/node_projection_consumer_flow/0003_grant_omninode_runtime_consumer_flow_tables.sql node:node_projection_consumer_flow node:node_projection_consumer_flow omninode_internal node:node_projection_consumer_flow:0003_grant_omninode_runtime_consumer_flow_tables.sql b5501f307278babfa25cdf857d4eff1e1419376302def0e691a1d38b77b25011 +nodes/node_projection_cost_by_repo/0002_grant_omninode_runtime_cost_by_repo_snapshots.sql node:node_projection_cost_by_repo node:node_projection_cost_by_repo omninode_internal node:node_projection_cost_by_repo:0002_grant_omninode_runtime_cost_by_repo_snapshots.sql fc54009197878a29eb4d8c342b6591e829e4f6a95363017b94de87de1b4d0231 +nodes/node_projection_delegation/0035_grant_omninode_runtime_generation_events.sql node:node_projection_delegation node:node_projection_delegation omninode_internal node:node_projection_delegation:0035_grant_omninode_runtime_generation_events.sql 91b652cffe8b45e3461b29ae568487574eeba3d6be37823a476eadb2bb0ba8d7 +nodes/node_projection_event_chain/0002_grant_omninode_runtime_event_chain.sql node:node_projection_event_chain node:node_projection_event_chain omninode_internal node:node_projection_event_chain:0002_grant_omninode_runtime_event_chain.sql d50723691cc0d2449cba3fe0d03978dd5bf93821021f8c7c597d2a9aadd09aca +nodes/node_projection_llm_cost/0002_grant_omninode_runtime_llm_call_metrics.sql node:node_projection_llm_cost node:node_projection_llm_cost omninode_internal node:node_projection_llm_cost:0002_grant_omninode_runtime_llm_call_metrics.sql 37dfc97f4d88770e9c7ad43554b25f964168b689afd57e1394796341927a7bab +nodes/node_projection_llm_routing/0002_grant_omninode_runtime_llm_routing_decisions.sql node:node_projection_llm_routing node:node_projection_llm_routing omninode_internal node:node_projection_llm_routing:0002_grant_omninode_runtime_llm_routing_decisions.sql 650852159b56e38c8d63f8662ac721b1342755ab219ef1a5c0a7e40c1cd4efff +nodes/node_projection_mcp_tools/0002_grant_omninode_runtime_mcp_tools.sql node:node_projection_mcp_tools node:node_projection_mcp_tools omninode_internal node:node_projection_mcp_tools:0002_grant_omninode_runtime_mcp_tools.sql bc569052aca8369bda3d04e3947e30ad74085b57ed05002828df67c6d0304068 +nodes/node_projection_registration/0006_grant_omninode_runtime_node_service_registry.sql node:node_projection_registration node:node_projection_registration omninode_internal node:node_projection_registration:0006_grant_omninode_runtime_node_service_registry.sql acee8d9a8c316986298b639cdfe02e01c4f416529a3f4f2fe3c78ceeeae5a0e4 +nodes/node_projection_sandbox_decisions/0002_grant_omninode_runtime_sandbox_decisions.sql node:node_projection_sandbox_decisions node:node_projection_sandbox_decisions omninode_internal node:node_projection_sandbox_decisions:0002_grant_omninode_runtime_sandbox_decisions.sql 164ced5dd108a6fed67449aaadd306291ed816abbe9aad6aa6e6f0049b4a3b76 +nodes/node_projection_session_outcome/0022_grant_omninode_runtime_session_outcomes.sql node:node_projection_session_outcome node:node_projection_session_outcome omninode_internal node:node_projection_session_outcome:0022_grant_omninode_runtime_session_outcomes.sql a6fb760e0669fe0157b12b67ed8083f15a6a61916bae5e188b1d44671a05db82 +nodes/node_projection_swarm/0002_grant_omninode_runtime_swarm_runs.sql node:node_projection_swarm node:node_projection_swarm omninode_internal node:node_projection_swarm:0002_grant_omninode_runtime_swarm_runs.sql 00ab3cf5e1d87a82f10331cffe5a3290bdf39ceaa109c874732dfa3c0edd9129 +nodes/node_projection_traces/0002_grant_omninode_runtime_traces.sql node:node_projection_traces node:node_projection_traces omninode_internal node:node_projection_traces:0002_grant_omninode_runtime_traces.sql ac6ddf2d26b8e9608ee53d0fd2c95ee9927a2a6d4776550207e7d8de3b43eae0 +nodes/node_projection_voice_sessions/0002_grant_omninode_runtime_voice_sessions.sql node:node_projection_voice_sessions node:node_projection_voice_sessions omninode_internal node:node_projection_voice_sessions:0002_grant_omninode_runtime_voice_sessions.sql 08c3dfad14758d423fb75656d5737496106dde0b81f167773b6cff75d9477311 +nodes/node_renderer_capability_projection/0002_grant_omninode_runtime_renderer_capability_projection.sql node:node_renderer_capability_projection node:node_renderer_capability_projection omninode_internal node:node_renderer_capability_projection:0002_grant_omninode_runtime_renderer_capability_projection.sql 56f3c455359f0ca5733a518b1136693ee1fd009e018a0c650dfb611cb34f55b6 diff --git a/docker/migrations/forward/nodes/node_deployment_evidence_reducer/0002_grant_omninode_runtime_deployment_evidence_tables.sql b/docker/migrations/forward/nodes/node_deployment_evidence_reducer/0002_grant_omninode_runtime_deployment_evidence_tables.sql new file mode 100644 index 0000000000..3d1f02e429 --- /dev/null +++ b/docker/migrations/forward/nodes/node_deployment_evidence_reducer/0002_grant_omninode_runtime_deployment_evidence_tables.sql @@ -0,0 +1,136 @@ +-- OMN-17440: topology-derived omninode_runtime TABLE grants for node_deployment_evidence_reducer. +-- Target DB: omnidash_analytics (NODE_POSTGRES_DB) +-- Node: node_deployment_evidence_reducer +-- +-- ============================================================================ +-- WHAT IS BROKEN +-- ============================================================================ +-- All three topology instances +-- (`omnibase_infra/src/omnibase_infra/topology/instances/{local,onex-dev, +-- onex-prod}.yaml`) declare `deployment_evidence_projection` and `deployment_readiness_projection` under +-- `databases.application.principals.omninode_runtime.grants[object_type: +-- TABLE, schema: public]` with `[INSERT, SELECT, UPDATE]`. That list is +-- GENERATED from node contract `db_io.db_tables` declarations by +-- `scripts/generate_application_database_table_grants.py --write`, so it is +-- the platform's machine-derived statement of intent. +-- +-- No migration in either repo has ever ISSUED them. The two halves -- +-- what the platform declares, and what a database is actually told -- drifted +-- apart silently, and the drift only ever surfaces as a live outage on +-- whichever relation happens to take traffic next: `InsufficientPrivilege` +-- on every write while the runtime reports healthy and commits its offsets +-- (the OMN-16993 / OMN-17379 failure shape). +-- +-- ============================================================================ +-- MEASURED, NOT ASSUMED +-- ============================================================================ +-- `scripts/validation/check_topology_grant_delivery.py` at omnibase_infra +-- dev 4053dc3c0 reported `topology grant delivery: 27 undelivered (of 65 +-- declared)`, and `deployment_evidence_projection`, `deployment_readiness_projection` were among them. +-- +-- Read-only probe of the `.201` dev lane (compose project `omnibase-infra`, +-- database `omnidash_analytics`) on 2026-09-03 against +-- `information_schema.role_table_grants`: 63 of the 65 declared grants are +-- present live, but only 38 of them are issued by any migration. The other +-- 26 -- `deployment_evidence_projection`, `deployment_readiness_projection` included -- exist ONLY because they were granted +-- out of band, BY HAND. That is precisely why the gap stayed invisible: a +-- lane that has never been hand-patched (a fresh staging namespace, a rebuilt +-- onex-dev, prod) gets none of them. +-- +-- ============================================================================ +-- WHY THIS LINEAGE, AND NOT ONE BULK GRANT FILE +-- ============================================================================ +-- This grant belongs next to the file that creates the relation, in the +-- lineage of the node that OWNS it -- the convention +-- `node_projection_session_replay/0002` (OMN-16993), +-- `node_projection_tenant_registry/0001` (OMN-17374) and the eight files the +-- first OMN-17440 tranche landed already follow. +-- +-- Deliberately NOT a shared cross-node grant file. That is the shape +-- `0004_grant_tenant_projection_writer.sql` took under protest -- "Homing a +-- cross-node grant block under one node is a real ownership compromise" -- +-- and it is exactly why a relation added to a node later silently misses its +-- grant. OMN-15701 is that failure: a pin regeneration reverted eight +-- house-tenant grants at once because they all lived in one place. +-- +-- The flat corpus cannot carry it either. Flat migrations are applied by a +-- `psql -f` loop gated on `directive_db == "$DB_NAME"`, while the node-owned +-- loop is the sanctioned path that connects directly to omnidash_analytics +-- (NODE_POSTGRES_DB). So the AUTHORIZATION rides here. +-- +-- ============================================================================ +-- PRIVILEGES +-- ============================================================================ +-- SELECT, INSERT, UPDATE and deliberately NO DELETE -- a projection writer +-- upserts, it does not reshape the table. Same invariant flat 096 states for +-- role_omnidash and flat 099 for this principal on +-- `omninode_internal.live_events`. PostgreSQL requires SELECT alongside +-- INSERT/UPDATE for the adapter's `INSERT ... ON CONFLICT DO UPDATE`, which +-- is why the write set is three privileges and not two. +-- +-- This widens NOTHING beyond what the generated topology already declares. +-- It grants only the relations THIS node owns. +-- +-- Physical schema is `public`: `0001_create_deployment_evidence_projection_tables.sql` is the file that creates +-- them, and a grant must name the PHYSICAL schema. +-- +-- `0001_create_deployment_evidence_projection_tables.sql` itself is NOT edited. It is applied on the `.201` dev +-- lane with a recorded content_sha256, so repairing its grant block in place +-- would raise "conflicting migration checksum in canonical node history" +-- (the OMN-16705 constraint). The grant is therefore a forward ADD. +-- +-- Idempotency: GRANT is idempotent; re-running is a no-op. Nothing here +-- touches RLS, ownership, table shape, or any role attribute. + +-- --------------------------------------------------------------------------- +-- 1. Schema USAGE, mirroring topology +-- `principals.omninode_runtime.grants[object_type: SCHEMA, schema: public]`. +-- Idempotent, and re-asserted here for the same reason flat 099 re-asserts +-- the omninode_internal one: a migration must not assume a sibling file ran. +-- --------------------------------------------------------------------------- +GRANT USAGE ON SCHEMA public TO omninode_runtime; + +-- --------------------------------------------------------------------------- +-- 2. Table grants (topology-derived, OMN-17440) +-- --------------------------------------------------------------------------- +GRANT SELECT, INSERT, UPDATE ON public.deployment_evidence_projection TO omninode_runtime; +GRANT SELECT, INSERT, UPDATE ON public.deployment_readiness_projection TO omninode_runtime; + +-- --------------------------------------------------------------------------- +-- 3. Assertions: fail the migration if a grant did not take. Division by zero +-- when the grant is absent -- the same fail-loud shape flat 099, +-- node_log_persistence_effect/0000, node_projection_session_replay/0002 and +-- node_projection_tenant_registry/0001 already use. +-- +-- BOTH directions are asserted per relation, not just INSERT. A lane where +-- the INSERT landed and the SELECT did not would accept writes and still +-- fail every `ON CONFLICT DO UPDATE` read-back, which is the harder of the +-- two failures to diagnose. +-- --------------------------------------------------------------------------- +SELECT 1 / count(*) AS deployment_evidence_projection_insert_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'deployment_evidence_projection' + AND grantee = 'omninode_runtime' + AND privilege_type = 'INSERT'; + +SELECT 1 / count(*) AS deployment_evidence_projection_select_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'deployment_evidence_projection' + AND grantee = 'omninode_runtime' + AND privilege_type = 'SELECT'; + +SELECT 1 / count(*) AS deployment_readiness_projection_insert_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'deployment_readiness_projection' + AND grantee = 'omninode_runtime' + AND privilege_type = 'INSERT'; + +SELECT 1 / count(*) AS deployment_readiness_projection_select_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'deployment_readiness_projection' + AND grantee = 'omninode_runtime' + AND privilege_type = 'SELECT'; diff --git a/docker/migrations/forward/nodes/node_evidence_dashboard_reducer/0002_grant_omninode_runtime_evidence_dashboard_tables.sql b/docker/migrations/forward/nodes/node_evidence_dashboard_reducer/0002_grant_omninode_runtime_evidence_dashboard_tables.sql new file mode 100644 index 0000000000..d224b61853 --- /dev/null +++ b/docker/migrations/forward/nodes/node_evidence_dashboard_reducer/0002_grant_omninode_runtime_evidence_dashboard_tables.sql @@ -0,0 +1,151 @@ +-- OMN-17440: topology-derived omninode_runtime TABLE grants for node_evidence_dashboard_reducer. +-- Target DB: omnidash_analytics (NODE_POSTGRES_DB) +-- Node: node_evidence_dashboard_reducer +-- +-- ============================================================================ +-- WHAT IS BROKEN +-- ============================================================================ +-- All three topology instances +-- (`omnibase_infra/src/omnibase_infra/topology/instances/{local,onex-dev, +-- onex-prod}.yaml`) declare `evidence_correlation_trace_projection`, `evidence_dashboard_projection` and `evidence_readiness_aggregate_projection` under +-- `databases.application.principals.omninode_runtime.grants[object_type: +-- TABLE, schema: public]` with `[INSERT, SELECT, UPDATE]`. That list is +-- GENERATED from node contract `db_io.db_tables` declarations by +-- `scripts/generate_application_database_table_grants.py --write`, so it is +-- the platform's machine-derived statement of intent. +-- +-- No migration in either repo has ever ISSUED them. The two halves -- +-- what the platform declares, and what a database is actually told -- drifted +-- apart silently, and the drift only ever surfaces as a live outage on +-- whichever relation happens to take traffic next: `InsufficientPrivilege` +-- on every write while the runtime reports healthy and commits its offsets +-- (the OMN-16993 / OMN-17379 failure shape). +-- +-- ============================================================================ +-- MEASURED, NOT ASSUMED +-- ============================================================================ +-- `scripts/validation/check_topology_grant_delivery.py` at omnibase_infra +-- dev 4053dc3c0 reported `topology grant delivery: 27 undelivered (of 65 +-- declared)`, and `evidence_correlation_trace_projection`, `evidence_dashboard_projection`, `evidence_readiness_aggregate_projection` were among them. +-- +-- Read-only probe of the `.201` dev lane (compose project `omnibase-infra`, +-- database `omnidash_analytics`) on 2026-09-03 against +-- `information_schema.role_table_grants`: 63 of the 65 declared grants are +-- present live, but only 38 of them are issued by any migration. The other +-- 26 -- `evidence_correlation_trace_projection`, `evidence_dashboard_projection`, `evidence_readiness_aggregate_projection` included -- exist ONLY because they were granted +-- out of band, BY HAND. That is precisely why the gap stayed invisible: a +-- lane that has never been hand-patched (a fresh staging namespace, a rebuilt +-- onex-dev, prod) gets none of them. +-- +-- ============================================================================ +-- WHY THIS LINEAGE, AND NOT ONE BULK GRANT FILE +-- ============================================================================ +-- This grant belongs next to the file that creates the relation, in the +-- lineage of the node that OWNS it -- the convention +-- `node_projection_session_replay/0002` (OMN-16993), +-- `node_projection_tenant_registry/0001` (OMN-17374) and the eight files the +-- first OMN-17440 tranche landed already follow. +-- +-- Deliberately NOT a shared cross-node grant file. That is the shape +-- `0004_grant_tenant_projection_writer.sql` took under protest -- "Homing a +-- cross-node grant block under one node is a real ownership compromise" -- +-- and it is exactly why a relation added to a node later silently misses its +-- grant. OMN-15701 is that failure: a pin regeneration reverted eight +-- house-tenant grants at once because they all lived in one place. +-- +-- The flat corpus cannot carry it either. Flat migrations are applied by a +-- `psql -f` loop gated on `directive_db == "$DB_NAME"`, while the node-owned +-- loop is the sanctioned path that connects directly to omnidash_analytics +-- (NODE_POSTGRES_DB). So the AUTHORIZATION rides here. +-- +-- ============================================================================ +-- PRIVILEGES +-- ============================================================================ +-- SELECT, INSERT, UPDATE and deliberately NO DELETE -- a projection writer +-- upserts, it does not reshape the table. Same invariant flat 096 states for +-- role_omnidash and flat 099 for this principal on +-- `omninode_internal.live_events`. PostgreSQL requires SELECT alongside +-- INSERT/UPDATE for the adapter's `INSERT ... ON CONFLICT DO UPDATE`, which +-- is why the write set is three privileges and not two. +-- +-- This widens NOTHING beyond what the generated topology already declares. +-- It grants only the relations THIS node owns. +-- +-- Physical schema is `public`: `0001_create_evidence_dashboard_projection_tables.sql` is the file that creates +-- them, and a grant must name the PHYSICAL schema. +-- +-- `0001_create_evidence_dashboard_projection_tables.sql` itself is NOT edited. It is applied on the `.201` dev +-- lane with a recorded content_sha256, so repairing its grant block in place +-- would raise "conflicting migration checksum in canonical node history" +-- (the OMN-16705 constraint). The grant is therefore a forward ADD. +-- +-- Idempotency: GRANT is idempotent; re-running is a no-op. Nothing here +-- touches RLS, ownership, table shape, or any role attribute. + +-- --------------------------------------------------------------------------- +-- 1. Schema USAGE, mirroring topology +-- `principals.omninode_runtime.grants[object_type: SCHEMA, schema: public]`. +-- Idempotent, and re-asserted here for the same reason flat 099 re-asserts +-- the omninode_internal one: a migration must not assume a sibling file ran. +-- --------------------------------------------------------------------------- +GRANT USAGE ON SCHEMA public TO omninode_runtime; + +-- --------------------------------------------------------------------------- +-- 2. Table grants (topology-derived, OMN-17440) +-- --------------------------------------------------------------------------- +GRANT SELECT, INSERT, UPDATE ON public.evidence_correlation_trace_projection TO omninode_runtime; +GRANT SELECT, INSERT, UPDATE ON public.evidence_dashboard_projection TO omninode_runtime; +GRANT SELECT, INSERT, UPDATE ON public.evidence_readiness_aggregate_projection TO omninode_runtime; + +-- --------------------------------------------------------------------------- +-- 3. Assertions: fail the migration if a grant did not take. Division by zero +-- when the grant is absent -- the same fail-loud shape flat 099, +-- node_log_persistence_effect/0000, node_projection_session_replay/0002 and +-- node_projection_tenant_registry/0001 already use. +-- +-- BOTH directions are asserted per relation, not just INSERT. A lane where +-- the INSERT landed and the SELECT did not would accept writes and still +-- fail every `ON CONFLICT DO UPDATE` read-back, which is the harder of the +-- two failures to diagnose. +-- --------------------------------------------------------------------------- +SELECT 1 / count(*) AS evidence_correlation_trace_projection_insert_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'evidence_correlation_trace_projection' + AND grantee = 'omninode_runtime' + AND privilege_type = 'INSERT'; + +SELECT 1 / count(*) AS evidence_correlation_trace_projection_select_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'evidence_correlation_trace_projection' + AND grantee = 'omninode_runtime' + AND privilege_type = 'SELECT'; + +SELECT 1 / count(*) AS evidence_dashboard_projection_insert_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'evidence_dashboard_projection' + AND grantee = 'omninode_runtime' + AND privilege_type = 'INSERT'; + +SELECT 1 / count(*) AS evidence_dashboard_projection_select_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'evidence_dashboard_projection' + AND grantee = 'omninode_runtime' + AND privilege_type = 'SELECT'; + +SELECT 1 / count(*) AS evidence_readiness_aggregate_projection_insert_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'evidence_readiness_aggregate_projection' + AND grantee = 'omninode_runtime' + AND privilege_type = 'INSERT'; + +SELECT 1 / count(*) AS evidence_readiness_aggregate_projection_select_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'evidence_readiness_aggregate_projection' + AND grantee = 'omninode_runtime' + AND privilege_type = 'SELECT'; diff --git a/docker/migrations/forward/nodes/node_llm_delegation_projection/0002_grant_omninode_runtime_llm_delegation_daily_projection.sql b/docker/migrations/forward/nodes/node_llm_delegation_projection/0002_grant_omninode_runtime_llm_delegation_daily_projection.sql new file mode 100644 index 0000000000..ecaf064a23 --- /dev/null +++ b/docker/migrations/forward/nodes/node_llm_delegation_projection/0002_grant_omninode_runtime_llm_delegation_daily_projection.sql @@ -0,0 +1,121 @@ +-- OMN-17440: topology-derived omninode_runtime TABLE grants for node_llm_delegation_projection. +-- Target DB: omnidash_analytics (NODE_POSTGRES_DB) +-- Node: node_llm_delegation_projection +-- +-- ============================================================================ +-- WHAT IS BROKEN +-- ============================================================================ +-- All three topology instances +-- (`omnibase_infra/src/omnibase_infra/topology/instances/{local,onex-dev, +-- onex-prod}.yaml`) declare `llm_delegation_daily_projection` under +-- `databases.application.principals.omninode_runtime.grants[object_type: +-- TABLE, schema: public]` with `[INSERT, SELECT, UPDATE]`. That list is +-- GENERATED from node contract `db_io.db_tables` declarations by +-- `scripts/generate_application_database_table_grants.py --write`, so it is +-- the platform's machine-derived statement of intent. +-- +-- No migration in either repo has ever ISSUED it. The two halves -- +-- what the platform declares, and what a database is actually told -- drifted +-- apart silently, and the drift only ever surfaces as a live outage on +-- whichever relation happens to take traffic next: `InsufficientPrivilege` +-- on every write while the runtime reports healthy and commits its offsets +-- (the OMN-16993 / OMN-17379 failure shape). +-- +-- ============================================================================ +-- MEASURED, NOT ASSUMED +-- ============================================================================ +-- `scripts/validation/check_topology_grant_delivery.py` at omnibase_infra +-- dev 4053dc3c0 reported `topology grant delivery: 27 undelivered (of 65 +-- declared)`, and `llm_delegation_daily_projection` was among them. +-- +-- Read-only probe of the `.201` dev lane (compose project `omnibase-infra`, +-- database `omnidash_analytics`) on 2026-09-03 against +-- `information_schema.role_table_grants`: 63 of the 65 declared grants are +-- present live, but only 38 of them are issued by any migration. The other +-- 26 -- `llm_delegation_daily_projection` included -- exist ONLY because they were granted +-- out of band, BY HAND. That is precisely why the gap stayed invisible: a +-- lane that has never been hand-patched (a fresh staging namespace, a rebuilt +-- onex-dev, prod) gets none of them. +-- +-- ============================================================================ +-- WHY THIS LINEAGE, AND NOT ONE BULK GRANT FILE +-- ============================================================================ +-- This grant belongs next to the file that creates the relation, in the +-- lineage of the node that OWNS it -- the convention +-- `node_projection_session_replay/0002` (OMN-16993), +-- `node_projection_tenant_registry/0001` (OMN-17374) and the eight files the +-- first OMN-17440 tranche landed already follow. +-- +-- Deliberately NOT a shared cross-node grant file. That is the shape +-- `0004_grant_tenant_projection_writer.sql` took under protest -- "Homing a +-- cross-node grant block under one node is a real ownership compromise" -- +-- and it is exactly why a relation added to a node later silently misses its +-- grant. OMN-15701 is that failure: a pin regeneration reverted eight +-- house-tenant grants at once because they all lived in one place. +-- +-- The flat corpus cannot carry it either. Flat migrations are applied by a +-- `psql -f` loop gated on `directive_db == "$DB_NAME"`, while the node-owned +-- loop is the sanctioned path that connects directly to omnidash_analytics +-- (NODE_POSTGRES_DB). So the AUTHORIZATION rides here. +-- +-- ============================================================================ +-- PRIVILEGES +-- ============================================================================ +-- SELECT, INSERT, UPDATE and deliberately NO DELETE -- a projection writer +-- upserts, it does not reshape the table. Same invariant flat 096 states for +-- role_omnidash and flat 099 for this principal on +-- `omninode_internal.live_events`. PostgreSQL requires SELECT alongside +-- INSERT/UPDATE for the adapter's `INSERT ... ON CONFLICT DO UPDATE`, which +-- is why the write set is three privileges and not two. +-- +-- This widens NOTHING beyond what the generated topology already declares. +-- It grants only the relations THIS node owns. +-- +-- Physical schema is `public`: `0001_create_llm_delegation_daily_projection.sql` is the file that creates +-- it, and a grant must name the PHYSICAL schema. +-- +-- `0001_create_llm_delegation_daily_projection.sql` itself is NOT edited. It is applied on the `.201` dev +-- lane with a recorded content_sha256, so repairing its grant block in place +-- would raise "conflicting migration checksum in canonical node history" +-- (the OMN-16705 constraint). The grant is therefore a forward ADD. +-- +-- Idempotency: GRANT is idempotent; re-running is a no-op. Nothing here +-- touches RLS, ownership, table shape, or any role attribute. + +-- --------------------------------------------------------------------------- +-- 1. Schema USAGE, mirroring topology +-- `principals.omninode_runtime.grants[object_type: SCHEMA, schema: public]`. +-- Idempotent, and re-asserted here for the same reason flat 099 re-asserts +-- the omninode_internal one: a migration must not assume a sibling file ran. +-- --------------------------------------------------------------------------- +GRANT USAGE ON SCHEMA public TO omninode_runtime; + +-- --------------------------------------------------------------------------- +-- 2. Table grants (topology-derived, OMN-17440) +-- --------------------------------------------------------------------------- +GRANT SELECT, INSERT, UPDATE ON public.llm_delegation_daily_projection TO omninode_runtime; + +-- --------------------------------------------------------------------------- +-- 3. Assertions: fail the migration if a grant did not take. Division by zero +-- when the grant is absent -- the same fail-loud shape flat 099, +-- node_log_persistence_effect/0000, node_projection_session_replay/0002 and +-- node_projection_tenant_registry/0001 already use. +-- +-- BOTH directions are asserted per relation, not just INSERT. A lane where +-- the INSERT landed and the SELECT did not would accept writes and still +-- fail every `ON CONFLICT DO UPDATE` read-back, which is the harder of the +-- two failures to diagnose. +-- --------------------------------------------------------------------------- +SELECT 1 / count(*) AS llm_delegation_daily_projection_insert_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'llm_delegation_daily_projection' + AND grantee = 'omninode_runtime' + AND privilege_type = 'INSERT'; + +SELECT 1 / count(*) AS llm_delegation_daily_projection_select_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'llm_delegation_daily_projection' + AND grantee = 'omninode_runtime' + AND privilege_type = 'SELECT'; diff --git a/docker/migrations/forward/nodes/node_nightly_loop_controller/002_grant_omninode_runtime_nightly_loop_tables.sql b/docker/migrations/forward/nodes/node_nightly_loop_controller/002_grant_omninode_runtime_nightly_loop_tables.sql new file mode 100644 index 0000000000..dd10b8b8da --- /dev/null +++ b/docker/migrations/forward/nodes/node_nightly_loop_controller/002_grant_omninode_runtime_nightly_loop_tables.sql @@ -0,0 +1,136 @@ +-- OMN-17440: topology-derived omninode_runtime TABLE grants for node_nightly_loop_controller. +-- Target DB: omnidash_analytics (NODE_POSTGRES_DB) +-- Node: node_nightly_loop_controller +-- +-- ============================================================================ +-- WHAT IS BROKEN +-- ============================================================================ +-- All three topology instances +-- (`omnibase_infra/src/omnibase_infra/topology/instances/{local,onex-dev, +-- onex-prod}.yaml`) declare `nightly_loop_decisions` and `nightly_loop_iterations` under +-- `databases.application.principals.omninode_runtime.grants[object_type: +-- TABLE, schema: public]` with `[INSERT, SELECT, UPDATE]`. That list is +-- GENERATED from node contract `db_io.db_tables` declarations by +-- `scripts/generate_application_database_table_grants.py --write`, so it is +-- the platform's machine-derived statement of intent. +-- +-- No migration in either repo has ever ISSUED them. The two halves -- +-- what the platform declares, and what a database is actually told -- drifted +-- apart silently, and the drift only ever surfaces as a live outage on +-- whichever relation happens to take traffic next: `InsufficientPrivilege` +-- on every write while the runtime reports healthy and commits its offsets +-- (the OMN-16993 / OMN-17379 failure shape). +-- +-- ============================================================================ +-- MEASURED, NOT ASSUMED +-- ============================================================================ +-- `scripts/validation/check_topology_grant_delivery.py` at omnibase_infra +-- dev 4053dc3c0 reported `topology grant delivery: 27 undelivered (of 65 +-- declared)`, and `nightly_loop_decisions`, `nightly_loop_iterations` were among them. +-- +-- Read-only probe of the `.201` dev lane (compose project `omnibase-infra`, +-- database `omnidash_analytics`) on 2026-09-03 against +-- `information_schema.role_table_grants`: 63 of the 65 declared grants are +-- present live, but only 38 of them are issued by any migration. The other +-- 26 -- `nightly_loop_decisions`, `nightly_loop_iterations` included -- exist ONLY because they were granted +-- out of band, BY HAND. That is precisely why the gap stayed invisible: a +-- lane that has never been hand-patched (a fresh staging namespace, a rebuilt +-- onex-dev, prod) gets none of them. +-- +-- ============================================================================ +-- WHY THIS LINEAGE, AND NOT ONE BULK GRANT FILE +-- ============================================================================ +-- This grant belongs next to the file that creates the relation, in the +-- lineage of the node that OWNS it -- the convention +-- `node_projection_session_replay/0002` (OMN-16993), +-- `node_projection_tenant_registry/0001` (OMN-17374) and the eight files the +-- first OMN-17440 tranche landed already follow. +-- +-- Deliberately NOT a shared cross-node grant file. That is the shape +-- `0004_grant_tenant_projection_writer.sql` took under protest -- "Homing a +-- cross-node grant block under one node is a real ownership compromise" -- +-- and it is exactly why a relation added to a node later silently misses its +-- grant. OMN-15701 is that failure: a pin regeneration reverted eight +-- house-tenant grants at once because they all lived in one place. +-- +-- The flat corpus cannot carry it either. Flat migrations are applied by a +-- `psql -f` loop gated on `directive_db == "$DB_NAME"`, while the node-owned +-- loop is the sanctioned path that connects directly to omnidash_analytics +-- (NODE_POSTGRES_DB). So the AUTHORIZATION rides here. +-- +-- ============================================================================ +-- PRIVILEGES +-- ============================================================================ +-- SELECT, INSERT, UPDATE and deliberately NO DELETE -- a projection writer +-- upserts, it does not reshape the table. Same invariant flat 096 states for +-- role_omnidash and flat 099 for this principal on +-- `omninode_internal.live_events`. PostgreSQL requires SELECT alongside +-- INSERT/UPDATE for the adapter's `INSERT ... ON CONFLICT DO UPDATE`, which +-- is why the write set is three privileges and not two. +-- +-- This widens NOTHING beyond what the generated topology already declares. +-- It grants only the relations THIS node owns. +-- +-- Physical schema is `public`: `001_create_nightly_loop_tables.sql` is the file that creates +-- them, and a grant must name the PHYSICAL schema. +-- +-- `001_create_nightly_loop_tables.sql` itself is NOT edited. It is applied on the `.201` dev +-- lane with a recorded content_sha256, so repairing its grant block in place +-- would raise "conflicting migration checksum in canonical node history" +-- (the OMN-16705 constraint). The grant is therefore a forward ADD. +-- +-- Idempotency: GRANT is idempotent; re-running is a no-op. Nothing here +-- touches RLS, ownership, table shape, or any role attribute. + +-- --------------------------------------------------------------------------- +-- 1. Schema USAGE, mirroring topology +-- `principals.omninode_runtime.grants[object_type: SCHEMA, schema: public]`. +-- Idempotent, and re-asserted here for the same reason flat 099 re-asserts +-- the omninode_internal one: a migration must not assume a sibling file ran. +-- --------------------------------------------------------------------------- +GRANT USAGE ON SCHEMA public TO omninode_runtime; + +-- --------------------------------------------------------------------------- +-- 2. Table grants (topology-derived, OMN-17440) +-- --------------------------------------------------------------------------- +GRANT SELECT, INSERT, UPDATE ON public.nightly_loop_decisions TO omninode_runtime; +GRANT SELECT, INSERT, UPDATE ON public.nightly_loop_iterations TO omninode_runtime; + +-- --------------------------------------------------------------------------- +-- 3. Assertions: fail the migration if a grant did not take. Division by zero +-- when the grant is absent -- the same fail-loud shape flat 099, +-- node_log_persistence_effect/0000, node_projection_session_replay/0002 and +-- node_projection_tenant_registry/0001 already use. +-- +-- BOTH directions are asserted per relation, not just INSERT. A lane where +-- the INSERT landed and the SELECT did not would accept writes and still +-- fail every `ON CONFLICT DO UPDATE` read-back, which is the harder of the +-- two failures to diagnose. +-- --------------------------------------------------------------------------- +SELECT 1 / count(*) AS nightly_loop_decisions_insert_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'nightly_loop_decisions' + AND grantee = 'omninode_runtime' + AND privilege_type = 'INSERT'; + +SELECT 1 / count(*) AS nightly_loop_decisions_select_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'nightly_loop_decisions' + AND grantee = 'omninode_runtime' + AND privilege_type = 'SELECT'; + +SELECT 1 / count(*) AS nightly_loop_iterations_insert_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'nightly_loop_iterations' + AND grantee = 'omninode_runtime' + AND privilege_type = 'INSERT'; + +SELECT 1 / count(*) AS nightly_loop_iterations_select_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'nightly_loop_iterations' + AND grantee = 'omninode_runtime' + AND privilege_type = 'SELECT'; diff --git a/docker/migrations/forward/nodes/node_projection_baselines_quality/003_grant_omninode_runtime_baselines_quality_snapshots.sql b/docker/migrations/forward/nodes/node_projection_baselines_quality/003_grant_omninode_runtime_baselines_quality_snapshots.sql new file mode 100644 index 0000000000..5e8276cb6a --- /dev/null +++ b/docker/migrations/forward/nodes/node_projection_baselines_quality/003_grant_omninode_runtime_baselines_quality_snapshots.sql @@ -0,0 +1,121 @@ +-- OMN-17440: topology-derived omninode_runtime TABLE grants for node_projection_baselines_quality. +-- Target DB: omnidash_analytics (NODE_POSTGRES_DB) +-- Node: node_projection_baselines_quality +-- +-- ============================================================================ +-- WHAT IS BROKEN +-- ============================================================================ +-- All three topology instances +-- (`omnibase_infra/src/omnibase_infra/topology/instances/{local,onex-dev, +-- onex-prod}.yaml`) declare `baselines_quality_snapshots` under +-- `databases.application.principals.omninode_runtime.grants[object_type: +-- TABLE, schema: public]` with `[INSERT, SELECT, UPDATE]`. That list is +-- GENERATED from node contract `db_io.db_tables` declarations by +-- `scripts/generate_application_database_table_grants.py --write`, so it is +-- the platform's machine-derived statement of intent. +-- +-- No migration in either repo has ever ISSUED it. The two halves -- +-- what the platform declares, and what a database is actually told -- drifted +-- apart silently, and the drift only ever surfaces as a live outage on +-- whichever relation happens to take traffic next: `InsufficientPrivilege` +-- on every write while the runtime reports healthy and commits its offsets +-- (the OMN-16993 / OMN-17379 failure shape). +-- +-- ============================================================================ +-- MEASURED, NOT ASSUMED +-- ============================================================================ +-- `scripts/validation/check_topology_grant_delivery.py` at omnibase_infra +-- dev 4053dc3c0 reported `topology grant delivery: 27 undelivered (of 65 +-- declared)`, and `baselines_quality_snapshots` was among them. +-- +-- Read-only probe of the `.201` dev lane (compose project `omnibase-infra`, +-- database `omnidash_analytics`) on 2026-09-03 against +-- `information_schema.role_table_grants`: 63 of the 65 declared grants are +-- present live, but only 38 of them are issued by any migration. The other +-- 26 -- `baselines_quality_snapshots` included -- exist ONLY because they were granted +-- out of band, BY HAND. That is precisely why the gap stayed invisible: a +-- lane that has never been hand-patched (a fresh staging namespace, a rebuilt +-- onex-dev, prod) gets none of them. +-- +-- ============================================================================ +-- WHY THIS LINEAGE, AND NOT ONE BULK GRANT FILE +-- ============================================================================ +-- This grant belongs next to the file that creates the relation, in the +-- lineage of the node that OWNS it -- the convention +-- `node_projection_session_replay/0002` (OMN-16993), +-- `node_projection_tenant_registry/0001` (OMN-17374) and the eight files the +-- first OMN-17440 tranche landed already follow. +-- +-- Deliberately NOT a shared cross-node grant file. That is the shape +-- `0004_grant_tenant_projection_writer.sql` took under protest -- "Homing a +-- cross-node grant block under one node is a real ownership compromise" -- +-- and it is exactly why a relation added to a node later silently misses its +-- grant. OMN-15701 is that failure: a pin regeneration reverted eight +-- house-tenant grants at once because they all lived in one place. +-- +-- The flat corpus cannot carry it either. Flat migrations are applied by a +-- `psql -f` loop gated on `directive_db == "$DB_NAME"`, while the node-owned +-- loop is the sanctioned path that connects directly to omnidash_analytics +-- (NODE_POSTGRES_DB). So the AUTHORIZATION rides here. +-- +-- ============================================================================ +-- PRIVILEGES +-- ============================================================================ +-- SELECT, INSERT, UPDATE and deliberately NO DELETE -- a projection writer +-- upserts, it does not reshape the table. Same invariant flat 096 states for +-- role_omnidash and flat 099 for this principal on +-- `omninode_internal.live_events`. PostgreSQL requires SELECT alongside +-- INSERT/UPDATE for the adapter's `INSERT ... ON CONFLICT DO UPDATE`, which +-- is why the write set is three privileges and not two. +-- +-- This widens NOTHING beyond what the generated topology already declares. +-- It grants only the relations THIS node owns. +-- +-- Physical schema is `public`: `001_create_baselines_quality_snapshots.sql` is the file that creates +-- it, and a grant must name the PHYSICAL schema. +-- +-- `001_create_baselines_quality_snapshots.sql` itself is NOT edited. It is applied on the `.201` dev +-- lane with a recorded content_sha256, so repairing its grant block in place +-- would raise "conflicting migration checksum in canonical node history" +-- (the OMN-16705 constraint). The grant is therefore a forward ADD. +-- +-- Idempotency: GRANT is idempotent; re-running is a no-op. Nothing here +-- touches RLS, ownership, table shape, or any role attribute. + +-- --------------------------------------------------------------------------- +-- 1. Schema USAGE, mirroring topology +-- `principals.omninode_runtime.grants[object_type: SCHEMA, schema: public]`. +-- Idempotent, and re-asserted here for the same reason flat 099 re-asserts +-- the omninode_internal one: a migration must not assume a sibling file ran. +-- --------------------------------------------------------------------------- +GRANT USAGE ON SCHEMA public TO omninode_runtime; + +-- --------------------------------------------------------------------------- +-- 2. Table grants (topology-derived, OMN-17440) +-- --------------------------------------------------------------------------- +GRANT SELECT, INSERT, UPDATE ON public.baselines_quality_snapshots TO omninode_runtime; + +-- --------------------------------------------------------------------------- +-- 3. Assertions: fail the migration if a grant did not take. Division by zero +-- when the grant is absent -- the same fail-loud shape flat 099, +-- node_log_persistence_effect/0000, node_projection_session_replay/0002 and +-- node_projection_tenant_registry/0001 already use. +-- +-- BOTH directions are asserted per relation, not just INSERT. A lane where +-- the INSERT landed and the SELECT did not would accept writes and still +-- fail every `ON CONFLICT DO UPDATE` read-back, which is the harder of the +-- two failures to diagnose. +-- --------------------------------------------------------------------------- +SELECT 1 / count(*) AS baselines_quality_snapshots_insert_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'baselines_quality_snapshots' + AND grantee = 'omninode_runtime' + AND privilege_type = 'INSERT'; + +SELECT 1 / count(*) AS baselines_quality_snapshots_select_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'baselines_quality_snapshots' + AND grantee = 'omninode_runtime' + AND privilege_type = 'SELECT'; diff --git a/docker/migrations/forward/nodes/node_projection_baselines_roi/003_grant_omninode_runtime_baselines_roi_snapshots.sql b/docker/migrations/forward/nodes/node_projection_baselines_roi/003_grant_omninode_runtime_baselines_roi_snapshots.sql new file mode 100644 index 0000000000..278ef1b9eb --- /dev/null +++ b/docker/migrations/forward/nodes/node_projection_baselines_roi/003_grant_omninode_runtime_baselines_roi_snapshots.sql @@ -0,0 +1,121 @@ +-- OMN-17440: topology-derived omninode_runtime TABLE grants for node_projection_baselines_roi. +-- Target DB: omnidash_analytics (NODE_POSTGRES_DB) +-- Node: node_projection_baselines_roi +-- +-- ============================================================================ +-- WHAT IS BROKEN +-- ============================================================================ +-- All three topology instances +-- (`omnibase_infra/src/omnibase_infra/topology/instances/{local,onex-dev, +-- onex-prod}.yaml`) declare `baselines_roi_snapshots` under +-- `databases.application.principals.omninode_runtime.grants[object_type: +-- TABLE, schema: public]` with `[INSERT, SELECT, UPDATE]`. That list is +-- GENERATED from node contract `db_io.db_tables` declarations by +-- `scripts/generate_application_database_table_grants.py --write`, so it is +-- the platform's machine-derived statement of intent. +-- +-- No migration in either repo has ever ISSUED it. The two halves -- +-- what the platform declares, and what a database is actually told -- drifted +-- apart silently, and the drift only ever surfaces as a live outage on +-- whichever relation happens to take traffic next: `InsufficientPrivilege` +-- on every write while the runtime reports healthy and commits its offsets +-- (the OMN-16993 / OMN-17379 failure shape). +-- +-- ============================================================================ +-- MEASURED, NOT ASSUMED +-- ============================================================================ +-- `scripts/validation/check_topology_grant_delivery.py` at omnibase_infra +-- dev 4053dc3c0 reported `topology grant delivery: 27 undelivered (of 65 +-- declared)`, and `baselines_roi_snapshots` was among them. +-- +-- Read-only probe of the `.201` dev lane (compose project `omnibase-infra`, +-- database `omnidash_analytics`) on 2026-09-03 against +-- `information_schema.role_table_grants`: 63 of the 65 declared grants are +-- present live, but only 38 of them are issued by any migration. The other +-- 26 -- `baselines_roi_snapshots` included -- exist ONLY because they were granted +-- out of band, BY HAND. That is precisely why the gap stayed invisible: a +-- lane that has never been hand-patched (a fresh staging namespace, a rebuilt +-- onex-dev, prod) gets none of them. +-- +-- ============================================================================ +-- WHY THIS LINEAGE, AND NOT ONE BULK GRANT FILE +-- ============================================================================ +-- This grant belongs next to the file that creates the relation, in the +-- lineage of the node that OWNS it -- the convention +-- `node_projection_session_replay/0002` (OMN-16993), +-- `node_projection_tenant_registry/0001` (OMN-17374) and the eight files the +-- first OMN-17440 tranche landed already follow. +-- +-- Deliberately NOT a shared cross-node grant file. That is the shape +-- `0004_grant_tenant_projection_writer.sql` took under protest -- "Homing a +-- cross-node grant block under one node is a real ownership compromise" -- +-- and it is exactly why a relation added to a node later silently misses its +-- grant. OMN-15701 is that failure: a pin regeneration reverted eight +-- house-tenant grants at once because they all lived in one place. +-- +-- The flat corpus cannot carry it either. Flat migrations are applied by a +-- `psql -f` loop gated on `directive_db == "$DB_NAME"`, while the node-owned +-- loop is the sanctioned path that connects directly to omnidash_analytics +-- (NODE_POSTGRES_DB). So the AUTHORIZATION rides here. +-- +-- ============================================================================ +-- PRIVILEGES +-- ============================================================================ +-- SELECT, INSERT, UPDATE and deliberately NO DELETE -- a projection writer +-- upserts, it does not reshape the table. Same invariant flat 096 states for +-- role_omnidash and flat 099 for this principal on +-- `omninode_internal.live_events`. PostgreSQL requires SELECT alongside +-- INSERT/UPDATE for the adapter's `INSERT ... ON CONFLICT DO UPDATE`, which +-- is why the write set is three privileges and not two. +-- +-- This widens NOTHING beyond what the generated topology already declares. +-- It grants only the relations THIS node owns. +-- +-- Physical schema is `public`: `001_create_baselines_roi_snapshots.sql` is the file that creates +-- it, and a grant must name the PHYSICAL schema. +-- +-- `001_create_baselines_roi_snapshots.sql` itself is NOT edited. It is applied on the `.201` dev +-- lane with a recorded content_sha256, so repairing its grant block in place +-- would raise "conflicting migration checksum in canonical node history" +-- (the OMN-16705 constraint). The grant is therefore a forward ADD. +-- +-- Idempotency: GRANT is idempotent; re-running is a no-op. Nothing here +-- touches RLS, ownership, table shape, or any role attribute. + +-- --------------------------------------------------------------------------- +-- 1. Schema USAGE, mirroring topology +-- `principals.omninode_runtime.grants[object_type: SCHEMA, schema: public]`. +-- Idempotent, and re-asserted here for the same reason flat 099 re-asserts +-- the omninode_internal one: a migration must not assume a sibling file ran. +-- --------------------------------------------------------------------------- +GRANT USAGE ON SCHEMA public TO omninode_runtime; + +-- --------------------------------------------------------------------------- +-- 2. Table grants (topology-derived, OMN-17440) +-- --------------------------------------------------------------------------- +GRANT SELECT, INSERT, UPDATE ON public.baselines_roi_snapshots TO omninode_runtime; + +-- --------------------------------------------------------------------------- +-- 3. Assertions: fail the migration if a grant did not take. Division by zero +-- when the grant is absent -- the same fail-loud shape flat 099, +-- node_log_persistence_effect/0000, node_projection_session_replay/0002 and +-- node_projection_tenant_registry/0001 already use. +-- +-- BOTH directions are asserted per relation, not just INSERT. A lane where +-- the INSERT landed and the SELECT did not would accept writes and still +-- fail every `ON CONFLICT DO UPDATE` read-back, which is the harder of the +-- two failures to diagnose. +-- --------------------------------------------------------------------------- +SELECT 1 / count(*) AS baselines_roi_snapshots_insert_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'baselines_roi_snapshots' + AND grantee = 'omninode_runtime' + AND privilege_type = 'INSERT'; + +SELECT 1 / count(*) AS baselines_roi_snapshots_select_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'baselines_roi_snapshots' + AND grantee = 'omninode_runtime' + AND privilege_type = 'SELECT'; diff --git a/docker/migrations/forward/nodes/node_projection_capsule_store/080_grant_omninode_runtime_capsule_store.sql b/docker/migrations/forward/nodes/node_projection_capsule_store/080_grant_omninode_runtime_capsule_store.sql new file mode 100644 index 0000000000..eb699d9a7f --- /dev/null +++ b/docker/migrations/forward/nodes/node_projection_capsule_store/080_grant_omninode_runtime_capsule_store.sql @@ -0,0 +1,121 @@ +-- OMN-17440: topology-derived omninode_runtime TABLE grants for node_projection_capsule_store. +-- Target DB: omnidash_analytics (NODE_POSTGRES_DB) +-- Node: node_projection_capsule_store +-- +-- ============================================================================ +-- WHAT IS BROKEN +-- ============================================================================ +-- All three topology instances +-- (`omnibase_infra/src/omnibase_infra/topology/instances/{local,onex-dev, +-- onex-prod}.yaml`) declare `capsule_store` under +-- `databases.application.principals.omninode_runtime.grants[object_type: +-- TABLE, schema: public]` with `[INSERT, SELECT, UPDATE]`. That list is +-- GENERATED from node contract `db_io.db_tables` declarations by +-- `scripts/generate_application_database_table_grants.py --write`, so it is +-- the platform's machine-derived statement of intent. +-- +-- No migration in either repo has ever ISSUED it. The two halves -- +-- what the platform declares, and what a database is actually told -- drifted +-- apart silently, and the drift only ever surfaces as a live outage on +-- whichever relation happens to take traffic next: `InsufficientPrivilege` +-- on every write while the runtime reports healthy and commits its offsets +-- (the OMN-16993 / OMN-17379 failure shape). +-- +-- ============================================================================ +-- MEASURED, NOT ASSUMED +-- ============================================================================ +-- `scripts/validation/check_topology_grant_delivery.py` at omnibase_infra +-- dev 4053dc3c0 reported `topology grant delivery: 27 undelivered (of 65 +-- declared)`, and `capsule_store` was among them. +-- +-- Read-only probe of the `.201` dev lane (compose project `omnibase-infra`, +-- database `omnidash_analytics`) on 2026-09-03 against +-- `information_schema.role_table_grants`: 63 of the 65 declared grants are +-- present live, but only 38 of them are issued by any migration. The other +-- 26 -- `capsule_store` included -- exist ONLY because they were granted +-- out of band, BY HAND. That is precisely why the gap stayed invisible: a +-- lane that has never been hand-patched (a fresh staging namespace, a rebuilt +-- onex-dev, prod) gets none of them. +-- +-- ============================================================================ +-- WHY THIS LINEAGE, AND NOT ONE BULK GRANT FILE +-- ============================================================================ +-- This grant belongs next to the file that creates the relation, in the +-- lineage of the node that OWNS it -- the convention +-- `node_projection_session_replay/0002` (OMN-16993), +-- `node_projection_tenant_registry/0001` (OMN-17374) and the eight files the +-- first OMN-17440 tranche landed already follow. +-- +-- Deliberately NOT a shared cross-node grant file. That is the shape +-- `0004_grant_tenant_projection_writer.sql` took under protest -- "Homing a +-- cross-node grant block under one node is a real ownership compromise" -- +-- and it is exactly why a relation added to a node later silently misses its +-- grant. OMN-15701 is that failure: a pin regeneration reverted eight +-- house-tenant grants at once because they all lived in one place. +-- +-- The flat corpus cannot carry it either. Flat migrations are applied by a +-- `psql -f` loop gated on `directive_db == "$DB_NAME"`, while the node-owned +-- loop is the sanctioned path that connects directly to omnidash_analytics +-- (NODE_POSTGRES_DB). So the AUTHORIZATION rides here. +-- +-- ============================================================================ +-- PRIVILEGES +-- ============================================================================ +-- SELECT, INSERT, UPDATE and deliberately NO DELETE -- a projection writer +-- upserts, it does not reshape the table. Same invariant flat 096 states for +-- role_omnidash and flat 099 for this principal on +-- `omninode_internal.live_events`. PostgreSQL requires SELECT alongside +-- INSERT/UPDATE for the adapter's `INSERT ... ON CONFLICT DO UPDATE`, which +-- is why the write set is three privileges and not two. +-- +-- This widens NOTHING beyond what the generated topology already declares. +-- It grants only the relations THIS node owns. +-- +-- Physical schema is `public`: `078_create_capsule_store.sql` is the file that creates +-- it, and a grant must name the PHYSICAL schema. +-- +-- `078_create_capsule_store.sql` itself is NOT edited. It is applied on the `.201` dev +-- lane with a recorded content_sha256, so repairing its grant block in place +-- would raise "conflicting migration checksum in canonical node history" +-- (the OMN-16705 constraint). The grant is therefore a forward ADD. +-- +-- Idempotency: GRANT is idempotent; re-running is a no-op. Nothing here +-- touches RLS, ownership, table shape, or any role attribute. + +-- --------------------------------------------------------------------------- +-- 1. Schema USAGE, mirroring topology +-- `principals.omninode_runtime.grants[object_type: SCHEMA, schema: public]`. +-- Idempotent, and re-asserted here for the same reason flat 099 re-asserts +-- the omninode_internal one: a migration must not assume a sibling file ran. +-- --------------------------------------------------------------------------- +GRANT USAGE ON SCHEMA public TO omninode_runtime; + +-- --------------------------------------------------------------------------- +-- 2. Table grants (topology-derived, OMN-17440) +-- --------------------------------------------------------------------------- +GRANT SELECT, INSERT, UPDATE ON public.capsule_store TO omninode_runtime; + +-- --------------------------------------------------------------------------- +-- 3. Assertions: fail the migration if a grant did not take. Division by zero +-- when the grant is absent -- the same fail-loud shape flat 099, +-- node_log_persistence_effect/0000, node_projection_session_replay/0002 and +-- node_projection_tenant_registry/0001 already use. +-- +-- BOTH directions are asserted per relation, not just INSERT. A lane where +-- the INSERT landed and the SELECT did not would accept writes and still +-- fail every `ON CONFLICT DO UPDATE` read-back, which is the harder of the +-- two failures to diagnose. +-- --------------------------------------------------------------------------- +SELECT 1 / count(*) AS capsule_store_insert_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'capsule_store' + AND grantee = 'omninode_runtime' + AND privilege_type = 'INSERT'; + +SELECT 1 / count(*) AS capsule_store_select_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'capsule_store' + AND grantee = 'omninode_runtime' + AND privilege_type = 'SELECT'; diff --git a/docker/migrations/forward/nodes/node_projection_consumer_flow/0003_grant_omninode_runtime_consumer_flow_tables.sql b/docker/migrations/forward/nodes/node_projection_consumer_flow/0003_grant_omninode_runtime_consumer_flow_tables.sql new file mode 100644 index 0000000000..d20edeaf6e --- /dev/null +++ b/docker/migrations/forward/nodes/node_projection_consumer_flow/0003_grant_omninode_runtime_consumer_flow_tables.sql @@ -0,0 +1,136 @@ +-- OMN-17440: topology-derived omninode_runtime TABLE grants for node_projection_consumer_flow. +-- Target DB: omnidash_analytics (NODE_POSTGRES_DB) +-- Node: node_projection_consumer_flow +-- +-- ============================================================================ +-- WHAT IS BROKEN +-- ============================================================================ +-- All three topology instances +-- (`omnibase_infra/src/omnibase_infra/topology/instances/{local,onex-dev, +-- onex-prod}.yaml`) declare `consumer_flow_windows` and `topic_produce_windows` under +-- `databases.application.principals.omninode_runtime.grants[object_type: +-- TABLE, schema: omninode_internal]` with `[INSERT, SELECT, UPDATE]`. That list is +-- GENERATED from node contract `db_io.db_tables` declarations by +-- `scripts/generate_application_database_table_grants.py --write`, so it is +-- the platform's machine-derived statement of intent. +-- +-- No migration in either repo has ever ISSUED them. The two halves -- +-- what the platform declares, and what a database is actually told -- drifted +-- apart silently, and the drift only ever surfaces as a live outage on +-- whichever relation happens to take traffic next: `InsufficientPrivilege` +-- on every write while the runtime reports healthy and commits its offsets +-- (the OMN-16993 / OMN-17379 failure shape). +-- +-- ============================================================================ +-- MEASURED, NOT ASSUMED +-- ============================================================================ +-- `scripts/validation/check_topology_grant_delivery.py` at omnibase_infra +-- dev 4053dc3c0 reported `topology grant delivery: 27 undelivered (of 65 +-- declared)`, and `consumer_flow_windows`, `topic_produce_windows` were among them. +-- +-- Read-only probe of the `.201` dev lane (compose project `omnibase-infra`, +-- database `omnidash_analytics`) on 2026-09-03 against +-- `information_schema.role_table_grants`: 63 of the 65 declared grants are +-- present live, but only 38 of them are issued by any migration. The other +-- 26 -- `consumer_flow_windows`, `topic_produce_windows` included -- exist ONLY because they were granted +-- out of band, BY HAND. That is precisely why the gap stayed invisible: a +-- lane that has never been hand-patched (a fresh staging namespace, a rebuilt +-- onex-dev, prod) gets none of them. +-- +-- ============================================================================ +-- WHY THIS LINEAGE, AND NOT ONE BULK GRANT FILE +-- ============================================================================ +-- This grant belongs next to the file that creates the relation, in the +-- lineage of the node that OWNS it -- the convention +-- `node_projection_session_replay/0002` (OMN-16993), +-- `node_projection_tenant_registry/0001` (OMN-17374) and the eight files the +-- first OMN-17440 tranche landed already follow. +-- +-- Deliberately NOT a shared cross-node grant file. That is the shape +-- `0004_grant_tenant_projection_writer.sql` took under protest -- "Homing a +-- cross-node grant block under one node is a real ownership compromise" -- +-- and it is exactly why a relation added to a node later silently misses its +-- grant. OMN-15701 is that failure: a pin regeneration reverted eight +-- house-tenant grants at once because they all lived in one place. +-- +-- The flat corpus cannot carry it either. Flat migrations are applied by a +-- `psql -f` loop gated on `directive_db == "$DB_NAME"`, while the node-owned +-- loop is the sanctioned path that connects directly to omnidash_analytics +-- (NODE_POSTGRES_DB). So the AUTHORIZATION rides here. +-- +-- ============================================================================ +-- PRIVILEGES +-- ============================================================================ +-- SELECT, INSERT, UPDATE and deliberately NO DELETE -- a projection writer +-- upserts, it does not reshape the table. Same invariant flat 096 states for +-- role_omnidash and flat 099 for this principal on +-- `omninode_internal.live_events`. PostgreSQL requires SELECT alongside +-- INSERT/UPDATE for the adapter's `INSERT ... ON CONFLICT DO UPDATE`, which +-- is why the write set is three privileges and not two. +-- +-- This widens NOTHING beyond what the generated topology already declares. +-- It grants only the relations THIS node owns. +-- +-- Physical schema is `omninode_internal`: `0000_create_consumer_flow_windows.sql` is the file that creates +-- them, and a grant must name the PHYSICAL schema. +-- +-- `0000_create_consumer_flow_windows.sql` itself is NOT edited. It is applied on the `.201` dev +-- lane with a recorded content_sha256, so repairing its grant block in place +-- would raise "conflicting migration checksum in canonical node history" +-- (the OMN-16705 constraint). The grant is therefore a forward ADD. +-- +-- Idempotency: GRANT is idempotent; re-running is a no-op. Nothing here +-- touches RLS, ownership, table shape, or any role attribute. + +-- --------------------------------------------------------------------------- +-- 1. Schema USAGE, mirroring topology +-- `principals.omninode_runtime.grants[object_type: SCHEMA, schema: omninode_internal]`. +-- Idempotent, and re-asserted here for the same reason flat 099 re-asserts +-- the omninode_internal one: a migration must not assume a sibling file ran. +-- --------------------------------------------------------------------------- +GRANT USAGE ON SCHEMA omninode_internal TO omninode_runtime; + +-- --------------------------------------------------------------------------- +-- 2. Table grants (topology-derived, OMN-17440) +-- --------------------------------------------------------------------------- +GRANT SELECT, INSERT, UPDATE ON omninode_internal.consumer_flow_windows TO omninode_runtime; +GRANT SELECT, INSERT, UPDATE ON omninode_internal.topic_produce_windows TO omninode_runtime; + +-- --------------------------------------------------------------------------- +-- 3. Assertions: fail the migration if a grant did not take. Division by zero +-- when the grant is absent -- the same fail-loud shape flat 099, +-- node_log_persistence_effect/0000, node_projection_session_replay/0002 and +-- node_projection_tenant_registry/0001 already use. +-- +-- BOTH directions are asserted per relation, not just INSERT. A lane where +-- the INSERT landed and the SELECT did not would accept writes and still +-- fail every `ON CONFLICT DO UPDATE` read-back, which is the harder of the +-- two failures to diagnose. +-- --------------------------------------------------------------------------- +SELECT 1 / count(*) AS consumer_flow_windows_insert_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'omninode_internal' + AND table_name = 'consumer_flow_windows' + AND grantee = 'omninode_runtime' + AND privilege_type = 'INSERT'; + +SELECT 1 / count(*) AS consumer_flow_windows_select_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'omninode_internal' + AND table_name = 'consumer_flow_windows' + AND grantee = 'omninode_runtime' + AND privilege_type = 'SELECT'; + +SELECT 1 / count(*) AS topic_produce_windows_insert_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'omninode_internal' + AND table_name = 'topic_produce_windows' + AND grantee = 'omninode_runtime' + AND privilege_type = 'INSERT'; + +SELECT 1 / count(*) AS topic_produce_windows_select_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'omninode_internal' + AND table_name = 'topic_produce_windows' + AND grantee = 'omninode_runtime' + AND privilege_type = 'SELECT'; diff --git a/docker/migrations/forward/nodes/node_projection_cost_by_repo/0002_grant_omninode_runtime_cost_by_repo_snapshots.sql b/docker/migrations/forward/nodes/node_projection_cost_by_repo/0002_grant_omninode_runtime_cost_by_repo_snapshots.sql new file mode 100644 index 0000000000..f827efe9c2 --- /dev/null +++ b/docker/migrations/forward/nodes/node_projection_cost_by_repo/0002_grant_omninode_runtime_cost_by_repo_snapshots.sql @@ -0,0 +1,121 @@ +-- OMN-17440: topology-derived omninode_runtime TABLE grants for node_projection_cost_by_repo. +-- Target DB: omnidash_analytics (NODE_POSTGRES_DB) +-- Node: node_projection_cost_by_repo +-- +-- ============================================================================ +-- WHAT IS BROKEN +-- ============================================================================ +-- All three topology instances +-- (`omnibase_infra/src/omnibase_infra/topology/instances/{local,onex-dev, +-- onex-prod}.yaml`) declare `cost_by_repo_snapshots` under +-- `databases.application.principals.omninode_runtime.grants[object_type: +-- TABLE, schema: public]` with `[INSERT, SELECT, UPDATE]`. That list is +-- GENERATED from node contract `db_io.db_tables` declarations by +-- `scripts/generate_application_database_table_grants.py --write`, so it is +-- the platform's machine-derived statement of intent. +-- +-- No migration in either repo has ever ISSUED it. The two halves -- +-- what the platform declares, and what a database is actually told -- drifted +-- apart silently, and the drift only ever surfaces as a live outage on +-- whichever relation happens to take traffic next: `InsufficientPrivilege` +-- on every write while the runtime reports healthy and commits its offsets +-- (the OMN-16993 / OMN-17379 failure shape). +-- +-- ============================================================================ +-- MEASURED, NOT ASSUMED +-- ============================================================================ +-- `scripts/validation/check_topology_grant_delivery.py` at omnibase_infra +-- dev 4053dc3c0 reported `topology grant delivery: 27 undelivered (of 65 +-- declared)`, and `cost_by_repo_snapshots` was among them. +-- +-- Read-only probe of the `.201` dev lane (compose project `omnibase-infra`, +-- database `omnidash_analytics`) on 2026-09-03 against +-- `information_schema.role_table_grants`: 63 of the 65 declared grants are +-- present live, but only 38 of them are issued by any migration. The other +-- 26 -- `cost_by_repo_snapshots` included -- exist ONLY because they were granted +-- out of band, BY HAND. That is precisely why the gap stayed invisible: a +-- lane that has never been hand-patched (a fresh staging namespace, a rebuilt +-- onex-dev, prod) gets none of them. +-- +-- ============================================================================ +-- WHY THIS LINEAGE, AND NOT ONE BULK GRANT FILE +-- ============================================================================ +-- This grant belongs next to the file that creates the relation, in the +-- lineage of the node that OWNS it -- the convention +-- `node_projection_session_replay/0002` (OMN-16993), +-- `node_projection_tenant_registry/0001` (OMN-17374) and the eight files the +-- first OMN-17440 tranche landed already follow. +-- +-- Deliberately NOT a shared cross-node grant file. That is the shape +-- `0004_grant_tenant_projection_writer.sql` took under protest -- "Homing a +-- cross-node grant block under one node is a real ownership compromise" -- +-- and it is exactly why a relation added to a node later silently misses its +-- grant. OMN-15701 is that failure: a pin regeneration reverted eight +-- house-tenant grants at once because they all lived in one place. +-- +-- The flat corpus cannot carry it either. Flat migrations are applied by a +-- `psql -f` loop gated on `directive_db == "$DB_NAME"`, while the node-owned +-- loop is the sanctioned path that connects directly to omnidash_analytics +-- (NODE_POSTGRES_DB). So the AUTHORIZATION rides here. +-- +-- ============================================================================ +-- PRIVILEGES +-- ============================================================================ +-- SELECT, INSERT, UPDATE and deliberately NO DELETE -- a projection writer +-- upserts, it does not reshape the table. Same invariant flat 096 states for +-- role_omnidash and flat 099 for this principal on +-- `omninode_internal.live_events`. PostgreSQL requires SELECT alongside +-- INSERT/UPDATE for the adapter's `INSERT ... ON CONFLICT DO UPDATE`, which +-- is why the write set is three privileges and not two. +-- +-- This widens NOTHING beyond what the generated topology already declares. +-- It grants only the relations THIS node owns. +-- +-- Physical schema is `public`: `0001_create_cost_by_repo_snapshots.sql` is the file that creates +-- it, and a grant must name the PHYSICAL schema. +-- +-- `0001_create_cost_by_repo_snapshots.sql` itself is NOT edited. It is applied on the `.201` dev +-- lane with a recorded content_sha256, so repairing its grant block in place +-- would raise "conflicting migration checksum in canonical node history" +-- (the OMN-16705 constraint). The grant is therefore a forward ADD. +-- +-- Idempotency: GRANT is idempotent; re-running is a no-op. Nothing here +-- touches RLS, ownership, table shape, or any role attribute. + +-- --------------------------------------------------------------------------- +-- 1. Schema USAGE, mirroring topology +-- `principals.omninode_runtime.grants[object_type: SCHEMA, schema: public]`. +-- Idempotent, and re-asserted here for the same reason flat 099 re-asserts +-- the omninode_internal one: a migration must not assume a sibling file ran. +-- --------------------------------------------------------------------------- +GRANT USAGE ON SCHEMA public TO omninode_runtime; + +-- --------------------------------------------------------------------------- +-- 2. Table grants (topology-derived, OMN-17440) +-- --------------------------------------------------------------------------- +GRANT SELECT, INSERT, UPDATE ON public.cost_by_repo_snapshots TO omninode_runtime; + +-- --------------------------------------------------------------------------- +-- 3. Assertions: fail the migration if a grant did not take. Division by zero +-- when the grant is absent -- the same fail-loud shape flat 099, +-- node_log_persistence_effect/0000, node_projection_session_replay/0002 and +-- node_projection_tenant_registry/0001 already use. +-- +-- BOTH directions are asserted per relation, not just INSERT. A lane where +-- the INSERT landed and the SELECT did not would accept writes and still +-- fail every `ON CONFLICT DO UPDATE` read-back, which is the harder of the +-- two failures to diagnose. +-- --------------------------------------------------------------------------- +SELECT 1 / count(*) AS cost_by_repo_snapshots_insert_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'cost_by_repo_snapshots' + AND grantee = 'omninode_runtime' + AND privilege_type = 'INSERT'; + +SELECT 1 / count(*) AS cost_by_repo_snapshots_select_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'cost_by_repo_snapshots' + AND grantee = 'omninode_runtime' + AND privilege_type = 'SELECT'; diff --git a/docker/migrations/forward/nodes/node_projection_delegation/0035_grant_omninode_runtime_generation_events.sql b/docker/migrations/forward/nodes/node_projection_delegation/0035_grant_omninode_runtime_generation_events.sql new file mode 100644 index 0000000000..982a323d09 --- /dev/null +++ b/docker/migrations/forward/nodes/node_projection_delegation/0035_grant_omninode_runtime_generation_events.sql @@ -0,0 +1,121 @@ +-- OMN-17440: topology-derived omninode_runtime TABLE grants for node_projection_delegation. +-- Target DB: omnidash_analytics (NODE_POSTGRES_DB) +-- Node: node_projection_delegation +-- +-- ============================================================================ +-- WHAT IS BROKEN +-- ============================================================================ +-- All three topology instances +-- (`omnibase_infra/src/omnibase_infra/topology/instances/{local,onex-dev, +-- onex-prod}.yaml`) declare `generation_events` under +-- `databases.application.principals.omninode_runtime.grants[object_type: +-- TABLE, schema: public]` with `[INSERT, SELECT, UPDATE]`. That list is +-- GENERATED from node contract `db_io.db_tables` declarations by +-- `scripts/generate_application_database_table_grants.py --write`, so it is +-- the platform's machine-derived statement of intent. +-- +-- No migration in either repo has ever ISSUED it. The two halves -- +-- what the platform declares, and what a database is actually told -- drifted +-- apart silently, and the drift only ever surfaces as a live outage on +-- whichever relation happens to take traffic next: `InsufficientPrivilege` +-- on every write while the runtime reports healthy and commits its offsets +-- (the OMN-16993 / OMN-17379 failure shape). +-- +-- ============================================================================ +-- MEASURED, NOT ASSUMED +-- ============================================================================ +-- `scripts/validation/check_topology_grant_delivery.py` at omnibase_infra +-- dev 4053dc3c0 reported `topology grant delivery: 27 undelivered (of 65 +-- declared)`, and `generation_events` was among them. +-- +-- Read-only probe of the `.201` dev lane (compose project `omnibase-infra`, +-- database `omnidash_analytics`) on 2026-09-03 against +-- `information_schema.role_table_grants`: 63 of the 65 declared grants are +-- present live, but only 38 of them are issued by any migration. The other +-- 26 -- `generation_events` included -- exist ONLY because they were granted +-- out of band, BY HAND. That is precisely why the gap stayed invisible: a +-- lane that has never been hand-patched (a fresh staging namespace, a rebuilt +-- onex-dev, prod) gets none of them. +-- +-- ============================================================================ +-- WHY THIS LINEAGE, AND NOT ONE BULK GRANT FILE +-- ============================================================================ +-- This grant belongs next to the file that creates the relation, in the +-- lineage of the node that OWNS it -- the convention +-- `node_projection_session_replay/0002` (OMN-16993), +-- `node_projection_tenant_registry/0001` (OMN-17374) and the eight files the +-- first OMN-17440 tranche landed already follow. +-- +-- Deliberately NOT a shared cross-node grant file. That is the shape +-- `0004_grant_tenant_projection_writer.sql` took under protest -- "Homing a +-- cross-node grant block under one node is a real ownership compromise" -- +-- and it is exactly why a relation added to a node later silently misses its +-- grant. OMN-15701 is that failure: a pin regeneration reverted eight +-- house-tenant grants at once because they all lived in one place. +-- +-- The flat corpus cannot carry it either. Flat migrations are applied by a +-- `psql -f` loop gated on `directive_db == "$DB_NAME"`, while the node-owned +-- loop is the sanctioned path that connects directly to omnidash_analytics +-- (NODE_POSTGRES_DB). So the AUTHORIZATION rides here. +-- +-- ============================================================================ +-- PRIVILEGES +-- ============================================================================ +-- SELECT, INSERT, UPDATE and deliberately NO DELETE -- a projection writer +-- upserts, it does not reshape the table. Same invariant flat 096 states for +-- role_omnidash and flat 099 for this principal on +-- `omninode_internal.live_events`. PostgreSQL requires SELECT alongside +-- INSERT/UPDATE for the adapter's `INSERT ... ON CONFLICT DO UPDATE`, which +-- is why the write set is three privileges and not two. +-- +-- This widens NOTHING beyond what the generated topology already declares. +-- It grants only the relations THIS node owns. +-- +-- Physical schema is `public`: `0008_generation_events.sql` is the file that creates +-- it, and a grant must name the PHYSICAL schema. +-- +-- `0008_generation_events.sql` itself is NOT edited. It is applied on the `.201` dev +-- lane with a recorded content_sha256, so repairing its grant block in place +-- would raise "conflicting migration checksum in canonical node history" +-- (the OMN-16705 constraint). The grant is therefore a forward ADD. +-- +-- Idempotency: GRANT is idempotent; re-running is a no-op. Nothing here +-- touches RLS, ownership, table shape, or any role attribute. + +-- --------------------------------------------------------------------------- +-- 1. Schema USAGE, mirroring topology +-- `principals.omninode_runtime.grants[object_type: SCHEMA, schema: public]`. +-- Idempotent, and re-asserted here for the same reason flat 099 re-asserts +-- the omninode_internal one: a migration must not assume a sibling file ran. +-- --------------------------------------------------------------------------- +GRANT USAGE ON SCHEMA public TO omninode_runtime; + +-- --------------------------------------------------------------------------- +-- 2. Table grants (topology-derived, OMN-17440) +-- --------------------------------------------------------------------------- +GRANT SELECT, INSERT, UPDATE ON public.generation_events TO omninode_runtime; + +-- --------------------------------------------------------------------------- +-- 3. Assertions: fail the migration if a grant did not take. Division by zero +-- when the grant is absent -- the same fail-loud shape flat 099, +-- node_log_persistence_effect/0000, node_projection_session_replay/0002 and +-- node_projection_tenant_registry/0001 already use. +-- +-- BOTH directions are asserted per relation, not just INSERT. A lane where +-- the INSERT landed and the SELECT did not would accept writes and still +-- fail every `ON CONFLICT DO UPDATE` read-back, which is the harder of the +-- two failures to diagnose. +-- --------------------------------------------------------------------------- +SELECT 1 / count(*) AS generation_events_insert_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'generation_events' + AND grantee = 'omninode_runtime' + AND privilege_type = 'INSERT'; + +SELECT 1 / count(*) AS generation_events_select_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'generation_events' + AND grantee = 'omninode_runtime' + AND privilege_type = 'SELECT'; diff --git a/docker/migrations/forward/nodes/node_projection_event_chain/0002_grant_omninode_runtime_event_chain.sql b/docker/migrations/forward/nodes/node_projection_event_chain/0002_grant_omninode_runtime_event_chain.sql new file mode 100644 index 0000000000..8fc37b5a07 --- /dev/null +++ b/docker/migrations/forward/nodes/node_projection_event_chain/0002_grant_omninode_runtime_event_chain.sql @@ -0,0 +1,121 @@ +-- OMN-17440: topology-derived omninode_runtime TABLE grants for node_projection_event_chain. +-- Target DB: omnidash_analytics (NODE_POSTGRES_DB) +-- Node: node_projection_event_chain +-- +-- ============================================================================ +-- WHAT IS BROKEN +-- ============================================================================ +-- All three topology instances +-- (`omnibase_infra/src/omnibase_infra/topology/instances/{local,onex-dev, +-- onex-prod}.yaml`) declare `event_chain` under +-- `databases.application.principals.omninode_runtime.grants[object_type: +-- TABLE, schema: public]` with `[INSERT, SELECT, UPDATE]`. That list is +-- GENERATED from node contract `db_io.db_tables` declarations by +-- `scripts/generate_application_database_table_grants.py --write`, so it is +-- the platform's machine-derived statement of intent. +-- +-- No migration in either repo has ever ISSUED it. The two halves -- +-- what the platform declares, and what a database is actually told -- drifted +-- apart silently, and the drift only ever surfaces as a live outage on +-- whichever relation happens to take traffic next: `InsufficientPrivilege` +-- on every write while the runtime reports healthy and commits its offsets +-- (the OMN-16993 / OMN-17379 failure shape). +-- +-- ============================================================================ +-- MEASURED, NOT ASSUMED +-- ============================================================================ +-- `scripts/validation/check_topology_grant_delivery.py` at omnibase_infra +-- dev 4053dc3c0 reported `topology grant delivery: 27 undelivered (of 65 +-- declared)`, and `event_chain` was among them. +-- +-- Read-only probe of the `.201` dev lane (compose project `omnibase-infra`, +-- database `omnidash_analytics`) on 2026-09-03 against +-- `information_schema.role_table_grants`: 63 of the 65 declared grants are +-- present live, but only 38 of them are issued by any migration. The other +-- 26 -- `event_chain` included -- exist ONLY because they were granted +-- out of band, BY HAND. That is precisely why the gap stayed invisible: a +-- lane that has never been hand-patched (a fresh staging namespace, a rebuilt +-- onex-dev, prod) gets none of them. +-- +-- ============================================================================ +-- WHY THIS LINEAGE, AND NOT ONE BULK GRANT FILE +-- ============================================================================ +-- This grant belongs next to the file that creates the relation, in the +-- lineage of the node that OWNS it -- the convention +-- `node_projection_session_replay/0002` (OMN-16993), +-- `node_projection_tenant_registry/0001` (OMN-17374) and the eight files the +-- first OMN-17440 tranche landed already follow. +-- +-- Deliberately NOT a shared cross-node grant file. That is the shape +-- `0004_grant_tenant_projection_writer.sql` took under protest -- "Homing a +-- cross-node grant block under one node is a real ownership compromise" -- +-- and it is exactly why a relation added to a node later silently misses its +-- grant. OMN-15701 is that failure: a pin regeneration reverted eight +-- house-tenant grants at once because they all lived in one place. +-- +-- The flat corpus cannot carry it either. Flat migrations are applied by a +-- `psql -f` loop gated on `directive_db == "$DB_NAME"`, while the node-owned +-- loop is the sanctioned path that connects directly to omnidash_analytics +-- (NODE_POSTGRES_DB). So the AUTHORIZATION rides here. +-- +-- ============================================================================ +-- PRIVILEGES +-- ============================================================================ +-- SELECT, INSERT, UPDATE and deliberately NO DELETE -- a projection writer +-- upserts, it does not reshape the table. Same invariant flat 096 states for +-- role_omnidash and flat 099 for this principal on +-- `omninode_internal.live_events`. PostgreSQL requires SELECT alongside +-- INSERT/UPDATE for the adapter's `INSERT ... ON CONFLICT DO UPDATE`, which +-- is why the write set is three privileges and not two. +-- +-- This widens NOTHING beyond what the generated topology already declares. +-- It grants only the relations THIS node owns. +-- +-- Physical schema is `public`: `0001_create_event_chain.sql` is the file that creates +-- it, and a grant must name the PHYSICAL schema. +-- +-- `0001_create_event_chain.sql` itself is NOT edited. It is applied on the `.201` dev +-- lane with a recorded content_sha256, so repairing its grant block in place +-- would raise "conflicting migration checksum in canonical node history" +-- (the OMN-16705 constraint). The grant is therefore a forward ADD. +-- +-- Idempotency: GRANT is idempotent; re-running is a no-op. Nothing here +-- touches RLS, ownership, table shape, or any role attribute. + +-- --------------------------------------------------------------------------- +-- 1. Schema USAGE, mirroring topology +-- `principals.omninode_runtime.grants[object_type: SCHEMA, schema: public]`. +-- Idempotent, and re-asserted here for the same reason flat 099 re-asserts +-- the omninode_internal one: a migration must not assume a sibling file ran. +-- --------------------------------------------------------------------------- +GRANT USAGE ON SCHEMA public TO omninode_runtime; + +-- --------------------------------------------------------------------------- +-- 2. Table grants (topology-derived, OMN-17440) +-- --------------------------------------------------------------------------- +GRANT SELECT, INSERT, UPDATE ON public.event_chain TO omninode_runtime; + +-- --------------------------------------------------------------------------- +-- 3. Assertions: fail the migration if a grant did not take. Division by zero +-- when the grant is absent -- the same fail-loud shape flat 099, +-- node_log_persistence_effect/0000, node_projection_session_replay/0002 and +-- node_projection_tenant_registry/0001 already use. +-- +-- BOTH directions are asserted per relation, not just INSERT. A lane where +-- the INSERT landed and the SELECT did not would accept writes and still +-- fail every `ON CONFLICT DO UPDATE` read-back, which is the harder of the +-- two failures to diagnose. +-- --------------------------------------------------------------------------- +SELECT 1 / count(*) AS event_chain_insert_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'event_chain' + AND grantee = 'omninode_runtime' + AND privilege_type = 'INSERT'; + +SELECT 1 / count(*) AS event_chain_select_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'event_chain' + AND grantee = 'omninode_runtime' + AND privilege_type = 'SELECT'; diff --git a/docker/migrations/forward/nodes/node_projection_llm_cost/0002_grant_omninode_runtime_llm_call_metrics.sql b/docker/migrations/forward/nodes/node_projection_llm_cost/0002_grant_omninode_runtime_llm_call_metrics.sql new file mode 100644 index 0000000000..69820c451a --- /dev/null +++ b/docker/migrations/forward/nodes/node_projection_llm_cost/0002_grant_omninode_runtime_llm_call_metrics.sql @@ -0,0 +1,121 @@ +-- OMN-17440: topology-derived omninode_runtime TABLE grants for node_projection_llm_cost. +-- Target DB: omnidash_analytics (NODE_POSTGRES_DB) +-- Node: node_projection_llm_cost +-- +-- ============================================================================ +-- WHAT IS BROKEN +-- ============================================================================ +-- All three topology instances +-- (`omnibase_infra/src/omnibase_infra/topology/instances/{local,onex-dev, +-- onex-prod}.yaml`) declare `llm_call_metrics` under +-- `databases.application.principals.omninode_runtime.grants[object_type: +-- TABLE, schema: public]` with `[INSERT, SELECT, UPDATE]`. That list is +-- GENERATED from node contract `db_io.db_tables` declarations by +-- `scripts/generate_application_database_table_grants.py --write`, so it is +-- the platform's machine-derived statement of intent. +-- +-- No migration in either repo has ever ISSUED it. The two halves -- +-- what the platform declares, and what a database is actually told -- drifted +-- apart silently, and the drift only ever surfaces as a live outage on +-- whichever relation happens to take traffic next: `InsufficientPrivilege` +-- on every write while the runtime reports healthy and commits its offsets +-- (the OMN-16993 / OMN-17379 failure shape). +-- +-- ============================================================================ +-- MEASURED, NOT ASSUMED +-- ============================================================================ +-- `scripts/validation/check_topology_grant_delivery.py` at omnibase_infra +-- dev 4053dc3c0 reported `topology grant delivery: 27 undelivered (of 65 +-- declared)`, and `llm_call_metrics` was among them. +-- +-- Read-only probe of the `.201` dev lane (compose project `omnibase-infra`, +-- database `omnidash_analytics`) on 2026-09-03 against +-- `information_schema.role_table_grants`: 63 of the 65 declared grants are +-- present live, but only 38 of them are issued by any migration. The other +-- 26 -- `llm_call_metrics` included -- exist ONLY because they were granted +-- out of band, BY HAND. That is precisely why the gap stayed invisible: a +-- lane that has never been hand-patched (a fresh staging namespace, a rebuilt +-- onex-dev, prod) gets none of them. +-- +-- ============================================================================ +-- WHY THIS LINEAGE, AND NOT ONE BULK GRANT FILE +-- ============================================================================ +-- This grant belongs next to the file that creates the relation, in the +-- lineage of the node that OWNS it -- the convention +-- `node_projection_session_replay/0002` (OMN-16993), +-- `node_projection_tenant_registry/0001` (OMN-17374) and the eight files the +-- first OMN-17440 tranche landed already follow. +-- +-- Deliberately NOT a shared cross-node grant file. That is the shape +-- `0004_grant_tenant_projection_writer.sql` took under protest -- "Homing a +-- cross-node grant block under one node is a real ownership compromise" -- +-- and it is exactly why a relation added to a node later silently misses its +-- grant. OMN-15701 is that failure: a pin regeneration reverted eight +-- house-tenant grants at once because they all lived in one place. +-- +-- The flat corpus cannot carry it either. Flat migrations are applied by a +-- `psql -f` loop gated on `directive_db == "$DB_NAME"`, while the node-owned +-- loop is the sanctioned path that connects directly to omnidash_analytics +-- (NODE_POSTGRES_DB). So the AUTHORIZATION rides here. +-- +-- ============================================================================ +-- PRIVILEGES +-- ============================================================================ +-- SELECT, INSERT, UPDATE and deliberately NO DELETE -- a projection writer +-- upserts, it does not reshape the table. Same invariant flat 096 states for +-- role_omnidash and flat 099 for this principal on +-- `omninode_internal.live_events`. PostgreSQL requires SELECT alongside +-- INSERT/UPDATE for the adapter's `INSERT ... ON CONFLICT DO UPDATE`, which +-- is why the write set is three privileges and not two. +-- +-- This widens NOTHING beyond what the generated topology already declares. +-- It grants only the relations THIS node owns. +-- +-- Physical schema is `public`: `0001_create_llm_call_metrics.sql` is the file that creates +-- it, and a grant must name the PHYSICAL schema. +-- +-- `0001_create_llm_call_metrics.sql` itself is NOT edited. It is applied on the `.201` dev +-- lane with a recorded content_sha256, so repairing its grant block in place +-- would raise "conflicting migration checksum in canonical node history" +-- (the OMN-16705 constraint). The grant is therefore a forward ADD. +-- +-- Idempotency: GRANT is idempotent; re-running is a no-op. Nothing here +-- touches RLS, ownership, table shape, or any role attribute. + +-- --------------------------------------------------------------------------- +-- 1. Schema USAGE, mirroring topology +-- `principals.omninode_runtime.grants[object_type: SCHEMA, schema: public]`. +-- Idempotent, and re-asserted here for the same reason flat 099 re-asserts +-- the omninode_internal one: a migration must not assume a sibling file ran. +-- --------------------------------------------------------------------------- +GRANT USAGE ON SCHEMA public TO omninode_runtime; + +-- --------------------------------------------------------------------------- +-- 2. Table grants (topology-derived, OMN-17440) +-- --------------------------------------------------------------------------- +GRANT SELECT, INSERT, UPDATE ON public.llm_call_metrics TO omninode_runtime; + +-- --------------------------------------------------------------------------- +-- 3. Assertions: fail the migration if a grant did not take. Division by zero +-- when the grant is absent -- the same fail-loud shape flat 099, +-- node_log_persistence_effect/0000, node_projection_session_replay/0002 and +-- node_projection_tenant_registry/0001 already use. +-- +-- BOTH directions are asserted per relation, not just INSERT. A lane where +-- the INSERT landed and the SELECT did not would accept writes and still +-- fail every `ON CONFLICT DO UPDATE` read-back, which is the harder of the +-- two failures to diagnose. +-- --------------------------------------------------------------------------- +SELECT 1 / count(*) AS llm_call_metrics_insert_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'llm_call_metrics' + AND grantee = 'omninode_runtime' + AND privilege_type = 'INSERT'; + +SELECT 1 / count(*) AS llm_call_metrics_select_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'llm_call_metrics' + AND grantee = 'omninode_runtime' + AND privilege_type = 'SELECT'; diff --git a/docker/migrations/forward/nodes/node_projection_llm_routing/0002_grant_omninode_runtime_llm_routing_decisions.sql b/docker/migrations/forward/nodes/node_projection_llm_routing/0002_grant_omninode_runtime_llm_routing_decisions.sql new file mode 100644 index 0000000000..7133414c9d --- /dev/null +++ b/docker/migrations/forward/nodes/node_projection_llm_routing/0002_grant_omninode_runtime_llm_routing_decisions.sql @@ -0,0 +1,121 @@ +-- OMN-17440: topology-derived omninode_runtime TABLE grants for node_projection_llm_routing. +-- Target DB: omnidash_analytics (NODE_POSTGRES_DB) +-- Node: node_projection_llm_routing +-- +-- ============================================================================ +-- WHAT IS BROKEN +-- ============================================================================ +-- All three topology instances +-- (`omnibase_infra/src/omnibase_infra/topology/instances/{local,onex-dev, +-- onex-prod}.yaml`) declare `llm_routing_decisions` under +-- `databases.application.principals.omninode_runtime.grants[object_type: +-- TABLE, schema: public]` with `[INSERT, SELECT, UPDATE]`. That list is +-- GENERATED from node contract `db_io.db_tables` declarations by +-- `scripts/generate_application_database_table_grants.py --write`, so it is +-- the platform's machine-derived statement of intent. +-- +-- No migration in either repo has ever ISSUED it. The two halves -- +-- what the platform declares, and what a database is actually told -- drifted +-- apart silently, and the drift only ever surfaces as a live outage on +-- whichever relation happens to take traffic next: `InsufficientPrivilege` +-- on every write while the runtime reports healthy and commits its offsets +-- (the OMN-16993 / OMN-17379 failure shape). +-- +-- ============================================================================ +-- MEASURED, NOT ASSUMED +-- ============================================================================ +-- `scripts/validation/check_topology_grant_delivery.py` at omnibase_infra +-- dev 4053dc3c0 reported `topology grant delivery: 27 undelivered (of 65 +-- declared)`, and `llm_routing_decisions` was among them. +-- +-- Read-only probe of the `.201` dev lane (compose project `omnibase-infra`, +-- database `omnidash_analytics`) on 2026-09-03 against +-- `information_schema.role_table_grants`: 63 of the 65 declared grants are +-- present live, but only 38 of them are issued by any migration. The other +-- 26 -- `llm_routing_decisions` included -- exist ONLY because they were granted +-- out of band, BY HAND. That is precisely why the gap stayed invisible: a +-- lane that has never been hand-patched (a fresh staging namespace, a rebuilt +-- onex-dev, prod) gets none of them. +-- +-- ============================================================================ +-- WHY THIS LINEAGE, AND NOT ONE BULK GRANT FILE +-- ============================================================================ +-- This grant belongs next to the file that creates the relation, in the +-- lineage of the node that OWNS it -- the convention +-- `node_projection_session_replay/0002` (OMN-16993), +-- `node_projection_tenant_registry/0001` (OMN-17374) and the eight files the +-- first OMN-17440 tranche landed already follow. +-- +-- Deliberately NOT a shared cross-node grant file. That is the shape +-- `0004_grant_tenant_projection_writer.sql` took under protest -- "Homing a +-- cross-node grant block under one node is a real ownership compromise" -- +-- and it is exactly why a relation added to a node later silently misses its +-- grant. OMN-15701 is that failure: a pin regeneration reverted eight +-- house-tenant grants at once because they all lived in one place. +-- +-- The flat corpus cannot carry it either. Flat migrations are applied by a +-- `psql -f` loop gated on `directive_db == "$DB_NAME"`, while the node-owned +-- loop is the sanctioned path that connects directly to omnidash_analytics +-- (NODE_POSTGRES_DB). So the AUTHORIZATION rides here. +-- +-- ============================================================================ +-- PRIVILEGES +-- ============================================================================ +-- SELECT, INSERT, UPDATE and deliberately NO DELETE -- a projection writer +-- upserts, it does not reshape the table. Same invariant flat 096 states for +-- role_omnidash and flat 099 for this principal on +-- `omninode_internal.live_events`. PostgreSQL requires SELECT alongside +-- INSERT/UPDATE for the adapter's `INSERT ... ON CONFLICT DO UPDATE`, which +-- is why the write set is three privileges and not two. +-- +-- This widens NOTHING beyond what the generated topology already declares. +-- It grants only the relations THIS node owns. +-- +-- Physical schema is `public`: `0000_create_llm_routing_decisions.sql` is the file that creates +-- it, and a grant must name the PHYSICAL schema. +-- +-- `0000_create_llm_routing_decisions.sql` itself is NOT edited. It is applied on the `.201` dev +-- lane with a recorded content_sha256, so repairing its grant block in place +-- would raise "conflicting migration checksum in canonical node history" +-- (the OMN-16705 constraint). The grant is therefore a forward ADD. +-- +-- Idempotency: GRANT is idempotent; re-running is a no-op. Nothing here +-- touches RLS, ownership, table shape, or any role attribute. + +-- --------------------------------------------------------------------------- +-- 1. Schema USAGE, mirroring topology +-- `principals.omninode_runtime.grants[object_type: SCHEMA, schema: public]`. +-- Idempotent, and re-asserted here for the same reason flat 099 re-asserts +-- the omninode_internal one: a migration must not assume a sibling file ran. +-- --------------------------------------------------------------------------- +GRANT USAGE ON SCHEMA public TO omninode_runtime; + +-- --------------------------------------------------------------------------- +-- 2. Table grants (topology-derived, OMN-17440) +-- --------------------------------------------------------------------------- +GRANT SELECT, INSERT, UPDATE ON public.llm_routing_decisions TO omninode_runtime; + +-- --------------------------------------------------------------------------- +-- 3. Assertions: fail the migration if a grant did not take. Division by zero +-- when the grant is absent -- the same fail-loud shape flat 099, +-- node_log_persistence_effect/0000, node_projection_session_replay/0002 and +-- node_projection_tenant_registry/0001 already use. +-- +-- BOTH directions are asserted per relation, not just INSERT. A lane where +-- the INSERT landed and the SELECT did not would accept writes and still +-- fail every `ON CONFLICT DO UPDATE` read-back, which is the harder of the +-- two failures to diagnose. +-- --------------------------------------------------------------------------- +SELECT 1 / count(*) AS llm_routing_decisions_insert_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'llm_routing_decisions' + AND grantee = 'omninode_runtime' + AND privilege_type = 'INSERT'; + +SELECT 1 / count(*) AS llm_routing_decisions_select_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'llm_routing_decisions' + AND grantee = 'omninode_runtime' + AND privilege_type = 'SELECT'; diff --git a/docker/migrations/forward/nodes/node_projection_mcp_tools/0002_grant_omninode_runtime_mcp_tools.sql b/docker/migrations/forward/nodes/node_projection_mcp_tools/0002_grant_omninode_runtime_mcp_tools.sql new file mode 100644 index 0000000000..5b7bc75d5f --- /dev/null +++ b/docker/migrations/forward/nodes/node_projection_mcp_tools/0002_grant_omninode_runtime_mcp_tools.sql @@ -0,0 +1,121 @@ +-- OMN-17440: topology-derived omninode_runtime TABLE grants for node_projection_mcp_tools. +-- Target DB: omnidash_analytics (NODE_POSTGRES_DB) +-- Node: node_projection_mcp_tools +-- +-- ============================================================================ +-- WHAT IS BROKEN +-- ============================================================================ +-- All three topology instances +-- (`omnibase_infra/src/omnibase_infra/topology/instances/{local,onex-dev, +-- onex-prod}.yaml`) declare `mcp_tools` under +-- `databases.application.principals.omninode_runtime.grants[object_type: +-- TABLE, schema: public]` with `[INSERT, SELECT, UPDATE]`. That list is +-- GENERATED from node contract `db_io.db_tables` declarations by +-- `scripts/generate_application_database_table_grants.py --write`, so it is +-- the platform's machine-derived statement of intent. +-- +-- No migration in either repo has ever ISSUED it. The two halves -- +-- what the platform declares, and what a database is actually told -- drifted +-- apart silently, and the drift only ever surfaces as a live outage on +-- whichever relation happens to take traffic next: `InsufficientPrivilege` +-- on every write while the runtime reports healthy and commits its offsets +-- (the OMN-16993 / OMN-17379 failure shape). +-- +-- ============================================================================ +-- MEASURED, NOT ASSUMED +-- ============================================================================ +-- `scripts/validation/check_topology_grant_delivery.py` at omnibase_infra +-- dev 4053dc3c0 reported `topology grant delivery: 27 undelivered (of 65 +-- declared)`, and `mcp_tools` was among them. +-- +-- Read-only probe of the `.201` dev lane (compose project `omnibase-infra`, +-- database `omnidash_analytics`) on 2026-09-03 against +-- `information_schema.role_table_grants`: 63 of the 65 declared grants are +-- present live, but only 38 of them are issued by any migration. The other +-- 26 -- `mcp_tools` included -- exist ONLY because they were granted +-- out of band, BY HAND. That is precisely why the gap stayed invisible: a +-- lane that has never been hand-patched (a fresh staging namespace, a rebuilt +-- onex-dev, prod) gets none of them. +-- +-- ============================================================================ +-- WHY THIS LINEAGE, AND NOT ONE BULK GRANT FILE +-- ============================================================================ +-- This grant belongs next to the file that creates the relation, in the +-- lineage of the node that OWNS it -- the convention +-- `node_projection_session_replay/0002` (OMN-16993), +-- `node_projection_tenant_registry/0001` (OMN-17374) and the eight files the +-- first OMN-17440 tranche landed already follow. +-- +-- Deliberately NOT a shared cross-node grant file. That is the shape +-- `0004_grant_tenant_projection_writer.sql` took under protest -- "Homing a +-- cross-node grant block under one node is a real ownership compromise" -- +-- and it is exactly why a relation added to a node later silently misses its +-- grant. OMN-15701 is that failure: a pin regeneration reverted eight +-- house-tenant grants at once because they all lived in one place. +-- +-- The flat corpus cannot carry it either. Flat migrations are applied by a +-- `psql -f` loop gated on `directive_db == "$DB_NAME"`, while the node-owned +-- loop is the sanctioned path that connects directly to omnidash_analytics +-- (NODE_POSTGRES_DB). So the AUTHORIZATION rides here. +-- +-- ============================================================================ +-- PRIVILEGES +-- ============================================================================ +-- SELECT, INSERT, UPDATE and deliberately NO DELETE -- a projection writer +-- upserts, it does not reshape the table. Same invariant flat 096 states for +-- role_omnidash and flat 099 for this principal on +-- `omninode_internal.live_events`. PostgreSQL requires SELECT alongside +-- INSERT/UPDATE for the adapter's `INSERT ... ON CONFLICT DO UPDATE`, which +-- is why the write set is three privileges and not two. +-- +-- This widens NOTHING beyond what the generated topology already declares. +-- It grants only the relations THIS node owns. +-- +-- Physical schema is `public`: `0001_create_mcp_tools.sql` is the file that creates +-- it, and a grant must name the PHYSICAL schema. +-- +-- `0001_create_mcp_tools.sql` itself is NOT edited. It is applied on the `.201` dev +-- lane with a recorded content_sha256, so repairing its grant block in place +-- would raise "conflicting migration checksum in canonical node history" +-- (the OMN-16705 constraint). The grant is therefore a forward ADD. +-- +-- Idempotency: GRANT is idempotent; re-running is a no-op. Nothing here +-- touches RLS, ownership, table shape, or any role attribute. + +-- --------------------------------------------------------------------------- +-- 1. Schema USAGE, mirroring topology +-- `principals.omninode_runtime.grants[object_type: SCHEMA, schema: public]`. +-- Idempotent, and re-asserted here for the same reason flat 099 re-asserts +-- the omninode_internal one: a migration must not assume a sibling file ran. +-- --------------------------------------------------------------------------- +GRANT USAGE ON SCHEMA public TO omninode_runtime; + +-- --------------------------------------------------------------------------- +-- 2. Table grants (topology-derived, OMN-17440) +-- --------------------------------------------------------------------------- +GRANT SELECT, INSERT, UPDATE ON public.mcp_tools TO omninode_runtime; + +-- --------------------------------------------------------------------------- +-- 3. Assertions: fail the migration if a grant did not take. Division by zero +-- when the grant is absent -- the same fail-loud shape flat 099, +-- node_log_persistence_effect/0000, node_projection_session_replay/0002 and +-- node_projection_tenant_registry/0001 already use. +-- +-- BOTH directions are asserted per relation, not just INSERT. A lane where +-- the INSERT landed and the SELECT did not would accept writes and still +-- fail every `ON CONFLICT DO UPDATE` read-back, which is the harder of the +-- two failures to diagnose. +-- --------------------------------------------------------------------------- +SELECT 1 / count(*) AS mcp_tools_insert_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'mcp_tools' + AND grantee = 'omninode_runtime' + AND privilege_type = 'INSERT'; + +SELECT 1 / count(*) AS mcp_tools_select_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'mcp_tools' + AND grantee = 'omninode_runtime' + AND privilege_type = 'SELECT'; diff --git a/docker/migrations/forward/nodes/node_projection_registration/0006_grant_omninode_runtime_node_service_registry.sql b/docker/migrations/forward/nodes/node_projection_registration/0006_grant_omninode_runtime_node_service_registry.sql new file mode 100644 index 0000000000..4f1164904f --- /dev/null +++ b/docker/migrations/forward/nodes/node_projection_registration/0006_grant_omninode_runtime_node_service_registry.sql @@ -0,0 +1,121 @@ +-- OMN-17440: topology-derived omninode_runtime TABLE grants for node_projection_registration. +-- Target DB: omnidash_analytics (NODE_POSTGRES_DB) +-- Node: node_projection_registration +-- +-- ============================================================================ +-- WHAT IS BROKEN +-- ============================================================================ +-- All three topology instances +-- (`omnibase_infra/src/omnibase_infra/topology/instances/{local,onex-dev, +-- onex-prod}.yaml`) declare `node_service_registry` under +-- `databases.application.principals.omninode_runtime.grants[object_type: +-- TABLE, schema: public]` with `[INSERT, SELECT, UPDATE]`. That list is +-- GENERATED from node contract `db_io.db_tables` declarations by +-- `scripts/generate_application_database_table_grants.py --write`, so it is +-- the platform's machine-derived statement of intent. +-- +-- No migration in either repo has ever ISSUED it. The two halves -- +-- what the platform declares, and what a database is actually told -- drifted +-- apart silently, and the drift only ever surfaces as a live outage on +-- whichever relation happens to take traffic next: `InsufficientPrivilege` +-- on every write while the runtime reports healthy and commits its offsets +-- (the OMN-16993 / OMN-17379 failure shape). +-- +-- ============================================================================ +-- MEASURED, NOT ASSUMED +-- ============================================================================ +-- `scripts/validation/check_topology_grant_delivery.py` at omnibase_infra +-- dev 4053dc3c0 reported `topology grant delivery: 27 undelivered (of 65 +-- declared)`, and `node_service_registry` was among them. +-- +-- Read-only probe of the `.201` dev lane (compose project `omnibase-infra`, +-- database `omnidash_analytics`) on 2026-09-03 against +-- `information_schema.role_table_grants`: 63 of the 65 declared grants are +-- present live, but only 38 of them are issued by any migration. The other +-- 26 -- `node_service_registry` included -- exist ONLY because they were granted +-- out of band, BY HAND. That is precisely why the gap stayed invisible: a +-- lane that has never been hand-patched (a fresh staging namespace, a rebuilt +-- onex-dev, prod) gets none of them. +-- +-- ============================================================================ +-- WHY THIS LINEAGE, AND NOT ONE BULK GRANT FILE +-- ============================================================================ +-- This grant belongs next to the file that creates the relation, in the +-- lineage of the node that OWNS it -- the convention +-- `node_projection_session_replay/0002` (OMN-16993), +-- `node_projection_tenant_registry/0001` (OMN-17374) and the eight files the +-- first OMN-17440 tranche landed already follow. +-- +-- Deliberately NOT a shared cross-node grant file. That is the shape +-- `0004_grant_tenant_projection_writer.sql` took under protest -- "Homing a +-- cross-node grant block under one node is a real ownership compromise" -- +-- and it is exactly why a relation added to a node later silently misses its +-- grant. OMN-15701 is that failure: a pin regeneration reverted eight +-- house-tenant grants at once because they all lived in one place. +-- +-- The flat corpus cannot carry it either. Flat migrations are applied by a +-- `psql -f` loop gated on `directive_db == "$DB_NAME"`, while the node-owned +-- loop is the sanctioned path that connects directly to omnidash_analytics +-- (NODE_POSTGRES_DB). So the AUTHORIZATION rides here. +-- +-- ============================================================================ +-- PRIVILEGES +-- ============================================================================ +-- SELECT, INSERT, UPDATE and deliberately NO DELETE -- a projection writer +-- upserts, it does not reshape the table. Same invariant flat 096 states for +-- role_omnidash and flat 099 for this principal on +-- `omninode_internal.live_events`. PostgreSQL requires SELECT alongside +-- INSERT/UPDATE for the adapter's `INSERT ... ON CONFLICT DO UPDATE`, which +-- is why the write set is three privileges and not two. +-- +-- This widens NOTHING beyond what the generated topology already declares. +-- It grants only the relations THIS node owns. +-- +-- Physical schema is `public`: `0000_create_node_service_registry.sql` is the file that creates +-- it, and a grant must name the PHYSICAL schema. +-- +-- `0000_create_node_service_registry.sql` itself is NOT edited. It is applied on the `.201` dev +-- lane with a recorded content_sha256, so repairing its grant block in place +-- would raise "conflicting migration checksum in canonical node history" +-- (the OMN-16705 constraint). The grant is therefore a forward ADD. +-- +-- Idempotency: GRANT is idempotent; re-running is a no-op. Nothing here +-- touches RLS, ownership, table shape, or any role attribute. + +-- --------------------------------------------------------------------------- +-- 1. Schema USAGE, mirroring topology +-- `principals.omninode_runtime.grants[object_type: SCHEMA, schema: public]`. +-- Idempotent, and re-asserted here for the same reason flat 099 re-asserts +-- the omninode_internal one: a migration must not assume a sibling file ran. +-- --------------------------------------------------------------------------- +GRANT USAGE ON SCHEMA public TO omninode_runtime; + +-- --------------------------------------------------------------------------- +-- 2. Table grants (topology-derived, OMN-17440) +-- --------------------------------------------------------------------------- +GRANT SELECT, INSERT, UPDATE ON public.node_service_registry TO omninode_runtime; + +-- --------------------------------------------------------------------------- +-- 3. Assertions: fail the migration if a grant did not take. Division by zero +-- when the grant is absent -- the same fail-loud shape flat 099, +-- node_log_persistence_effect/0000, node_projection_session_replay/0002 and +-- node_projection_tenant_registry/0001 already use. +-- +-- BOTH directions are asserted per relation, not just INSERT. A lane where +-- the INSERT landed and the SELECT did not would accept writes and still +-- fail every `ON CONFLICT DO UPDATE` read-back, which is the harder of the +-- two failures to diagnose. +-- --------------------------------------------------------------------------- +SELECT 1 / count(*) AS node_service_registry_insert_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'node_service_registry' + AND grantee = 'omninode_runtime' + AND privilege_type = 'INSERT'; + +SELECT 1 / count(*) AS node_service_registry_select_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'node_service_registry' + AND grantee = 'omninode_runtime' + AND privilege_type = 'SELECT'; diff --git a/docker/migrations/forward/nodes/node_projection_sandbox_decisions/0002_grant_omninode_runtime_sandbox_decisions.sql b/docker/migrations/forward/nodes/node_projection_sandbox_decisions/0002_grant_omninode_runtime_sandbox_decisions.sql new file mode 100644 index 0000000000..97a06afbc0 --- /dev/null +++ b/docker/migrations/forward/nodes/node_projection_sandbox_decisions/0002_grant_omninode_runtime_sandbox_decisions.sql @@ -0,0 +1,121 @@ +-- OMN-17440: topology-derived omninode_runtime TABLE grants for node_projection_sandbox_decisions. +-- Target DB: omnidash_analytics (NODE_POSTGRES_DB) +-- Node: node_projection_sandbox_decisions +-- +-- ============================================================================ +-- WHAT IS BROKEN +-- ============================================================================ +-- All three topology instances +-- (`omnibase_infra/src/omnibase_infra/topology/instances/{local,onex-dev, +-- onex-prod}.yaml`) declare `sandbox_decisions` under +-- `databases.application.principals.omninode_runtime.grants[object_type: +-- TABLE, schema: public]` with `[INSERT, SELECT, UPDATE]`. That list is +-- GENERATED from node contract `db_io.db_tables` declarations by +-- `scripts/generate_application_database_table_grants.py --write`, so it is +-- the platform's machine-derived statement of intent. +-- +-- No migration in either repo has ever ISSUED it. The two halves -- +-- what the platform declares, and what a database is actually told -- drifted +-- apart silently, and the drift only ever surfaces as a live outage on +-- whichever relation happens to take traffic next: `InsufficientPrivilege` +-- on every write while the runtime reports healthy and commits its offsets +-- (the OMN-16993 / OMN-17379 failure shape). +-- +-- ============================================================================ +-- MEASURED, NOT ASSUMED +-- ============================================================================ +-- `scripts/validation/check_topology_grant_delivery.py` at omnibase_infra +-- dev 4053dc3c0 reported `topology grant delivery: 27 undelivered (of 65 +-- declared)`, and `sandbox_decisions` was among them. +-- +-- Read-only probe of the `.201` dev lane (compose project `omnibase-infra`, +-- database `omnidash_analytics`) on 2026-09-03 against +-- `information_schema.role_table_grants`: 63 of the 65 declared grants are +-- present live, but only 38 of them are issued by any migration. The other +-- 26 -- `sandbox_decisions` included -- exist ONLY because they were granted +-- out of band, BY HAND. That is precisely why the gap stayed invisible: a +-- lane that has never been hand-patched (a fresh staging namespace, a rebuilt +-- onex-dev, prod) gets none of them. +-- +-- ============================================================================ +-- WHY THIS LINEAGE, AND NOT ONE BULK GRANT FILE +-- ============================================================================ +-- This grant belongs next to the file that creates the relation, in the +-- lineage of the node that OWNS it -- the convention +-- `node_projection_session_replay/0002` (OMN-16993), +-- `node_projection_tenant_registry/0001` (OMN-17374) and the eight files the +-- first OMN-17440 tranche landed already follow. +-- +-- Deliberately NOT a shared cross-node grant file. That is the shape +-- `0004_grant_tenant_projection_writer.sql` took under protest -- "Homing a +-- cross-node grant block under one node is a real ownership compromise" -- +-- and it is exactly why a relation added to a node later silently misses its +-- grant. OMN-15701 is that failure: a pin regeneration reverted eight +-- house-tenant grants at once because they all lived in one place. +-- +-- The flat corpus cannot carry it either. Flat migrations are applied by a +-- `psql -f` loop gated on `directive_db == "$DB_NAME"`, while the node-owned +-- loop is the sanctioned path that connects directly to omnidash_analytics +-- (NODE_POSTGRES_DB). So the AUTHORIZATION rides here. +-- +-- ============================================================================ +-- PRIVILEGES +-- ============================================================================ +-- SELECT, INSERT, UPDATE and deliberately NO DELETE -- a projection writer +-- upserts, it does not reshape the table. Same invariant flat 096 states for +-- role_omnidash and flat 099 for this principal on +-- `omninode_internal.live_events`. PostgreSQL requires SELECT alongside +-- INSERT/UPDATE for the adapter's `INSERT ... ON CONFLICT DO UPDATE`, which +-- is why the write set is three privileges and not two. +-- +-- This widens NOTHING beyond what the generated topology already declares. +-- It grants only the relations THIS node owns. +-- +-- Physical schema is `public`: `0001_create_sandbox_decisions.sql` is the file that creates +-- it, and a grant must name the PHYSICAL schema. +-- +-- `0001_create_sandbox_decisions.sql` itself is NOT edited. It is applied on the `.201` dev +-- lane with a recorded content_sha256, so repairing its grant block in place +-- would raise "conflicting migration checksum in canonical node history" +-- (the OMN-16705 constraint). The grant is therefore a forward ADD. +-- +-- Idempotency: GRANT is idempotent; re-running is a no-op. Nothing here +-- touches RLS, ownership, table shape, or any role attribute. + +-- --------------------------------------------------------------------------- +-- 1. Schema USAGE, mirroring topology +-- `principals.omninode_runtime.grants[object_type: SCHEMA, schema: public]`. +-- Idempotent, and re-asserted here for the same reason flat 099 re-asserts +-- the omninode_internal one: a migration must not assume a sibling file ran. +-- --------------------------------------------------------------------------- +GRANT USAGE ON SCHEMA public TO omninode_runtime; + +-- --------------------------------------------------------------------------- +-- 2. Table grants (topology-derived, OMN-17440) +-- --------------------------------------------------------------------------- +GRANT SELECT, INSERT, UPDATE ON public.sandbox_decisions TO omninode_runtime; + +-- --------------------------------------------------------------------------- +-- 3. Assertions: fail the migration if a grant did not take. Division by zero +-- when the grant is absent -- the same fail-loud shape flat 099, +-- node_log_persistence_effect/0000, node_projection_session_replay/0002 and +-- node_projection_tenant_registry/0001 already use. +-- +-- BOTH directions are asserted per relation, not just INSERT. A lane where +-- the INSERT landed and the SELECT did not would accept writes and still +-- fail every `ON CONFLICT DO UPDATE` read-back, which is the harder of the +-- two failures to diagnose. +-- --------------------------------------------------------------------------- +SELECT 1 / count(*) AS sandbox_decisions_insert_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'sandbox_decisions' + AND grantee = 'omninode_runtime' + AND privilege_type = 'INSERT'; + +SELECT 1 / count(*) AS sandbox_decisions_select_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'sandbox_decisions' + AND grantee = 'omninode_runtime' + AND privilege_type = 'SELECT'; diff --git a/docker/migrations/forward/nodes/node_projection_session_outcome/0022_grant_omninode_runtime_session_outcomes.sql b/docker/migrations/forward/nodes/node_projection_session_outcome/0022_grant_omninode_runtime_session_outcomes.sql new file mode 100644 index 0000000000..e89795e295 --- /dev/null +++ b/docker/migrations/forward/nodes/node_projection_session_outcome/0022_grant_omninode_runtime_session_outcomes.sql @@ -0,0 +1,121 @@ +-- OMN-17440: topology-derived omninode_runtime TABLE grants for node_projection_session_outcome. +-- Target DB: omnidash_analytics (NODE_POSTGRES_DB) +-- Node: node_projection_session_outcome +-- +-- ============================================================================ +-- WHAT IS BROKEN +-- ============================================================================ +-- All three topology instances +-- (`omnibase_infra/src/omnibase_infra/topology/instances/{local,onex-dev, +-- onex-prod}.yaml`) declare `session_outcomes` under +-- `databases.application.principals.omninode_runtime.grants[object_type: +-- TABLE, schema: public]` with `[INSERT, SELECT, UPDATE]`. That list is +-- GENERATED from node contract `db_io.db_tables` declarations by +-- `scripts/generate_application_database_table_grants.py --write`, so it is +-- the platform's machine-derived statement of intent. +-- +-- No migration in either repo has ever ISSUED it. The two halves -- +-- what the platform declares, and what a database is actually told -- drifted +-- apart silently, and the drift only ever surfaces as a live outage on +-- whichever relation happens to take traffic next: `InsufficientPrivilege` +-- on every write while the runtime reports healthy and commits its offsets +-- (the OMN-16993 / OMN-17379 failure shape). +-- +-- ============================================================================ +-- MEASURED, NOT ASSUMED +-- ============================================================================ +-- `scripts/validation/check_topology_grant_delivery.py` at omnibase_infra +-- dev 4053dc3c0 reported `topology grant delivery: 27 undelivered (of 65 +-- declared)`, and `session_outcomes` was among them. +-- +-- Read-only probe of the `.201` dev lane (compose project `omnibase-infra`, +-- database `omnidash_analytics`) on 2026-09-03 against +-- `information_schema.role_table_grants`: 63 of the 65 declared grants are +-- present live, but only 38 of them are issued by any migration. The other +-- 26 -- `session_outcomes` included -- exist ONLY because they were granted +-- out of band, BY HAND. That is precisely why the gap stayed invisible: a +-- lane that has never been hand-patched (a fresh staging namespace, a rebuilt +-- onex-dev, prod) gets none of them. +-- +-- ============================================================================ +-- WHY THIS LINEAGE, AND NOT ONE BULK GRANT FILE +-- ============================================================================ +-- This grant belongs next to the file that creates the relation, in the +-- lineage of the node that OWNS it -- the convention +-- `node_projection_session_replay/0002` (OMN-16993), +-- `node_projection_tenant_registry/0001` (OMN-17374) and the eight files the +-- first OMN-17440 tranche landed already follow. +-- +-- Deliberately NOT a shared cross-node grant file. That is the shape +-- `0004_grant_tenant_projection_writer.sql` took under protest -- "Homing a +-- cross-node grant block under one node is a real ownership compromise" -- +-- and it is exactly why a relation added to a node later silently misses its +-- grant. OMN-15701 is that failure: a pin regeneration reverted eight +-- house-tenant grants at once because they all lived in one place. +-- +-- The flat corpus cannot carry it either. Flat migrations are applied by a +-- `psql -f` loop gated on `directive_db == "$DB_NAME"`, while the node-owned +-- loop is the sanctioned path that connects directly to omnidash_analytics +-- (NODE_POSTGRES_DB). So the AUTHORIZATION rides here. +-- +-- ============================================================================ +-- PRIVILEGES +-- ============================================================================ +-- SELECT, INSERT, UPDATE and deliberately NO DELETE -- a projection writer +-- upserts, it does not reshape the table. Same invariant flat 096 states for +-- role_omnidash and flat 099 for this principal on +-- `omninode_internal.live_events`. PostgreSQL requires SELECT alongside +-- INSERT/UPDATE for the adapter's `INSERT ... ON CONFLICT DO UPDATE`, which +-- is why the write set is three privileges and not two. +-- +-- This widens NOTHING beyond what the generated topology already declares. +-- It grants only the relations THIS node owns. +-- +-- Physical schema is `public`: `0021_session_outcomes.sql` is the file that creates +-- it, and a grant must name the PHYSICAL schema. +-- +-- `0021_session_outcomes.sql` itself is NOT edited. It is applied on the `.201` dev +-- lane with a recorded content_sha256, so repairing its grant block in place +-- would raise "conflicting migration checksum in canonical node history" +-- (the OMN-16705 constraint). The grant is therefore a forward ADD. +-- +-- Idempotency: GRANT is idempotent; re-running is a no-op. Nothing here +-- touches RLS, ownership, table shape, or any role attribute. + +-- --------------------------------------------------------------------------- +-- 1. Schema USAGE, mirroring topology +-- `principals.omninode_runtime.grants[object_type: SCHEMA, schema: public]`. +-- Idempotent, and re-asserted here for the same reason flat 099 re-asserts +-- the omninode_internal one: a migration must not assume a sibling file ran. +-- --------------------------------------------------------------------------- +GRANT USAGE ON SCHEMA public TO omninode_runtime; + +-- --------------------------------------------------------------------------- +-- 2. Table grants (topology-derived, OMN-17440) +-- --------------------------------------------------------------------------- +GRANT SELECT, INSERT, UPDATE ON public.session_outcomes TO omninode_runtime; + +-- --------------------------------------------------------------------------- +-- 3. Assertions: fail the migration if a grant did not take. Division by zero +-- when the grant is absent -- the same fail-loud shape flat 099, +-- node_log_persistence_effect/0000, node_projection_session_replay/0002 and +-- node_projection_tenant_registry/0001 already use. +-- +-- BOTH directions are asserted per relation, not just INSERT. A lane where +-- the INSERT landed and the SELECT did not would accept writes and still +-- fail every `ON CONFLICT DO UPDATE` read-back, which is the harder of the +-- two failures to diagnose. +-- --------------------------------------------------------------------------- +SELECT 1 / count(*) AS session_outcomes_insert_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'session_outcomes' + AND grantee = 'omninode_runtime' + AND privilege_type = 'INSERT'; + +SELECT 1 / count(*) AS session_outcomes_select_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'session_outcomes' + AND grantee = 'omninode_runtime' + AND privilege_type = 'SELECT'; diff --git a/docker/migrations/forward/nodes/node_projection_swarm/0002_grant_omninode_runtime_swarm_runs.sql b/docker/migrations/forward/nodes/node_projection_swarm/0002_grant_omninode_runtime_swarm_runs.sql new file mode 100644 index 0000000000..9bc65a8b68 --- /dev/null +++ b/docker/migrations/forward/nodes/node_projection_swarm/0002_grant_omninode_runtime_swarm_runs.sql @@ -0,0 +1,121 @@ +-- OMN-17440: topology-derived omninode_runtime TABLE grants for node_projection_swarm. +-- Target DB: omnidash_analytics (NODE_POSTGRES_DB) +-- Node: node_projection_swarm +-- +-- ============================================================================ +-- WHAT IS BROKEN +-- ============================================================================ +-- All three topology instances +-- (`omnibase_infra/src/omnibase_infra/topology/instances/{local,onex-dev, +-- onex-prod}.yaml`) declare `swarm_runs` under +-- `databases.application.principals.omninode_runtime.grants[object_type: +-- TABLE, schema: public]` with `[INSERT, SELECT, UPDATE]`. That list is +-- GENERATED from node contract `db_io.db_tables` declarations by +-- `scripts/generate_application_database_table_grants.py --write`, so it is +-- the platform's machine-derived statement of intent. +-- +-- No migration in either repo has ever ISSUED it. The two halves -- +-- what the platform declares, and what a database is actually told -- drifted +-- apart silently, and the drift only ever surfaces as a live outage on +-- whichever relation happens to take traffic next: `InsufficientPrivilege` +-- on every write while the runtime reports healthy and commits its offsets +-- (the OMN-16993 / OMN-17379 failure shape). +-- +-- ============================================================================ +-- MEASURED, NOT ASSUMED +-- ============================================================================ +-- `scripts/validation/check_topology_grant_delivery.py` at omnibase_infra +-- dev 4053dc3c0 reported `topology grant delivery: 27 undelivered (of 65 +-- declared)`, and `swarm_runs` was among them. +-- +-- Read-only probe of the `.201` dev lane (compose project `omnibase-infra`, +-- database `omnidash_analytics`) on 2026-09-03 against +-- `information_schema.role_table_grants`: 63 of the 65 declared grants are +-- present live, but only 38 of them are issued by any migration. The other +-- 26 -- `swarm_runs` included -- exist ONLY because they were granted +-- out of band, BY HAND. That is precisely why the gap stayed invisible: a +-- lane that has never been hand-patched (a fresh staging namespace, a rebuilt +-- onex-dev, prod) gets none of them. +-- +-- ============================================================================ +-- WHY THIS LINEAGE, AND NOT ONE BULK GRANT FILE +-- ============================================================================ +-- This grant belongs next to the file that creates the relation, in the +-- lineage of the node that OWNS it -- the convention +-- `node_projection_session_replay/0002` (OMN-16993), +-- `node_projection_tenant_registry/0001` (OMN-17374) and the eight files the +-- first OMN-17440 tranche landed already follow. +-- +-- Deliberately NOT a shared cross-node grant file. That is the shape +-- `0004_grant_tenant_projection_writer.sql` took under protest -- "Homing a +-- cross-node grant block under one node is a real ownership compromise" -- +-- and it is exactly why a relation added to a node later silently misses its +-- grant. OMN-15701 is that failure: a pin regeneration reverted eight +-- house-tenant grants at once because they all lived in one place. +-- +-- The flat corpus cannot carry it either. Flat migrations are applied by a +-- `psql -f` loop gated on `directive_db == "$DB_NAME"`, while the node-owned +-- loop is the sanctioned path that connects directly to omnidash_analytics +-- (NODE_POSTGRES_DB). So the AUTHORIZATION rides here. +-- +-- ============================================================================ +-- PRIVILEGES +-- ============================================================================ +-- SELECT, INSERT, UPDATE and deliberately NO DELETE -- a projection writer +-- upserts, it does not reshape the table. Same invariant flat 096 states for +-- role_omnidash and flat 099 for this principal on +-- `omninode_internal.live_events`. PostgreSQL requires SELECT alongside +-- INSERT/UPDATE for the adapter's `INSERT ... ON CONFLICT DO UPDATE`, which +-- is why the write set is three privileges and not two. +-- +-- This widens NOTHING beyond what the generated topology already declares. +-- It grants only the relations THIS node owns. +-- +-- Physical schema is `public`: `0001_create_swarm_runs.sql` is the file that creates +-- it, and a grant must name the PHYSICAL schema. +-- +-- `0001_create_swarm_runs.sql` itself is NOT edited. It is applied on the `.201` dev +-- lane with a recorded content_sha256, so repairing its grant block in place +-- would raise "conflicting migration checksum in canonical node history" +-- (the OMN-16705 constraint). The grant is therefore a forward ADD. +-- +-- Idempotency: GRANT is idempotent; re-running is a no-op. Nothing here +-- touches RLS, ownership, table shape, or any role attribute. + +-- --------------------------------------------------------------------------- +-- 1. Schema USAGE, mirroring topology +-- `principals.omninode_runtime.grants[object_type: SCHEMA, schema: public]`. +-- Idempotent, and re-asserted here for the same reason flat 099 re-asserts +-- the omninode_internal one: a migration must not assume a sibling file ran. +-- --------------------------------------------------------------------------- +GRANT USAGE ON SCHEMA public TO omninode_runtime; + +-- --------------------------------------------------------------------------- +-- 2. Table grants (topology-derived, OMN-17440) +-- --------------------------------------------------------------------------- +GRANT SELECT, INSERT, UPDATE ON public.swarm_runs TO omninode_runtime; + +-- --------------------------------------------------------------------------- +-- 3. Assertions: fail the migration if a grant did not take. Division by zero +-- when the grant is absent -- the same fail-loud shape flat 099, +-- node_log_persistence_effect/0000, node_projection_session_replay/0002 and +-- node_projection_tenant_registry/0001 already use. +-- +-- BOTH directions are asserted per relation, not just INSERT. A lane where +-- the INSERT landed and the SELECT did not would accept writes and still +-- fail every `ON CONFLICT DO UPDATE` read-back, which is the harder of the +-- two failures to diagnose. +-- --------------------------------------------------------------------------- +SELECT 1 / count(*) AS swarm_runs_insert_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'swarm_runs' + AND grantee = 'omninode_runtime' + AND privilege_type = 'INSERT'; + +SELECT 1 / count(*) AS swarm_runs_select_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'swarm_runs' + AND grantee = 'omninode_runtime' + AND privilege_type = 'SELECT'; diff --git a/docker/migrations/forward/nodes/node_projection_traces/0002_grant_omninode_runtime_traces.sql b/docker/migrations/forward/nodes/node_projection_traces/0002_grant_omninode_runtime_traces.sql new file mode 100644 index 0000000000..59ea28db6e --- /dev/null +++ b/docker/migrations/forward/nodes/node_projection_traces/0002_grant_omninode_runtime_traces.sql @@ -0,0 +1,121 @@ +-- OMN-17440: topology-derived omninode_runtime TABLE grants for node_projection_traces. +-- Target DB: omnidash_analytics (NODE_POSTGRES_DB) +-- Node: node_projection_traces +-- +-- ============================================================================ +-- WHAT IS BROKEN +-- ============================================================================ +-- All three topology instances +-- (`omnibase_infra/src/omnibase_infra/topology/instances/{local,onex-dev, +-- onex-prod}.yaml`) declare `traces` under +-- `databases.application.principals.omninode_runtime.grants[object_type: +-- TABLE, schema: public]` with `[INSERT, SELECT, UPDATE]`. That list is +-- GENERATED from node contract `db_io.db_tables` declarations by +-- `scripts/generate_application_database_table_grants.py --write`, so it is +-- the platform's machine-derived statement of intent. +-- +-- No migration in either repo has ever ISSUED it. The two halves -- +-- what the platform declares, and what a database is actually told -- drifted +-- apart silently, and the drift only ever surfaces as a live outage on +-- whichever relation happens to take traffic next: `InsufficientPrivilege` +-- on every write while the runtime reports healthy and commits its offsets +-- (the OMN-16993 / OMN-17379 failure shape). +-- +-- ============================================================================ +-- MEASURED, NOT ASSUMED +-- ============================================================================ +-- `scripts/validation/check_topology_grant_delivery.py` at omnibase_infra +-- dev 4053dc3c0 reported `topology grant delivery: 27 undelivered (of 65 +-- declared)`, and `traces` was among them. +-- +-- Read-only probe of the `.201` dev lane (compose project `omnibase-infra`, +-- database `omnidash_analytics`) on 2026-09-03 against +-- `information_schema.role_table_grants`: 63 of the 65 declared grants are +-- present live, but only 38 of them are issued by any migration. The other +-- 26 -- `traces` included -- exist ONLY because they were granted +-- out of band, BY HAND. That is precisely why the gap stayed invisible: a +-- lane that has never been hand-patched (a fresh staging namespace, a rebuilt +-- onex-dev, prod) gets none of them. +-- +-- ============================================================================ +-- WHY THIS LINEAGE, AND NOT ONE BULK GRANT FILE +-- ============================================================================ +-- This grant belongs next to the file that creates the relation, in the +-- lineage of the node that OWNS it -- the convention +-- `node_projection_session_replay/0002` (OMN-16993), +-- `node_projection_tenant_registry/0001` (OMN-17374) and the eight files the +-- first OMN-17440 tranche landed already follow. +-- +-- Deliberately NOT a shared cross-node grant file. That is the shape +-- `0004_grant_tenant_projection_writer.sql` took under protest -- "Homing a +-- cross-node grant block under one node is a real ownership compromise" -- +-- and it is exactly why a relation added to a node later silently misses its +-- grant. OMN-15701 is that failure: a pin regeneration reverted eight +-- house-tenant grants at once because they all lived in one place. +-- +-- The flat corpus cannot carry it either. Flat migrations are applied by a +-- `psql -f` loop gated on `directive_db == "$DB_NAME"`, while the node-owned +-- loop is the sanctioned path that connects directly to omnidash_analytics +-- (NODE_POSTGRES_DB). So the AUTHORIZATION rides here. +-- +-- ============================================================================ +-- PRIVILEGES +-- ============================================================================ +-- SELECT, INSERT, UPDATE and deliberately NO DELETE -- a projection writer +-- upserts, it does not reshape the table. Same invariant flat 096 states for +-- role_omnidash and flat 099 for this principal on +-- `omninode_internal.live_events`. PostgreSQL requires SELECT alongside +-- INSERT/UPDATE for the adapter's `INSERT ... ON CONFLICT DO UPDATE`, which +-- is why the write set is three privileges and not two. +-- +-- This widens NOTHING beyond what the generated topology already declares. +-- It grants only the relations THIS node owns. +-- +-- Physical schema is `public`: `0001_create_traces.sql` is the file that creates +-- it, and a grant must name the PHYSICAL schema. +-- +-- `0001_create_traces.sql` itself is NOT edited. It is applied on the `.201` dev +-- lane with a recorded content_sha256, so repairing its grant block in place +-- would raise "conflicting migration checksum in canonical node history" +-- (the OMN-16705 constraint). The grant is therefore a forward ADD. +-- +-- Idempotency: GRANT is idempotent; re-running is a no-op. Nothing here +-- touches RLS, ownership, table shape, or any role attribute. + +-- --------------------------------------------------------------------------- +-- 1. Schema USAGE, mirroring topology +-- `principals.omninode_runtime.grants[object_type: SCHEMA, schema: public]`. +-- Idempotent, and re-asserted here for the same reason flat 099 re-asserts +-- the omninode_internal one: a migration must not assume a sibling file ran. +-- --------------------------------------------------------------------------- +GRANT USAGE ON SCHEMA public TO omninode_runtime; + +-- --------------------------------------------------------------------------- +-- 2. Table grants (topology-derived, OMN-17440) +-- --------------------------------------------------------------------------- +GRANT SELECT, INSERT, UPDATE ON public.traces TO omninode_runtime; + +-- --------------------------------------------------------------------------- +-- 3. Assertions: fail the migration if a grant did not take. Division by zero +-- when the grant is absent -- the same fail-loud shape flat 099, +-- node_log_persistence_effect/0000, node_projection_session_replay/0002 and +-- node_projection_tenant_registry/0001 already use. +-- +-- BOTH directions are asserted per relation, not just INSERT. A lane where +-- the INSERT landed and the SELECT did not would accept writes and still +-- fail every `ON CONFLICT DO UPDATE` read-back, which is the harder of the +-- two failures to diagnose. +-- --------------------------------------------------------------------------- +SELECT 1 / count(*) AS traces_insert_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'traces' + AND grantee = 'omninode_runtime' + AND privilege_type = 'INSERT'; + +SELECT 1 / count(*) AS traces_select_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'traces' + AND grantee = 'omninode_runtime' + AND privilege_type = 'SELECT'; diff --git a/docker/migrations/forward/nodes/node_projection_voice_sessions/0002_grant_omninode_runtime_voice_sessions.sql b/docker/migrations/forward/nodes/node_projection_voice_sessions/0002_grant_omninode_runtime_voice_sessions.sql new file mode 100644 index 0000000000..bb9e06463c --- /dev/null +++ b/docker/migrations/forward/nodes/node_projection_voice_sessions/0002_grant_omninode_runtime_voice_sessions.sql @@ -0,0 +1,121 @@ +-- OMN-17440: topology-derived omninode_runtime TABLE grants for node_projection_voice_sessions. +-- Target DB: omnidash_analytics (NODE_POSTGRES_DB) +-- Node: node_projection_voice_sessions +-- +-- ============================================================================ +-- WHAT IS BROKEN +-- ============================================================================ +-- All three topology instances +-- (`omnibase_infra/src/omnibase_infra/topology/instances/{local,onex-dev, +-- onex-prod}.yaml`) declare `voice_sessions` under +-- `databases.application.principals.omninode_runtime.grants[object_type: +-- TABLE, schema: public]` with `[INSERT, SELECT, UPDATE]`. That list is +-- GENERATED from node contract `db_io.db_tables` declarations by +-- `scripts/generate_application_database_table_grants.py --write`, so it is +-- the platform's machine-derived statement of intent. +-- +-- No migration in either repo has ever ISSUED it. The two halves -- +-- what the platform declares, and what a database is actually told -- drifted +-- apart silently, and the drift only ever surfaces as a live outage on +-- whichever relation happens to take traffic next: `InsufficientPrivilege` +-- on every write while the runtime reports healthy and commits its offsets +-- (the OMN-16993 / OMN-17379 failure shape). +-- +-- ============================================================================ +-- MEASURED, NOT ASSUMED +-- ============================================================================ +-- `scripts/validation/check_topology_grant_delivery.py` at omnibase_infra +-- dev 4053dc3c0 reported `topology grant delivery: 27 undelivered (of 65 +-- declared)`, and `voice_sessions` was among them. +-- +-- Read-only probe of the `.201` dev lane (compose project `omnibase-infra`, +-- database `omnidash_analytics`) on 2026-09-03 against +-- `information_schema.role_table_grants`: 63 of the 65 declared grants are +-- present live, but only 38 of them are issued by any migration. The other +-- 26 -- `voice_sessions` included -- exist ONLY because they were granted +-- out of band, BY HAND. That is precisely why the gap stayed invisible: a +-- lane that has never been hand-patched (a fresh staging namespace, a rebuilt +-- onex-dev, prod) gets none of them. +-- +-- ============================================================================ +-- WHY THIS LINEAGE, AND NOT ONE BULK GRANT FILE +-- ============================================================================ +-- This grant belongs next to the file that creates the relation, in the +-- lineage of the node that OWNS it -- the convention +-- `node_projection_session_replay/0002` (OMN-16993), +-- `node_projection_tenant_registry/0001` (OMN-17374) and the eight files the +-- first OMN-17440 tranche landed already follow. +-- +-- Deliberately NOT a shared cross-node grant file. That is the shape +-- `0004_grant_tenant_projection_writer.sql` took under protest -- "Homing a +-- cross-node grant block under one node is a real ownership compromise" -- +-- and it is exactly why a relation added to a node later silently misses its +-- grant. OMN-15701 is that failure: a pin regeneration reverted eight +-- house-tenant grants at once because they all lived in one place. +-- +-- The flat corpus cannot carry it either. Flat migrations are applied by a +-- `psql -f` loop gated on `directive_db == "$DB_NAME"`, while the node-owned +-- loop is the sanctioned path that connects directly to omnidash_analytics +-- (NODE_POSTGRES_DB). So the AUTHORIZATION rides here. +-- +-- ============================================================================ +-- PRIVILEGES +-- ============================================================================ +-- SELECT, INSERT, UPDATE and deliberately NO DELETE -- a projection writer +-- upserts, it does not reshape the table. Same invariant flat 096 states for +-- role_omnidash and flat 099 for this principal on +-- `omninode_internal.live_events`. PostgreSQL requires SELECT alongside +-- INSERT/UPDATE for the adapter's `INSERT ... ON CONFLICT DO UPDATE`, which +-- is why the write set is three privileges and not two. +-- +-- This widens NOTHING beyond what the generated topology already declares. +-- It grants only the relations THIS node owns. +-- +-- Physical schema is `public`: `0001_create_voice_sessions.sql` is the file that creates +-- it, and a grant must name the PHYSICAL schema. +-- +-- `0001_create_voice_sessions.sql` itself is NOT edited. It is applied on the `.201` dev +-- lane with a recorded content_sha256, so repairing its grant block in place +-- would raise "conflicting migration checksum in canonical node history" +-- (the OMN-16705 constraint). The grant is therefore a forward ADD. +-- +-- Idempotency: GRANT is idempotent; re-running is a no-op. Nothing here +-- touches RLS, ownership, table shape, or any role attribute. + +-- --------------------------------------------------------------------------- +-- 1. Schema USAGE, mirroring topology +-- `principals.omninode_runtime.grants[object_type: SCHEMA, schema: public]`. +-- Idempotent, and re-asserted here for the same reason flat 099 re-asserts +-- the omninode_internal one: a migration must not assume a sibling file ran. +-- --------------------------------------------------------------------------- +GRANT USAGE ON SCHEMA public TO omninode_runtime; + +-- --------------------------------------------------------------------------- +-- 2. Table grants (topology-derived, OMN-17440) +-- --------------------------------------------------------------------------- +GRANT SELECT, INSERT, UPDATE ON public.voice_sessions TO omninode_runtime; + +-- --------------------------------------------------------------------------- +-- 3. Assertions: fail the migration if a grant did not take. Division by zero +-- when the grant is absent -- the same fail-loud shape flat 099, +-- node_log_persistence_effect/0000, node_projection_session_replay/0002 and +-- node_projection_tenant_registry/0001 already use. +-- +-- BOTH directions are asserted per relation, not just INSERT. A lane where +-- the INSERT landed and the SELECT did not would accept writes and still +-- fail every `ON CONFLICT DO UPDATE` read-back, which is the harder of the +-- two failures to diagnose. +-- --------------------------------------------------------------------------- +SELECT 1 / count(*) AS voice_sessions_insert_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'voice_sessions' + AND grantee = 'omninode_runtime' + AND privilege_type = 'INSERT'; + +SELECT 1 / count(*) AS voice_sessions_select_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'voice_sessions' + AND grantee = 'omninode_runtime' + AND privilege_type = 'SELECT'; diff --git a/docker/migrations/forward/nodes/node_renderer_capability_projection/0002_grant_omninode_runtime_renderer_capability_projection.sql b/docker/migrations/forward/nodes/node_renderer_capability_projection/0002_grant_omninode_runtime_renderer_capability_projection.sql new file mode 100644 index 0000000000..cfdbe70eed --- /dev/null +++ b/docker/migrations/forward/nodes/node_renderer_capability_projection/0002_grant_omninode_runtime_renderer_capability_projection.sql @@ -0,0 +1,121 @@ +-- OMN-17440: topology-derived omninode_runtime TABLE grants for node_renderer_capability_projection. +-- Target DB: omnidash_analytics (NODE_POSTGRES_DB) +-- Node: node_renderer_capability_projection +-- +-- ============================================================================ +-- WHAT IS BROKEN +-- ============================================================================ +-- All three topology instances +-- (`omnibase_infra/src/omnibase_infra/topology/instances/{local,onex-dev, +-- onex-prod}.yaml`) declare `renderer_capability_projection` under +-- `databases.application.principals.omninode_runtime.grants[object_type: +-- TABLE, schema: public]` with `[INSERT, SELECT, UPDATE]`. That list is +-- GENERATED from node contract `db_io.db_tables` declarations by +-- `scripts/generate_application_database_table_grants.py --write`, so it is +-- the platform's machine-derived statement of intent. +-- +-- No migration in either repo has ever ISSUED it. The two halves -- +-- what the platform declares, and what a database is actually told -- drifted +-- apart silently, and the drift only ever surfaces as a live outage on +-- whichever relation happens to take traffic next: `InsufficientPrivilege` +-- on every write while the runtime reports healthy and commits its offsets +-- (the OMN-16993 / OMN-17379 failure shape). +-- +-- ============================================================================ +-- MEASURED, NOT ASSUMED +-- ============================================================================ +-- `scripts/validation/check_topology_grant_delivery.py` at omnibase_infra +-- dev 4053dc3c0 reported `topology grant delivery: 27 undelivered (of 65 +-- declared)`, and `renderer_capability_projection` was among them. +-- +-- Read-only probe of the `.201` dev lane (compose project `omnibase-infra`, +-- database `omnidash_analytics`) on 2026-09-03 against +-- `information_schema.role_table_grants`: 63 of the 65 declared grants are +-- present live, but only 38 of them are issued by any migration. The other +-- 26 -- `renderer_capability_projection` included -- exist ONLY because they were granted +-- out of band, BY HAND. That is precisely why the gap stayed invisible: a +-- lane that has never been hand-patched (a fresh staging namespace, a rebuilt +-- onex-dev, prod) gets none of them. +-- +-- ============================================================================ +-- WHY THIS LINEAGE, AND NOT ONE BULK GRANT FILE +-- ============================================================================ +-- This grant belongs next to the file that creates the relation, in the +-- lineage of the node that OWNS it -- the convention +-- `node_projection_session_replay/0002` (OMN-16993), +-- `node_projection_tenant_registry/0001` (OMN-17374) and the eight files the +-- first OMN-17440 tranche landed already follow. +-- +-- Deliberately NOT a shared cross-node grant file. That is the shape +-- `0004_grant_tenant_projection_writer.sql` took under protest -- "Homing a +-- cross-node grant block under one node is a real ownership compromise" -- +-- and it is exactly why a relation added to a node later silently misses its +-- grant. OMN-15701 is that failure: a pin regeneration reverted eight +-- house-tenant grants at once because they all lived in one place. +-- +-- The flat corpus cannot carry it either. Flat migrations are applied by a +-- `psql -f` loop gated on `directive_db == "$DB_NAME"`, while the node-owned +-- loop is the sanctioned path that connects directly to omnidash_analytics +-- (NODE_POSTGRES_DB). So the AUTHORIZATION rides here. +-- +-- ============================================================================ +-- PRIVILEGES +-- ============================================================================ +-- SELECT, INSERT, UPDATE and deliberately NO DELETE -- a projection writer +-- upserts, it does not reshape the table. Same invariant flat 096 states for +-- role_omnidash and flat 099 for this principal on +-- `omninode_internal.live_events`. PostgreSQL requires SELECT alongside +-- INSERT/UPDATE for the adapter's `INSERT ... ON CONFLICT DO UPDATE`, which +-- is why the write set is three privileges and not two. +-- +-- This widens NOTHING beyond what the generated topology already declares. +-- It grants only the relations THIS node owns. +-- +-- Physical schema is `public`: `0001_create_renderer_capability_projection.sql` is the file that creates +-- it, and a grant must name the PHYSICAL schema. +-- +-- `0001_create_renderer_capability_projection.sql` itself is NOT edited. It is applied on the `.201` dev +-- lane with a recorded content_sha256, so repairing its grant block in place +-- would raise "conflicting migration checksum in canonical node history" +-- (the OMN-16705 constraint). The grant is therefore a forward ADD. +-- +-- Idempotency: GRANT is idempotent; re-running is a no-op. Nothing here +-- touches RLS, ownership, table shape, or any role attribute. + +-- --------------------------------------------------------------------------- +-- 1. Schema USAGE, mirroring topology +-- `principals.omninode_runtime.grants[object_type: SCHEMA, schema: public]`. +-- Idempotent, and re-asserted here for the same reason flat 099 re-asserts +-- the omninode_internal one: a migration must not assume a sibling file ran. +-- --------------------------------------------------------------------------- +GRANT USAGE ON SCHEMA public TO omninode_runtime; + +-- --------------------------------------------------------------------------- +-- 2. Table grants (topology-derived, OMN-17440) +-- --------------------------------------------------------------------------- +GRANT SELECT, INSERT, UPDATE ON public.renderer_capability_projection TO omninode_runtime; + +-- --------------------------------------------------------------------------- +-- 3. Assertions: fail the migration if a grant did not take. Division by zero +-- when the grant is absent -- the same fail-loud shape flat 099, +-- node_log_persistence_effect/0000, node_projection_session_replay/0002 and +-- node_projection_tenant_registry/0001 already use. +-- +-- BOTH directions are asserted per relation, not just INSERT. A lane where +-- the INSERT landed and the SELECT did not would accept writes and still +-- fail every `ON CONFLICT DO UPDATE` read-back, which is the harder of the +-- two failures to diagnose. +-- --------------------------------------------------------------------------- +SELECT 1 / count(*) AS renderer_capability_projection_insert_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'renderer_capability_projection' + AND grantee = 'omninode_runtime' + AND privilege_type = 'INSERT'; + +SELECT 1 / count(*) AS renderer_capability_projection_select_grant_assertion +FROM information_schema.role_table_grants +WHERE table_schema = 'public' + AND table_name = 'renderer_capability_projection' + AND grantee = 'omninode_runtime' + AND privilege_type = 'SELECT'; diff --git a/scripts/validation/check_topology_grant_delivery.py b/scripts/validation/check_topology_grant_delivery.py index 63b2d98460..fcf311768f 100644 --- a/scripts/validation/check_topology_grant_delivery.py +++ b/scripts/validation/check_topology_grant_delivery.py @@ -77,16 +77,28 @@ OMN-17379. The ``node_projection_baselines`` relations in that tranche turned out NOT to be sequence-backed (see THE SEQUENCE HALF below); their TABLE grants were just as undelivered, so they stayed. - -WHAT THE REMAINING 27 ARE -------------------------- -26 are deliverable and simply not yet done -- each has an owning node with a -creating migration to land the grant beside. The 27th, ``nightly_loop_configs``, -is NOT deliverable by any migration: nothing in the corpus issues a CREATE TABLE -for it, so there is no lineage to put a grant in and no relation for a grant to -bite on. It is named here so the floor is understood as 1 rather than 0, and -``test_residual_relation_has_no_creating_migration`` asserts that reason still -holds instead of trusting this prose. + * OMN-17440 tranche 2 delivered the remaining 26 across 21 node lineages, + taking the bound to 1 -- the undeliverable residual below. The tranche is + the whole rest of the set rather than another sub-selection, because the + live measurement made further staging pointless: of the 63 declared grants + the ``.201`` dev lane actually holds (read-only probe of + ``information_schema.role_table_grants`` in ``omnidash_analytics``, + 2026-09-03), exactly 38 were issued by a migration. The other 26 were + granted OUT OF BAND BY HAND, so every one of them is already a live + dependency on a lane nobody can rebuild. A fresh staging namespace, a + rebuilt onex-dev or prod gets 38 of 65 and refuses the rest. + +WHAT THE REMAINING 1 IS +----------------------- +``nightly_loop_configs`` is NOT deliverable by any migration: nothing in the +corpus issues a CREATE TABLE for it, so there is no lineage to put a grant in +and no relation for a grant to bite on. It is the reason the floor is 1 rather +than 0, and ``test_residual_relation_has_no_creating_migration`` asserts that +reason still holds instead of trusting this prose. Closing it to 0 is not a +grant-delivery change: either ``node_nightly_loop_controller`` grows a CREATE +TABLE for the relation its contract declares under ``db_io.db_tables``, or that +declaration is removed. Both are OMN-17440 AC2/AC4 decisions, and neither is +made by adding a GRANT for a relation that does not exist. THE SEQUENCE HALF (OMN-17447) ----------------------------- @@ -162,12 +174,15 @@ re.IGNORECASE, ) -# The count measured on 2026-09-01 after OMN-17440 landed the first delivery -# tranche (13 grants across 8 node lineages) on top of OMN-17374's own. +# The count measured on 2026-09-03 after OMN-17440's second delivery tranche +# (the remaining 26 grants across 21 node lineages) landed on top of the first +# (13 across 8) and OMN-17374's own. What is left is the single undeliverable +# residual named in WHAT THE REMAINING 1 IS above, so this is now the floor and +# not merely the current reading. # It may only ever go down. Moving it UP is the one edit this file rejects on # sight: it converts the gate into a record of the drift instead of a bound on # it. -MAX_UNDELIVERED = 27 +MAX_UNDELIVERED = 1 # OMN-17447: the SEQUENCE half of the same defect class, measured after this # change lands its own seven deliveries. Same ratchet discipline as above. diff --git a/tests/ci/test_topology_grant_delivery.py b/tests/ci/test_topology_grant_delivery.py index 38e18abf6b..467d75ccb2 100644 --- a/tests/ci/test_topology_grant_delivery.py +++ b/tests/ci/test_topology_grant_delivery.py @@ -101,6 +101,85 @@ } ) +# OMN-17440 tranche 2: the WHOLE remainder of the declared set, 26 grants across +# 21 node lineages. Not another sub-selection, because the live measurement made +# further staging pointless. +# +# Read-only probe of the `.201` dev lane (compose project `omnibase-infra`, +# database `omnidash_analytics`) on 2026-09-03 against +# `information_schema.role_table_grants`, diffed against the generated topology: +# +# declared 65 +# present live 63 +# present live AND issued by a migration 38 +# present live and issued by NOTHING 26 <-- this set +# declared, absent live 2 (nightly_loop_configs and +# delegation_shadow_comparisons; +# both relations are absent +# from the lane entirely) +# live, declared by nothing 4 (OMN-17440 AC3, not this change) +# +# So every pair below is ALREADY a live dependency of the dev lane that exists +# only because somebody typed the GRANT. A fresh staging namespace, a rebuilt +# onex-dev, or prod gets 38 of 65 and refuses the rest -- which is the +# OMN-16993 / OMN-17379 failure shape, with the offset committed anyway. +# +# One case per pair, same reason tranche 1 gives: an aggregate assertion would +# report "26 missing" and make the next reader re-derive which. +OMN_17440_TRANCHE2_DELIVERED = frozenset( + { + # node_projection_consumer_flow / 0000_create_consumer_flow_windows.sql + GrantKey("omninode_runtime", "omninode_internal", "consumer_flow_windows"), + GrantKey("omninode_runtime", "omninode_internal", "topic_produce_windows"), + # node_projection_baselines_quality / 001 + GrantKey("omninode_runtime", "public", "baselines_quality_snapshots"), + # node_projection_baselines_roi / 001 + GrantKey("omninode_runtime", "public", "baselines_roi_snapshots"), + # node_projection_capsule_store / 078 + GrantKey("omninode_runtime", "public", "capsule_store"), + # node_projection_cost_by_repo / 0001 + GrantKey("omninode_runtime", "public", "cost_by_repo_snapshots"), + # node_deployment_evidence_reducer / 0001 + GrantKey("omninode_runtime", "public", "deployment_evidence_projection"), + GrantKey("omninode_runtime", "public", "deployment_readiness_projection"), + # node_projection_event_chain / 0001 + GrantKey("omninode_runtime", "public", "event_chain"), + # node_evidence_dashboard_reducer / 0001 + GrantKey("omninode_runtime", "public", "evidence_correlation_trace_projection"), + GrantKey("omninode_runtime", "public", "evidence_dashboard_projection"), + GrantKey( + "omninode_runtime", "public", "evidence_readiness_aggregate_projection" + ), + # node_projection_delegation / 0008_generation_events.sql + GrantKey("omninode_runtime", "public", "generation_events"), + # node_projection_llm_cost / 0001 + GrantKey("omninode_runtime", "public", "llm_call_metrics"), + # node_llm_delegation_projection / 0001 + GrantKey("omninode_runtime", "public", "llm_delegation_daily_projection"), + # node_projection_llm_routing / 0000 + GrantKey("omninode_runtime", "public", "llm_routing_decisions"), + # node_projection_mcp_tools / 0001 + GrantKey("omninode_runtime", "public", "mcp_tools"), + # node_nightly_loop_controller / 001_create_nightly_loop_tables.sql + GrantKey("omninode_runtime", "public", "nightly_loop_decisions"), + GrantKey("omninode_runtime", "public", "nightly_loop_iterations"), + # node_projection_registration / 0000 + GrantKey("omninode_runtime", "public", "node_service_registry"), + # node_renderer_capability_projection / 0001 + GrantKey("omninode_runtime", "public", "renderer_capability_projection"), + # node_projection_sandbox_decisions / 0001 + GrantKey("omninode_runtime", "public", "sandbox_decisions"), + # node_projection_session_outcome / 0021 + GrantKey("omninode_runtime", "public", "session_outcomes"), + # node_projection_swarm / 0001 + GrantKey("omninode_runtime", "public", "swarm_runs"), + # node_projection_traces / 0001 + GrantKey("omninode_runtime", "public", "traces"), + # node_projection_voice_sessions / 0001 + GrantKey("omninode_runtime", "public", "voice_sessions"), + } +) + # The one relation in the declared set that NO migration can deliver: nothing in # the corpus issues a CREATE TABLE for it, so there is no lineage to land a # grant in. Named here so the residual stays visible as a fact rather than as an @@ -186,6 +265,60 @@ def test_omn_17440_subset_is_declared_by_the_topology() -> None: ) +@pytest.mark.unit +@pytest.mark.parametrize("key", sorted(OMN_17440_TRANCHE2_DELIVERED, key=str)) +def test_omn_17440_tranche2_is_delivered_by_a_migration(key: GrantKey) -> None: + """OMN-17440 tranche 2: every remaining declared grant reaches a database. + + RED before this change: running + ``scripts/validation/check_topology_grant_delivery.py`` at omnibase_infra + dev ``4053dc3c0`` printed ``topology grant delivery: 27 undelivered (of 65 + declared)`` and listed every pair in this set. GREEN after: 1 undelivered, + the undeliverable residual below. + """ + delivered = delivered_grants(REPO_ROOT / "docker/migrations/forward") + assert key in delivered, ( + f"{key} is declared by the topology and issued by no migration. " + "Land the GRANT in the owning node's own migration lineage, next to " + "the file that creates the relation." + ) + + +@pytest.mark.unit +def test_omn_17440_tranche2_is_declared_by_the_topology() -> None: + """Tranche 2 grants only what the generated topology actually declares. + + Same outward-drift guard tranche 1 carries: a migration that grants a + relation nobody declares is drift in the other direction, and 26 new GRANT + statements is exactly the size of change that could introduce one. + """ + declared = declared_grants( + REPO_ROOT / "src/omnibase_infra/topology/instances/local.yaml" + ) + undeclared = OMN_17440_TRANCHE2_DELIVERED - declared + assert not undeclared, ( + "these grants are delivered by a migration but declared by no " + f"contract-derived topology entry: {sorted(map(str, undeclared))}" + ) + + +@pytest.mark.unit +def test_the_two_tranches_together_are_the_whole_deliverable_set() -> None: + """Nothing declared is left undelivered except the undeliverable residual. + + This is the assertion that makes the bound a FLOOR rather than a reading. + ``test_undelivered_count_is_exactly_the_ratchet_bound`` proves the count + matches ``MAX_UNDELIVERED``; this proves the one member of that residual is + the relation no migration can deliver, so a future change cannot satisfy the + count by leaving a *different* grant undelivered and calling it the floor. + """ + missing = set(undelivered(REPO_ROOT)) + assert missing == {UNDELIVERABLE_NO_CREATING_MIGRATION}, ( + "the undelivered set is no longer exactly the one relation with no " + f"creating migration: {sorted(map(str, missing))}" + ) + + @pytest.mark.unit def test_residual_relation_has_no_creating_migration() -> None: """The gap between the bound and zero is one relation, and it is explained. diff --git a/tests/unit/scripts/validation/test_application_migration_manifest.py b/tests/unit/scripts/validation/test_application_migration_manifest.py index 264e9e5b07..3c5882aaae 100644 --- a/tests/unit/scripts/validation/test_application_migration_manifest.py +++ b/tests/unit/scripts/validation/test_application_migration_manifest.py @@ -435,7 +435,65 @@ def test_checked_in_manifest_is_exact_and_all_blockers_are_explicit() -> None: # spelling __seq, RAISEs if the column is not sequence-backed # instead of no-oping into another silent half-grant, and asserts # has_sequence_privilege post-grant. - assert len(result.declarations) == 150 + # + # OMN-17440 tranche 2 moves this count 150 -> 171 by ADDING twenty-one + # declarations, one per owning node lineage, every one a pure ADD next to an + # untouched creating migration and every one domain `omninode_internal` to + # match the migration that creates its relation (checked against the + # existing rows for all twenty-one lineages, including + # node_projection_delegation, whose `generation_*` files are + # omninode_internal while its `delegation_*` files are `tenant`): + # + # node_deployment_evidence_reducer/0002 (deployment_evidence_projection, + # deployment_readiness_projection) + # node_evidence_dashboard_reducer/0002 (evidence_correlation_trace_projection, + # evidence_dashboard_projection, + # evidence_readiness_aggregate_projection) + # node_llm_delegation_projection/0002 (llm_delegation_daily_projection) + # node_nightly_loop_controller/002 (nightly_loop_decisions, + # nightly_loop_iterations) + # node_projection_baselines_quality/003 (baselines_quality_snapshots) + # node_projection_baselines_roi/003 (baselines_roi_snapshots) + # node_projection_capsule_store/080 (capsule_store) + # node_projection_consumer_flow/0003 (omninode_internal.consumer_flow_windows, + # omninode_internal.topic_produce_windows) + # node_projection_cost_by_repo/0002 (cost_by_repo_snapshots) + # node_projection_delegation/0035 (generation_events) + # node_projection_event_chain/0002 (event_chain) + # node_projection_llm_cost/0002 (llm_call_metrics) + # node_projection_llm_routing/0002 (llm_routing_decisions) + # node_projection_mcp_tools/0002 (mcp_tools) + # node_projection_registration/0006 (node_service_registry) + # node_projection_sandbox_decisions/0002 (sandbox_decisions) + # node_projection_session_outcome/0022 (session_outcomes) + # node_projection_swarm/0002 (swarm_runs) + # node_projection_traces/0002 (traces) + # node_projection_voice_sessions/0002 (voice_sessions) + # node_renderer_capability_projection/0002 (renderer_capability_projection) + # + # Together these issue the 26 remaining topology-declared TABLE grants that + # no migration in either repo has ever issued, taking + # check_topology_grant_delivery.py's MAX_UNDELIVERED from 27 to 1 -- the one + # relation (nightly_loop_configs) that has no CREATE TABLE anywhere in the + # corpus and so no lineage to land a grant in. + # + # Not a bulk grant file, for the OMN-15701 reason the tranche-1 files state: + # a shared cross-node grant block is how a relation added to a node later + # silently misses its grant. + # + # No creating migration is edited. Each of the twenty-one parents is applied + # on the `.201` dev lane with a recorded content_sha256, so an in-place + # repair would raise "conflicting migration checksum in canonical node + # history" (the OMN-16705 constraint that 083/087 and 0032/0033 already hit). + # + # Vendored into omnibase_infra first per the node-migration-vendor-parity- + # gate ordering, ahead of the omnimarket source PR: an omnimarket PR + # touching src/omnimarket/nodes/*/migrations/*.sql cannot land until + # omnibase_infra@dev already carries a byte-identical copy. These tsv rows + # are what make that infra-ahead-by-one state legal rather than drift -- + # sync-node-migrations.sh --check reads them as preserved history via the + # OMN-15717 legacy-declared exemption. + assert len(result.declarations) == 171 assert result.blocked == () assert len(result.legacy_node_declarations) == 2 assert len(result.cloud_aliases) == 30