feat(db): V010 audit infrastructure + temporal tables
Applied to SIGCM2 (dev) and SIGCM2_Test.
V010__audit_infrastructure.sql (idempotent, ~280 LoC):
- Filegroups AUDIT_HOT + AUDIT_COLD with physical files (per-DB logical names
via DB_NAME() prefix to avoid collision in dev/test).
- pf/ps_AuditEvent_Monthly + pf/ps_SecurityEvent_Monthly (RANGE RIGHT,
DATETIME2(3), 14 boundaries 2026-01..2027-02 → 15 partitions). Job extends
forward monthly in B11.
- dbo.AuditEvent (partitioned, clustered PK on OccurredAt+Id) + 4 indexes
(Actor/Target/Action/Correlation) with PAGE compression.
- dbo.SecurityEvent (partitioned) + 3 indexes (Actor/Action_Result/Ip_Failure).
- CHECK constraints: Action LIKE '%.%', ISJSON(Metadata), Result IN (success|failure).
- SYSTEM_VERSIONING ON in Usuario/Rol/Permiso/RolPermiso with 10 YEARS retention +
PAGE compression in history tables.
- No hard FK on ActorUserId → Usuario.Id (soft FK — audit must survive user deletion).
V010_ROLLBACK.sql: emergency reversal (WARNING: destroys all audit history).
database/README.md: migration order + V010 prod-apply notes.
tests/SIGCM2.TestSupport/SqlTestFixture.cs:
- EnsureV010SchemaAsync() validates audit infra is applied (fails fast with
clear message if not — migration itself requires ALTER DATABASE privileges
and is applied manually via sqlcmd).
- Respawn TablesToIgnore extended with *_History (engine rejects direct DELETE
on system-versioned history tables).
tests/SIGCM2.Api.Tests/Audit/V010MigrationTests.cs — 5 smoke tests:
- AuditEvent insert+roundtrip with CorrelationId.
- CK_AuditEvent_Action rejects Action without '.'.
- CK_AuditEvent_Metadata rejects non-JSON.
- CK_SecurityEvent_Result rejects invalid Result.
- Usuario SYSTEM_VERSIONING: temporal query FOR SYSTEM_TIME AS OF returns
pre-update state + Usuario_History populated.
Suite: 130/130 passing (previous 124 + spike B0 + 5 new B1). No regressions.
Refs: sdd/udt-010-auditoria-trazabilidad/{spec#REQ-AUD-1,2, #REQ-SEC-1,
design#D-4, tasks}
This commit is contained in:
183
database/migrations/V010_ROLLBACK.sql
Normal file
183
database/migrations/V010_ROLLBACK.sql
Normal file
@@ -0,0 +1,183 @@
|
||||
-- V010_ROLLBACK.sql
|
||||
-- Reversa de V010__audit_infrastructure.sql.
|
||||
--
|
||||
-- ⚠️ ADVERTENCIA: ejecutar este script ELIMINA toda la historia auditada.
|
||||
-- - dbo.AuditEvent y dbo.SecurityEvent se dropean (junto con datos).
|
||||
-- - History tables (Usuario_History, Rol_History, Permiso_History, RolPermiso_History) se dropean.
|
||||
-- - Particionamiento, filegroups y archivos físicos se desmontan.
|
||||
--
|
||||
-- Uso intended: ROLLBACK de emergencia en entornos NO-productivos.
|
||||
-- En prod futuro, este script NO se ejecuta: si hace falta revertir, se hace
|
||||
-- restore de backup previo a V010.
|
||||
|
||||
SET QUOTED_IDENTIFIER ON;
|
||||
SET ANSI_NULLS ON;
|
||||
SET NOCOUNT ON;
|
||||
GO
|
||||
|
||||
-- ═══════════════════════════════════════════════════════════════════════
|
||||
-- 1. Apagar SYSTEM_VERSIONING + remover columnas PERIOD en las 4 tablas
|
||||
-- ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
-- Usuario
|
||||
IF EXISTS (SELECT 1 FROM sys.tables WHERE object_id = OBJECT_ID('dbo.Usuario') AND temporal_type = 2)
|
||||
BEGIN
|
||||
ALTER TABLE dbo.Usuario SET (SYSTEM_VERSIONING = OFF);
|
||||
PRINT 'Usuario: SYSTEM_VERSIONING OFF.';
|
||||
END
|
||||
GO
|
||||
|
||||
IF EXISTS (SELECT 1 FROM sys.periods WHERE object_id = OBJECT_ID('dbo.Usuario'))
|
||||
BEGIN
|
||||
ALTER TABLE dbo.Usuario DROP PERIOD FOR SYSTEM_TIME;
|
||||
PRINT 'Usuario: PERIOD FOR SYSTEM_TIME dropped.';
|
||||
END
|
||||
GO
|
||||
|
||||
IF COL_LENGTH('dbo.Usuario', 'ValidFrom') IS NOT NULL
|
||||
BEGIN
|
||||
ALTER TABLE dbo.Usuario DROP CONSTRAINT IF EXISTS DF_Usuario_ValidFrom;
|
||||
ALTER TABLE dbo.Usuario DROP CONSTRAINT IF EXISTS DF_Usuario_ValidTo;
|
||||
ALTER TABLE dbo.Usuario DROP COLUMN ValidFrom, ValidTo;
|
||||
PRINT 'Usuario: ValidFrom/ValidTo dropped.';
|
||||
END
|
||||
GO
|
||||
|
||||
IF OBJECT_ID(N'dbo.Usuario_History', N'U') IS NOT NULL
|
||||
BEGIN
|
||||
DROP TABLE dbo.Usuario_History;
|
||||
PRINT 'Usuario_History dropped.';
|
||||
END
|
||||
GO
|
||||
|
||||
-- Rol
|
||||
IF EXISTS (SELECT 1 FROM sys.tables WHERE object_id = OBJECT_ID('dbo.Rol') AND temporal_type = 2)
|
||||
ALTER TABLE dbo.Rol SET (SYSTEM_VERSIONING = OFF);
|
||||
GO
|
||||
|
||||
IF EXISTS (SELECT 1 FROM sys.periods WHERE object_id = OBJECT_ID('dbo.Rol'))
|
||||
ALTER TABLE dbo.Rol DROP PERIOD FOR SYSTEM_TIME;
|
||||
GO
|
||||
|
||||
IF COL_LENGTH('dbo.Rol', 'ValidFrom') IS NOT NULL
|
||||
BEGIN
|
||||
ALTER TABLE dbo.Rol DROP CONSTRAINT IF EXISTS DF_Rol_ValidFrom;
|
||||
ALTER TABLE dbo.Rol DROP CONSTRAINT IF EXISTS DF_Rol_ValidTo;
|
||||
ALTER TABLE dbo.Rol DROP COLUMN ValidFrom, ValidTo;
|
||||
END
|
||||
GO
|
||||
|
||||
IF OBJECT_ID(N'dbo.Rol_History', N'U') IS NOT NULL DROP TABLE dbo.Rol_History;
|
||||
GO
|
||||
|
||||
-- Permiso
|
||||
IF EXISTS (SELECT 1 FROM sys.tables WHERE object_id = OBJECT_ID('dbo.Permiso') AND temporal_type = 2)
|
||||
ALTER TABLE dbo.Permiso SET (SYSTEM_VERSIONING = OFF);
|
||||
GO
|
||||
|
||||
IF EXISTS (SELECT 1 FROM sys.periods WHERE object_id = OBJECT_ID('dbo.Permiso'))
|
||||
ALTER TABLE dbo.Permiso DROP PERIOD FOR SYSTEM_TIME;
|
||||
GO
|
||||
|
||||
IF COL_LENGTH('dbo.Permiso', 'ValidFrom') IS NOT NULL
|
||||
BEGIN
|
||||
ALTER TABLE dbo.Permiso DROP CONSTRAINT IF EXISTS DF_Permiso_ValidFrom;
|
||||
ALTER TABLE dbo.Permiso DROP CONSTRAINT IF EXISTS DF_Permiso_ValidTo;
|
||||
ALTER TABLE dbo.Permiso DROP COLUMN ValidFrom, ValidTo;
|
||||
END
|
||||
GO
|
||||
|
||||
IF OBJECT_ID(N'dbo.Permiso_History', N'U') IS NOT NULL DROP TABLE dbo.Permiso_History;
|
||||
GO
|
||||
|
||||
-- RolPermiso
|
||||
IF EXISTS (SELECT 1 FROM sys.tables WHERE object_id = OBJECT_ID('dbo.RolPermiso') AND temporal_type = 2)
|
||||
ALTER TABLE dbo.RolPermiso SET (SYSTEM_VERSIONING = OFF);
|
||||
GO
|
||||
|
||||
IF EXISTS (SELECT 1 FROM sys.periods WHERE object_id = OBJECT_ID('dbo.RolPermiso'))
|
||||
ALTER TABLE dbo.RolPermiso DROP PERIOD FOR SYSTEM_TIME;
|
||||
GO
|
||||
|
||||
IF COL_LENGTH('dbo.RolPermiso', 'ValidFrom') IS NOT NULL
|
||||
BEGIN
|
||||
ALTER TABLE dbo.RolPermiso DROP CONSTRAINT IF EXISTS DF_RolPermiso_ValidFrom;
|
||||
ALTER TABLE dbo.RolPermiso DROP CONSTRAINT IF EXISTS DF_RolPermiso_ValidTo;
|
||||
ALTER TABLE dbo.RolPermiso DROP COLUMN ValidFrom, ValidTo;
|
||||
END
|
||||
GO
|
||||
|
||||
IF OBJECT_ID(N'dbo.RolPermiso_History', N'U') IS NOT NULL DROP TABLE dbo.RolPermiso_History;
|
||||
GO
|
||||
|
||||
-- ═══════════════════════════════════════════════════════════════════════
|
||||
-- 2. Drop AuditEvent + SecurityEvent
|
||||
-- ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
IF OBJECT_ID(N'dbo.AuditEvent', N'U') IS NOT NULL
|
||||
BEGIN
|
||||
DROP TABLE dbo.AuditEvent;
|
||||
PRINT 'Table dbo.AuditEvent dropped.';
|
||||
END
|
||||
GO
|
||||
|
||||
IF OBJECT_ID(N'dbo.SecurityEvent', N'U') IS NOT NULL
|
||||
BEGIN
|
||||
DROP TABLE dbo.SecurityEvent;
|
||||
PRINT 'Table dbo.SecurityEvent dropped.';
|
||||
END
|
||||
GO
|
||||
|
||||
-- ═══════════════════════════════════════════════════════════════════════
|
||||
-- 3. Drop partition schemes + functions
|
||||
-- ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
IF EXISTS (SELECT 1 FROM sys.partition_schemes WHERE name = 'ps_AuditEvent_Monthly')
|
||||
DROP PARTITION SCHEME ps_AuditEvent_Monthly;
|
||||
GO
|
||||
|
||||
IF EXISTS (SELECT 1 FROM sys.partition_functions WHERE name = 'pf_AuditEvent_Monthly')
|
||||
DROP PARTITION FUNCTION pf_AuditEvent_Monthly;
|
||||
GO
|
||||
|
||||
IF EXISTS (SELECT 1 FROM sys.partition_schemes WHERE name = 'ps_SecurityEvent_Monthly')
|
||||
DROP PARTITION SCHEME ps_SecurityEvent_Monthly;
|
||||
GO
|
||||
|
||||
IF EXISTS (SELECT 1 FROM sys.partition_functions WHERE name = 'pf_SecurityEvent_Monthly')
|
||||
DROP PARTITION FUNCTION pf_SecurityEvent_Monthly;
|
||||
GO
|
||||
|
||||
-- ═══════════════════════════════════════════════════════════════════════
|
||||
-- 4. Remover archivos físicos y filegroups
|
||||
-- ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
DECLARE @dbName NVARCHAR(128) = DB_NAME();
|
||||
DECLARE @hotLogical NVARCHAR(128) = @dbName + N'_AUDIT_HOT';
|
||||
DECLARE @coldLogical NVARCHAR(128) = @dbName + N'_AUDIT_COLD';
|
||||
DECLARE @sql NVARCHAR(MAX);
|
||||
|
||||
IF EXISTS (SELECT 1 FROM sys.database_files WHERE name = @hotLogical)
|
||||
BEGIN
|
||||
SET @sql = N'ALTER DATABASE CURRENT REMOVE FILE [' + @hotLogical + N'];';
|
||||
EXEC sp_executesql @sql;
|
||||
PRINT 'File ' + @hotLogical + ' removed.';
|
||||
END
|
||||
|
||||
IF EXISTS (SELECT 1 FROM sys.database_files WHERE name = @coldLogical)
|
||||
BEGIN
|
||||
SET @sql = N'ALTER DATABASE CURRENT REMOVE FILE [' + @coldLogical + N'];';
|
||||
EXEC sp_executesql @sql;
|
||||
PRINT 'File ' + @coldLogical + ' removed.';
|
||||
END
|
||||
|
||||
IF EXISTS (SELECT 1 FROM sys.filegroups WHERE name = 'AUDIT_HOT')
|
||||
EXEC sp_executesql N'ALTER DATABASE CURRENT REMOVE FILEGROUP AUDIT_HOT;';
|
||||
|
||||
IF EXISTS (SELECT 1 FROM sys.filegroups WHERE name = 'AUDIT_COLD')
|
||||
EXEC sp_executesql N'ALTER DATABASE CURRENT REMOVE FILEGROUP AUDIT_COLD;';
|
||||
GO
|
||||
|
||||
PRINT '';
|
||||
PRINT 'V010 rolled back. Audit infrastructure removed. All audit history is permanently LOST.';
|
||||
GO
|
||||
Reference in New Issue
Block a user