diff --git a/docs/plans/2026-02-23-full-repo-remaining-parity-plan.md b/docs/plans/2026-02-23-full-repo-remaining-parity-plan.md new file mode 100644 index 0000000..8a09735 --- /dev/null +++ b/docs/plans/2026-02-23-full-repo-remaining-parity-plan.md @@ -0,0 +1,923 @@ +# Full-Repo Remaining Parity Implementation Plan + +> **For Codex:** REQUIRED SUB-SKILL: Use `executeplan` to implement this plan task-by-task. + +**Goal:** Close every currently unresolved `Baseline` / `N` / `Stub` parity row in `differences.md` with strict behavior-level parity and test-backed evidence. + +**Architecture:** Use a truth-matrix workflow where each unresolved row is tracked by behavior, test, and docs state. Implement dependencies in layers: core server/protocol/sublist first, then auth/monitoring, then JetStream runtime/storage/RAFT/clustering, then docs synchronization. Rows move to `Y` only when behavior is implemented and validated by meaningful contract tests. + +**Tech Stack:** .NET 10, C# 14, xUnit 3, Shouldly, ASP.NET Core minimal APIs, System.IO.Pipelines, System.Buffers, System.Text.Json. + +--- + +**Execution guardrails** +- Use `@test-driven-development` for every task. +- If behavior diverges from protocol/runtime expectations, switch to `@systematic-debugging` before code changes. +- Keep one commit per task. +- Run `@verification-before-completion` before final status updates. + +### Task 1: Add Truth-Matrix Parity Guard and Fix Summary/Table Drift Detection + +**Files:** +- Modify: `tests/NATS.Server.Tests/DifferencesParityClosureTests.cs` +- Create: `tests/NATS.Server.Tests/Parity/ParityRowInspector.cs` +- Modify: `differences.md` + +**Step 1: Write the failing test** + +```csharp +[Fact] +public void Differences_md_has_no_remaining_baseline_n_or_stub_rows_in_tracked_scope() +{ + var report = ParityRowInspector.Load("differences.md"); + report.UnresolvedRows.ShouldBeEmpty(); +} +``` + +**Step 2: Run test to verify it fails** + +Run: `dotnet test tests/NATS.Server.Tests --filter "FullyQualifiedName~DifferencesParityClosureTests" -v minimal` +Expected: FAIL with unresolved rows list from table entries (not summary prose). + +**Step 3: Write minimal implementation** + +```csharp +public sealed record ParityRow(string Section, string SubSection, string Feature, string DotNetStatus); +public IReadOnlyList UnresolvedRows => Rows.Where(r => r.DotNetStatus is "N" or "Baseline" or "Stub").ToArray(); +``` + +**Step 4: Run test to verify it passes** + +Run: `dotnet test tests/NATS.Server.Tests --filter "FullyQualifiedName~DifferencesParityClosureTests" -v minimal` +Expected: PASS once unresolved rows are fully closed at end of plan. + +**Step 5: Commit** + +```bash +git add tests/NATS.Server.Tests/DifferencesParityClosureTests.cs tests/NATS.Server.Tests/Parity/ParityRowInspector.cs differences.md +git commit -m "test: enforce row-level parity closure from differences table" +``` + +### Task 2: Implement Profiling Endpoint (`/debug/pprof`) Support + +**Files:** +- Modify: `src/NATS.Server/NatsServer.cs` +- Modify: `src/NATS.Server/Monitoring/MonitorServer.cs` +- Create: `src/NATS.Server/Monitoring/PprofHandler.cs` +- Test: `tests/NATS.Server.Tests/Monitoring/PprofEndpointTests.cs` + +**Step 1: Write the failing test** + +```csharp +[Fact] +public async Task Debug_pprof_endpoint_returns_profile_index_when_profport_enabled() +{ + await using var fx = await MonitorFixture.StartWithProfilingAsync(); + var body = await fx.GetStringAsync("/debug/pprof"); + body.ShouldContain("profiles"); +} +``` + +**Step 2: Run test to verify it fails** + +Run: `dotnet test tests/NATS.Server.Tests --filter "FullyQualifiedName~PprofEndpointTests" -v minimal` +Expected: FAIL with 404 or endpoint missing. + +**Step 3: Write minimal implementation** + +```csharp +app.MapGet("/debug/pprof", (PprofHandler h) => Results.Text(h.Index(), "text/plain")); +app.MapGet("/debug/pprof/profile", (PprofHandler h, int seconds) => Results.File(h.CaptureCpuProfile(seconds), "application/octet-stream")); +``` + +**Step 4: Run test to verify it passes** + +Run: `dotnet test tests/NATS.Server.Tests --filter "FullyQualifiedName~PprofEndpointTests" -v minimal` +Expected: PASS. + +**Step 5: Commit** + +```bash +git add src/NATS.Server/NatsServer.cs src/NATS.Server/Monitoring/MonitorServer.cs src/NATS.Server/Monitoring/PprofHandler.cs tests/NATS.Server.Tests/Monitoring/PprofEndpointTests.cs +git commit -m "feat: add profiling endpoint parity support" +``` + +### Task 3: Add Accept-Loop Reload Lock and Callback Error Hook Parity + +**Files:** +- Modify: `src/NATS.Server/NatsServer.cs` +- Modify: `src/NATS.Server/Configuration/ConfigReloader.cs` +- Create: `src/NATS.Server/Server/AcceptLoopErrorHandler.cs` +- Test: `tests/NATS.Server.Tests/Server/AcceptLoopReloadLockTests.cs` +- Test: `tests/NATS.Server.Tests/Server/AcceptLoopErrorCallbackTests.cs` + +**Step 1: Write the failing test** + +```csharp +[Fact] +public async Task Accept_loop_blocks_client_creation_while_reload_lock_is_held() +{ + await using var fx = await AcceptLoopFixture.StartAsync(); + await fx.HoldReloadLockAsync(); + (await fx.TryConnectClientAsync(timeoutMs: 150)).ShouldBeFalse(); +} +``` + +**Step 2: Run test to verify it fails** + +Run: `dotnet test tests/NATS.Server.Tests --filter "FullyQualifiedName~AcceptLoopReloadLockTests|FullyQualifiedName~AcceptLoopErrorCallbackTests" -v minimal` +Expected: FAIL because create-client path does not acquire reload lock and has no callback-based hook. + +**Step 3: Write minimal implementation** + +```csharp +await _reloadMu.WaitAsync(ct); +try { await CreateClientAsync(socket, ct); } +finally { _reloadMu.Release(); } +``` + +```csharp +_errorHandler?.OnAcceptError(ex, endpoint, delay); +``` + +**Step 4: Run test to verify it passes** + +Run: `dotnet test tests/NATS.Server.Tests --filter "FullyQualifiedName~AcceptLoopReloadLockTests|FullyQualifiedName~AcceptLoopErrorCallbackTests" -v minimal` +Expected: PASS. + +**Step 5: Commit** + +```bash +git add src/NATS.Server/NatsServer.cs src/NATS.Server/Configuration/ConfigReloader.cs src/NATS.Server/Server/AcceptLoopErrorHandler.cs tests/NATS.Server.Tests/Server/AcceptLoopReloadLockTests.cs tests/NATS.Server.Tests/Server/AcceptLoopErrorCallbackTests.cs +git commit -m "feat: add accept-loop reload lock and error callback parity" +``` + +### Task 4: Implement Dynamic Buffer Sizing and 3-Tier Output Buffer Pooling + +**Files:** +- Modify: `src/NATS.Server/NatsClient.cs` +- Create: `src/NATS.Server/IO/AdaptiveReadBuffer.cs` +- Create: `src/NATS.Server/IO/OutboundBufferPool.cs` +- Test: `tests/NATS.Server.Tests/IO/AdaptiveReadBufferTests.cs` +- Test: `tests/NATS.Server.Tests/IO/OutboundBufferPoolTests.cs` + +**Step 1: Write the failing test** + +```csharp +[Fact] +public void Read_buffer_scales_between_512_and_65536_based_on_recent_payload_pattern() +{ + var b = new AdaptiveReadBuffer(); + b.RecordRead(512); b.RecordRead(4096); b.RecordRead(32000); + b.CurrentSize.ShouldBeGreaterThan(4096); +} +``` + +**Step 2: Run test to verify it fails** + +Run: `dotnet test tests/NATS.Server.Tests --filter "FullyQualifiedName~AdaptiveReadBufferTests|FullyQualifiedName~OutboundBufferPoolTests" -v minimal` +Expected: FAIL because no adaptive model or 3-tier pool exists. + +**Step 3: Write minimal implementation** + +```csharp +public int CurrentSize => Math.Clamp(_target, 512, 64 * 1024); +public IMemoryOwner Rent(int size) => size <= 512 ? _small.Rent(512) : size <= 4096 ? _medium.Rent(4096) : _large.Rent(64 * 1024); +``` + +**Step 4: Run test to verify it passes** + +Run: `dotnet test tests/NATS.Server.Tests --filter "FullyQualifiedName~AdaptiveReadBufferTests|FullyQualifiedName~OutboundBufferPoolTests" -v minimal` +Expected: PASS. + +**Step 5: Commit** + +```bash +git add src/NATS.Server/NatsClient.cs src/NATS.Server/IO/AdaptiveReadBuffer.cs src/NATS.Server/IO/OutboundBufferPool.cs tests/NATS.Server.Tests/IO/AdaptiveReadBufferTests.cs tests/NATS.Server.Tests/IO/OutboundBufferPoolTests.cs +git commit -m "feat: add adaptive read buffers and outbound buffer pooling" +``` + +### Task 5: Unify Inter-Server Opcode Semantics With Client-Kind Routing and Trace Initialization + +**Files:** +- Modify: `src/NATS.Server/Protocol/NatsParser.cs` +- Modify: `src/NATS.Server/Protocol/ClientCommandMatrix.cs` +- Modify: `src/NATS.Server/NatsClient.cs` +- Modify: `src/NATS.Server/Routes/RouteConnection.cs` +- Modify: `src/NATS.Server/Gateways/GatewayConnection.cs` +- Modify: `src/NATS.Server/LeafNodes/LeafConnection.cs` +- Test: `tests/NATS.Server.Tests/Protocol/InterServerOpcodeRoutingTests.cs` +- Test: `tests/NATS.Server.Tests/Protocol/MessageTraceInitializationTests.cs` + +**Step 1: Write the failing test** + +```csharp +[Fact] +public void Parser_dispatch_rejects_Aplus_for_client_kind_client_but_allows_for_gateway() +{ + var m = new ClientCommandMatrix(); + m.IsAllowed(ClientKind.Client, "A+").ShouldBeFalse(); + m.IsAllowed(ClientKind.Gateway, "A+").ShouldBeTrue(); +} +``` + +**Step 2: Run test to verify it fails** + +Run: `dotnet test tests/NATS.Server.Tests --filter "FullyQualifiedName~InterServerOpcodeRoutingTests|FullyQualifiedName~MessageTraceInitializationTests" -v minimal` +Expected: FAIL due incomplete parser/dispatch trace-init parity. + +**Step 3: Write minimal implementation** + +```csharp +if (!CommandMatrix.IsAllowed(kind, op)) + throw new ProtocolViolationException($"operation {op} not allowed for {kind}"); +``` + +```csharp +_traceContext = MessageTraceContext.CreateFromConnect(connectOpts); +``` + +**Step 4: Run test to verify it passes** + +Run: `dotnet test tests/NATS.Server.Tests --filter "FullyQualifiedName~InterServerOpcodeRoutingTests|FullyQualifiedName~MessageTraceInitializationTests" -v minimal` +Expected: PASS. + +**Step 5: Commit** + +```bash +git add src/NATS.Server/Protocol/NatsParser.cs src/NATS.Server/Protocol/ClientCommandMatrix.cs src/NATS.Server/NatsClient.cs src/NATS.Server/Routes/RouteConnection.cs src/NATS.Server/Gateways/GatewayConnection.cs src/NATS.Server/LeafNodes/LeafConnection.cs tests/NATS.Server.Tests/Protocol/InterServerOpcodeRoutingTests.cs tests/NATS.Server.Tests/Protocol/MessageTraceInitializationTests.cs +git commit -m "feat: enforce inter-server opcode routing and trace initialization" +``` + +### Task 6: Implement SubList Missing Features (Notifications, Local/Remote Filters, Queue Weight, MatchBytes) + +**Files:** +- Modify: `src/NATS.Server/Subscriptions/SubList.cs` +- Modify: `src/NATS.Server/Subscriptions/RemoteSubscription.cs` +- Modify: `src/NATS.Server/Subscriptions/Subscription.cs` +- Test: `tests/NATS.Server.Tests/SubList/SubListNotificationTests.cs` +- Test: `tests/NATS.Server.Tests/SubList/SubListRemoteFilterTests.cs` +- Test: `tests/NATS.Server.Tests/SubList/SubListQueueWeightTests.cs` +- Test: `tests/NATS.Server.Tests/SubList/SubListMatchBytesTests.cs` + +**Step 1: Write the failing test** + +```csharp +[Fact] +public void MatchBytes_matches_subject_without_string_allocation_and_respects_remote_filter() +{ + var sl = new SubList(); + sl.MatchBytes("orders.created"u8.ToArray()).PlainSubs.Length.ShouldBe(0); +} +``` + +**Step 2: Run test to verify it fails** + +Run: `dotnet test tests/NATS.Server.Tests --filter "FullyQualifiedName~SubListNotificationTests|FullyQualifiedName~SubListRemoteFilterTests|FullyQualifiedName~SubListQueueWeightTests|FullyQualifiedName~SubListMatchBytesTests" -v minimal` +Expected: FAIL because APIs and behavior are missing. + +**Step 3: Write minimal implementation** + +```csharp +public SubListResult MatchBytes(ReadOnlySpan subjectUtf8) => Match(Encoding.ASCII.GetString(subjectUtf8)); +public event Action? InterestChanged; +``` + +```csharp +if (remoteSub.QueueWeight > 0) expanded.AddRange(Enumerable.Repeat(remoteSub, remoteSub.QueueWeight)); +``` + +**Step 4: Run test to verify it passes** + +Run: `dotnet test tests/NATS.Server.Tests --filter "FullyQualifiedName~SubListNotificationTests|FullyQualifiedName~SubListRemoteFilterTests|FullyQualifiedName~SubListQueueWeightTests|FullyQualifiedName~SubListMatchBytesTests" -v minimal` +Expected: PASS. + +**Step 5: Commit** + +```bash +git add src/NATS.Server/Subscriptions/SubList.cs src/NATS.Server/Subscriptions/RemoteSubscription.cs src/NATS.Server/Subscriptions/Subscription.cs tests/NATS.Server.Tests/SubList/SubListNotificationTests.cs tests/NATS.Server.Tests/SubList/SubListRemoteFilterTests.cs tests/NATS.Server.Tests/SubList/SubListQueueWeightTests.cs tests/NATS.Server.Tests/SubList/SubListMatchBytesTests.cs +git commit -m "feat: add remaining sublist parity behaviors" +``` + +### Task 7: Add Trie Fanout Optimization and Async Cache Sweep Behavior + +**Files:** +- Modify: `src/NATS.Server/Subscriptions/SubList.cs` +- Create: `src/NATS.Server/Subscriptions/SubListCacheSweeper.cs` +- Test: `tests/NATS.Server.Tests/SubList/SubListHighFanoutOptimizationTests.cs` +- Test: `tests/NATS.Server.Tests/SubList/SubListAsyncCacheSweepTests.cs` + +**Step 1: Write the failing test** + +```csharp +[Fact] +public async Task Cache_sweep_runs_async_and_prunes_stale_entries_without_write_locking_match_path() +{ + var fx = await SubListSweepFixture.BuildLargeCacheAsync(); + await fx.TriggerSweepAsync(); + fx.CacheCount.ShouldBeLessThan(fx.InitialCacheCount); +} +``` + +**Step 2: Run test to verify it fails** + +Run: `dotnet test tests/NATS.Server.Tests --filter "FullyQualifiedName~SubListHighFanoutOptimizationTests|FullyQualifiedName~SubListAsyncCacheSweepTests" -v minimal` +Expected: FAIL because sweep is currently inline and no high-fanout node optimization exists. + +**Step 3: Write minimal implementation** + +```csharp +if (node.PlainSubs.Count > 256) node.EnablePackedList(); +_sweeper.ScheduleSweep(_cache, generation); +``` + +**Step 4: Run test to verify it passes** + +Run: `dotnet test tests/NATS.Server.Tests --filter "FullyQualifiedName~SubListHighFanoutOptimizationTests|FullyQualifiedName~SubListAsyncCacheSweepTests" -v minimal` +Expected: PASS. + +**Step 5: Commit** + +```bash +git add src/NATS.Server/Subscriptions/SubList.cs src/NATS.Server/Subscriptions/SubListCacheSweeper.cs tests/NATS.Server.Tests/SubList/SubListHighFanoutOptimizationTests.cs tests/NATS.Server.Tests/SubList/SubListAsyncCacheSweepTests.cs +git commit -m "feat: add trie fanout optimization and async cache sweep" +``` + +### Task 8: Complete Route Parity (Account-Specific Routes, Topology Gossip, Route Compression) + +**Files:** +- Modify: `src/NATS.Server/Routes/RouteConnection.cs` +- Modify: `src/NATS.Server/Routes/RouteManager.cs` +- Modify: `src/NATS.Server/Configuration/ClusterOptions.cs` +- Test: `tests/NATS.Server.Tests/Routes/RouteAccountScopedTests.cs` +- Test: `tests/NATS.Server.Tests/Routes/RouteTopologyGossipTests.cs` +- Test: `tests/NATS.Server.Tests/Routes/RouteCompressionTests.cs` + +**Step 1: Write the failing test** + +```csharp +[Fact] +public async Task Route_connect_exchange_includes_account_scope_and_topology_gossip_snapshot() +{ + await using var fx = await RouteGossipFixture.StartPairAsync(); + var info = await fx.ReadRouteConnectInfoAsync(); + info.Accounts.ShouldContain("A"); +} +``` + +**Step 2: Run test to verify it fails** + +Run: `dotnet test tests/NATS.Server.Tests --filter "FullyQualifiedName~RouteAccountScopedTests|FullyQualifiedName~RouteTopologyGossipTests|FullyQualifiedName~RouteCompressionTests" -v minimal` +Expected: FAIL because route handshake is still minimal text and no compression/account-scoped route model. + +**Step 3: Write minimal implementation** + +```csharp +await WriteLineAsync($"CONNECT {JsonSerializer.Serialize(routeInfo)}", ct); +if (_options.Compression == RouteCompression.S2) payload = S2Codec.Compress(payload); +``` + +**Step 4: Run test to verify it passes** + +Run: `dotnet test tests/NATS.Server.Tests --filter "FullyQualifiedName~RouteAccountScopedTests|FullyQualifiedName~RouteTopologyGossipTests|FullyQualifiedName~RouteCompressionTests" -v minimal` +Expected: PASS. + +**Step 5: Commit** + +```bash +git add src/NATS.Server/Routes/RouteConnection.cs src/NATS.Server/Routes/RouteManager.cs src/NATS.Server/Configuration/ClusterOptions.cs tests/NATS.Server.Tests/Routes/RouteAccountScopedTests.cs tests/NATS.Server.Tests/Routes/RouteTopologyGossipTests.cs tests/NATS.Server.Tests/Routes/RouteCompressionTests.cs +git commit -m "feat: complete route account gossip and compression parity" +``` + +### Task 9: Complete Gateway and Leaf Advanced Semantics (Interest-Only, Hub/Spoke Mapping) + +**Files:** +- Modify: `src/NATS.Server/Gateways/GatewayConnection.cs` +- Modify: `src/NATS.Server/Gateways/GatewayManager.cs` +- Modify: `src/NATS.Server/LeafNodes/LeafConnection.cs` +- Modify: `src/NATS.Server/LeafNodes/LeafNodeManager.cs` +- Modify: `src/NATS.Server/NatsServer.cs` +- Test: `tests/NATS.Server.Tests/Gateways/GatewayInterestOnlyParityTests.cs` +- Test: `tests/NATS.Server.Tests/LeafNodes/LeafHubSpokeMappingParityTests.cs` + +**Step 1: Write the failing test** + +```csharp +[Fact] +public async Task Gateway_interest_only_mode_forwards_only_subjects_with_remote_interest_and_reply_map_roundtrips() +{ + await using var fx = await GatewayInterestFixture.StartAsync(); + (await fx.ForwardedWithoutInterestCountAsync()).ShouldBe(0); +} +``` + +**Step 2: Run test to verify it fails** + +Run: `dotnet test tests/NATS.Server.Tests --filter "FullyQualifiedName~GatewayInterestOnlyParityTests|FullyQualifiedName~LeafHubSpokeMappingParityTests" -v minimal` +Expected: FAIL because advanced interest-only and leaf account remap semantics are incomplete. + +**Step 3: Write minimal implementation** + +```csharp +if (!_interestTable.HasInterest(account, subject)) return; +var mapped = _hubSpokeMapper.Map(account, subject, direction); +``` + +**Step 4: Run test to verify it passes** + +Run: `dotnet test tests/NATS.Server.Tests --filter "FullyQualifiedName~GatewayInterestOnlyParityTests|FullyQualifiedName~LeafHubSpokeMappingParityTests" -v minimal` +Expected: PASS. + +**Step 5: Commit** + +```bash +git add src/NATS.Server/Gateways/GatewayConnection.cs src/NATS.Server/Gateways/GatewayManager.cs src/NATS.Server/LeafNodes/LeafConnection.cs src/NATS.Server/LeafNodes/LeafNodeManager.cs src/NATS.Server/NatsServer.cs tests/NATS.Server.Tests/Gateways/GatewayInterestOnlyParityTests.cs tests/NATS.Server.Tests/LeafNodes/LeafHubSpokeMappingParityTests.cs +git commit -m "feat: complete gateway and leaf advanced parity semantics" +``` + +### Task 10: Add Auth Extension Parity (Custom Interface, External Callout, Proxy Auth) + +**Files:** +- Modify: `src/NATS.Server/Auth/AuthService.cs` +- Modify: `src/NATS.Server/Auth/IAuthenticator.cs` +- Create: `src/NATS.Server/Auth/ExternalAuthCalloutAuthenticator.cs` +- Create: `src/NATS.Server/Auth/ProxyAuthenticator.cs` +- Modify: `src/NATS.Server/NatsOptions.cs` +- Test: `tests/NATS.Server.Tests/Auth/AuthExtensionParityTests.cs` +- Test: `tests/NATS.Server.Tests/Auth/ExternalAuthCalloutTests.cs` +- Test: `tests/NATS.Server.Tests/Auth/ProxyAuthTests.cs` + +**Step 1: Write the failing test** + +```csharp +[Fact] +public async Task External_callout_authenticator_can_allow_and_deny_with_timeout_and_reason_mapping() +{ + var result = await AuthExtensionFixture.AuthenticateViaExternalAsync("u", "p"); + result.Success.ShouldBeTrue(); +} +``` + +**Step 2: Run test to verify it fails** + +Run: `dotnet test tests/NATS.Server.Tests --filter "FullyQualifiedName~AuthExtensionParityTests|FullyQualifiedName~ExternalAuthCalloutTests|FullyQualifiedName~ProxyAuthTests" -v minimal` +Expected: FAIL because extension points are not wired. + +**Step 3: Write minimal implementation** + +```csharp +public interface IExternalAuthClient { Task AuthorizeAsync(ExternalAuthRequest req, CancellationToken ct); } +if (_options.ExternalAuth is { Enabled: true }) authenticators.Add(new ExternalAuthCalloutAuthenticator(...)); +``` + +**Step 4: Run test to verify it passes** + +Run: `dotnet test tests/NATS.Server.Tests --filter "FullyQualifiedName~AuthExtensionParityTests|FullyQualifiedName~ExternalAuthCalloutTests|FullyQualifiedName~ProxyAuthTests" -v minimal` +Expected: PASS. + +**Step 5: Commit** + +```bash +git add src/NATS.Server/Auth/AuthService.cs src/NATS.Server/Auth/IAuthenticator.cs src/NATS.Server/Auth/ExternalAuthCalloutAuthenticator.cs src/NATS.Server/Auth/ProxyAuthenticator.cs src/NATS.Server/NatsOptions.cs tests/NATS.Server.Tests/Auth/AuthExtensionParityTests.cs tests/NATS.Server.Tests/Auth/ExternalAuthCalloutTests.cs tests/NATS.Server.Tests/Auth/ProxyAuthTests.cs +git commit -m "feat: add custom external and proxy authentication parity" +``` + +### Task 11: Close Monitoring Parity Gaps (`connz` filters/details and missing identity/tls/proxy fields) + +**Files:** +- Modify: `src/NATS.Server/Monitoring/ConnzHandler.cs` +- Modify: `src/NATS.Server/Monitoring/Connz.cs` +- Modify: `src/NATS.Server/Monitoring/VarzHandler.cs` +- Modify: `src/NATS.Server/Monitoring/Varz.cs` +- Modify: `src/NATS.Server/Monitoring/ClosedClient.cs` +- Test: `tests/NATS.Server.Tests/Monitoring/ConnzParityFilterTests.cs` +- Test: `tests/NATS.Server.Tests/Monitoring/ConnzParityFieldTests.cs` +- Test: `tests/NATS.Server.Tests/Monitoring/VarzSlowConsumerBreakdownTests.cs` + +**Step 1: Write the failing test** + +```csharp +[Fact] +public async Task Connz_filters_by_user_account_and_subject_and_includes_tls_peer_and_jwt_metadata() +{ + await using var fx = await MonitoringParityFixture.StartAsync(); + var connz = await fx.GetConnzAsync("?user=u&acc=A&filter_subject=orders.*&subs=detail"); + connz.Conns.ShouldAllBe(c => c.Account == "A"); +} +``` + +**Step 2: Run test to verify it fails** + +Run: `dotnet test tests/NATS.Server.Tests --filter "FullyQualifiedName~ConnzParityFilterTests|FullyQualifiedName~ConnzParityFieldTests|FullyQualifiedName~VarzSlowConsumerBreakdownTests" -v minimal` +Expected: FAIL because filters/fields are not fully populated. + +**Step 3: Write minimal implementation** + +```csharp +if (!string.IsNullOrEmpty(opts.User)) conns = conns.Where(c => c.AuthorizedUser == opts.User).ToList(); +if (!string.IsNullOrEmpty(opts.Account)) conns = conns.Where(c => c.Account == opts.Account).ToList(); +if (!string.IsNullOrEmpty(opts.FilterSubject)) conns = conns.Where(c => c.Subs.Any(s => SubjectMatch.MatchLiteral(s, opts.FilterSubject))).ToList(); +``` + +```csharp +info.TlsPeerCertSubject = client.TlsState?.PeerSubject ?? ""; +info.JwtIssuerKey = client.AuthContext?.IssuerKey ?? ""; +``` + +**Step 4: Run test to verify it passes** + +Run: `dotnet test tests/NATS.Server.Tests --filter "FullyQualifiedName~ConnzParityFilterTests|FullyQualifiedName~ConnzParityFieldTests|FullyQualifiedName~VarzSlowConsumerBreakdownTests" -v minimal` +Expected: PASS. + +**Step 5: Commit** + +```bash +git add src/NATS.Server/Monitoring/ConnzHandler.cs src/NATS.Server/Monitoring/Connz.cs src/NATS.Server/Monitoring/VarzHandler.cs src/NATS.Server/Monitoring/Varz.cs src/NATS.Server/Monitoring/ClosedClient.cs tests/NATS.Server.Tests/Monitoring/ConnzParityFilterTests.cs tests/NATS.Server.Tests/Monitoring/ConnzParityFieldTests.cs tests/NATS.Server.Tests/Monitoring/VarzSlowConsumerBreakdownTests.cs +git commit -m "feat: close monitoring parity filters and field coverage" +``` + +### Task 12: Complete JetStream Stream Runtime Feature Parity + +**Files:** +- Modify: `src/NATS.Server/JetStream/Models/StreamConfig.cs` +- Modify: `src/NATS.Server/JetStream/StreamManager.cs` +- Modify: `src/NATS.Server/JetStream/Publish/JetStreamPublisher.cs` +- Modify: `src/NATS.Server/JetStream/Publish/PublishPreconditions.cs` +- Modify: `src/NATS.Server/JetStream/Api/Handlers/StreamApiHandlers.cs` +- Modify: `src/NATS.Server/JetStream/Validation/JetStreamConfigValidator.cs` +- Test: `tests/NATS.Server.Tests/JetStream/JetStreamStreamRuntimeParityTests.cs` +- Test: `tests/NATS.Server.Tests/JetStream/JetStreamStreamFeatureToggleParityTests.cs` + +**Step 1: Write the failing test** + +```csharp +[Fact] +public async Task Stream_runtime_enforces_retention_ttl_per_subject_max_msg_size_and_guard_flags_with_go_error_contracts() +{ + await using var fx = await JetStreamRuntimeFixture.StartWithStrictPolicyAsync(); + var ack = await fx.PublishAsync("orders.created", payloadSize: 2048); + ack.ErrorCode.ShouldBe(10054); +} +``` + +**Step 2: Run test to verify it fails** + +Run: `dotnet test tests/NATS.Server.Tests --filter "FullyQualifiedName~JetStreamStreamRuntimeParityTests|FullyQualifiedName~JetStreamStreamFeatureToggleParityTests" -v minimal` +Expected: FAIL due incomplete runtime semantics for remaining stream rows. + +**Step 3: Write minimal implementation** + +```csharp +ApplyRetentionPolicy(stream, nowUtc); // Limits / Interest / WorkQueue behavior +ApplyPerSubjectCaps(stream); +if (config.Sealed || (isDelete && config.DenyDelete) || (isPurge && config.DenyPurge)) return Error(10052); +``` + +**Step 4: Run test to verify it passes** + +Run: `dotnet test tests/NATS.Server.Tests --filter "FullyQualifiedName~JetStreamStreamRuntimeParityTests|FullyQualifiedName~JetStreamStreamFeatureToggleParityTests" -v minimal` +Expected: PASS. + +**Step 5: Commit** + +```bash +git add src/NATS.Server/JetStream/Models/StreamConfig.cs src/NATS.Server/JetStream/StreamManager.cs src/NATS.Server/JetStream/Publish/JetStreamPublisher.cs src/NATS.Server/JetStream/Publish/PublishPreconditions.cs src/NATS.Server/JetStream/Api/Handlers/StreamApiHandlers.cs src/NATS.Server/JetStream/Validation/JetStreamConfigValidator.cs tests/NATS.Server.Tests/JetStream/JetStreamStreamRuntimeParityTests.cs tests/NATS.Server.Tests/JetStream/JetStreamStreamFeatureToggleParityTests.cs +git commit -m "feat: complete jetstream stream runtime parity" +``` + +### Task 13: Complete JetStream Consumer Runtime Parity + +**Files:** +- Modify: `src/NATS.Server/JetStream/Models/ConsumerConfig.cs` +- Modify: `src/NATS.Server/JetStream/ConsumerManager.cs` +- Modify: `src/NATS.Server/JetStream/Consumers/AckProcessor.cs` +- Modify: `src/NATS.Server/JetStream/Consumers/PullConsumerEngine.cs` +- Modify: `src/NATS.Server/JetStream/Consumers/PushConsumerEngine.cs` +- Modify: `src/NATS.Server/JetStream/Api/Handlers/ConsumerApiHandlers.cs` +- Test: `tests/NATS.Server.Tests/JetStream/JetStreamConsumerRuntimeParityTests.cs` +- Test: `tests/NATS.Server.Tests/JetStream/JetStreamConsumerFlowReplayParityTests.cs` + +**Step 1: Write the failing test** + +```csharp +[Fact] +public async Task Consumer_runtime_honors_deliver_policy_ack_all_redelivery_max_deliver_backoff_flow_rate_and_replay_timing() +{ + await using var fx = await JetStreamConsumerRuntimeFixture.StartAsync(); + var result = await fx.RunScenarioAsync(); + result.UnexpectedTransitions.ShouldBeEmpty(); +} +``` + +**Step 2: Run test to verify it fails** + +Run: `dotnet test tests/NATS.Server.Tests --filter "FullyQualifiedName~JetStreamConsumerRuntimeParityTests|FullyQualifiedName~JetStreamConsumerFlowReplayParityTests" -v minimal` +Expected: FAIL while baseline behavior remains. + +**Step 3: Write minimal implementation** + +```csharp +if (ackPolicy == AckPolicy.All) _ackState.AdvanceFloor(seq); +if (deliveries >= config.MaxDeliver) return DeliveryDecision.Drop; +if (config.FlowControl) enqueue(FlowControlFrame()); +``` + +**Step 4: Run test to verify it passes** + +Run: `dotnet test tests/NATS.Server.Tests --filter "FullyQualifiedName~JetStreamConsumerRuntimeParityTests|FullyQualifiedName~JetStreamConsumerFlowReplayParityTests" -v minimal` +Expected: PASS. + +**Step 5: Commit** + +```bash +git add src/NATS.Server/JetStream/Models/ConsumerConfig.cs src/NATS.Server/JetStream/ConsumerManager.cs src/NATS.Server/JetStream/Consumers/AckProcessor.cs src/NATS.Server/JetStream/Consumers/PullConsumerEngine.cs src/NATS.Server/JetStream/Consumers/PushConsumerEngine.cs src/NATS.Server/JetStream/Api/Handlers/ConsumerApiHandlers.cs tests/NATS.Server.Tests/JetStream/JetStreamConsumerRuntimeParityTests.cs tests/NATS.Server.Tests/JetStream/JetStreamConsumerFlowReplayParityTests.cs +git commit -m "feat: complete jetstream consumer runtime parity" +``` + +### Task 14: Complete JetStream Storage Backend Parity (Layout, Indexing, TTL, Compression, Encryption) + +**Files:** +- Modify: `src/NATS.Server/JetStream/Storage/IStreamStore.cs` +- Modify: `src/NATS.Server/JetStream/Storage/FileStoreOptions.cs` +- Modify: `src/NATS.Server/JetStream/Storage/FileStoreBlock.cs` +- Modify: `src/NATS.Server/JetStream/Storage/FileStore.cs` +- Modify: `src/NATS.Server/JetStream/Storage/MemStore.cs` +- Test: `tests/NATS.Server.Tests/JetStream/JetStreamFileStoreLayoutParityTests.cs` +- Test: `tests/NATS.Server.Tests/JetStream/JetStreamFileStoreCryptoCompressionTests.cs` + +**Step 1: Write the failing test** + +```csharp +[Fact] +public async Task File_store_uses_block_index_layout_with_ttl_prune_and_optional_compression_encryption_roundtrip() +{ + await using var fx = await FileStoreParityFixture.StartAsync(); + await fx.AppendManyAsync(10000); + (await fx.ValidateBlockAndIndexInvariantsAsync()).ShouldBeTrue(); +} +``` + +**Step 2: Run test to verify it fails** + +Run: `dotnet test tests/NATS.Server.Tests --filter "FullyQualifiedName~JetStreamFileStoreLayoutParityTests|FullyQualifiedName~JetStreamFileStoreCryptoCompressionTests" -v minimal` +Expected: FAIL because storage semantics are still simplified. + +**Step 3: Write minimal implementation** + +```csharp +public sealed record SequencePointer(int BlockId, int Slot, long Offset); +if (_options.EnableCompression) bytes = S2Codec.Compress(bytes); +if (_options.EnableEncryption) bytes = _crypto.Encrypt(bytes, nonce); +``` + +**Step 4: Run test to verify it passes** + +Run: `dotnet test tests/NATS.Server.Tests --filter "FullyQualifiedName~JetStreamFileStoreLayoutParityTests|FullyQualifiedName~JetStreamFileStoreCryptoCompressionTests" -v minimal` +Expected: PASS. + +**Step 5: Commit** + +```bash +git add src/NATS.Server/JetStream/Storage/IStreamStore.cs src/NATS.Server/JetStream/Storage/FileStoreOptions.cs src/NATS.Server/JetStream/Storage/FileStoreBlock.cs src/NATS.Server/JetStream/Storage/FileStore.cs src/NATS.Server/JetStream/Storage/MemStore.cs tests/NATS.Server.Tests/JetStream/JetStreamFileStoreLayoutParityTests.cs tests/NATS.Server.Tests/JetStream/JetStreamFileStoreCryptoCompressionTests.cs +git commit -m "feat: complete jetstream storage backend parity" +``` + +### Task 15: Complete Mirror/Source Parity (Mirror Consumer, Source Mapping, Cross-Account) + +**Files:** +- Modify: `src/NATS.Server/JetStream/MirrorSource/MirrorCoordinator.cs` +- Modify: `src/NATS.Server/JetStream/MirrorSource/SourceCoordinator.cs` +- Modify: `src/NATS.Server/JetStream/StreamManager.cs` +- Modify: `src/NATS.Server/Auth/Account.cs` +- Test: `tests/NATS.Server.Tests/JetStream/JetStreamMirrorSourceRuntimeParityTests.cs` + +**Step 1: Write the failing test** + +```csharp +[Fact] +public async Task Mirror_source_runtime_enforces_cross_account_permissions_and_subject_mapping_with_sync_state_tracking() +{ + await using var fx = await MirrorSourceParityFixture.StartAsync(); + var sync = await fx.GetSyncStateAsync("AGG"); + sync.LastOriginSequence.ShouldBeGreaterThan(0); +} +``` + +**Step 2: Run test to verify it fails** + +Run: `dotnet test tests/NATS.Server.Tests --filter "FullyQualifiedName~JetStreamMirrorSourceRuntimeParityTests" -v minimal` +Expected: FAIL due missing runtime parity semantics. + +**Step 3: Write minimal implementation** + +```csharp +if (!_accountPolicy.CanMirror(sourceAccount, targetAccount)) return; +subject = _sourceTransform.Apply(subject); +_mirrorState.Update(originSequence, DateTime.UtcNow); +``` + +**Step 4: Run test to verify it passes** + +Run: `dotnet test tests/NATS.Server.Tests --filter "FullyQualifiedName~JetStreamMirrorSourceRuntimeParityTests" -v minimal` +Expected: PASS. + +**Step 5: Commit** + +```bash +git add src/NATS.Server/JetStream/MirrorSource/MirrorCoordinator.cs src/NATS.Server/JetStream/MirrorSource/SourceCoordinator.cs src/NATS.Server/JetStream/StreamManager.cs src/NATS.Server/Auth/Account.cs tests/NATS.Server.Tests/JetStream/JetStreamMirrorSourceRuntimeParityTests.cs +git commit -m "feat: complete mirror and source runtime parity" +``` + +### Task 16: Complete RAFT Consensus Parity (Heartbeat, NextIndex, Snapshot Transfer, Membership) + +**Files:** +- Modify: `src/NATS.Server/Raft/RaftRpcContracts.cs` +- Modify: `src/NATS.Server/Raft/RaftTransport.cs` +- Modify: `src/NATS.Server/Raft/RaftReplicator.cs` +- Modify: `src/NATS.Server/Raft/RaftNode.cs` +- Modify: `src/NATS.Server/Raft/RaftLog.cs` +- Modify: `src/NATS.Server/Raft/RaftSnapshotStore.cs` +- Test: `tests/NATS.Server.Tests/Raft/RaftConsensusRuntimeParityTests.cs` +- Test: `tests/NATS.Server.Tests/Raft/RaftSnapshotTransferRuntimeParityTests.cs` +- Test: `tests/NATS.Server.Tests/Raft/RaftMembershipRuntimeParityTests.cs` + +**Step 1: Write the failing test** + +```csharp +[Fact] +public async Task Raft_cluster_commits_with_next_index_backtracking_and_snapshot_install_for_lagging_follower() +{ + await using var cluster = await RaftRuntimeFixture.StartThreeNodeAsync(); + (await cluster.RunCommitAndCatchupScenarioAsync()).ShouldBeTrue(); +} +``` + +**Step 2: Run test to verify it fails** + +Run: `dotnet test tests/NATS.Server.Tests --filter "FullyQualifiedName~RaftConsensusRuntimeParityTests|FullyQualifiedName~RaftSnapshotTransferRuntimeParityTests|FullyQualifiedName~RaftMembershipRuntimeParityTests" -v minimal` +Expected: FAIL under current hook-level behavior. + +**Step 3: Write minimal implementation** + +```csharp +while (!AppendEntriesAccepted(follower, nextIndex[follower])) nextIndex[follower]--; +if (nextIndex[follower] <= snapshot.LastIncludedIndex) await transport.InstallSnapshotAsync(...); +``` + +**Step 4: Run test to verify it passes** + +Run: `dotnet test tests/NATS.Server.Tests --filter "FullyQualifiedName~RaftConsensusRuntimeParityTests|FullyQualifiedName~RaftSnapshotTransferRuntimeParityTests|FullyQualifiedName~RaftMembershipRuntimeParityTests" -v minimal` +Expected: PASS. + +**Step 5: Commit** + +```bash +git add src/NATS.Server/Raft/RaftRpcContracts.cs src/NATS.Server/Raft/RaftTransport.cs src/NATS.Server/Raft/RaftReplicator.cs src/NATS.Server/Raft/RaftNode.cs src/NATS.Server/Raft/RaftLog.cs src/NATS.Server/Raft/RaftSnapshotStore.cs tests/NATS.Server.Tests/Raft/RaftConsensusRuntimeParityTests.cs tests/NATS.Server.Tests/Raft/RaftSnapshotTransferRuntimeParityTests.cs tests/NATS.Server.Tests/Raft/RaftMembershipRuntimeParityTests.cs +git commit -m "feat: complete raft runtime consensus parity" +``` + +### Task 17: Complete JetStream Cluster Governance and Cross-Cluster JetStream Parity + +**Files:** +- Modify: `src/NATS.Server/JetStream/Cluster/JetStreamMetaGroup.cs` +- Modify: `src/NATS.Server/JetStream/Cluster/StreamReplicaGroup.cs` +- Modify: `src/NATS.Server/JetStream/Cluster/AssetPlacementPlanner.cs` +- Modify: `src/NATS.Server/NatsServer.cs` +- Modify: `src/NATS.Server/Gateways/GatewayManager.cs` +- Test: `tests/NATS.Server.Tests/JetStream/JetStreamClusterGovernanceRuntimeParityTests.cs` +- Test: `tests/NATS.Server.Tests/JetStream/JetStreamCrossClusterRuntimeParityTests.cs` + +**Step 1: Write the failing test** + +```csharp +[Fact] +public async Task Jetstream_cluster_governance_applies_consensus_backed_placement_and_cross_cluster_replication() +{ + await using var fx = await JetStreamClusterRuntimeFixture.StartAsync(); + var result = await fx.CreateAndReplicateStreamAsync(); + result.Success.ShouldBeTrue(); +} +``` + +**Step 2: Run test to verify it fails** + +Run: `dotnet test tests/NATS.Server.Tests --filter "FullyQualifiedName~JetStreamClusterGovernanceRuntimeParityTests|FullyQualifiedName~JetStreamCrossClusterRuntimeParityTests" -v minimal` +Expected: FAIL while governance and cross-cluster paths are still partial. + +**Step 3: Write minimal implementation** + +```csharp +await _metaGroup.ProposePlacementAsync(stream, replicas, ct); +await _replicaGroup.ApplyCommittedPlacementAsync(plan, ct); +if (message.Subject.StartsWith("$JS.CLUSTER.")) await _gatewayManager.ForwardJetStreamClusterMessageAsync(message, ct); +``` + +**Step 4: Run test to verify it passes** + +Run: `dotnet test tests/NATS.Server.Tests --filter "FullyQualifiedName~JetStreamClusterGovernanceRuntimeParityTests|FullyQualifiedName~JetStreamCrossClusterRuntimeParityTests" -v minimal` +Expected: PASS. + +**Step 5: Commit** + +```bash +git add src/NATS.Server/JetStream/Cluster/JetStreamMetaGroup.cs src/NATS.Server/JetStream/Cluster/StreamReplicaGroup.cs src/NATS.Server/JetStream/Cluster/AssetPlacementPlanner.cs src/NATS.Server/NatsServer.cs src/NATS.Server/Gateways/GatewayManager.cs tests/NATS.Server.Tests/JetStream/JetStreamClusterGovernanceRuntimeParityTests.cs tests/NATS.Server.Tests/JetStream/JetStreamCrossClusterRuntimeParityTests.cs +git commit -m "feat: complete jetstream cluster governance and cross-cluster parity" +``` + +### Task 18: Implement MQTT Transport Parity Baseline-to-Feature Completion + +**Files:** +- Modify: `src/NATS.Server/NatsServer.cs` +- Create: `src/NATS.Server/Mqtt/MqttListener.cs` +- Create: `src/NATS.Server/Mqtt/MqttConnection.cs` +- Create: `src/NATS.Server/Mqtt/MqttProtocolParser.cs` +- Modify: `src/NATS.Server/Configuration/MqttOptions.cs` +- Test: `tests/NATS.Server.Tests/Mqtt/MqttListenerParityTests.cs` +- Test: `tests/NATS.Server.Tests/Mqtt/MqttPublishSubscribeParityTests.cs` + +**Step 1: Write the failing test** + +```csharp +[Fact] +public async Task Mqtt_listener_accepts_connect_and_routes_publish_to_matching_subscription() +{ + await using var fx = await MqttFixture.StartAsync(); + var payload = await fx.PublishAndReceiveAsync("sensors.temp", "42"); + payload.ShouldBe("42"); +} +``` + +**Step 2: Run test to verify it fails** + +Run: `dotnet test tests/NATS.Server.Tests --filter "FullyQualifiedName~MqttListenerParityTests|FullyQualifiedName~MqttPublishSubscribeParityTests" -v minimal` +Expected: FAIL because MQTT transport listener is not implemented. + +**Step 3: Write minimal implementation** + +```csharp +_listener = new TcpListener(IPAddress.Parse(_opts.Host), _opts.Port); +while (!ct.IsCancellationRequested) _ = HandleAsync(await _listener.AcceptTcpClientAsync(ct), ct); +``` + +```csharp +if (packet.Type == MqttPacketType.Publish) _router.ProcessMessage(topic, null, default, payload, mqttClientAdapter); +``` + +**Step 4: Run test to verify it passes** + +Run: `dotnet test tests/NATS.Server.Tests --filter "FullyQualifiedName~MqttListenerParityTests|FullyQualifiedName~MqttPublishSubscribeParityTests" -v minimal` +Expected: PASS. + +**Step 5: Commit** + +```bash +git add src/NATS.Server/NatsServer.cs src/NATS.Server/Mqtt/MqttListener.cs src/NATS.Server/Mqtt/MqttConnection.cs src/NATS.Server/Mqtt/MqttProtocolParser.cs src/NATS.Server/Configuration/MqttOptions.cs tests/NATS.Server.Tests/Mqtt/MqttListenerParityTests.cs tests/NATS.Server.Tests/Mqtt/MqttPublishSubscribeParityTests.cs +git commit -m "feat: add mqtt transport parity implementation" +``` + +### Task 19: Final Docs and Verification Closure + +**Files:** +- Modify: `differences.md` +- Modify: `docs/plans/2026-02-23-jetstream-remaining-parity-map.md` +- Modify: `docs/plans/2026-02-23-jetstream-remaining-parity-verification.md` + +**Step 1: Write the failing test** + +```csharp +[Fact] +public void Differences_table_has_no_remaining_unresolved_rows_after_full_parity_execution() +{ + var report = ParityRowInspector.Load("differences.md"); + report.UnresolvedRows.ShouldBeEmpty(); +} +``` + +**Step 2: Run test to verify it fails** + +Run: `dotnet test tests/NATS.Server.Tests --filter "FullyQualifiedName~DifferencesParityClosureTests" -v minimal` +Expected: FAIL until all rows are updated from validated evidence. + +**Step 3: Write minimal implementation** + +```markdown +## Summary: Remaining Gaps + +### Full Repo +None in tracked scope after this plan; unresolved table rows are closed or explicitly blocked with evidence. +``` + +**Step 4: Run verification gates** + +Run: `dotnet test tests/NATS.Server.Tests --filter "FullyQualifiedName~JetStream|FullyQualifiedName~Raft|FullyQualifiedName~Route|FullyQualifiedName~Gateway|FullyQualifiedName~Leaf|FullyQualifiedName~SubList|FullyQualifiedName~Connz|FullyQualifiedName~Varz|FullyQualifiedName~Auth|FullyQualifiedName~Mqtt|FullyQualifiedName~DifferencesParityClosureTests" -v minimal` +Expected: PASS. + +Run: `dotnet test -v minimal` +Expected: PASS. + +**Step 5: Commit** + +```bash +git add differences.md docs/plans/2026-02-23-jetstream-remaining-parity-map.md docs/plans/2026-02-23-jetstream-remaining-parity-verification.md +git commit -m "docs: close full-repo parity gaps with verified evidence" +```