feat: add audit-verified status updates with override tracking

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.
This commit is contained in:
Joseph Doherty
2026-02-27 05:50:15 -05:00
parent 3297334261
commit 7a338dd510
9 changed files with 343 additions and 8 deletions

View File

@@ -1,4 +1,5 @@
using System.CommandLine;
using NatsNet.PortTracker.Audit;
using NatsNet.PortTracker.Data;
namespace NatsNet.PortTracker.Commands;
@@ -96,31 +97,59 @@ public static class FeatureCommands
var updateId = new Argument<int>("id") { Description = "Feature ID (use 0 with --all-in-module)" };
var updateStatus = new Option<string>("--status") { Description = "New status", Required = true };
var updateAllInModule = new Option<int?>("--all-in-module") { Description = "Update all features in this module ID" };
var updateCmd = new Command("update", "Update feature status");
var updateOverride = new Option<string?>("--override") { Description = "Override audit mismatch with this comment" };
var updateCmd = new Command("update", "Update feature status (audit-verified)");
updateCmd.Add(updateId);
updateCmd.Add(updateStatus);
updateCmd.Add(updateAllInModule);
updateCmd.Add(updateOverride);
updateCmd.SetAction(parseResult =>
{
var dbPath = parseResult.GetValue(dbOption)!;
var id = parseResult.GetValue(updateId);
var status = parseResult.GetValue(updateStatus)!;
var allInModule = parseResult.GetValue(updateAllInModule);
var overrideComment = parseResult.GetValue(updateOverride);
using var db = new Database(dbPath);
var indexer = AuditVerifier.BuildIndexer("features");
if (allInModule is not null)
{
var verifications = AuditVerifier.VerifyItems(db, indexer, "features",
" WHERE module_id = @module", [("@module", (object?)allInModule)], status);
if (!AuditVerifier.CheckAndReport(verifications, status, overrideComment))
return;
var affected = db.Execute(
"UPDATE features SET status = @status WHERE module_id = @module",
("@status", status), ("@module", allInModule));
Console.WriteLine($"Updated {affected} features in module {allInModule} to '{status}'.");
var mismatches = verifications.Where(r => !r.Matches).ToList();
if (mismatches.Count > 0 && overrideComment is not null)
AuditVerifier.LogOverrides(db, "features", mismatches, status, overrideComment);
}
else
{
var verifications = AuditVerifier.VerifyItems(db, indexer, "features",
" WHERE id = @id", [("@id", (object?)id)], status);
if (verifications.Count == 0)
{
Console.WriteLine($"Feature {id} not found.");
return;
}
if (!AuditVerifier.CheckAndReport(verifications, status, overrideComment))
return;
var affected = db.Execute(
"UPDATE features SET status = @status WHERE id = @id",
("@status", status), ("@id", id));
Console.WriteLine(affected > 0 ? $"Feature {id} updated to '{status}'." : $"Feature {id} not found.");
var mismatches = verifications.Where(r => !r.Matches).ToList();
if (mismatches.Count > 0 && overrideComment is not null)
AuditVerifier.LogOverrides(db, "features", mismatches, status, overrideComment);
}
});
@@ -179,13 +208,14 @@ public static class FeatureCommands
private static Command CreateBatchUpdate(Option<string> dbOption)
{
var cmd = new Command("batch-update", "Bulk update feature status");
var cmd = new Command("batch-update", "Bulk update feature status (audit-verified)");
var idsOpt = BatchFilters.IdsOption();
var moduleOpt = BatchFilters.ModuleOption();
var statusOpt = BatchFilters.StatusOption();
var executeOpt = BatchFilters.ExecuteOption();
var setStatus = new Option<string>("--set-status") { Description = "New status to set", Required = true };
var setNotes = new Option<string?>("--set-notes") { Description = "Notes to set" };
var overrideOpt = new Option<string?>("--override") { Description = "Override audit mismatches with this comment" };
cmd.Add(idsOpt);
cmd.Add(moduleOpt);
@@ -193,6 +223,7 @@ public static class FeatureCommands
cmd.Add(executeOpt);
cmd.Add(setStatus);
cmd.Add(setNotes);
cmd.Add(overrideOpt);
cmd.SetAction(parseResult =>
{
@@ -203,6 +234,7 @@ public static class FeatureCommands
var execute = parseResult.GetValue(executeOpt);
var newStatus = parseResult.GetValue(setStatus)!;
var notes = parseResult.GetValue(setNotes);
var overrideComment = parseResult.GetValue(overrideOpt);
if (string.IsNullOrWhiteSpace(ids) && module is null && string.IsNullOrWhiteSpace(status))
{
@@ -213,6 +245,12 @@ public static class FeatureCommands
using var db = new Database(dbPath);
var (whereClause, filterParams) = BatchFilters.BuildWhereClause(ids, module, status);
// Audit verification
var indexer = AuditVerifier.BuildIndexer("features");
var verifications = AuditVerifier.VerifyItems(db, indexer, "features", whereClause, filterParams, newStatus);
if (!AuditVerifier.CheckAndReport(verifications, newStatus, overrideComment))
return;
var setClauses = new List<string> { "status = @newStatus" };
var updateParams = new List<(string, object?)> { ("@newStatus", newStatus) };
if (notes is not null)
@@ -225,6 +263,14 @@ public static class FeatureCommands
"id, name, status, module_id, notes",
string.Join(", ", setClauses), updateParams,
whereClause, filterParams, execute);
// Log overrides after successful execute
if (execute)
{
var mismatches = verifications.Where(r => !r.Matches).ToList();
if (mismatches.Count > 0 && overrideComment is not null)
AuditVerifier.LogOverrides(db, "features", mismatches, newStatus, overrideComment);
}
});
return cmd;