docs: add full-repo remaining parity plan
This commit is contained in:
923
docs/plans/2026-02-23-full-repo-remaining-parity-plan.md
Normal file
923
docs/plans/2026-02-23-full-repo-remaining-parity-plan.md
Normal file
@@ -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<ParityRow> 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<byte> 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<byte> subjectUtf8) => Match(Encoding.ASCII.GetString(subjectUtf8));
|
||||
public event Action<InterestChange>? 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<ExternalAuthDecision> 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"
|
||||
```
|
||||
Reference in New Issue
Block a user