pg_sql_triggers 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +83 -0
  3. data/COVERAGE.md +58 -0
  4. data/Goal.md +180 -138
  5. data/README.md +6 -0
  6. data/app/controllers/pg_sql_triggers/dashboard_controller.rb +4 -1
  7. data/app/controllers/pg_sql_triggers/generator_controller.rb +67 -5
  8. data/app/models/pg_sql_triggers/trigger_registry.rb +73 -10
  9. data/app/views/pg_sql_triggers/generator/new.html.erb +18 -0
  10. data/app/views/pg_sql_triggers/generator/preview.html.erb +233 -13
  11. data/app/views/pg_sql_triggers/shared/_confirmation_modal.html.erb +32 -0
  12. data/config/initializers/pg_sql_triggers.rb +69 -0
  13. data/db/migrate/20251222000001_create_pg_sql_triggers_tables.rb +2 -0
  14. data/db/migrate/20251229071916_add_timing_to_pg_sql_triggers_registry.rb +8 -0
  15. data/docs/api-reference.md +22 -4
  16. data/docs/usage-guide.md +73 -0
  17. data/docs/web-ui.md +14 -0
  18. data/lib/generators/pg_sql_triggers/templates/create_pg_sql_triggers_tables.rb +2 -0
  19. data/lib/generators/pg_sql_triggers/templates/initializer.rb +8 -0
  20. data/lib/pg_sql_triggers/drift/db_queries.rb +116 -0
  21. data/lib/pg_sql_triggers/drift/detector.rb +187 -0
  22. data/lib/pg_sql_triggers/drift/reporter.rb +179 -0
  23. data/lib/pg_sql_triggers/drift.rb +14 -11
  24. data/lib/pg_sql_triggers/dsl/trigger_definition.rb +15 -1
  25. data/lib/pg_sql_triggers/generator/form.rb +3 -1
  26. data/lib/pg_sql_triggers/generator/service.rb +81 -25
  27. data/lib/pg_sql_triggers/migrator/pre_apply_comparator.rb +344 -0
  28. data/lib/pg_sql_triggers/migrator/pre_apply_diff_reporter.rb +143 -0
  29. data/lib/pg_sql_triggers/migrator/safety_validator.rb +258 -0
  30. data/lib/pg_sql_triggers/migrator.rb +58 -0
  31. data/lib/pg_sql_triggers/registry/manager.rb +96 -9
  32. data/lib/pg_sql_triggers/testing/function_tester.rb +66 -24
  33. data/lib/pg_sql_triggers/testing/syntax_validator.rb +24 -1
  34. data/lib/pg_sql_triggers/version.rb +1 -1
  35. data/lib/pg_sql_triggers.rb +12 -0
  36. data/scripts/generate_coverage_report.rb +129 -0
  37. metadata +12 -2
data/Goal.md CHANGED
@@ -83,7 +83,7 @@ Registry tracks:
83
83
  - ~~table_name~~
84
84
  - ~~version~~
85
85
  - ~~enabled~~
86
- - ~~checksum~~ (⚠️ partially - uses placeholder in registry manager)
86
+ - ~~checksum~~ ( fully implemented - consistent field-concatenation algorithm)
87
87
  - ~~source (dsl / generated / manual_sql)~~
88
88
  - ~~environment~~
89
89
  - ~~installed_at~~
@@ -92,37 +92,39 @@ Registry tracks:
92
92
  Rails must always know:
93
93
  - ~~what exists~~
94
94
  - ~~how it was created~~
95
- - ⚠️ whether it drifted (drift detection not fully implemented)
95
+ - whether it drifted (drift detection fully implemented)
96
96
 
97
97
  ---
98
98
 
99
- ### D. Safe Apply & Deploy (not implemented)
99
+ ### D. Safe Apply & Deploy (fully implemented via migrations)
100
100
 
101
101
  Applying triggers must:
102
- - ⚠️ Run in a transaction (migrations use transactions, but no explicit "apply" method)
103
- - Diff expected vs actual (not implemented)
104
- - ⚠️ Never blindly DROP + CREATE (migrations handle this, but no explicit safety checks)
105
- - ⚠️ Support rollback on failure (migration rollback exists, but not explicit apply rollback)
106
- - ⚠️ Update registry atomically (registry updated but not in explicit apply method)
102
+ - Run in a transaction (migrations run in transactions)
103
+ - Diff expected vs actual (fully implemented - pre-apply comparison before migration execution)
104
+ - Never blindly DROP + CREATE (fully implemented - safety validator blocks unsafe DROP + CREATE patterns)
105
+ - Support rollback on failure (migration rollback exists)
106
+ - Update registry atomically (registry updated during migration execution)
107
+
108
+ **Status:** Core functionality fully implemented through migration system. Pre-apply comparison shows diff between expected (from migration) and actual (from database) state before applying migrations. Safety validator explicitly blocks unsafe DROP + CREATE operations, preventing migrations from blindly dropping and recreating existing database objects without validation.
107
109
 
