From 69344d137fdfe33fdedd131a91e32911d1718bfc Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Tue, 2 Jun 2026 18:25:30 -0400 Subject: [PATCH 1/2] Fix: create config.remediation_action_log in install/, not the upgrade folder The B3 Apply-Fix audit table (config.remediation_action_log) was placed ONLY in upgrades/2.11.0-to-2.12.0/02_create_remediation_action_log.sql and nowhere in install/. A FRESH 2.12.0 install therefore never created it, so every privileged remediation (force-plan, DB-config, RCSI, clear-plan) would hit its audit-table-absent hard block on a clean install. The upgrade folder is for schema changes to EXISTING objects only (ALTER ADD COLUMN, change datatype, nullable). New tables and view/proc code belong in install/* (idempotent), which the installer runs on BOTH fresh installs and upgrades (Installer/Program.cs runs upgrade folders, then re-runs install/*). - Move the table's CREATE (final shape incl. consent_acknowledged) into install/03_create_config_tables.sql, inside config.ensure_config_tables, with the same IF OBJECT_ID guard / @tables_created increment / collection_log entry as its siblings. Created on fresh AND upgrade. - Delete upgrades/2.11.0-to-2.12.0/02_create_remediation_action_log.sql and its upgrade.txt line; renumber 03_make_other_process_cpu_nullable.sql -> 02_. - The upgrade folder now holds only ALTER-existing-object scripts (01 add columns, 02 make column nullable). Verified by upgrade-path-validator: column shape identical to the deleted script; idempotent; no dangling references; fresh + upgrade both create it. Co-Authored-By: Claude Opus 4.8 (1M context) --- install/03_create_config_tables.sql | 66 +++++++++++++ .../02_create_remediation_action_log.sql | 99 ------------------- ...=> 02_make_other_process_cpu_nullable.sql} | 0 upgrades/2.11.0-to-2.12.0/upgrade.txt | 3 +- 4 files changed, 67 insertions(+), 101 deletions(-) delete mode 100644 upgrades/2.11.0-to-2.12.0/02_create_remediation_action_log.sql rename upgrades/2.11.0-to-2.12.0/{03_make_other_process_cpu_nullable.sql => 02_make_other_process_cpu_nullable.sql} (100%) diff --git a/install/03_create_config_tables.sql b/install/03_create_config_tables.sql index dde59ef3..6f4941f3 100644 --- a/install/03_create_config_tables.sql +++ b/install/03_create_config_tables.sql @@ -712,6 +712,72 @@ BEGIN ); END; + /* + Create config.remediation_action_log — durable, append-only audit trail for the + approval-gated "Apply Fix" feature (B3). One row per apply/unapply attempt + (success/skip/error/abort) for every privileged mutation the Dashboard issues + against a monitored server: sys.sp_query_store_(un)force_plan (PLAN_REGRESSION), + ALTER DATABASE SET (always-safe DB-config + RCSI), or DBCC FREEPROCCACHE + (CLEAR_PLAN). Force-plan rows carry query_id/plan_id; the others leave those NULL + and record the prior state in prior_value. consent_acknowledged marks a DESTRUCTIVE + apply (RCSI) that passed the informed-consent gate (0 otherwise). Lives in config + alongside the other operational logs. Created here (not in upgrades/) so a FRESH + install gets it; the installer re-runs install/* on upgrade too, so existing DBs + get it as well. + */ + IF OBJECT_ID(N'config.remediation_action_log', N'U') IS NULL + BEGIN + IF @debug = 1 + BEGIN + RAISERROR(N'Creating config.remediation_action_log table', 0, 1) WITH NOWAIT; + END; + + CREATE TABLE + config.remediation_action_log + ( + action_id bigint IDENTITY(1, 1) NOT NULL, + applied_utc datetime2(7) NOT NULL + CONSTRAINT df_remediation_action_log_applied_utc + DEFAULT (SYSUTCDATETIME()), + operator_identity nvarchar(256) NULL, /* app / OS identity that clicked Apply */ + executing_login sysname NULL, /* SUSER_SNAME() actually used on the target */ + used_elevated_cred bit NOT NULL + CONSTRAINT df_remediation_action_log_used_elevated_cred + DEFAULT (0), /* always 0 in v1 (no in-app elevation); reserved */ + target_server nvarchar(256) NULL, + target_database sysname NOT NULL, + finding_fact_key varchar(64) NOT NULL, /* e.g. 'PLAN_REGRESSION' | 'DB_CONFIG' | 'RCSI' | 'CLEAR_PLAN' */ + query_id bigint NULL, /* force-plan only; NULL otherwise */ + plan_id bigint NULL, /* force-plan only; NULL otherwise */ + action varchar(32) NOT NULL, /* 'force'|'unforce'|'set_auto_shrink_off'|'set_auto_close_off'|'set_page_verify_checksum'|'set_read_committed_snapshot_on'|'clear_cached_plan' */ + prior_value nvarchar(128) NULL, /* prior state for manual reversal; NULL for force-plan */ + generated_sql nvarchar(max) NULL, /* the previewed statement, recorded only — never executed */ + result varchar(16) NOT NULL, /* 'success' | 'skipped' | 'error' | 'aborted' */ + error_message nvarchar(max) NULL, + source_alert_ref nvarchar(256) NULL, /* metric_name / story hash for traceback */ + consent_acknowledged bit NOT NULL + CONSTRAINT df_remediation_action_log_consent_acknowledged + DEFAULT (0), /* destructive apply (RCSI) passed the consent gate; 0 otherwise */ + CONSTRAINT pk_remediation_action_log PRIMARY KEY CLUSTERED (action_id) WITH (DATA_COMPRESSION = PAGE) + ); + + SET @tables_created = @tables_created + 1; + + INSERT INTO + config.collection_log + ( + collector_name, + collection_status, + error_message + ) + VALUES + ( + N'ensure_config_tables', + N'TABLE_CREATED', + N'Created config.remediation_action_log table' + ); + END; + /* Log final summary */ diff --git a/upgrades/2.11.0-to-2.12.0/02_create_remediation_action_log.sql b/upgrades/2.11.0-to-2.12.0/02_create_remediation_action_log.sql deleted file mode 100644 index 9ff63faa..00000000 --- a/upgrades/2.11.0-to-2.12.0/02_create_remediation_action_log.sql +++ /dev/null @@ -1,99 +0,0 @@ -/* -Copyright 2026 Darling Data, LLC -https://www.erikdarling.com/ - -Upgrade from 2.11.0 to 2.12.0 -Creates config.remediation_action_log — the durable, append-only audit trail for -the approval-gated "Apply Fix" feature (B3). One row is written per apply/unapply -attempt (success, skip, error, or abort), so every privileged mutation the -Dashboard issues against a monitored server (sys.sp_query_store_force_plan for a -plan regression, or ALTER DATABASE SET for an always-safe DB-config fix) is -recorded with who/what/when/where/result. Force-plan rows carry query_id/plan_id; -DB_CONFIG rows leave those NULL and record the prior setting in prior_value. - -Lives in the config schema alongside the other operational logs -(config.collection_log, config.installation_history). Idempotent: guarded by -OBJECT_ID so a re-run is a no-op. - -B3 is coupled to this 2.12.0 schema: the Dashboard hard-blocks Apply on any -server where this table does not yet exist (no mutation is attempted), so an -un-upgraded server can never produce an applied-but-unlogged action. -*/ - -SET ANSI_NULLS ON; -SET ANSI_PADDING ON; -SET ANSI_WARNINGS ON; -SET ARITHABORT ON; -SET CONCAT_NULL_YIELDS_NULL ON; -SET QUOTED_IDENTIFIER ON; -SET NUMERIC_ROUNDABORT OFF; -SET IMPLICIT_TRANSACTIONS OFF; -SET STATISTICS TIME, IO OFF; -GO - -USE PerformanceMonitor; -GO - -IF OBJECT_ID(N'config.remediation_action_log', N'U') IS NULL -BEGIN - CREATE TABLE - config.remediation_action_log - ( - action_id bigint IDENTITY(1, 1) NOT NULL - CONSTRAINT pk_remediation_action_log - PRIMARY KEY CLUSTERED, - applied_utc datetime2(7) NOT NULL - CONSTRAINT df_remediation_action_log_applied_utc - DEFAULT (SYSUTCDATETIME()), - operator_identity nvarchar(256) NULL, /* app / OS identity that clicked Apply */ - executing_login sysname NULL, /* SUSER_SNAME() actually used on the target */ - used_elevated_cred bit NOT NULL - CONSTRAINT df_remediation_action_log_used_elevated_cred - DEFAULT (0), /* always 0 in v1 (no in-app elevation); reserved */ - target_server nvarchar(256) NULL, - target_database sysname NOT NULL, - finding_fact_key varchar(64) NOT NULL, /* e.g. 'PLAN_REGRESSION' | 'DB_CONFIG' */ - query_id bigint NULL, /* force-plan only; NULL for DB_CONFIG rows */ - plan_id bigint NULL, /* force-plan only; NULL for DB_CONFIG rows */ - action varchar(32) NOT NULL, /* 'force' | 'unforce' | 'set_auto_shrink_off' | 'set_auto_close_off' | 'set_page_verify_checksum' */ - prior_value nvarchar(128) NULL, /* DB_CONFIG prior value for manual reversal ('ON' | 'NONE' | 'TORN_PAGE_DETECTION'); NULL for force-plan */ - generated_sql nvarchar(max) NULL, /* the previewed statement, recorded only — never executed */ - result varchar(16) NOT NULL, /* 'success' | 'skipped' | 'error' | 'aborted' */ - error_message nvarchar(max) NULL, - source_alert_ref nvarchar(256) NULL /* metric_name / story hash for traceback */ - ); - - PRINT 'Created config.remediation_action_log'; -END; -ELSE -BEGIN - PRINT 'config.remediation_action_log already exists — no action taken'; -END; -GO - -/* -B3 Phase 3 (consent_acknowledged): idempotent guarded ALTER, NOT a create-body amend. -The CREATE above is guarded by IF OBJECT_ID(...) IS NULL, so adding the column inside -that block would only land on servers where the table does not yet exist; servers that -already ran an earlier 2.11.0-to-2.12.0/02_ (incl. multi-machine test servers) would -keep the column-less table and turn EVERY audit INSERT into "invalid column name". -This COL_LENGTH-guarded ALTER is correct on BOTH fresh and pre-existing tables. - -consent_acknowledged records that a DESTRUCTIVE apply (RCSI) passed the informed-consent -(acknowledge-each-risk) gate. Always 0 for the always-safe DB-config and force-plan rows. -*/ -IF COL_LENGTH(N'config.remediation_action_log', N'consent_acknowledged') IS NULL -BEGIN - ALTER TABLE - config.remediation_action_log - ADD consent_acknowledged bit NOT NULL - CONSTRAINT df_remediation_action_log_consent_acknowledged - DEFAULT (0); - - PRINT 'Added config.remediation_action_log.consent_acknowledged'; -END; -ELSE -BEGIN - PRINT 'config.remediation_action_log.consent_acknowledged already exists — no action taken'; -END; -GO diff --git a/upgrades/2.11.0-to-2.12.0/03_make_other_process_cpu_nullable.sql b/upgrades/2.11.0-to-2.12.0/02_make_other_process_cpu_nullable.sql similarity index 100% rename from upgrades/2.11.0-to-2.12.0/03_make_other_process_cpu_nullable.sql rename to upgrades/2.11.0-to-2.12.0/02_make_other_process_cpu_nullable.sql diff --git a/upgrades/2.11.0-to-2.12.0/upgrade.txt b/upgrades/2.11.0-to-2.12.0/upgrade.txt index 4218c2d6..dc2756ff 100644 --- a/upgrades/2.11.0-to-2.12.0/upgrade.txt +++ b/upgrades/2.11.0-to-2.12.0/upgrade.txt @@ -1,3 +1,2 @@ 01_extend_blocked_process_report_columns.sql -02_create_remediation_action_log.sql -03_make_other_process_cpu_nullable.sql +02_make_other_process_cpu_nullable.sql From 8b8f9acbda4f585fe44b62420ca472464df107b4 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Tue, 2 Jun 2026 18:58:02 -0400 Subject: [PATCH 2/2] Fix nested-block-comment syntax error in the remediation_action_log block The header comment contained "install/*" twice; in a T-SQL block comment /* opens a NESTED comment, so the closing */ closed the nested one and left the outer comment unterminated -> "Missing end comment mark '*/'" aborted 03_create_config_tables. Reworded to "the install scripts" (no /* sequence). Verified by a clean fresh install on SQL2022 (exit 0): config.remediation_action_log created with all 17 columns incl. consent_acknowledged, action varchar(32); PerformanceMonitor DB online. Co-Authored-By: Claude Opus 4.8 (1M context) --- install/03_create_config_tables.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/install/03_create_config_tables.sql b/install/03_create_config_tables.sql index 6f4941f3..cc6781e8 100644 --- a/install/03_create_config_tables.sql +++ b/install/03_create_config_tables.sql @@ -721,9 +721,9 @@ BEGIN (CLEAR_PLAN). Force-plan rows carry query_id/plan_id; the others leave those NULL and record the prior state in prior_value. consent_acknowledged marks a DESTRUCTIVE apply (RCSI) that passed the informed-consent gate (0 otherwise). Lives in config - alongside the other operational logs. Created here (not in upgrades/) so a FRESH - install gets it; the installer re-runs install/* on upgrade too, so existing DBs - get it as well. + alongside the other operational logs. Created here (in the install scripts, not the + upgrade folder) so a FRESH install gets it; the installer re-runs the install scripts + on upgrade too, so existing databases get it as well. */ IF OBJECT_ID(N'config.remediation_action_log', N'U') IS NULL BEGIN