Status updates (feature/test update and batch-update) now verify the requested status against Roslyn audit classification. Mismatches require --override "reason" to force. Overrides are logged to a new status_overrides table and reviewable via 'override list' command.
137 lines
5.4 KiB
C#
137 lines
5.4 KiB
C#
namespace NatsNet.PortTracker.Audit;
|
|
|
|
using NatsNet.PortTracker.Data;
|
|
|
|
/// <summary>
|
|
/// Verifies status updates against audit classification results.
|
|
/// Used by feature and test update commands to ensure status accuracy.
|
|
/// </summary>
|
|
public static class AuditVerifier
|
|
{
|
|
public record VerificationResult(
|
|
long ItemId,
|
|
string AuditStatus,
|
|
string AuditReason,
|
|
bool Matches);
|
|
|
|
private static readonly Dictionary<string, string> DefaultSourcePaths = new()
|
|
{
|
|
["features"] = Path.Combine(Directory.GetCurrentDirectory(), "dotnet", "src", "ZB.MOM.NatsNet.Server"),
|
|
["unit_tests"] = Path.Combine(Directory.GetCurrentDirectory(), "dotnet", "tests", "ZB.MOM.NatsNet.Server.Tests")
|
|
};
|
|
|
|
/// <summary>
|
|
/// Build a SourceIndexer for the appropriate table type.
|
|
/// </summary>
|
|
public static SourceIndexer BuildIndexer(string tableName)
|
|
{
|
|
var sourcePath = DefaultSourcePaths[tableName];
|
|
if (!Directory.Exists(sourcePath))
|
|
throw new DirectoryNotFoundException($"Source directory not found: {sourcePath}");
|
|
|
|
Console.WriteLine($"Building audit index from {sourcePath}...");
|
|
var indexer = new SourceIndexer();
|
|
indexer.IndexDirectory(sourcePath);
|
|
Console.WriteLine($"Indexed {indexer.FilesIndexed} files, {indexer.MethodsIndexed} methods/properties.");
|
|
return indexer;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify items matching a WHERE clause against audit classification.
|
|
/// </summary>
|
|
public static List<VerificationResult> VerifyItems(
|
|
Database db, SourceIndexer indexer, string tableName,
|
|
string whereClause, List<(string, object?)> parameters, string requestedStatus)
|
|
{
|
|
var sql = $"SELECT id, dotnet_class, dotnet_method, go_file, go_method FROM {tableName}{whereClause} ORDER BY id";
|
|
var rows = db.Query(sql, parameters.ToArray());
|
|
|
|
var classifier = new FeatureClassifier(indexer);
|
|
var results = new List<VerificationResult>();
|
|
|
|
foreach (var row in rows)
|
|
{
|
|
var record = new FeatureClassifier.FeatureRecord(
|
|
Id: Convert.ToInt64(row["id"]),
|
|
DotnetClass: row["dotnet_class"]?.ToString() ?? "",
|
|
DotnetMethod: row["dotnet_method"]?.ToString() ?? "",
|
|
GoFile: row["go_file"]?.ToString() ?? "",
|
|
GoMethod: row["go_method"]?.ToString() ?? "");
|
|
|
|
var classification = classifier.Classify(record);
|
|
var matches = classification.Status == requestedStatus;
|
|
results.Add(new VerificationResult(record.Id, classification.Status, classification.Reason, matches));
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check verification results and print a report.
|
|
/// Returns true if the update should proceed.
|
|
/// </summary>
|
|
public static bool CheckAndReport(
|
|
List<VerificationResult> results, string requestedStatus, string? overrideComment)
|
|
{
|
|
var matches = results.Where(r => r.Matches).ToList();
|
|
var mismatches = results.Where(r => !r.Matches).ToList();
|
|
|
|
Console.WriteLine($"\nAudit verification: {matches.Count} match, {mismatches.Count} mismatch");
|
|
|
|
if (mismatches.Count == 0)
|
|
return true;
|
|
|
|
Console.WriteLine($"\nMismatches (requested '{requestedStatus}'):");
|
|
foreach (var m in mismatches.Take(20))
|
|
Console.WriteLine($" ID {m.ItemId}: audit says '{m.AuditStatus}' ({m.AuditReason})");
|
|
if (mismatches.Count > 20)
|
|
Console.WriteLine($" ... and {mismatches.Count - 20} more");
|
|
|
|
if (overrideComment is null)
|
|
{
|
|
Console.WriteLine($"\n{mismatches.Count} items have audit mismatches. Use --override \"reason\" to force.");
|
|
return false;
|
|
}
|
|
|
|
Console.WriteLine($"\nOverride applied: \"{overrideComment}\"");
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Log override records to the status_overrides table.
|
|
/// </summary>
|
|
public static void LogOverrides(
|
|
Database db, string tableName, IEnumerable<VerificationResult> mismatches,
|
|
string requestedStatus, string comment)
|
|
{
|
|
var mismatchList = mismatches.ToList();
|
|
if (mismatchList.Count == 0) return;
|
|
|
|
using var transaction = db.Connection.BeginTransaction();
|
|
try
|
|
{
|
|
foreach (var mismatch in mismatchList)
|
|
{
|
|
using var cmd = db.CreateCommand(
|
|
"INSERT INTO status_overrides (table_name, item_id, audit_status, audit_reason, requested_status, comment) " +
|
|
"VALUES (@table, @item, @auditStatus, @auditReason, @requestedStatus, @comment)");
|
|
cmd.Parameters.AddWithValue("@table", tableName);
|
|
cmd.Parameters.AddWithValue("@item", mismatch.ItemId);
|
|
cmd.Parameters.AddWithValue("@auditStatus", mismatch.AuditStatus);
|
|
cmd.Parameters.AddWithValue("@auditReason", mismatch.AuditReason);
|
|
cmd.Parameters.AddWithValue("@requestedStatus", requestedStatus);
|
|
cmd.Parameters.AddWithValue("@comment", comment);
|
|
cmd.Transaction = transaction;
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
transaction.Commit();
|
|
Console.WriteLine($"Logged {mismatchList.Count} override(s) to status_overrides table.");
|
|
}
|
|
catch
|
|
{
|
|
transaction.Rollback();
|
|
throw;
|
|
}
|
|
}
|
|
}
|