108
110
  ---
109
111
 
110
- ### E. Drift Detection ⚠️ (autoloaded but implementation missing)
112
+ ### E. Drift Detection (fully implemented)
111
113
 
112
114
  System must detect:
113
- - Missing triggers (not implemented)
114
- - Version mismatch (not implemented)
115
- - Function body drift (not implemented)
116
- - Manual SQL overrides (not implemented)
117
- - Unknown external triggers (not implemented)
115
+ - Missing triggers (implemented via DROPPED state)
116
+ - Version mismatch (implemented via checksum comparison)
117
+ - Function body drift (implemented via checksum comparison)
118
+ - Manual SQL overrides (implemented via MANUAL_OVERRIDE state)
119
+ - Unknown external triggers (implemented via UNKNOWN state)
118
120
 
119
121
  Drift states:
120
- 1. Managed & In Sync (constants defined, logic missing)
121
- 2. Managed & Drifted (constants defined, logic missing)
122
- 3. Manual Override (constants defined, logic missing)
123
- 4. Disabled (constants defined, logic missing)
124
- 5. Dropped (Recorded) (constants defined, logic missing)
125
- 6. Unknown (External) (constants defined, logic missing)
122
+ 1. Managed & In Sync (fully implemented)
123
+ 2. Managed & Drifted (fully implemented)
124
+ 3. Manual Override (fully implemented)
125
+ 4. Disabled (fully implemented)
126
+ 5. Dropped (Recorded) (fully implemented)
127
+ 6. Unknown (External) (fully implemented)
126
128
 
127
129
  ---
128
130
 
@@ -134,14 +136,14 @@ Provide console APIs:
134
136
  ~~PgSqlTriggers::Registry.enabled~~
135
137
  ~~PgSqlTriggers::Registry.disabled~~
136
138
  ~~PgSqlTriggers::Registry.for_table(:users)~~
137
- ~~PgSqlTriggers::Registry.diff~~ (⚠️ calls drift detection which is not fully implemented)
139
+ ~~PgSqlTriggers::Registry.diff~~ ( fully working with drift detection)
138
140
  ~~PgSqlTriggers::Registry.validate!~~
139
141
 
140
142
  ~~No raw SQL required by users.~~
141
143
 
142
144
  ---
143
145
 
144
- ## 4. Free-Form SQL Execution (MANDATORY) ❌ (routes exist but implementation missing)
146
+ ## 4. Free-Form SQL Execution (MANDATORY) ❌ (routes defined but no implementation)
145
147
 
146
148
  The gem MUST support free-form SQL execution.
147
149
 
@@ -154,7 +156,7 @@ This is required for:
154
156
 
155
157
  Free-form SQL is wrapped in **named SQL capsules**:
156
158
 
157
- - ❌ Must be named (routes exist, implementation missing)
159
+ - ❌ Must be named (routes defined in `config/routes.rb`, no controller exists)
158
160
  - ❌ Must declare environment (not implemented)
159
161
  - ❌ Must declare purpose (not implemented)
160
162
  - ❌ Must be applied explicitly (not implemented)
@@ -165,6 +167,8 @@ Rules:
165
167
  - ❌ Registry updated (not implemented)
166
168
  - ❌ Marked as `source = manual_sql` (not implemented)
167
169
 
170
+ **Status:** Routes exist for `sql_capsules#new`, `sql_capsules#create`, `sql_capsules#show`, and `sql_capsules#execute`, but no controller, views, or logic implemented. Autoload reference exists in `lib/pg_sql_triggers/sql.rb` but file does not exist.
171
+
168
172
  ---
169
173
 
170
174
  ## 5. Permissions Model v1 ⚠️ (structure exists, not enforced)
@@ -264,27 +268,27 @@ trigger.enable!(confirmation: "EXECUTE TRIGGER_ENABLE")
264
268
 
265
269
  UI is operational, not decorative.
266
270
 
