diff --git a/porting-schema.sql b/porting-schema.sql
index 41b6ed1..f016122 100644
--- a/porting-schema.sql
+++ b/porting-schema.sql
@@ -88,6 +88,17 @@ CREATE TABLE IF NOT EXISTS library_mappings (
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
+CREATE TABLE IF NOT EXISTS status_overrides (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ table_name TEXT NOT NULL CHECK (table_name IN ('features', 'unit_tests')),
+ item_id INTEGER NOT NULL,
+ audit_status TEXT NOT NULL,
+ audit_reason TEXT NOT NULL,
+ requested_status TEXT NOT NULL,
+ comment TEXT NOT NULL,
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
+);
+
-- Indexes
CREATE INDEX IF NOT EXISTS idx_features_module ON features(module_id);
CREATE INDEX IF NOT EXISTS idx_features_status ON features(status);
diff --git a/porting.db b/porting.db
index 72f8487..1f330f3 100644
Binary files a/porting.db and b/porting.db differ
diff --git a/reports/current.md b/reports/current.md
index e1e403e..e772497 100644
--- a/reports/current.md
+++ b/reports/current.md
@@ -1,6 +1,6 @@
# NATS .NET Porting Status Report
-Generated: 2026-02-27 10:46:13 UTC
+Generated: 2026-02-27 10:50:16 UTC
## Modules (12 total)
@@ -12,10 +12,10 @@ Generated: 2026-02-27 10:46:13 UTC
| Status | Count |
|--------|-------|
-| deferred | 2500 |
+| deferred | 2501 |
| n_a | 18 |
| stub | 168 |
-| verified | 987 |
+| verified | 986 |
## Unit Tests (3257 total)
@@ -35,4 +35,4 @@ Generated: 2026-02-27 10:46:13 UTC
## Overall Progress
-**1594/6942 items complete (23.0%)**
+**1593/6942 items complete (22.9%)**
diff --git a/reports/report_3297334.md b/reports/report_3297334.md
new file mode 100644
index 0000000..e772497
--- /dev/null
+++ b/reports/report_3297334.md
@@ -0,0 +1,38 @@
+# NATS .NET Porting Status Report
+
+Generated: 2026-02-27 10:50:16 UTC
+
+## Modules (12 total)
+
+| Status | Count |
+|--------|-------|
+| verified | 12 |
+
+## Features (3673 total)
+
+| Status | Count |
+|--------|-------|
+| deferred | 2501 |
+| n_a | 18 |
+| stub | 168 |
+| verified | 986 |
+
+## Unit Tests (3257 total)
+
+| Status | Count |
+|--------|-------|
+| deferred | 2662 |
+| n_a | 187 |
+| stub | 18 |
+| verified | 390 |
+
+## Library Mappings (36 total)
+
+| Status | Count |
+|--------|-------|
+| mapped | 36 |
+
+
+## Overall Progress
+
+**1593/6942 items complete (22.9%)**
diff --git a/tools/NatsNet.PortTracker/Audit/AuditVerifier.cs b/tools/NatsNet.PortTracker/Audit/AuditVerifier.cs
new file mode 100644
index 0000000..ffe4fcb
--- /dev/null
+++ b/tools/NatsNet.PortTracker/Audit/AuditVerifier.cs
@@ -0,0 +1,136 @@
+namespace NatsNet.PortTracker.Audit;
+
+using NatsNet.PortTracker.Data;
+
+///
+/// Verifies status updates against audit classification results.
+/// Used by feature and test update commands to ensure status accuracy.
+///
+public static class AuditVerifier
+{
+ public record VerificationResult(
+ long ItemId,
+ string AuditStatus,
+ string AuditReason,
+ bool Matches);
+
+ private static readonly Dictionary 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")
+ };
+
+ ///
+ /// Build a SourceIndexer for the appropriate table type.
+ ///
+ 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;
+ }
+
+ ///
+ /// Verify items matching a WHERE clause against audit classification.
+ ///
+ public static List 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();
+
+ 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;
+ }
+
+ ///
+ /// Check verification results and print a report.
+ /// Returns true if the update should proceed.
+ ///
+ public static bool CheckAndReport(
+ List 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;
+ }
+
+ ///
+ /// Log override records to the status_overrides table.
+ ///
+ public static void LogOverrides(
+ Database db, string tableName, IEnumerable 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;
+ }
+ }
+}
diff --git a/tools/NatsNet.PortTracker/Commands/FeatureCommands.cs b/tools/NatsNet.PortTracker/Commands/FeatureCommands.cs
index b6bc620..a928a1b 100644
--- a/tools/NatsNet.PortTracker/Commands/FeatureCommands.cs
+++ b/tools/NatsNet.PortTracker/Commands/FeatureCommands.cs
@@ -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("id") { Description = "Feature ID (use 0 with --all-in-module)" };
var updateStatus = new Option("--status") { Description = "New status", Required = true };
var updateAllInModule = new Option("--all-in-module") { Description = "Update all features in this module ID" };
- var updateCmd = new Command("update", "Update feature status");
+ var updateOverride = new Option("--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 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("--set-status") { Description = "New status to set", Required = true };
var setNotes = new Option("--set-notes") { Description = "Notes to set" };
+ var overrideOpt = new Option("--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 { "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;
diff --git a/tools/NatsNet.PortTracker/Commands/OverrideCommands.cs b/tools/NatsNet.PortTracker/Commands/OverrideCommands.cs
new file mode 100644
index 0000000..288a4e5
--- /dev/null
+++ b/tools/NatsNet.PortTracker/Commands/OverrideCommands.cs
@@ -0,0 +1,66 @@
+using System.CommandLine;
+using NatsNet.PortTracker.Data;
+
+namespace NatsNet.PortTracker.Commands;
+
+public static class OverrideCommands
+{
+ public static Command Create(Option dbOption)
+ {
+ var overrideCommand = new Command("override", "Review status override records");
+
+ var typeOpt = new Option("--type")
+ {
+ Description = "Filter by table: features or tests"
+ };
+
+ var listCmd = new Command("list", "List all status overrides");
+ listCmd.Add(typeOpt);
+ listCmd.SetAction(parseResult =>
+ {
+ var dbPath = parseResult.GetValue(dbOption)!;
+ var type = parseResult.GetValue(typeOpt);
+ using var db = new Database(dbPath);
+
+ var sql = "SELECT id, table_name, item_id, audit_status, requested_status, comment, created_at FROM status_overrides";
+ var parameters = new List<(string, object?)>();
+
+ if (type is not null)
+ {
+ var tableName = type switch
+ {
+ "features" => "features",
+ "tests" => "unit_tests",
+ _ => type
+ };
+ sql += " WHERE table_name = @table";
+ parameters.Add(("@table", tableName));
+ }
+ sql += " ORDER BY created_at DESC";
+
+ var rows = db.Query(sql, parameters.ToArray());
+ if (rows.Count == 0)
+ {
+ Console.WriteLine("No overrides found.");
+ return;
+ }
+
+ Console.WriteLine($"{"ID",-5} {"Table",-12} {"Item",-6} {"Audit",-10} {"Requested",-10} {"Comment",-35} {"Date",-20}");
+ Console.WriteLine(new string('-', 98));
+ foreach (var row in rows)
+ {
+ Console.WriteLine($"{row["id"],-5} {row["table_name"],-12} {row["item_id"],-6} {row["audit_status"],-10} {row["requested_status"],-10} {Truncate(row["comment"]?.ToString(), 34),-35} {row["created_at"],-20}");
+ }
+ Console.WriteLine($"\nTotal: {rows.Count} overrides");
+ });
+
+ overrideCommand.Add(listCmd);
+ return overrideCommand;
+ }
+
+ private static string Truncate(string? s, int maxLen)
+ {
+ if (s is null) return "";
+ return s.Length <= maxLen ? s : s[..(maxLen - 2)] + "..";
+ }
+}
diff --git a/tools/NatsNet.PortTracker/Commands/TestCommands.cs b/tools/NatsNet.PortTracker/Commands/TestCommands.cs
index f2ce4f6..5bbcb9d 100644
--- a/tools/NatsNet.PortTracker/Commands/TestCommands.cs
+++ b/tools/NatsNet.PortTracker/Commands/TestCommands.cs
@@ -1,4 +1,5 @@
using System.CommandLine;
+using NatsNet.PortTracker.Audit;
using NatsNet.PortTracker.Data;
namespace NatsNet.PortTracker.Commands;
@@ -89,18 +90,37 @@ public static class TestCommands
// update
var updateId = new Argument("id") { Description = "Test ID" };
var updateStatus = new Option("--status") { Description = "New status", Required = true };
- var updateCmd = new Command("update", "Update test status");
+ var updateOverride = new Option("--override") { Description = "Override audit mismatch with this comment" };
+ var updateCmd = new Command("update", "Update test status (audit-verified)");
updateCmd.Add(updateId);
updateCmd.Add(updateStatus);
+ updateCmd.Add(updateOverride);
updateCmd.SetAction(parseResult =>
{
var dbPath = parseResult.GetValue(dbOption)!;
var id = parseResult.GetValue(updateId);
var status = parseResult.GetValue(updateStatus)!;
+ var overrideComment = parseResult.GetValue(updateOverride);
using var db = new Database(dbPath);
+
+ var indexer = AuditVerifier.BuildIndexer("unit_tests");
+ var verifications = AuditVerifier.VerifyItems(db, indexer, "unit_tests",
+ " WHERE id = @id", [("@id", (object?)id)], status);
+ if (verifications.Count == 0)
+ {
+ Console.WriteLine($"Test {id} not found.");
+ return;
+ }
+ if (!AuditVerifier.CheckAndReport(verifications, status, overrideComment))
+ return;
+
var affected = db.Execute("UPDATE unit_tests SET status = @status WHERE id = @id",
("@status", status), ("@id", id));
Console.WriteLine(affected > 0 ? $"Test {id} updated to '{status}'." : $"Test {id} not found.");
+
+ var mismatches = verifications.Where(r => !r.Matches).ToList();
+ if (mismatches.Count > 0 && overrideComment is not null)
+ AuditVerifier.LogOverrides(db, "unit_tests", mismatches, status, overrideComment);
});
// map
@@ -139,13 +159,14 @@ public static class TestCommands
private static Command CreateBatchUpdate(Option dbOption)
{
- var cmd = new Command("batch-update", "Bulk update test status");
+ var cmd = new Command("batch-update", "Bulk update test status (audit-verified)");
var idsOpt = BatchFilters.IdsOption();
var moduleOpt = BatchFilters.ModuleOption();
var statusOpt = BatchFilters.StatusOption();
var executeOpt = BatchFilters.ExecuteOption();
var setStatus = new Option("--set-status") { Description = "New status to set", Required = true };
var setNotes = new Option("--set-notes") { Description = "Notes to set" };
+ var overrideOpt = new Option("--override") { Description = "Override audit mismatches with this comment" };
cmd.Add(idsOpt);
cmd.Add(moduleOpt);
@@ -153,6 +174,7 @@ public static class TestCommands
cmd.Add(executeOpt);
cmd.Add(setStatus);
cmd.Add(setNotes);
+ cmd.Add(overrideOpt);
cmd.SetAction(parseResult =>
{
@@ -163,6 +185,7 @@ public static class TestCommands
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))
{
@@ -173,6 +196,12 @@ public static class TestCommands
using var db = new Database(dbPath);
var (whereClause, filterParams) = BatchFilters.BuildWhereClause(ids, module, status);
+ // Audit verification
+ var indexer = AuditVerifier.BuildIndexer("unit_tests");
+ var verifications = AuditVerifier.VerifyItems(db, indexer, "unit_tests", whereClause, filterParams, newStatus);
+ if (!AuditVerifier.CheckAndReport(verifications, newStatus, overrideComment))
+ return;
+
var setClauses = new List { "status = @newStatus" };
var updateParams = new List<(string, object?)> { ("@newStatus", newStatus) };
if (notes is not null)
@@ -185,6 +214,14 @@ public static class TestCommands
"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, "unit_tests", mismatches, newStatus, overrideComment);
+ }
});
return cmd;
diff --git a/tools/NatsNet.PortTracker/Program.cs b/tools/NatsNet.PortTracker/Program.cs
index 1c943f8..49f1a8d 100644
--- a/tools/NatsNet.PortTracker/Program.cs
+++ b/tools/NatsNet.PortTracker/Program.cs
@@ -40,6 +40,7 @@ rootCommand.Add(DependencyCommands.Create(dbOption, schemaOption));
rootCommand.Add(ReportCommands.Create(dbOption, schemaOption));
rootCommand.Add(PhaseCommands.Create(dbOption, schemaOption));
rootCommand.Add(AuditCommand.Create(dbOption));
+rootCommand.Add(OverrideCommands.Create(dbOption));
var parseResult = rootCommand.Parse(args);
return await parseResult.InvokeAsync();