renderSingleAuditDiffPretty outputs a single audit diff as formatted console output to stderr
(diff *AuditDiff)
| 73 | |
| 74 | // renderSingleAuditDiffPretty outputs a single audit diff as formatted console output to stderr |
| 75 | func renderSingleAuditDiffPretty(diff *AuditDiff) { |
| 76 | auditDiffRenderLog.Printf("Rendering audit diff as pretty output: run1=%d, run2=%d", diff.Run1ID, diff.Run2ID) |
| 77 | fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Audit Diff: Run #%d → Run #%d", diff.Run1ID, diff.Run2ID))) |
| 78 | fmt.Fprintln(os.Stderr) |
| 79 | |
| 80 | if isEmptyAuditDiff(diff) { |
| 81 | fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("No behavioral changes detected between the two runs.")) |
| 82 | return |
| 83 | } |
| 84 | |
| 85 | // Collect top-level summary across all sections |
| 86 | var summaryParts []string |
| 87 | anomalyCount := 0 |
| 88 | |
| 89 | if diff.FirewallDiff != nil && !isEmptyFirewallDiff(diff.FirewallDiff) { |
| 90 | fwParts := []string{} |
| 91 | if len(diff.FirewallDiff.NewDomains) > 0 { |
| 92 | fwParts = append(fwParts, fmt.Sprintf("%d new domains", len(diff.FirewallDiff.NewDomains))) |
| 93 | } |
| 94 | if len(diff.FirewallDiff.RemovedDomains) > 0 { |
| 95 | fwParts = append(fwParts, fmt.Sprintf("%d removed domains", len(diff.FirewallDiff.RemovedDomains))) |
| 96 | } |
| 97 | if len(diff.FirewallDiff.StatusChanges) > 0 { |
| 98 | fwParts = append(fwParts, fmt.Sprintf("%d status changes", len(diff.FirewallDiff.StatusChanges))) |
| 99 | } |
| 100 | if len(diff.FirewallDiff.VolumeChanges) > 0 { |
| 101 | fwParts = append(fwParts, fmt.Sprintf("%d volume changes", len(diff.FirewallDiff.VolumeChanges))) |
| 102 | } |
| 103 | if len(fwParts) > 0 { |
| 104 | summaryParts = append(summaryParts, "Firewall: "+strings.Join(fwParts, ", ")) |
| 105 | } |
| 106 | anomalyCount += diff.FirewallDiff.Summary.AnomalyCount |
| 107 | } |
| 108 | |
| 109 | if diff.MCPToolsDiff != nil && !isEmptyMCPToolsDiff(diff.MCPToolsDiff) { |
| 110 | mcpParts := []string{} |
| 111 | if diff.MCPToolsDiff.Summary.NewToolCount > 0 { |
| 112 | mcpParts = append(mcpParts, fmt.Sprintf("%d new tools", diff.MCPToolsDiff.Summary.NewToolCount)) |
| 113 | } |
| 114 | if diff.MCPToolsDiff.Summary.RemovedToolCount > 0 { |
| 115 | mcpParts = append(mcpParts, fmt.Sprintf("%d removed tools", diff.MCPToolsDiff.Summary.RemovedToolCount)) |
| 116 | } |
| 117 | if diff.MCPToolsDiff.Summary.ChangedToolCount > 0 { |
| 118 | mcpParts = append(mcpParts, fmt.Sprintf("%d changed tools", diff.MCPToolsDiff.Summary.ChangedToolCount)) |
| 119 | } |
| 120 | if len(mcpParts) > 0 { |
| 121 | summaryParts = append(summaryParts, "MCP tools: "+strings.Join(mcpParts, ", ")) |
| 122 | } |
| 123 | anomalyCount += diff.MCPToolsDiff.Summary.AnomalyCount |
| 124 | } |
| 125 | |
| 126 | if len(summaryParts) > 0 { |
| 127 | fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Changes: "+strings.Join(summaryParts, " | "))) |
| 128 | } |
| 129 | if anomalyCount > 0 { |
| 130 | fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("⚠️ %d anomalies detected", anomalyCount))) |
| 131 | } |
| 132 | fmt.Fprintln(os.Stderr) |
no test coverage detected