267
- ### Dashboard ✅ (partial)
268
- - ~~Trigger name~~
269
- - ~~Table~~
270
- - ~~Version~~
271
- - ~~Status~~
272
- - ~~Source~~
273
- - ⚠️ Drift state (displayed but drift detection not fully implemented)
274
- - ~~Environment~~
275
- - ⚠️ Last applied (installed_at exists but not displayed)
276
-
277
- ### Trigger Detail Page (not implemented)
278
- - Summary panel (trigger info shown in tables/show but no dedicated page)
279
- - ❌ SQL diff
280
- - Registry state
281
-
282
- ### Actions (State-Based) ⚠️ (structure exists, not fully implemented)
283
- - ⚠️ Enable (method exists but no UI buttons/flow)
284
- - ⚠️ Disable (method exists but no UI buttons/flow)
285
- - ❌ Drop (not implemented)
286
- - ❌ Re-execute (not implemented)
287
- - ❌ Execute SQL capsule (not implemented)
271
+ ### Dashboard ✅ (implemented, drift display pending)
272
+ - Trigger name
273
+ - Table
274
+ - Version
275
+ - Status (enabled/disabled)
276
+ - Source
277
+ - ⚠️ Drift state (UI shows drift count but drift detection logic not implemented)
278
+ - Environment
279
+ - Last applied (installed_at exists in registry but not displayed in dashboard)
280
+
281
+ ### Trigger Detail Page ⚠️ (partial - shown in tables/show but not dedicated)
282
+ - ⚠️ Summary panel (trigger info shown in tables/show view but no dedicated detail route/page)
283
+ - ❌ SQL diff (expected vs actual comparison)
284
+ - ⚠️ Registry state (basic info shown, but not comprehensive state display)
285
+
286
+ ### Actions (State-Based) ⚠️ (backend methods exist, UI actions missing)
287
+ - ⚠️ Enable (console method `TriggerRegistry#enable!` exists with kill switch protection, but no UI buttons)
288
+ - ⚠️ Disable (console method `TriggerRegistry#disable!` exists with kill switch protection, but no UI buttons)
289
+ - ❌ Drop (not implemented - no method or UI)
290
+ - ❌ Re-execute (not implemented - no method or UI)
291
+ - ❌ Execute SQL capsule (not implemented - SQL capsules not implemented)
288
292
 
289
293
  Buttons must:
290
294
  - ❌ Be permission-aware (permissions defined but not enforced in UI)
@@ -335,6 +339,26 @@ This gem must be described as:
335
339
 
336
340
  ## 13. Implementation Status & Improvements Needed
337
341
 
342
+ ### 📊 Quick Status Summary
343
+
344
+ **Fully Implemented:**
345
+ - ✅ Trigger Declaration DSL (Section 3.A)
346
+ - ✅ Trigger Generation (Section 3.B)
347
+ - ✅ Trigger Registry (Section 3.C) - with consistent field-concatenation checksum algorithm
348
+ - ✅ Safe Apply & Deploy (Section 3.D) - fully implemented with safety validation
349
+ - ✅ Drift Detection (Section 3.E) - fully implemented with all 6 drift states
350
+ - ✅ Rails Console Introspection (Section 3.F) - including working diff method
351
+ - ✅ Kill Switch for Production Safety (Section 6) - fully implemented
352
+ - ✅ Basic UI Dashboard (Section 8) - migration management, tables view, generator
353
+
354
+ **Partially Implemented:**
355
+ - ⚠️ UI (Section 8) - dashboard and tables view exist, but no dedicated trigger detail page, no enable/disable buttons
356
+ - ⚠️ Permissions Model (Section 5) - structure exists but not enforced
357
+
358
+ **Not Implemented (Critical):**
359
+ - ❌ SQL Capsules (Section 4) - MANDATORY feature, routes exist but no implementation
360
+ - ❌ Drop & Re-Execute Flow (Section 9) - CRITICAL operational requirement
361
+
338
362
  ### ✅ Achieved Features
339
363
 
340
364
  **Core Infrastructure:**
@@ -343,6 +367,7 @@ This gem must be described as:
343
367
  - ✅ Trigger Generation (form-based wizard, DSL + migration files) - Section 3.B
344
368
  - ✅ Database Introspection (tables, triggers, columns) - Supporting infrastructure
345
369
  - ✅ Trigger Migrations system (rake tasks + UI) - Supporting infrastructure
370
+ - ✅ Drift Detection (all 6 states, detector, reporter, console APIs) - Section 3.E
346
371
  - ✅ Rails Console Introspection APIs (`PgSqlTriggers::Registry.*`) - Section 3.F
347
372
  - ✅ Enable/Disable trigger methods on TriggerRegistry model - Basic functionality
348
373
  - ✅ Kill Switch for Production Safety (fully implemented) - Section 6
@@ -363,16 +388,35 @@ This gem must be described as:
363
388
 
364
389
  **From Section 3.C (Trigger Registry):**
365
390
  - ✅ Registry tracks: trigger_name, table_name, version, enabled, source, environment, installed_at, last_verified_at
366
- - ✅ Registry tracks checksum (⚠️ partially - uses placeholder in registry manager)
391
+ - ✅ Registry tracks checksum ( consistent field-concatenation algorithm across all creation paths)
367
392
  - ✅ Rails knows what exists and how it was created
368
393
 
