From 3abddc862055add63e172e728db8546e1310e664 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 17:29:15 +0000 Subject: [PATCH 1/4] Initial plan From 92db5bcaf6c5664a3fa438ca68664c4afcd09fa1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 17:32:43 +0000 Subject: [PATCH 2/4] Address review comments: fix paths, docstrings, cleanup gating, and align docs - Remove duplicate sys.path.append, use sys.path.insert(0, ...) - Anchor log_dir and report_dir to project root using Path(__file__).resolve() - Update docstring to match actual status code check (>= 400) - Remove unused service instantiations (MetricsService, LoggingService) - Gate database cleanup on actual database component health, not all health issues - Update AGENTS.md to clarify automated vs advisory actions - Align AGENTS.md thresholds with implementation (>= 400, complete log scan) - Add note that agent is internal monitoring tool, not part of core workflow Co-authored-by: groupthinking <154503486+groupthinking@users.noreply.github.com> --- AGENTS.md | 33 ++++++++++++++----------- scripts/nightly_audit_agent.py | 44 ++++++++++++++++++---------------- 2 files changed, 43 insertions(+), 34 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index f0ecab4fb..80ecd8da9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,9 +4,12 @@ This document contains system prompts and protocols for specialized agents withi ## Audit & Remediation Agent -**Role:** High-Integrity Systems Auditor & First-Principles Engineer +**Role:** High-Integrity Systems Auditor & First-Principles Engineer +**Type:** Internal Monitoring Tool (Not Part of Core YouTube-Link Workflow) **Frequency:** Nightly Execution (02:00 UTC) +**Note:** This agent is an internal monitoring and maintenance tool that operates independently of EventRelay's core workflow (YouTube link → context extraction → agent dispatch → outputs). It performs system health audits and generates recommendations for operator review. + ### 1. Objective: The Audit Trace You are tasked with a nightly deep-scan of all system logs, transaction traces, and state changes. Your goal is not just to find "failures," but to identify **divergences from first principles** (e.g., "Is this data state logically possible given the laws of the system?"). @@ -21,25 +24,27 @@ For every error, bottleneck, or inconsistency discovered, you must execute a "Fi ### 3. Execution Phase: Ruthless Solutions -You are authorized to take autonomous action on **all** issues found. You must bypass "band-aid" fixes in favor of **ruthless, proven solutions**: +You may autonomously execute only **pre-approved, low-risk maintenance actions** (e.g., log aggregation, report generation, and safe database cleanup routines when database components are specifically unhealthy). For all other issues, you must generate **ruthless, first-principles recommendations** for a human operator to review and implement: -* **Discard the Fragile:** If a component fails consistently, your suggested action should be a structural rewrite rather than a patch. -* **Atomic Remediation:** Ensure every fix is idempotent and verified against the system's core constraints. -* **No Half-Measures:** If a record is corrupt, quarantine and rebuild from the last known-good state; do not attempt to "guess" missing data. +* **Discard the Fragile (Advisory):** If a component fails consistently, your suggested action should be a structural rewrite rather than a patch. This is a recommendation only; you do not perform structural rewrites yourself. +* **Atomic Remediation (Advisory):** For each issue, propose fixes that would be idempotent and verifiable against the system's core constraints. Clearly label these as recommendations requiring manual approval. +* **No Half-Measures (Advisory):** If a record appears corrupt, flag it, explain why, and recommend quarantining and rebuilding from the last known-good state. Do **not** attempt to directly modify, quarantine, or rebuild production records autonomously. ### 4. Fortification: Preventative Measures -Every remediation must be accompanied by a hard-coded preventative measure. This includes: +Every **recommended** remediation must be accompanied by a proposed preventative measure. This includes recommendations such as: + +* **Constraint Injection (Advisory):** Suggest schema-level or logic-level guards that would make the error mathematically impossible to repeat, but do not change schemas or business logic directly. +* **Automated Regression (Advisory):** Propose new trace-points or monitoring hooks for this failure mode so it can be caught in real-time before the next nightly audit; implementation is left to human operators. -* **Constraint Injection:** Adding schema-level or logic-level guards to make the error mathematically impossible to repeat. -* **Automated Regression:** Creating a new trace-point specifically for this failure mode to catch it in real-time before the next nightly audit. +_Current implementation note:_ Automated behavior is limited to log analysis, report generation, and safe maintenance tasks like database cleanup when database components are specifically unhealthy. Structural changes, schema updates, and record-level repairs are **advisory-only** and require human review. ### Implementation Instructions for Jules -1. **Initialize Audit Agent:** Load the trace logs for the previous 24-hour window. -2. **Filter Logic:** Flag any status code > 400 or any latency > 200ms. +1. **Initialize Audit Agent:** Load the complete trace logs from the available log file(s). +2. **Filter Logic:** Flag any status code >= 400 or any latency > 200ms. 3. **Action Loop:** - * **IF** issue found **THEN** execute `FirstPrinciplesAnalysis()`. - * **EXECUTE** `RuthlessCleanup()`. - * **DEPLOY** `PreventativeGuard()`. -4. **Reporting:** Summarize all "Ruthless Actions" taken and list the new constraints added to the system. + * **IF** issue found **THEN** execute `FirstPrinciplesAnalysis()` to generate a root-cause narrative and proposed remediations. + * **EXECUTE** `RuthlessCleanup()` only for pre-approved maintenance tasks (e.g., database cleanup when database components are unhealthy); for all other items, record "ruthless" cleanup steps as recommendations rather than actions. + * **DEPLOY** `PreventativeGuard()` as a set of recommended constraints and monitoring additions for human review, not as direct schema or code changes. +4. **Reporting:** Summarize (a) all automated maintenance actions actually executed and (b) all advisory "Ruthless Actions" and preventative guards recommended for operators to implement. diff --git a/scripts/nightly_audit_agent.py b/scripts/nightly_audit_agent.py index 680a4cd0c..120bf604f 100644 --- a/scripts/nightly_audit_agent.py +++ b/scripts/nightly_audit_agent.py @@ -19,16 +19,15 @@ from datetime import datetime, timezone from pathlib import Path from typing import Dict, Any + # Add src to python path to allow imports -sys.path.append(str(Path(__file__).parent.parent / "src")) +sys.path.insert(0, str(Path(__file__).parent.parent / "src")) # Imports - Fail fast if missing dependencies from youtube_extension.backend.services.health_monitoring_service import ( HealthMonitoringService, HealthStatus ) -from youtube_extension.backend.services.metrics_service import MetricsService -from youtube_extension.backend.services.logging_service import LoggingService from youtube_extension.backend.services.database_cleanup_service import run_database_cleanup # Configure logging @@ -48,17 +47,16 @@ def __init__(self, dry_run: bool = False): self.issues = [] self.remediations = [] self.fortifications = [] - self.log_dir = Path("logs") - self.report_dir = Path("audit_reports") + # Anchor paths to project root + project_root = Path(__file__).resolve().parent.parent + self.log_dir = project_root / "logs" + self.report_dir = project_root / "audit_reports" # Ensure directories exist self.report_dir.mkdir(parents=True, exist_ok=True) - # Initialize services + # Initialize services (only those needed) self.health_service = HealthMonitoringService() - self.metrics_service = MetricsService() - # Logging service is usually a singleton - self.logging_service = LoggingService() async def run_audit(self): """Main execution loop.""" @@ -105,7 +103,7 @@ async def analyze_health(self): }) async def analyze_logs(self): - """Analyze logs for status codes > 400.""" + """Analyze logs for status codes >= 400.""" logger.info("Analyzing logs...") log_file = self.log_dir / "structured_logs.jsonl" @@ -190,7 +188,8 @@ async def first_principles_analysis(self, issue: Dict[str, Any]) -> Dict[str, An async def ruthless_remediation(self, diagnosis: Dict[str, Any]): """ - Execute ruthless solutions. + Execute ruthless solutions (pre-approved maintenance tasks only). + Structural changes and schema updates are advisory-only. """ fix = diagnosis['proposed_fix'] logger.info(f"Executing remediation: {fix}") @@ -203,21 +202,26 @@ async def ruthless_remediation(self, diagnosis: Dict[str, Any]): # "Ruthless" Actions implementation if "Restart" in fix: # In a real env, this might trigger a k8s restart or systemctl - self.remediations.append(f"Triggered restart for components related to {diagnosis['issue']['type']}") + self.remediations.append(f"[ADVISORY] Triggered restart for components related to {diagnosis['issue']['type']}") elif "Review" in fix: - self.remediations.append(f"Flagged {diagnosis['issue']['type']} for immediate manual review (Ticket created)") + self.remediations.append(f"[ADVISORY] Flagged {diagnosis['issue']['type']} for immediate manual review (Ticket created)") elif "Optimize" in fix: - self.remediations.append("Triggered auto-optimization (e.g., ANALYZE DB)") + self.remediations.append("[ADVISORY] Triggered auto-optimization (e.g., ANALYZE DB)") - # Always run DB cleanup if it's a health issue, just in case + # Only run DB cleanup if database component is specifically unhealthy if diagnosis['issue']['type'] == 'health_check': - try: - results = await run_database_cleanup() - self.remediations.append(f"Ran database cleanup: {len(results)} databases cleaned") - except Exception as e: - self.remediations.append(f"Database cleanup failed: {e}") + unhealthy_components = diagnosis['issue'].get('components', []) + db_components = [c for c in unhealthy_components if 'database' in c.lower() or 'db' in c.lower()] + + if db_components: + try: + results = await run_database_cleanup() + self.remediations.append(f"Ran database cleanup for unhealthy DB components {db_components}: {len(results)} databases cleaned") + except Exception as e: + logger.error(f"Database cleanup failed: {e}") + self.remediations.append(f"Database cleanup failed: {e}") async def fortify(self, diagnosis: Dict[str, Any]): """ From 03c44f7c794d9da6d01b16cee3595cb02d8e1d7d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 17:34:14 +0000 Subject: [PATCH 3/4] Add comprehensive unit tests for nightly_audit_agent - Test initialization (dry-run, live mode, path anchoring) - Test log analysis (missing file, various status codes, >= 400 threshold) - Test database cleanup gating (only triggers for DB health issues) - Test dry-run mode (skips remediation and fortification) - Test report generation and saving - Test advisory labeling for remediations - All 22 tests passing Co-authored-by: groupthinking <154503486+groupthinking@users.noreply.github.com> --- tests/unit/test_nightly_audit_agent.py | 395 +++++++++++++++++++++++++ 1 file changed, 395 insertions(+) create mode 100644 tests/unit/test_nightly_audit_agent.py diff --git a/tests/unit/test_nightly_audit_agent.py b/tests/unit/test_nightly_audit_agent.py new file mode 100644 index 000000000..3a2658833 --- /dev/null +++ b/tests/unit/test_nightly_audit_agent.py @@ -0,0 +1,395 @@ +""" +Tests for scripts/nightly_audit_agent.py - Nightly Audit & Remediation Agent +""" +import sys +import tempfile +import pytest +import asyncio +import json +from pathlib import Path +from unittest.mock import patch, MagicMock, AsyncMock +from datetime import datetime, timezone + +# Add scripts to path for imports +sys.path.insert(0, str(Path(__file__).parent.parent.parent / "scripts")) + +# Mock the services before importing the agent +sys.modules['youtube_extension.backend.services.health_monitoring_service'] = MagicMock() +sys.modules['youtube_extension.backend.services.database_cleanup_service'] = MagicMock() + +from nightly_audit_agent import AuditAgent + + +class TestAuditAgentInitialization: + """Test AuditAgent initialization.""" + + def test_initialization_dry_run(self): + """Test agent initialization in dry-run mode.""" + agent = AuditAgent(dry_run=True) + + assert agent.dry_run is True + assert agent.issues == [] + assert agent.remediations == [] + assert agent.fortifications == [] + assert agent.log_dir.is_absolute() + assert agent.report_dir.is_absolute() + + def test_initialization_live_mode(self): + """Test agent initialization in live mode.""" + agent = AuditAgent(dry_run=False) + + assert agent.dry_run is False + assert agent.issues == [] + + def test_paths_anchored_to_project_root(self): + """Test that log_dir and report_dir are anchored to project root.""" + agent = AuditAgent() + + # Paths should be absolute + assert agent.log_dir.is_absolute() + assert agent.report_dir.is_absolute() + + # Paths should contain project name + assert "EventRelay" in str(agent.log_dir) + assert "EventRelay" in str(agent.report_dir) + + def test_report_dir_created(self): + """Test that report directory is created on initialization.""" + agent = AuditAgent() + + # Report dir should exist + assert agent.report_dir.exists() + assert agent.report_dir.is_dir() + + +class TestLogAnalysis: + """Test log analysis functionality.""" + + @pytest.mark.asyncio + async def test_analyze_logs_missing_file(self): + """Test log analysis with missing log file.""" + with tempfile.TemporaryDirectory() as tmp_dir: + agent = AuditAgent() + agent.log_dir = Path(tmp_dir) + + await agent.analyze_logs() + + # Should handle gracefully without crashing + assert True # If we get here, no exception was raised + + @pytest.mark.asyncio + async def test_analyze_logs_status_code_400(self): + """Test that status code 400 is flagged (>= 400 threshold).""" + with tempfile.TemporaryDirectory() as tmp_dir: + agent = AuditAgent() + agent.log_dir = Path(tmp_dir) + + # Create log file with status 400 + log_file = agent.log_dir / "structured_logs.jsonl" + log_file.write_text(json.dumps({ + "status_code": 400, + "endpoint": "/api/test", + "timestamp": "2026-01-29T12:00:00Z" + }) + "\n") + + await agent.analyze_logs() + + # Should flag status 400 + assert len(agent.issues) == 1 + assert agent.issues[0]["type"] == "http_error" + assert agent.issues[0]["severity"] == "medium" + + @pytest.mark.asyncio + async def test_analyze_logs_status_code_500(self): + """Test that status code 500 is flagged as high severity.""" + with tempfile.TemporaryDirectory() as tmp_dir: + agent = AuditAgent() + agent.log_dir = Path(tmp_dir) + + # Create log file with status 500 + log_file = agent.log_dir / "structured_logs.jsonl" + log_file.write_text(json.dumps({ + "status_code": 500, + "endpoint": "/api/error", + "timestamp": "2026-01-29T12:00:00Z" + }) + "\n") + + await agent.analyze_logs() + + # Should flag status 500 as high severity + assert len(agent.issues) == 1 + assert agent.issues[0]["type"] == "http_error" + assert agent.issues[0]["severity"] == "high" + + @pytest.mark.asyncio + async def test_analyze_logs_status_code_200(self): + """Test that status code 200 is not flagged.""" + with tempfile.TemporaryDirectory() as tmp_dir: + agent = AuditAgent() + agent.log_dir = Path(tmp_dir) + + # Create log file with status 200 + log_file = agent.log_dir / "structured_logs.jsonl" + log_file.write_text(json.dumps({ + "status_code": 200, + "endpoint": "/api/success", + "timestamp": "2026-01-29T12:00:00Z" + }) + "\n") + + await agent.analyze_logs() + + # Should not flag status 200 + assert len(agent.issues) == 0 + + @pytest.mark.asyncio + async def test_analyze_logs_multiple_entries(self): + """Test log analysis with multiple entries.""" + with tempfile.TemporaryDirectory() as tmp_dir: + agent = AuditAgent() + agent.log_dir = Path(tmp_dir) + + # Create log file with multiple entries + log_file = agent.log_dir / "structured_logs.jsonl" + with open(log_file, 'w') as f: + f.write(json.dumps({"status_code": 200, "endpoint": "/api/ok"}) + "\n") + f.write(json.dumps({"status_code": 404, "endpoint": "/api/notfound"}) + "\n") + f.write(json.dumps({"status_code": 500, "endpoint": "/api/error"}) + "\n") + + await agent.analyze_logs() + + # Should flag 404 and 500 + assert len(agent.issues) == 2 + + +class TestDatabaseCleanupGating: + """Test database cleanup gating logic.""" + + @pytest.mark.asyncio + async def test_cleanup_not_triggered_for_http_errors(self): + """Test that database cleanup is not triggered for HTTP errors.""" + agent = AuditAgent(dry_run=False) + + diagnosis = { + "issue": {"type": "http_error", "severity": "high"}, + "proposed_fix": "Review endpoint logic" + } + + with patch('nightly_audit_agent.run_database_cleanup') as mock_cleanup: + await agent.ruthless_remediation(diagnosis) + + # Database cleanup should not be called for HTTP errors + mock_cleanup.assert_not_called() + + @pytest.mark.asyncio + async def test_cleanup_not_triggered_for_non_db_health_issues(self): + """Test that cleanup is not triggered for non-database health issues.""" + agent = AuditAgent(dry_run=False) + + diagnosis = { + "issue": { + "type": "health_check", + "severity": "high", + "components": ["redis", "external_api"] + }, + "proposed_fix": "Restart unhealthy services" + } + + with patch('nightly_audit_agent.run_database_cleanup') as mock_cleanup: + await agent.ruthless_remediation(diagnosis) + + # Database cleanup should not be called for non-DB components + mock_cleanup.assert_not_called() + + @pytest.mark.asyncio + async def test_cleanup_triggered_for_database_health_issues(self): + """Test that cleanup IS triggered when database is specifically unhealthy.""" + agent = AuditAgent(dry_run=False) + + diagnosis = { + "issue": { + "type": "health_check", + "severity": "high", + "components": ["database", "redis"] + }, + "proposed_fix": "Restart unhealthy services" + } + + mock_results = [{"db": "test_db", "status": "cleaned"}] + with patch('nightly_audit_agent.run_database_cleanup', new_callable=AsyncMock, return_value=mock_results) as mock_cleanup: + await agent.ruthless_remediation(diagnosis) + + # Database cleanup should be called + mock_cleanup.assert_called_once() + assert any("database cleanup" in r.lower() for r in agent.remediations) + + @pytest.mark.asyncio + async def test_cleanup_triggered_for_db_component_case_insensitive(self): + """Test that cleanup works with case-insensitive DB component names.""" + agent = AuditAgent(dry_run=False) + + diagnosis = { + "issue": { + "type": "health_check", + "severity": "high", + "components": ["Database", "DB_Connection"] + }, + "proposed_fix": "Restart unhealthy services" + } + + mock_results = [] + with patch('nightly_audit_agent.run_database_cleanup', new_callable=AsyncMock, return_value=mock_results) as mock_cleanup: + await agent.ruthless_remediation(diagnosis) + + # Database cleanup should be called for case variations + mock_cleanup.assert_called_once() + + +class TestDryRunMode: + """Test dry-run mode functionality.""" + + @pytest.mark.asyncio + async def test_dry_run_skips_remediation(self): + """Test that dry-run mode skips actual remediation.""" + agent = AuditAgent(dry_run=True) + + diagnosis = { + "issue": { + "type": "health_check", + "severity": "high", + "components": ["database"] + }, + "proposed_fix": "Restart unhealthy services" + } + + with patch('nightly_audit_agent.run_database_cleanup') as mock_cleanup: + await agent.ruthless_remediation(diagnosis) + + # Database cleanup should not be called in dry-run mode + mock_cleanup.assert_not_called() + + # Should have dry-run entry in remediations + assert len(agent.remediations) > 0 + assert any("[DRY RUN]" in r for r in agent.remediations) + + @pytest.mark.asyncio + async def test_dry_run_skips_fortification(self): + """Test that dry-run mode skips fortification.""" + agent = AuditAgent(dry_run=True) + + diagnosis = { + "issue": {"type": "http_error"}, + "preventative_measure": "Add integration test" + } + + await agent.fortify(diagnosis) + + # Should have dry-run entry in fortifications + assert len(agent.fortifications) > 0 + assert any("[DRY RUN]" in f for f in agent.fortifications) + + +class TestReportGeneration: + """Test report generation.""" + + def test_generate_report_no_issues(self): + """Test report generation with no issues.""" + agent = AuditAgent() + + report = agent.generate_report() + + assert "NIGHTLY AUDIT & REMEDIATION REPORT" in report + assert "ISSUES FOUND:" in report + assert "None" in report + + def test_generate_report_with_issues(self): + """Test report generation with issues.""" + agent = AuditAgent() + agent.issues = [ + {"type": "http_error", "severity": "high", "details": "500 error"} + ] + agent.remediations = ["Fixed issue"] + agent.fortifications = ["Added constraint"] + + report = agent.generate_report() + + assert "ISSUES FOUND:" in report + assert "http_error" in report + assert "REMEDIATIONS EXECUTED:" in report + assert "Fixed issue" in report + assert "FORTIFICATIONS APPLIED:" in report + assert "Added constraint" in report + + def test_generate_report_dry_run_mode(self): + """Test report generation in dry-run mode.""" + agent = AuditAgent(dry_run=True) + agent.issues = [{"type": "test", "severity": "low", "details": "test"}] + + report = agent.generate_report() + + assert "Mode: DRY RUN" in report + + def test_save_report(self): + """Test saving report to file.""" + with tempfile.TemporaryDirectory() as tmp_dir: + agent = AuditAgent() + agent.report_dir = Path(tmp_dir) + + report = "Test report" + agent.save_report(report) + + # Check that report file was created + report_files = list(agent.report_dir.glob("audit_report_*.txt")) + assert len(report_files) == 1 + + # Check content + content = report_files[0].read_text() + assert content == report + + +class TestAdvisoryLabeling: + """Test that remediations are properly labeled as advisory.""" + + @pytest.mark.asyncio + async def test_restart_labeled_advisory(self): + """Test that restart actions are labeled as advisory.""" + agent = AuditAgent(dry_run=False) + + diagnosis = { + "issue": {"type": "health_check"}, + "proposed_fix": "Restart unhealthy services" + } + + # Mock to avoid actual health check components + diagnosis["issue"]["components"] = [] + + await agent.ruthless_remediation(diagnosis) + + assert any("[ADVISORY]" in r for r in agent.remediations) + + @pytest.mark.asyncio + async def test_review_labeled_advisory(self): + """Test that review actions are labeled as advisory.""" + agent = AuditAgent(dry_run=False) + + diagnosis = { + "issue": {"type": "http_error"}, + "proposed_fix": "Review endpoint logic" + } + + await agent.ruthless_remediation(diagnosis) + + assert any("[ADVISORY]" in r for r in agent.remediations) + + @pytest.mark.asyncio + async def test_optimize_labeled_advisory(self): + """Test that optimization actions are labeled as advisory.""" + agent = AuditAgent(dry_run=False) + + diagnosis = { + "issue": {"type": "high_latency"}, + "proposed_fix": "Optimize query" + } + + await agent.ruthless_remediation(diagnosis) + + assert any("[ADVISORY]" in r for r in agent.remediations) From a37aa59d48973253dbd9c5041faf3ea46ff306c0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 17:36:33 +0000 Subject: [PATCH 4/4] Fix indentation issues in nightly_audit_agent.py - Fixed inconsistent indentation (5 spaces instead of 4) in lines 208, 211, 234, 235 - All tests still passing Co-authored-by: groupthinking <154503486+groupthinking@users.noreply.github.com> --- scripts/nightly_audit_agent.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/nightly_audit_agent.py b/scripts/nightly_audit_agent.py index 120bf604f..232bf7e18 100644 --- a/scripts/nightly_audit_agent.py +++ b/scripts/nightly_audit_agent.py @@ -205,10 +205,10 @@ async def ruthless_remediation(self, diagnosis: Dict[str, Any]): self.remediations.append(f"[ADVISORY] Triggered restart for components related to {diagnosis['issue']['type']}") elif "Review" in fix: - self.remediations.append(f"[ADVISORY] Flagged {diagnosis['issue']['type']} for immediate manual review (Ticket created)") + self.remediations.append(f"[ADVISORY] Flagged {diagnosis['issue']['type']} for immediate manual review (Ticket created)") elif "Optimize" in fix: - self.remediations.append("[ADVISORY] Triggered auto-optimization (e.g., ANALYZE DB)") + self.remediations.append("[ADVISORY] Triggered auto-optimization (e.g., ANALYZE DB)") # Only run DB cleanup if database component is specifically unhealthy if diagnosis['issue']['type'] == 'health_check': @@ -231,9 +231,9 @@ async def fortify(self, diagnosis: Dict[str, Any]): logger.info(f"Applying fortification: {measure}") if self.dry_run: - logger.info("[DRY RUN] Fortification skipped.") - self.fortifications.append(f"[DRY RUN] {measure}") - return + logger.info("[DRY RUN] Fortification skipped.") + self.fortifications.append(f"[DRY RUN] {measure}") + return self.fortifications.append(f"Applied: {measure}") # In a real system, this might write to a 'constraints.json' or update WAF rules.