394
+ **From Section 3.E (Drift Detection):**
395
+ - ✅ Drift::Detector class with all 6 drift states
396
+ - ✅ Drift::Reporter class for formatting drift reports
397
+ - ✅ Drift::DbQueries helper for PostgreSQL system catalog queries
398
+ - ✅ Detection of missing triggers (DROPPED state)
399
+ - ✅ Detection of version/function body drift (DRIFTED state via checksum)
400
+ - ✅ Detection of manual SQL overrides (MANUAL_OVERRIDE state)
401
+ - ✅ Detection of unknown external triggers (UNKNOWN state)
402
+ - ✅ Detection of disabled triggers (DISABLED state)
403
+ - ✅ Detection of in-sync triggers (IN_SYNC state)
404
+ - ✅ Registry convenience methods (drifted, in_sync, unknown_triggers, dropped)
405
+ - ✅ TriggerRegistry instance methods (drift_state, drift_result, drifted?, in_sync?, dropped?)
406
+ - ✅ Comprehensive test coverage for Detector and Reporter
407
+
369
408
  **From Section 3.F (Rails Console Introspection):**
370
409
  - ✅ `PgSqlTriggers::Registry.list` (note: namespace differs slightly from goal)
371
410
  - ✅ `PgSqlTriggers::Registry.enabled`
372
411
  - ✅ `PgSqlTriggers::Registry.disabled`
373
412
  - ✅ `PgSqlTriggers::Registry.for_table(:users)`
374
413
  - ✅ `PgSqlTriggers::Registry.validate!`
375
- - ✅ No raw SQL required by users for basic operations
414
+ - ✅ `PgSqlTriggers::Registry.diff` (fully working with drift detection)
415
+ - ✅ `PgSqlTriggers::Registry.drifted` (returns all drifted triggers)
416
+ - ✅ `PgSqlTriggers::Registry.in_sync` (returns all in-sync triggers)
417
+ - ✅ `PgSqlTriggers::Registry.unknown_triggers` (returns all external triggers)
418
+ - ✅ `PgSqlTriggers::Registry.dropped` (returns all dropped triggers)
419
+ - ✅ No raw SQL required by users for basic operations (enable/disable via console methods)
376
420
 
377
421
  **From Section 5 (Permissions Model):**
378
422
  - ✅ Permission structure exists (Viewer, Operator, Admin roles defined)
@@ -388,44 +432,43 @@ This gem must be described as:
388
432
  - ✅ Thread-safe override mechanism
389
433
 
390
434
  **From Section 8 (UI):**
391
- - ✅ Dashboard with: Trigger name, Table, Version, Status, Source, Environment
392
- - Dashboard displays drift state (⚠️ drift detection not fully implemented)
435
+ - ✅ Dashboard with: Trigger name, Table, Version, Status (enabled/disabled), Source, Environment
436
+ - ⚠️ Dashboard displays drift count (UI shows drifted stat, but drift detection logic not implemented, so will be 0 or error)
437
+ - ✅ Tables view with table listing and trigger details
438
+ - ✅ Tables/show view shows trigger info for a specific table (not a dedicated trigger detail page)
439
+ - ✅ Generator UI (form-based wizard for creating triggers)
440
+ - ✅ Migration management UI (up/down/redo with kill switch protection)
441
+ - ❌ Trigger detail page (no dedicated route/page, only shown in tables/show)
393
442
 
394
443
  ---
395
444
 
396
445
  ### 🔴 HIGH PRIORITY - Critical Missing Features
397
446
 
398
- #### 1. Drift Detection (Section 3.E)
399
- **Priority:** HIGH - Core functionality
447
+ **Note:** Priorities have been adjusted based on actual implementation status. SQL Capsules (marked MANDATORY in Section 4) moved from MEDIUM to HIGH priority as it's a critical missing feature.
448
+
449
+ #### 1. SQL Capsules (MANDATORY - Section 4) - CRITICAL
450
+ **Priority:** HIGH - Mandatory feature for emergency operations
400
451
 
401
- **Status:** Autoloaded but implementation files missing
452
+ **Status:** Routes defined, but no implementation
402
453
 
403
454
  **Missing Files:**
404
- - ❌ `lib/pg_sql_triggers/drift/detector.rb` - Drift detection logic
405
- - ❌ `lib/pg_sql_triggers/drift/reporter.rb` - Drift reporting
455
+ - ❌ `lib/pg_sql_triggers/sql/capsule.rb` - SQL capsule definition class (autoloaded but file doesn't exist)
456
+ - ❌ `lib/pg_sql_triggers/sql/executor.rb` - SQL execution with transaction, checksum, registry update
457
+ - ❌ `app/controllers/pg_sql_triggers/sql_capsules_controller.rb` - UI controller (routes reference it but it doesn't exist)
458
+ - ❌ SQL capsule views (new, show, create, execute)
459
+ - ❌ SQL capsule storage mechanism (could use registry table with `source = manual_sql`)
406
460
 
407
461
  **Missing Functionality:**
408
- - ❌ Detection of missing triggers
409
- - ❌ Version mismatch detection
410
- - ❌ Function body drift detection
411
- - ❌ Manual SQL override detection
412
- - ❌ Unknown external trigger detection
413
- - ❌ All 6 drift states properly implemented (Managed & In Sync, Managed & Drifted, Manual Override, Disabled, Dropped (Recorded), Unknown (External))
414
-
415
- #### 2. Safe Apply & Deploy (Section 3.D)
416
- **Priority:** HIGH - Deployment safety
462
+ - ❌ Named SQL capsules with environment and purpose declaration
463
+ - ❌ Explicit application workflow with confirmation
464
+ - ❌ Transactional execution
465
+ - ❌ Checksum verification
466
+ - ❌ Registry update with `source = manual_sql`
467
+ - ❌ Kill switch protection (should block in production)
417
468
 
418
- **Status:** Not implemented
469
+ **Impact:** Critical feature marked as MANDATORY in goal but completely missing. Emergency SQL execution not possible.
419
470
 
420
- **Missing:**
421
- - ❌ Safe apply method that runs in a transaction
422
- - ❌ Diff expected vs actual state before applying
423
- - ❌ Explicit safety checks (never blindly DROP + CREATE)
424
- - ❌ Rollback on failure with registry rollback
425
- - ❌ Atomic registry update
426
- - ❌ Integration with migrations and generator service
427
-
428
- #### 3. Drop & Re-Execute Flow (CRITICAL - Section 9)
471
+ #### 2. Drop & Re-Execute Flow (Section 9) - CRITICAL
429
472
  **Priority:** HIGH - Operational requirements
430
473
 
431
474
  **Status:** Not implemented
@@ -437,30 +480,35 @@ This gem must be described as:
437
480
  - ❌ Confirmation dialogs with typed confirmation text
438
481
  - ❌ Transactional execution and registry update
439
482
 
440
- ---
483
+ **Impact:** Cannot safely drop or re-execute triggers. Operational workflows blocked.
441
484
 
442
- ### 🟡 MEDIUM PRIORITY - User-Facing Features
485
+ #### 3. Safe Apply & Deploy (Section 3.D) - ✅ FULLY IMPLEMENTED
486
+ **Priority:** MEDIUM-HIGH - Deployment safety enhancement
443
487
 
444
- #### 4. SQL Capsules (MANDATORY - Section 4)
445
- **Priority:** MEDIUM - Emergency operations
488
+ **Status:** Fully implemented - pre-apply comparison and safety validation added
446
489
 
447
- **Status:** Routes exist but implementation missing
490
+ **What Works:**
491
+ - ✅ Migrations run in transactions
492
+ - ✅ Migration rollback supported
493
+ - ✅ Registry updated during migrations
494
+ - ✅ Pre-apply comparison (diff expected vs actual) before migration execution
495
+ - ✅ Diff reporting shows what will change before applying
496
+ - ✅ Safety validator blocks unsafe DROP + CREATE operations
497
+ - ✅ Explicit validation prevents migrations from blindly dropping and recreating existing objects
448
498
 
449
- **Missing Files:**
450
- - `lib/pg_sql_triggers/sql/capsule.rb` - SQL capsule definition class
451
- - `lib/pg_sql_triggers/sql/executor.rb` - SQL execution with transaction, checksum, registry update
452
- - `app/controllers/pg_sql_triggers/sql_capsules_controller.rb` - UI controller
453
- - SQL capsule views (new, show, create)
454
- - SQL capsule storage mechanism
455
-
456
- **Requirements to implement:**
457
- - Named SQL capsules with environment and purpose declaration
458
- - Explicit application workflow
459
- - Transactional execution
460
- - Checksum verification
461
- - Registry update with `source = manual_sql`
462
-
463
- #### 5. Trigger Detail Page (Section 8 - UI)
499
+ **Implementation Details:**
500
+ - `Migrator::SafetyValidator` class detects unsafe DROP + CREATE patterns in migrations
501
+ - Validator checks if migrations would drop existing database objects and recreate them
502
+ - Blocks migration execution if unsafe patterns detected (unless explicitly allowed)
503
+ - Configuration option `allow_unsafe_migrations` (default: false) for global override
504
+ - Environment variable `ALLOW_UNSAFE_MIGRATIONS=true` for per-migration override
505
+ - Provides clear error messages explaining unsafe operations and how to proceed
506
+
507
+ ---
508
+
509
+ ### 🟡 MEDIUM PRIORITY - User-Facing Features
510
+
511
+ #### 4. Trigger Detail Page (Section 8 - UI)
464
512
  **Priority:** MEDIUM - Usability
465
513
 
466
514
  **Status:** Partial (shown in tables/show but not dedicated page)
@@ -473,51 +521,43 @@ This gem must be described as:
473
521
  - ❌ Action buttons (Enable/Disable/Drop/Re-execute/Execute SQL capsule)
474
522
  - ❌ Permission-aware, environment-aware, kill switch-aware button visibility
475
523
 
476
- #### 6. UI Actions & Permissions Enforcement (Section 8)
477
- **Priority:** MEDIUM - Usability & security
524
+ #### 5. UI Actions (Section 8)
525
+ **Priority:** MEDIUM - Usability
478
526
 
479
- **Status:** Structure exists but not fully enforced
527
+ **Status:** Backend methods exist, UI buttons missing
480
528
 
481
529
  **Missing:**
482
- - ❌ Enable/Disable buttons in dashboard and detail pages
483
- - ❌ Drop button (Admin only)
484
- - ❌ Re-execute button with flow
485
- - ❌ Execute SQL capsule button (Admin only)
486
- - ❌ Permission checking in controllers
487
- - ❌ Permission checking in UI (hide/disable buttons)
488
- - ✅ Kill switch enforcement in UI (fully implemented - see Section 6)
489
- - ❌ Environment awareness in UI actions
490
-
491
- ---
530
+ - ❌ Enable/Disable buttons in dashboard and tables/show pages (methods exist: `TriggerRegistry#enable!` and `#disable!`)
531
+ - ❌ Drop button (requires drop functionality from Section 9)
532
+ - ❌ Re-execute button (requires re-execute functionality from Section 9)
533
+ - ❌ Execute SQL capsule button (requires SQL capsules from Section 4)
492
534
 
493
- ### 🟢 LOW PRIORITY - Polish & Improvements
535
+ **What Works:**
536
+ - ✅ Kill switch enforcement in UI (fully implemented - see Section 6)
537
+ - ✅ Migration actions (up/down/redo) with kill switch protection
494
538
 
495
- #### 8. Console/CLI Permission Enforcement (Section 5)
496
- **Priority:** LOW - Security polish
539
+ #### 6. Permissions Enforcement (Section 5)
540
+ **Priority:** MEDIUM - Security
497
541
 
498
- **Status:** Not enforced
542
+ **Status:** Permission structure exists but not enforced
499
543
 
500
544
  **Missing:**
501
- - ❌ Permission checks in `TriggerRegistry#enable!` and `disable!`
545
+ - ❌ Permission checking in controllers (UI actions should check permissions)
546
+ - ❌ Permission checking in UI (hide/disable buttons based on role)
547
+ - ❌ Permission checks in `TriggerRegistry#enable!` and `disable!` (currently only kill switch checked)
502
548
  - ❌ Permission checks in rake tasks
503
549
  - ❌ Permission checks in console APIs
504
550
  - ❌ Actor context passing through all operations
505
551
 
506
- #### 9. Checksum Implementation Consistency
507
- **Priority:** LOW - Technical debt
508
-
509
- **Status:** Partially implemented
552
+ **What Exists:**
553
+ - Permission structure (Viewer, Operator, Admin roles defined)
554
+ - ✅ Permission model classes (`PgSqlTriggers::Permissions::Checker`)
510
555
 
511
- **Issues:**
512
- - ⚠️ Registry manager uses "placeholder" checksum instead of calculating real checksum
513
- - ✅ Generator service calculates checksum correctly
514
- - ⚠️ Need consistent checksum calculation across all creation paths
556
+ ---
515
557
 
516
- **Fix Required:**
517
- - Replace "placeholder" in `Registry::Manager.register` with actual checksum calculation
518
- - Ensure checksum is calculated consistently (same algorithm as generator)
558
+ ### 🟢 LOW PRIORITY - Polish & Improvements
519
559
 
520
- #### 10. Enhanced Logging & Audit Trail
560
+ #### 7. Enhanced Logging & Audit Trail
521
561
  **Priority:** LOW - Operational polish
522
562
 
523
563
  **Status:** Kill switch logging is comprehensive; audit trail could be enhanced
@@ -527,7 +567,7 @@ This gem must be described as:
527
567
  - ✅ Kill switch overrides logging (fully implemented)
528
568
  - ⚠️ Comprehensive audit trail table for production operation attempts (optional enhancement - logging exists but structured audit table would be better)
529
569
 
530
- #### 11. Error Handling Consistency
570
+ #### 8. Error Handling Consistency
531
571
  **Priority:** LOW - Code quality
532
572
 
533
573
  **Status:** Kill switch errors are properly implemented; other error types need consistency
@@ -535,10 +575,10 @@ This gem must be described as:
535
575
  **Missing:**
536
576
  - ✅ Kill switch violations raise `KillSwitchError` (fully implemented)
537
577
  - ❌ Permission violations should raise `PermissionError`
538
- - Drift issues should raise `DriftError`
578
+ - Drift detection implemented (can be used for error handling)
539
579
  - ❌ Consistent error handling across all operations
540
580
 
541
- #### 12. Testing Coverage
581
+ #### 9. Testing Coverage
542
582
  **Priority:** LOW - Quality assurance
543
583
 
544
584
  **Status:** Kill switch has comprehensive tests; other areas need coverage
@@ -546,11 +586,11 @@ This gem must be described as:
546
586
  **Missing:**
547
587
  - ❌ SQL capsules need tests
548
588
  - ✅ Kill switch has comprehensive tests (fully tested)
549
- - Drift detection needs tests
589
+ - Drift detection has comprehensive tests (fully tested)
550
590
  - ❌ Permission enforcement needs tests
551
591
  - ❌ Drop/re-execute flow needs tests
552
592
 
553
- #### 13. Documentation Updates
593
+ #### 10. Documentation Updates
554
594
  **Priority:** LOW - User experience
555
595
 
556
596
  **Status:** Kill switch is well documented; other areas need documentation
@@ -560,17 +600,19 @@ This gem must be described as:
560
600
  - ✅ README includes kill switch documentation with enforcement details (fully documented)
561
601
  - ❌ Need examples for SQL capsules
562
602
  - ❌ Need examples for permission configuration
563
-
564
- #### 14. Partial Implementation Notes
565
- **Priority:** LOW - Known issues
566
-
567
- - ⚠️ Permissions Model - Structure exists but not enforced in UI/CLI/console
568
- - ✅ Kill Switch - Fully implemented (see Section 6 for details)
569
- - ⚠️ Checksum - Implemented in generator service correctly, but Registry::Manager.register uses "placeholder" (needs fix for DSL-registered triggers)
570
- - ⚠️ Drift Detection - Constants defined, Detector and Reporter classes missing
571
- - ⚠️ Dashboard - Drift state displayed but drift detection not fully implemented (will work once drift detection is implemented)
572
- - ⚠️ Dashboard - Last applied (installed_at exists in registry but not displayed in UI)
573
- - ⚠️ `PgSqlTriggers::Registry.diff` - Calls drift detection which is not fully implemented
603
+ - ✅ Drift detection fully documented in implementation plan
604
+
605
+ #### 11. Partial Implementation Notes
606
+ **Priority:** LOW - Known issues and technical debt
607
+
608
+ **Known Issues:**
609
+ - ⚠️ **Permissions Model** - Structure exists but not enforced in UI/CLI/console
610
+ - **Kill Switch** - Fully implemented (see Section 6 for details)
611
+ - **Checksum** - Fully implemented with consistent field-concatenation algorithm across all creation paths
612
+ - **Drift Detection** - Fully implemented with all 6 drift states, comprehensive tests, and console APIs
613
+ - ⚠️ **Dashboard** - `installed_at` exists in registry table but not displayed in UI
614
+ - ⚠️ **Trigger Detail Page** - No dedicated route/page, info shown in tables/show view only
615
+ - ⚠️ **Enable/Disable UI** - Console methods exist with kill switch protection, but no UI buttons
574
616
 
575
617
  ---
576
618
 
data/README.md CHANGED
@@ -117,6 +117,12 @@ After checking out the repo, run `bin/setup` to install dependencies. Run `rake
117
117
 
118
118
  To install this gem locally, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and run `bundle exec rake release`.
119
119
 
120
+ ## Test Coverage
121
+
122
+ See [COVERAGE.md](COVERAGE.md) for detailed coverage information.
123
+
124
+ **Total Coverage: 84.97%**
125
+
120
126
  ## Contributing
121
127
 
122
128
  Bug reports and pull requests are welcome on GitHub at https://github.com/samaswin87/pg_sql_triggers.
@@ -4,11 +4,14 @@ module PgSqlTriggers
4
4
  class DashboardController < ApplicationController
5
5
  def index
6
6
  @triggers = PgSqlTriggers::TriggerRegistry.order(created_at: :desc)
7
+
8
+ # Get drift summary
9
+ drift_summary = PgSqlTriggers::Drift::Reporter.summary
7
10
  @stats = {
8
11
  total: @triggers.count,
9
12
  enabled: @triggers.enabled.count,
10
13
  disabled: @triggers.disabled.count,
11
- drifted: 0 # Will be calculated by Drift::Detector
14
+ drifted: drift_summary[:drifted]
12
15
  }
13
16
 
14
17
  # Migration status with pagination
@@ -8,7 +8,16 @@ module PgSqlTriggers
8
8
  # GET /generator/new
9
9
  # Display the multi-step form wizard
10
10
  def new
11
- @form = PgSqlTriggers::Generator::Form.new
11
+ # Restore form data from session if available (when user clicks "Back to Edit")
12
+ session_data = session[:generator_form_data]
13
+ if session_data
14
+ # Convert to hash and symbolize keys for Form initialization
15
+ form_data = session_data.is_a?(Hash) ? session_data.symbolize_keys : session_data.to_h.symbolize_keys
16
+ @form = PgSqlTriggers::Generator::Form.new(form_data)
17
+ session.delete(:generator_form_data)
18
+ else
19
+ @form = PgSqlTriggers::Generator::Form.new
20
+ end
12
21
  @available_tables = fetch_available_tables
13
22
  end
14
23
 
@@ -17,7 +26,19 @@ module PgSqlTriggers
17
26
  def preview
18
27
  @form = PgSqlTriggers::Generator::Form.new(generator_params)
19
28
 
29
+ # If user clicked "Back to Edit", store form data in session and redirect
30
+ if params[:back_to_edit].present?
31
+ # Store form data as a hash (convert ActionController::Parameters to hash)
32
+ session[:generator_form_data] = generator_params.to_h
33
+ redirect_to new_generator_path
34
+ return
35
+ end
36
+
20
37
  if @form.valid?
38
+ # Store form data in session so it can be restored if user clicks "Back to Edit"
39
+ # Convert ActionController::Parameters to hash
40
+ session[:generator_form_data] = generator_params.to_h
41
+
21
42
  # Validate SQL function body (required field)
22
43
  @sql_validation = validate_function_sql(@form)
23
44
 
@@ -58,6 +79,8 @@ module PgSqlTriggers
58
79
  result = PgSqlTriggers::Generator::Service.create_trigger(@form, actor: current_actor)
59
80
 
60
81
  if result[:success]
82
+ # Clear session data after successful creation
83
+ session.delete(:generator_form_data)
61
84
  files_msg = "Migration: #{result[:migration_path]}, DSL: #{result[:dsl_path]}"
62
85
  redirect_to root_path,
63
86
  notice: "Trigger generated successfully. Files created: #{files_msg}"
@@ -104,7 +127,7 @@ module PgSqlTriggers
104
127
  def generator_params
105
128
  params.require(:pg_sql_triggers_generator_form).permit(
106
129
  :trigger_name, :table_name, :function_name, :version,
107
- :enabled, :condition, :generate_function_stub, :function_body,
130
+ :enabled, :condition, :timing, :generate_function_stub, :function_body,
108
131
  events: [], environments: []
109
132
  )
110
133
  end
@@ -137,13 +160,52 @@ module PgSqlTriggers
137
160
  return nil if form.function_body.blank?
138
161
 
139
162
  # Create a temporary trigger registry object for validation
140
- temp_registry = PgSqlTriggers::TriggerRegistry.new(
163
+ # Only include condition if the column exists
164
+ registry_attributes = {
141
165
  trigger_name: form.trigger_name,
166
+ table_name: form.table_name,
142
167
  function_body: form.function_body
143
- )
168
+ }
169
+ # Only set condition if the column exists in the database
170
+ if PgSqlTriggers::TriggerRegistry.column_names.include?("condition")
171
+ registry_attributes[:condition] = form.condition
172
+ end
173
+
174
+ temp_registry = PgSqlTriggers::TriggerRegistry.new(registry_attributes)
175
+
176
+ # Build definition JSON for condition validation
177
+ definition = {
178
+ name: form.trigger_name,
179
+ table_name: form.table_name,
180
+ function_name: form.function_name,
181
+ events: form.events.compact_blank,
182
+ version: form.version,
183
+ enabled: form.enabled,
184
+ environments: form.environments.compact_blank,
185
+ condition: form.condition,
186
+ timing: form.timing || "before",
187
+ function_body: form.function_body
188
+ }
189
+ temp_registry.definition = definition.to_json
144
190
 
145
191
  validator = PgSqlTriggers::Testing::SyntaxValidator.new(temp_registry)
146
- validator.validate_function_syntax
192
+
193
+ # Validate function syntax
194
+ function_result = validator.validate_function_syntax
195
+ return function_result unless function_result[:valid]
196
+
197
+ # Validate condition if present
198
+ if form.condition.present?
199
+ condition_result = validator.validate_condition
200
+ unless condition_result[:valid]
201
+ return {
202
+ valid: false,
203
+ error: "WHEN condition validation failed: #{condition_result[:error]}"
204
+ }
205
+ end
206
+ end
207
+
208
+ function_result
147
209
  rescue StandardError => e
148
210
  { valid: false, error: "Validation error: #{e.message}" }
149
211
  end