Fix E2E test gaps and add comprehensive E2E + parity test suites
- Fix pull consumer fetch: send original stream subject in HMSG (not inbox) so NATS client distinguishes data messages from control messages - Fix MaxAge expiry: add background timer in StreamManager for periodic pruning - Fix JetStream wire format: Go-compatible anonymous objects with string enums, proper offset-based pagination for stream/consumer list APIs - Add 42 E2E black-box tests (core messaging, auth, TLS, accounts, JetStream) - Add ~1000 parity tests across all subsystems (gaps closure) - Update gap inventory docs to reflect implementation status
This commit is contained in:
@@ -34,5 +34,6 @@
|
||||
|
||||
<!-- NATS Client (integration tests) -->
|
||||
<PackageVersion Include="NATS.Client.Core" Version="2.7.2" />
|
||||
<PackageVersion Include="NATS.Client.JetStream" Version="2.7.2" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -5,5 +5,6 @@
|
||||
</Folder>
|
||||
<Folder Name="/tests/">
|
||||
<Project Path="tests/NATS.Server.Tests/NATS.Server.Tests.csproj" />
|
||||
<Project Path="tests/NATS.E2E.Tests/NATS.E2E.Tests.csproj" />
|
||||
</Folder>
|
||||
</Solution>
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
# Auth & Accounts Full Parity Design
|
||||
|
||||
**Date:** 2026-02-25
|
||||
**Category:** `auth-and-accounts`
|
||||
**Gap Inventory:** `gaps/auth-and-accounts.md`
|
||||
**Target Outcome:** Full Go-to-.NET behavioral parity for this category.
|
||||
|
||||
## Assumptions
|
||||
- Full parity is the required outcome.
|
||||
- No additional requirements gathering is needed for this pass.
|
||||
- Existing public .NET APIs are preserved unless parity requires targeted, documented adjustments.
|
||||
|
||||
## Current State Snapshot
|
||||
- `MISSING`: 128
|
||||
- `PARTIAL`: 38
|
||||
- `PORTED`: 64
|
||||
- `NOT_APPLICABLE`: 9
|
||||
- `DEFERRED`: 0
|
||||
- **Open parity work items:** 166
|
||||
|
||||
## Approaches Considered
|
||||
1. **Symbol-first closure (Recommended):** execute directly against each `MISSING` and `PARTIAL` row in `gaps/auth-and-accounts.md`, closing items in deterministic order.
|
||||
Trade-off: strongest traceability and auditability, but can require broad context switching.
|
||||
2. **Test-first closure:** derive all work from Go test scenarios first, then backfill missing symbols.
|
||||
Trade-off: maximizes behavioral confidence, but can miss non-test-covered API and helper parity.
|
||||
3. **Vertical runtime slices:** port connect/auth/pubsub/cluster flows end-to-end before moving to next slice.
|
||||
Trade-off: smoother integration validation, but weaker one-to-one mapping against inventory rows.
|
||||
|
||||
## Recommended Design
|
||||
### Architecture
|
||||
Use the gap inventory as the authoritative backlog. For each row, implement or complete the .NET equivalent, then attach test evidence and update the inventory status. Keep implementation inside existing category boundaries and only refactor when needed to match Go behavior.
|
||||
|
||||
### Components
|
||||
- **Backlog driver:** `gaps/auth-and-accounts.md` (row-level tracking for all parity items).
|
||||
- **Implementation surface:** category-specific .NET source files.
|
||||
- **Verification surface:** category-specific .NET tests plus cross-module regression tests.
|
||||
- **Parity bookkeeping:** updates to `gaps/auth-and-accounts.md` and `gaps/stillmissing.md` after each closure batch.
|
||||
|
||||
### Data Flow
|
||||
1. Read one `MISSING` or `PARTIAL` row from `gaps/auth-and-accounts.md`.
|
||||
2. Inspect referenced Go implementation and tests.
|
||||
3. Add/adjust .NET implementation in listed category files.
|
||||
4. Add failing .NET tests that reproduce the missing/partial behavior, then make them pass.
|
||||
5. Re-run targeted and regression tests.
|
||||
6. Update the row to `PORTED` (or keep `PARTIAL` with explicit residual notes).
|
||||
|
||||
### Error Handling and Risk Controls
|
||||
- Avoid broad rewrites; prefer narrow patches tied to specific inventory rows.
|
||||
- Preserve protocol and timing semantics when replacing Go concurrency constructs with .NET async patterns.
|
||||
- Treat edge cases in Go tests as mandatory parity requirements unless explicitly marked `NOT_APPLICABLE`.
|
||||
- Require evidence (tests + inventory status updates) before declaring row completion.
|
||||
|
||||
### Testing Strategy
|
||||
- Add or expand tests for each row moved from `MISSING`/`PARTIAL` toward `PORTED`.
|
||||
- Prioritize wire/protocol compatibility, lifecycle transitions, auth boundaries, and backpressure/timeout behavior.
|
||||
- Keep category-targeted tests fast; run full `NATS.Server.Tests` regression before closure.
|
||||
|
||||
### Definition of Done
|
||||
- All category rows are either `PORTED` or explicitly justified `NOT_APPLICABLE`/`DEFERRED`.
|
||||
- No unresolved `MISSING` or unqualified `PARTIAL` rows remain for this category.
|
||||
- Relevant .NET tests pass locally and parity evidence is written into inventory notes.
|
||||
|
||||
## Implementation Scope Inputs
|
||||
### Go Source Files
|
||||
- `golang/nats-server/server/auth.go`
|
||||
- `golang/nats-server/server/auth_callout.go`
|
||||
- `golang/nats-server/server/nkey.go`
|
||||
- `golang/nats-server/server/jwt.go`
|
||||
- `golang/nats-server/server/accounts.go`
|
||||
|
||||
### Go Test Files
|
||||
- `golang/nats-server/server/auth_test.go`
|
||||
- `golang/nats-server/server/auth_callout_test.go`
|
||||
- `golang/nats-server/server/nkey_test.go`
|
||||
- `golang/nats-server/server/jwt_test.go`
|
||||
- `golang/nats-server/server/accounts_test.go`
|
||||
- `golang/nats-server/server/trust_test.go`
|
||||
|
||||
### .NET Source Files
|
||||
- `src/NATS.Server/Auth/ (all files including Jwt/ subdirectory)`
|
||||
- `src/NATS.Server/Imports/ (account import/export)`
|
||||
|
||||
### .NET Test Files
|
||||
- `tests/NATS.Server.Tests/Auth/`
|
||||
- `tests/NATS.Server.Tests/Accounts/`
|
||||
@@ -0,0 +1,103 @@
|
||||
# Auth & Accounts Gap Port Implementation Plan
|
||||
|
||||
> **For Codex:** REQUIRED SUB-SKILL: Use `executeplan` to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Port all remaining `auth-and-accounts` gap inventory items to achieve full behavior parity with Go.
|
||||
|
||||
**Architecture:** Use the category gap inventory as the execution backlog. Drive work row-by-row from `MISSING` and `PARTIAL` toward `PORTED`, with test-first validation and explicit parity evidence in notes.
|
||||
|
||||
**Tech Stack:** .NET 10, C#, xUnit, existing `NATS.Server` runtime modules, Go NATS server reference source.
|
||||
|
||||
---
|
||||
|
||||
## Category Inputs
|
||||
- Gap Inventory: `gaps/auth-and-accounts.md`
|
||||
- Primary .NET Source Anchor: `src/NATS.Server/Auth/ (all files including Jwt/ subdirectory)`
|
||||
- Primary .NET Test Anchor: `tests/NATS.Server.Tests/Auth/`
|
||||
- Full .NET test project: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
## Task 1: Freeze Row-Level Backlog
|
||||
|
||||
**Files:**
|
||||
- Modify: `gaps/auth-and-accounts.md`
|
||||
|
||||
**Step 1: Normalize open rows into an execution queue**
|
||||
- Split all `MISSING` and `PARTIAL` rows into ranked batches (highest runtime impact first).
|
||||
|
||||
**Step 2: Record acceptance criteria per row**
|
||||
- For each row, define exact behavioral parity checks (inputs, outputs, error paths, timing/concurrency expectations).
|
||||
|
||||
**Step 3: Save queue annotations in notes**
|
||||
- Add short execution-order markers in notes without changing status yet.
|
||||
|
||||
## Task 2: Write Failing Tests for Batch 1
|
||||
|
||||
**Files:**
|
||||
- Modify: `tests/NATS.Server.Tests/Auth/`
|
||||
- Test: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
**Step 1: Add failing tests for first open row batch**
|
||||
- Create tests that reproduce the exact missing/partial Go behavior.
|
||||
|
||||
**Step 2: Run tests to confirm failure**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: FAIL in newly added tests only.
|
||||
|
||||
## Task 3: Implement Minimal Parity for Batch 1
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/NATS.Server/Auth/ (all files including Jwt/ subdirectory)`
|
||||
- Modify: additional category files listed in `gaps/auth-and-accounts.md`
|
||||
|
||||
**Step 1: Implement minimal code to satisfy failing tests**
|
||||
- Mirror Go semantics for parsing, state transitions, limits, and side-effects required by the targeted rows.
|
||||
|
||||
**Step 2: Re-run tests for validation**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: PASS for batch-1 tests; no new regressions.
|
||||
|
||||
## Task 4: Close Remaining Batches Iteratively
|
||||
|
||||
**Files:**
|
||||
- Modify: category .NET source and test files referenced in `gaps/auth-and-accounts.md`
|
||||
- Modify: `gaps/auth-and-accounts.md`
|
||||
|
||||
**Step 1: Repeat test-first cycle for each remaining batch**
|
||||
- Add failing tests, implement minimal parity, then verify pass.
|
||||
|
||||
**Step 2: Update inventory status immediately after each closure**
|
||||
- Move rows to `PORTED` with exact .NET file:line references.
|
||||
- Keep `PARTIAL` only if explicit residual behavior remains.
|
||||
|
||||
## Task 5: Cross-Module Regression and Hardening
|
||||
|
||||
**Files:**
|
||||
- Test: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
**Step 1: Run full regression suite**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: PASS.
|
||||
|
||||
**Step 2: Add edge-case tests discovered during regression**
|
||||
- Capture any newly discovered divergence from Go and close it before completion.
|
||||
|
||||
## Task 6: Finalize Parity Bookkeeping
|
||||
|
||||
**Files:**
|
||||
- Modify: `gaps/auth-and-accounts.md`
|
||||
- Modify: `gaps/stillmissing.md`
|
||||
|
||||
**Step 1: Ensure final status correctness**
|
||||
- Verify no unresolved `MISSING` rows remain for `auth-and-accounts`.
|
||||
|
||||
**Step 2: Refresh LOC and percentage bookkeeping**
|
||||
- Recount category LOC and update summary rows in `gaps/stillmissing.md`.
|
||||
|
||||
**Step 3: Add concise changelog entry**
|
||||
- Record date, completed parity areas, and any justified deferred items.
|
||||
|
||||
## Completion Checklist
|
||||
- [ ] All prioritized `auth-and-accounts` rows are `PORTED` or explicitly justified as `NOT_APPLICABLE`/`DEFERRED`.
|
||||
- [ ] New/updated tests demonstrate parity for all previously open rows.
|
||||
- [ ] `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release` passes.
|
||||
- [ ] `gaps/auth-and-accounts.md` and `gaps/stillmissing.md` are fully synchronized.
|
||||
@@ -0,0 +1,83 @@
|
||||
# Configuration Full Parity Design
|
||||
|
||||
**Date:** 2026-02-25
|
||||
**Category:** `configuration`
|
||||
**Gap Inventory:** `gaps/configuration.md`
|
||||
**Target Outcome:** Full Go-to-.NET behavioral parity for this category.
|
||||
|
||||
## Assumptions
|
||||
- Full parity is the required outcome.
|
||||
- No additional requirements gathering is needed for this pass.
|
||||
- Existing public .NET APIs are preserved unless parity requires targeted, documented adjustments.
|
||||
|
||||
## Current State Snapshot
|
||||
- `MISSING`: 12
|
||||
- `PARTIAL`: 8
|
||||
- `PORTED`: 108
|
||||
- `NOT_APPLICABLE`: 3
|
||||
- `DEFERRED`: 0
|
||||
- **Open parity work items:** 20
|
||||
|
||||
## Approaches Considered
|
||||
1. **Symbol-first closure (Recommended):** execute directly against each `MISSING` and `PARTIAL` row in `gaps/configuration.md`, closing items in deterministic order.
|
||||
Trade-off: strongest traceability and auditability, but can require broad context switching.
|
||||
2. **Test-first closure:** derive all work from Go test scenarios first, then backfill missing symbols.
|
||||
Trade-off: maximizes behavioral confidence, but can miss non-test-covered API and helper parity.
|
||||
3. **Vertical runtime slices:** port connect/auth/pubsub/cluster flows end-to-end before moving to next slice.
|
||||
Trade-off: smoother integration validation, but weaker one-to-one mapping against inventory rows.
|
||||
|
||||
## Recommended Design
|
||||
### Architecture
|
||||
Use the gap inventory as the authoritative backlog. For each row, implement or complete the .NET equivalent, then attach test evidence and update the inventory status. Keep implementation inside existing category boundaries and only refactor when needed to match Go behavior.
|
||||
|
||||
### Components
|
||||
- **Backlog driver:** `gaps/configuration.md` (row-level tracking for all parity items).
|
||||
- **Implementation surface:** category-specific .NET source files.
|
||||
- **Verification surface:** category-specific .NET tests plus cross-module regression tests.
|
||||
- **Parity bookkeeping:** updates to `gaps/configuration.md` and `gaps/stillmissing.md` after each closure batch.
|
||||
|
||||
### Data Flow
|
||||
1. Read one `MISSING` or `PARTIAL` row from `gaps/configuration.md`.
|
||||
2. Inspect referenced Go implementation and tests.
|
||||
3. Add/adjust .NET implementation in listed category files.
|
||||
4. Add failing .NET tests that reproduce the missing/partial behavior, then make them pass.
|
||||
5. Re-run targeted and regression tests.
|
||||
6. Update the row to `PORTED` (or keep `PARTIAL` with explicit residual notes).
|
||||
|
||||
### Error Handling and Risk Controls
|
||||
- Avoid broad rewrites; prefer narrow patches tied to specific inventory rows.
|
||||
- Preserve protocol and timing semantics when replacing Go concurrency constructs with .NET async patterns.
|
||||
- Treat edge cases in Go tests as mandatory parity requirements unless explicitly marked `NOT_APPLICABLE`.
|
||||
- Require evidence (tests + inventory status updates) before declaring row completion.
|
||||
|
||||
### Testing Strategy
|
||||
- Add or expand tests for each row moved from `MISSING`/`PARTIAL` toward `PORTED`.
|
||||
- Prioritize wire/protocol compatibility, lifecycle transitions, auth boundaries, and backpressure/timeout behavior.
|
||||
- Keep category-targeted tests fast; run full `NATS.Server.Tests` regression before closure.
|
||||
|
||||
### Definition of Done
|
||||
- All category rows are either `PORTED` or explicitly justified `NOT_APPLICABLE`/`DEFERRED`.
|
||||
- No unresolved `MISSING` or unqualified `PARTIAL` rows remain for this category.
|
||||
- Relevant .NET tests pass locally and parity evidence is written into inventory notes.
|
||||
|
||||
## Implementation Scope Inputs
|
||||
### Go Source Files
|
||||
- `golang/nats-server/conf/lex.go`
|
||||
- `golang/nats-server/conf/parse.go`
|
||||
- `golang/nats-server/conf/token.go`
|
||||
|
||||
### Go Test Files
|
||||
- `golang/nats-server/conf/lex_test.go`
|
||||
- `golang/nats-server/conf/parse_test.go`
|
||||
- `golang/nats-server/server/config_check_test.go`
|
||||
|
||||
### .NET Source Files
|
||||
- `src/NATS.Server/Configuration/NatsConfLexer.cs`
|
||||
- `src/NATS.Server/Configuration/NatsConfParser.cs`
|
||||
- `src/NATS.Server/Configuration/NatsConfToken.cs`
|
||||
- `src/NATS.Server/Configuration/ConfigProcessor.cs`
|
||||
- `src/NATS.Server/Configuration/ConfigReloader.cs`
|
||||
- `All other files in src/NATS.Server/Configuration/`
|
||||
|
||||
### .NET Test Files
|
||||
- `tests/NATS.Server.Tests/Configuration/`
|
||||
@@ -0,0 +1,103 @@
|
||||
# Configuration Gap Port Implementation Plan
|
||||
|
||||
> **For Codex:** REQUIRED SUB-SKILL: Use `executeplan` to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Port all remaining `configuration` gap inventory items to achieve full behavior parity with Go.
|
||||
|
||||
**Architecture:** Use the category gap inventory as the execution backlog. Drive work row-by-row from `MISSING` and `PARTIAL` toward `PORTED`, with test-first validation and explicit parity evidence in notes.
|
||||
|
||||
**Tech Stack:** .NET 10, C#, xUnit, existing `NATS.Server` runtime modules, Go NATS server reference source.
|
||||
|
||||
---
|
||||
|
||||
## Category Inputs
|
||||
- Gap Inventory: `gaps/configuration.md`
|
||||
- Primary .NET Source Anchor: `src/NATS.Server/Configuration/NatsConfLexer.cs`
|
||||
- Primary .NET Test Anchor: `tests/NATS.Server.Tests/Configuration/`
|
||||
- Full .NET test project: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
## Task 1: Freeze Row-Level Backlog
|
||||
|
||||
**Files:**
|
||||
- Modify: `gaps/configuration.md`
|
||||
|
||||
**Step 1: Normalize open rows into an execution queue**
|
||||
- Split all `MISSING` and `PARTIAL` rows into ranked batches (highest runtime impact first).
|
||||
|
||||
**Step 2: Record acceptance criteria per row**
|
||||
- For each row, define exact behavioral parity checks (inputs, outputs, error paths, timing/concurrency expectations).
|
||||
|
||||
**Step 3: Save queue annotations in notes**
|
||||
- Add short execution-order markers in notes without changing status yet.
|
||||
|
||||
## Task 2: Write Failing Tests for Batch 1
|
||||
|
||||
**Files:**
|
||||
- Modify: `tests/NATS.Server.Tests/Configuration/`
|
||||
- Test: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
**Step 1: Add failing tests for first open row batch**
|
||||
- Create tests that reproduce the exact missing/partial Go behavior.
|
||||
|
||||
**Step 2: Run tests to confirm failure**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: FAIL in newly added tests only.
|
||||
|
||||
## Task 3: Implement Minimal Parity for Batch 1
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/NATS.Server/Configuration/NatsConfLexer.cs`
|
||||
- Modify: additional category files listed in `gaps/configuration.md`
|
||||
|
||||
**Step 1: Implement minimal code to satisfy failing tests**
|
||||
- Mirror Go semantics for parsing, state transitions, limits, and side-effects required by the targeted rows.
|
||||
|
||||
**Step 2: Re-run tests for validation**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: PASS for batch-1 tests; no new regressions.
|
||||
|
||||
## Task 4: Close Remaining Batches Iteratively
|
||||
|
||||
**Files:**
|
||||
- Modify: category .NET source and test files referenced in `gaps/configuration.md`
|
||||
- Modify: `gaps/configuration.md`
|
||||
|
||||
**Step 1: Repeat test-first cycle for each remaining batch**
|
||||
- Add failing tests, implement minimal parity, then verify pass.
|
||||
|
||||
**Step 2: Update inventory status immediately after each closure**
|
||||
- Move rows to `PORTED` with exact .NET file:line references.
|
||||
- Keep `PARTIAL` only if explicit residual behavior remains.
|
||||
|
||||
## Task 5: Cross-Module Regression and Hardening
|
||||
|
||||
**Files:**
|
||||
- Test: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
**Step 1: Run full regression suite**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: PASS.
|
||||
|
||||
**Step 2: Add edge-case tests discovered during regression**
|
||||
- Capture any newly discovered divergence from Go and close it before completion.
|
||||
|
||||
## Task 6: Finalize Parity Bookkeeping
|
||||
|
||||
**Files:**
|
||||
- Modify: `gaps/configuration.md`
|
||||
- Modify: `gaps/stillmissing.md`
|
||||
|
||||
**Step 1: Ensure final status correctness**
|
||||
- Verify no unresolved `MISSING` rows remain for `configuration`.
|
||||
|
||||
**Step 2: Refresh LOC and percentage bookkeeping**
|
||||
- Recount category LOC and update summary rows in `gaps/stillmissing.md`.
|
||||
|
||||
**Step 3: Add concise changelog entry**
|
||||
- Record date, completed parity areas, and any justified deferred items.
|
||||
|
||||
## Completion Checklist
|
||||
- [ ] All prioritized `configuration` rows are `PORTED` or explicitly justified as `NOT_APPLICABLE`/`DEFERRED`.
|
||||
- [ ] New/updated tests demonstrate parity for all previously open rows.
|
||||
- [ ] `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release` passes.
|
||||
- [ ] `gaps/configuration.md` and `gaps/stillmissing.md` are fully synchronized.
|
||||
@@ -0,0 +1,98 @@
|
||||
# Core Server Full Parity Design
|
||||
|
||||
**Date:** 2026-02-25
|
||||
**Category:** `core-server`
|
||||
**Gap Inventory:** `gaps/core-server.md`
|
||||
**Target Outcome:** Full Go-to-.NET behavioral parity for this category.
|
||||
|
||||
## Assumptions
|
||||
- Full parity is the required outcome.
|
||||
- No additional requirements gathering is needed for this pass.
|
||||
- Existing public .NET APIs are preserved unless parity requires targeted, documented adjustments.
|
||||
|
||||
## Current State Snapshot
|
||||
- `MISSING`: 55
|
||||
- `PARTIAL`: 30
|
||||
- `PORTED`: 123
|
||||
- `NOT_APPLICABLE`: 14
|
||||
- `DEFERRED`: 0
|
||||
- **Open parity work items:** 85
|
||||
|
||||
## Approaches Considered
|
||||
1. **Symbol-first closure (Recommended):** execute directly against each `MISSING` and `PARTIAL` row in `gaps/core-server.md`, closing items in deterministic order.
|
||||
Trade-off: strongest traceability and auditability, but can require broad context switching.
|
||||
2. **Test-first closure:** derive all work from Go test scenarios first, then backfill missing symbols.
|
||||
Trade-off: maximizes behavioral confidence, but can miss non-test-covered API and helper parity.
|
||||
3. **Vertical runtime slices:** port connect/auth/pubsub/cluster flows end-to-end before moving to next slice.
|
||||
Trade-off: smoother integration validation, but weaker one-to-one mapping against inventory rows.
|
||||
|
||||
## Recommended Design
|
||||
### Architecture
|
||||
Use the gap inventory as the authoritative backlog. For each row, implement or complete the .NET equivalent, then attach test evidence and update the inventory status. Keep implementation inside existing category boundaries and only refactor when needed to match Go behavior.
|
||||
|
||||
### Components
|
||||
- **Backlog driver:** `gaps/core-server.md` (row-level tracking for all parity items).
|
||||
- **Implementation surface:** category-specific .NET source files.
|
||||
- **Verification surface:** category-specific .NET tests plus cross-module regression tests.
|
||||
- **Parity bookkeeping:** updates to `gaps/core-server.md` and `gaps/stillmissing.md` after each closure batch.
|
||||
|
||||
### Data Flow
|
||||
1. Read one `MISSING` or `PARTIAL` row from `gaps/core-server.md`.
|
||||
2. Inspect referenced Go implementation and tests.
|
||||
3. Add/adjust .NET implementation in listed category files.
|
||||
4. Add failing .NET tests that reproduce the missing/partial behavior, then make them pass.
|
||||
5. Re-run targeted and regression tests.
|
||||
6. Update the row to `PORTED` (or keep `PARTIAL` with explicit residual notes).
|
||||
|
||||
### Error Handling and Risk Controls
|
||||
- Avoid broad rewrites; prefer narrow patches tied to specific inventory rows.
|
||||
- Preserve protocol and timing semantics when replacing Go concurrency constructs with .NET async patterns.
|
||||
- Treat edge cases in Go tests as mandatory parity requirements unless explicitly marked `NOT_APPLICABLE`.
|
||||
- Require evidence (tests + inventory status updates) before declaring row completion.
|
||||
|
||||
### Testing Strategy
|
||||
- Add or expand tests for each row moved from `MISSING`/`PARTIAL` toward `PORTED`.
|
||||
- Prioritize wire/protocol compatibility, lifecycle transitions, auth boundaries, and backpressure/timeout behavior.
|
||||
- Keep category-targeted tests fast; run full `NATS.Server.Tests` regression before closure.
|
||||
|
||||
### Definition of Done
|
||||
- All category rows are either `PORTED` or explicitly justified `NOT_APPLICABLE`/`DEFERRED`.
|
||||
- No unresolved `MISSING` or unqualified `PARTIAL` rows remain for this category.
|
||||
- Relevant .NET tests pass locally and parity evidence is written into inventory notes.
|
||||
|
||||
## Implementation Scope Inputs
|
||||
### Go Source Files
|
||||
- `golang/nats-server/server/server.go`
|
||||
- `golang/nats-server/server/client.go`
|
||||
- `golang/nats-server/server/opts.go`
|
||||
- `golang/nats-server/server/reload.go`
|
||||
- `golang/nats-server/server/service.go`
|
||||
- `golang/nats-server/server/signal.go`
|
||||
- `golang/nats-server/main.go`
|
||||
|
||||
### Go Test Files
|
||||
- `golang/nats-server/server/server_test.go`
|
||||
- `golang/nats-server/server/client_test.go`
|
||||
- `golang/nats-server/server/opts_test.go`
|
||||
- `golang/nats-server/server/reload_test.go`
|
||||
- `golang/nats-server/server/signal_test.go`
|
||||
- `golang/nats-server/server/test_test.go (test helpers)`
|
||||
|
||||
### .NET Source Files
|
||||
- `src/NATS.Server/NatsServer.cs`
|
||||
- `src/NATS.Server/NatsClient.cs`
|
||||
- `src/NATS.Server/NatsOptions.cs`
|
||||
- `src/NATS.Server/ClientFlags.cs`
|
||||
- `src/NATS.Server/ClientKind.cs`
|
||||
- `src/NATS.Server/ClientClosedReason.cs`
|
||||
- `src/NATS.Server/ClientTraceInfo.cs`
|
||||
- `src/NATS.Server/ClosedState.cs`
|
||||
- `src/NATS.Server/INatsClient.cs`
|
||||
- `src/NATS.Server/InternalClient.cs`
|
||||
- `src/NATS.Server/ServerStats.cs`
|
||||
- `src/NATS.Server/SlowConsumerTracker.cs`
|
||||
- `src/NATS.Server/MqttOptions.cs`
|
||||
- `src/NATS.Server.Host/Program.cs`
|
||||
|
||||
### .NET Test Files
|
||||
- `tests/NATS.Server.Tests/ (root-level test files)`
|
||||
@@ -0,0 +1,103 @@
|
||||
# Core Server Gap Port Implementation Plan
|
||||
|
||||
> **For Codex:** REQUIRED SUB-SKILL: Use `executeplan` to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Port all remaining `core-server` gap inventory items to achieve full behavior parity with Go.
|
||||
|
||||
**Architecture:** Use the category gap inventory as the execution backlog. Drive work row-by-row from `MISSING` and `PARTIAL` toward `PORTED`, with test-first validation and explicit parity evidence in notes.
|
||||
|
||||
**Tech Stack:** .NET 10, C#, xUnit, existing `NATS.Server` runtime modules, Go NATS server reference source.
|
||||
|
||||
---
|
||||
|
||||
## Category Inputs
|
||||
- Gap Inventory: `gaps/core-server.md`
|
||||
- Primary .NET Source Anchor: `src/NATS.Server/NatsServer.cs`
|
||||
- Primary .NET Test Anchor: `tests/NATS.Server.Tests/ (root-level test files)`
|
||||
- Full .NET test project: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
## Task 1: Freeze Row-Level Backlog
|
||||
|
||||
**Files:**
|
||||
- Modify: `gaps/core-server.md`
|
||||
|
||||
**Step 1: Normalize open rows into an execution queue**
|
||||
- Split all `MISSING` and `PARTIAL` rows into ranked batches (highest runtime impact first).
|
||||
|
||||
**Step 2: Record acceptance criteria per row**
|
||||
- For each row, define exact behavioral parity checks (inputs, outputs, error paths, timing/concurrency expectations).
|
||||
|
||||
**Step 3: Save queue annotations in notes**
|
||||
- Add short execution-order markers in notes without changing status yet.
|
||||
|
||||
## Task 2: Write Failing Tests for Batch 1
|
||||
|
||||
**Files:**
|
||||
- Modify: `tests/NATS.Server.Tests/ (root-level test files)`
|
||||
- Test: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
**Step 1: Add failing tests for first open row batch**
|
||||
- Create tests that reproduce the exact missing/partial Go behavior.
|
||||
|
||||
**Step 2: Run tests to confirm failure**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: FAIL in newly added tests only.
|
||||
|
||||
## Task 3: Implement Minimal Parity for Batch 1
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/NATS.Server/NatsServer.cs`
|
||||
- Modify: additional category files listed in `gaps/core-server.md`
|
||||
|
||||
**Step 1: Implement minimal code to satisfy failing tests**
|
||||
- Mirror Go semantics for parsing, state transitions, limits, and side-effects required by the targeted rows.
|
||||
|
||||
**Step 2: Re-run tests for validation**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: PASS for batch-1 tests; no new regressions.
|
||||
|
||||
## Task 4: Close Remaining Batches Iteratively
|
||||
|
||||
**Files:**
|
||||
- Modify: category .NET source and test files referenced in `gaps/core-server.md`
|
||||
- Modify: `gaps/core-server.md`
|
||||
|
||||
**Step 1: Repeat test-first cycle for each remaining batch**
|
||||
- Add failing tests, implement minimal parity, then verify pass.
|
||||
|
||||
**Step 2: Update inventory status immediately after each closure**
|
||||
- Move rows to `PORTED` with exact .NET file:line references.
|
||||
- Keep `PARTIAL` only if explicit residual behavior remains.
|
||||
|
||||
## Task 5: Cross-Module Regression and Hardening
|
||||
|
||||
**Files:**
|
||||
- Test: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
**Step 1: Run full regression suite**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: PASS.
|
||||
|
||||
**Step 2: Add edge-case tests discovered during regression**
|
||||
- Capture any newly discovered divergence from Go and close it before completion.
|
||||
|
||||
## Task 6: Finalize Parity Bookkeeping
|
||||
|
||||
**Files:**
|
||||
- Modify: `gaps/core-server.md`
|
||||
- Modify: `gaps/stillmissing.md`
|
||||
|
||||
**Step 1: Ensure final status correctness**
|
||||
- Verify no unresolved `MISSING` rows remain for `core-server`.
|
||||
|
||||
**Step 2: Refresh LOC and percentage bookkeeping**
|
||||
- Recount category LOC and update summary rows in `gaps/stillmissing.md`.
|
||||
|
||||
**Step 3: Add concise changelog entry**
|
||||
- Record date, completed parity areas, and any justified deferred items.
|
||||
|
||||
## Completion Checklist
|
||||
- [ ] All prioritized `core-server` rows are `PORTED` or explicitly justified as `NOT_APPLICABLE`/`DEFERRED`.
|
||||
- [ ] New/updated tests demonstrate parity for all previously open rows.
|
||||
- [ ] `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release` passes.
|
||||
- [ ] `gaps/core-server.md` and `gaps/stillmissing.md` are fully synchronized.
|
||||
@@ -0,0 +1,76 @@
|
||||
# Events Full Parity Design
|
||||
|
||||
**Date:** 2026-02-25
|
||||
**Category:** `events`
|
||||
**Gap Inventory:** `gaps/events.md`
|
||||
**Target Outcome:** Full Go-to-.NET behavioral parity for this category.
|
||||
|
||||
## Assumptions
|
||||
- Full parity is the required outcome.
|
||||
- No additional requirements gathering is needed for this pass.
|
||||
- Existing public .NET APIs are preserved unless parity requires targeted, documented adjustments.
|
||||
|
||||
## Current State Snapshot
|
||||
- `MISSING`: 154
|
||||
- `PARTIAL`: 20
|
||||
- `PORTED`: 42
|
||||
- `NOT_APPLICABLE`: 23
|
||||
- `DEFERRED`: 0
|
||||
- **Open parity work items:** 174
|
||||
|
||||
## Approaches Considered
|
||||
1. **Symbol-first closure (Recommended):** execute directly against each `MISSING` and `PARTIAL` row in `gaps/events.md`, closing items in deterministic order.
|
||||
Trade-off: strongest traceability and auditability, but can require broad context switching.
|
||||
2. **Test-first closure:** derive all work from Go test scenarios first, then backfill missing symbols.
|
||||
Trade-off: maximizes behavioral confidence, but can miss non-test-covered API and helper parity.
|
||||
3. **Vertical runtime slices:** port connect/auth/pubsub/cluster flows end-to-end before moving to next slice.
|
||||
Trade-off: smoother integration validation, but weaker one-to-one mapping against inventory rows.
|
||||
|
||||
## Recommended Design
|
||||
### Architecture
|
||||
Use the gap inventory as the authoritative backlog. For each row, implement or complete the .NET equivalent, then attach test evidence and update the inventory status. Keep implementation inside existing category boundaries and only refactor when needed to match Go behavior.
|
||||
|
||||
### Components
|
||||
- **Backlog driver:** `gaps/events.md` (row-level tracking for all parity items).
|
||||
- **Implementation surface:** category-specific .NET source files.
|
||||
- **Verification surface:** category-specific .NET tests plus cross-module regression tests.
|
||||
- **Parity bookkeeping:** updates to `gaps/events.md` and `gaps/stillmissing.md` after each closure batch.
|
||||
|
||||
### Data Flow
|
||||
1. Read one `MISSING` or `PARTIAL` row from `gaps/events.md`.
|
||||
2. Inspect referenced Go implementation and tests.
|
||||
3. Add/adjust .NET implementation in listed category files.
|
||||
4. Add failing .NET tests that reproduce the missing/partial behavior, then make them pass.
|
||||
5. Re-run targeted and regression tests.
|
||||
6. Update the row to `PORTED` (or keep `PARTIAL` with explicit residual notes).
|
||||
|
||||
### Error Handling and Risk Controls
|
||||
- Avoid broad rewrites; prefer narrow patches tied to specific inventory rows.
|
||||
- Preserve protocol and timing semantics when replacing Go concurrency constructs with .NET async patterns.
|
||||
- Treat edge cases in Go tests as mandatory parity requirements unless explicitly marked `NOT_APPLICABLE`.
|
||||
- Require evidence (tests + inventory status updates) before declaring row completion.
|
||||
|
||||
### Testing Strategy
|
||||
- Add or expand tests for each row moved from `MISSING`/`PARTIAL` toward `PORTED`.
|
||||
- Prioritize wire/protocol compatibility, lifecycle transitions, auth boundaries, and backpressure/timeout behavior.
|
||||
- Keep category-targeted tests fast; run full `NATS.Server.Tests` regression before closure.
|
||||
|
||||
### Definition of Done
|
||||
- All category rows are either `PORTED` or explicitly justified `NOT_APPLICABLE`/`DEFERRED`.
|
||||
- No unresolved `MISSING` or unqualified `PARTIAL` rows remain for this category.
|
||||
- Relevant .NET tests pass locally and parity evidence is written into inventory notes.
|
||||
|
||||
## Implementation Scope Inputs
|
||||
### Go Source Files
|
||||
- `golang/nats-server/server/events.go`
|
||||
- `golang/nats-server/server/msgtrace.go`
|
||||
|
||||
### Go Test Files
|
||||
- `golang/nats-server/server/events_test.go`
|
||||
- `golang/nats-server/server/msgtrace_test.go`
|
||||
|
||||
### .NET Source Files
|
||||
- `src/NATS.Server/Events/ (all files)`
|
||||
|
||||
### .NET Test Files
|
||||
- `tests/NATS.Server.Tests/Events/`
|
||||
@@ -0,0 +1,103 @@
|
||||
# Events Gap Port Implementation Plan
|
||||
|
||||
> **For Codex:** REQUIRED SUB-SKILL: Use `executeplan` to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Port all remaining `events` gap inventory items to achieve full behavior parity with Go.
|
||||
|
||||
**Architecture:** Use the category gap inventory as the execution backlog. Drive work row-by-row from `MISSING` and `PARTIAL` toward `PORTED`, with test-first validation and explicit parity evidence in notes.
|
||||
|
||||
**Tech Stack:** .NET 10, C#, xUnit, existing `NATS.Server` runtime modules, Go NATS server reference source.
|
||||
|
||||
---
|
||||
|
||||
## Category Inputs
|
||||
- Gap Inventory: `gaps/events.md`
|
||||
- Primary .NET Source Anchor: `src/NATS.Server/Events/ (all files)`
|
||||
- Primary .NET Test Anchor: `tests/NATS.Server.Tests/Events/`
|
||||
- Full .NET test project: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
## Task 1: Freeze Row-Level Backlog
|
||||
|
||||
**Files:**
|
||||
- Modify: `gaps/events.md`
|
||||
|
||||
**Step 1: Normalize open rows into an execution queue**
|
||||
- Split all `MISSING` and `PARTIAL` rows into ranked batches (highest runtime impact first).
|
||||
|
||||
**Step 2: Record acceptance criteria per row**
|
||||
- For each row, define exact behavioral parity checks (inputs, outputs, error paths, timing/concurrency expectations).
|
||||
|
||||
**Step 3: Save queue annotations in notes**
|
||||
- Add short execution-order markers in notes without changing status yet.
|
||||
|
||||
## Task 2: Write Failing Tests for Batch 1
|
||||
|
||||
**Files:**
|
||||
- Modify: `tests/NATS.Server.Tests/Events/`
|
||||
- Test: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
**Step 1: Add failing tests for first open row batch**
|
||||
- Create tests that reproduce the exact missing/partial Go behavior.
|
||||
|
||||
**Step 2: Run tests to confirm failure**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: FAIL in newly added tests only.
|
||||
|
||||
## Task 3: Implement Minimal Parity for Batch 1
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/NATS.Server/Events/ (all files)`
|
||||
- Modify: additional category files listed in `gaps/events.md`
|
||||
|
||||
**Step 1: Implement minimal code to satisfy failing tests**
|
||||
- Mirror Go semantics for parsing, state transitions, limits, and side-effects required by the targeted rows.
|
||||
|
||||
**Step 2: Re-run tests for validation**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: PASS for batch-1 tests; no new regressions.
|
||||
|
||||
## Task 4: Close Remaining Batches Iteratively
|
||||
|
||||
**Files:**
|
||||
- Modify: category .NET source and test files referenced in `gaps/events.md`
|
||||
- Modify: `gaps/events.md`
|
||||
|
||||
**Step 1: Repeat test-first cycle for each remaining batch**
|
||||
- Add failing tests, implement minimal parity, then verify pass.
|
||||
|
||||
**Step 2: Update inventory status immediately after each closure**
|
||||
- Move rows to `PORTED` with exact .NET file:line references.
|
||||
- Keep `PARTIAL` only if explicit residual behavior remains.
|
||||
|
||||
## Task 5: Cross-Module Regression and Hardening
|
||||
|
||||
**Files:**
|
||||
- Test: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
**Step 1: Run full regression suite**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: PASS.
|
||||
|
||||
**Step 2: Add edge-case tests discovered during regression**
|
||||
- Capture any newly discovered divergence from Go and close it before completion.
|
||||
|
||||
## Task 6: Finalize Parity Bookkeeping
|
||||
|
||||
**Files:**
|
||||
- Modify: `gaps/events.md`
|
||||
- Modify: `gaps/stillmissing.md`
|
||||
|
||||
**Step 1: Ensure final status correctness**
|
||||
- Verify no unresolved `MISSING` rows remain for `events`.
|
||||
|
||||
**Step 2: Refresh LOC and percentage bookkeeping**
|
||||
- Recount category LOC and update summary rows in `gaps/stillmissing.md`.
|
||||
|
||||
**Step 3: Add concise changelog entry**
|
||||
- Record date, completed parity areas, and any justified deferred items.
|
||||
|
||||
## Completion Checklist
|
||||
- [ ] All prioritized `events` rows are `PORTED` or explicitly justified as `NOT_APPLICABLE`/`DEFERRED`.
|
||||
- [ ] New/updated tests demonstrate parity for all previously open rows.
|
||||
- [ ] `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release` passes.
|
||||
- [ ] `gaps/events.md` and `gaps/stillmissing.md` are fully synchronized.
|
||||
@@ -0,0 +1,75 @@
|
||||
# Gateways Full Parity Design
|
||||
|
||||
**Date:** 2026-02-25
|
||||
**Category:** `gateways`
|
||||
**Gap Inventory:** `gaps/gateways.md`
|
||||
**Target Outcome:** Full Go-to-.NET behavioral parity for this category.
|
||||
|
||||
## Assumptions
|
||||
- Full parity is the required outcome.
|
||||
- No additional requirements gathering is needed for this pass.
|
||||
- Existing public .NET APIs are preserved unless parity requires targeted, documented adjustments.
|
||||
|
||||
## Current State Snapshot
|
||||
- `MISSING`: 57
|
||||
- `PARTIAL`: 37
|
||||
- `PORTED`: 10
|
||||
- `NOT_APPLICABLE`: 4
|
||||
- `DEFERRED`: 0
|
||||
- **Open parity work items:** 94
|
||||
|
||||
## Approaches Considered
|
||||
1. **Symbol-first closure (Recommended):** execute directly against each `MISSING` and `PARTIAL` row in `gaps/gateways.md`, closing items in deterministic order.
|
||||
Trade-off: strongest traceability and auditability, but can require broad context switching.
|
||||
2. **Test-first closure:** derive all work from Go test scenarios first, then backfill missing symbols.
|
||||
Trade-off: maximizes behavioral confidence, but can miss non-test-covered API and helper parity.
|
||||
3. **Vertical runtime slices:** port connect/auth/pubsub/cluster flows end-to-end before moving to next slice.
|
||||
Trade-off: smoother integration validation, but weaker one-to-one mapping against inventory rows.
|
||||
|
||||
## Recommended Design
|
||||
### Architecture
|
||||
Use the gap inventory as the authoritative backlog. For each row, implement or complete the .NET equivalent, then attach test evidence and update the inventory status. Keep implementation inside existing category boundaries and only refactor when needed to match Go behavior.
|
||||
|
||||
### Components
|
||||
- **Backlog driver:** `gaps/gateways.md` (row-level tracking for all parity items).
|
||||
- **Implementation surface:** category-specific .NET source files.
|
||||
- **Verification surface:** category-specific .NET tests plus cross-module regression tests.
|
||||
- **Parity bookkeeping:** updates to `gaps/gateways.md` and `gaps/stillmissing.md` after each closure batch.
|
||||
|
||||
### Data Flow
|
||||
1. Read one `MISSING` or `PARTIAL` row from `gaps/gateways.md`.
|
||||
2. Inspect referenced Go implementation and tests.
|
||||
3. Add/adjust .NET implementation in listed category files.
|
||||
4. Add failing .NET tests that reproduce the missing/partial behavior, then make them pass.
|
||||
5. Re-run targeted and regression tests.
|
||||
6. Update the row to `PORTED` (or keep `PARTIAL` with explicit residual notes).
|
||||
|
||||
### Error Handling and Risk Controls
|
||||
- Avoid broad rewrites; prefer narrow patches tied to specific inventory rows.
|
||||
- Preserve protocol and timing semantics when replacing Go concurrency constructs with .NET async patterns.
|
||||
- Treat edge cases in Go tests as mandatory parity requirements unless explicitly marked `NOT_APPLICABLE`.
|
||||
- Require evidence (tests + inventory status updates) before declaring row completion.
|
||||
|
||||
### Testing Strategy
|
||||
- Add or expand tests for each row moved from `MISSING`/`PARTIAL` toward `PORTED`.
|
||||
- Prioritize wire/protocol compatibility, lifecycle transitions, auth boundaries, and backpressure/timeout behavior.
|
||||
- Keep category-targeted tests fast; run full `NATS.Server.Tests` regression before closure.
|
||||
|
||||
### Definition of Done
|
||||
- All category rows are either `PORTED` or explicitly justified `NOT_APPLICABLE`/`DEFERRED`.
|
||||
- No unresolved `MISSING` or unqualified `PARTIAL` rows remain for this category.
|
||||
- Relevant .NET tests pass locally and parity evidence is written into inventory notes.
|
||||
|
||||
## Implementation Scope Inputs
|
||||
### Go Source Files
|
||||
- `golang/nats-server/server/gateway.go`
|
||||
|
||||
### Go Test Files
|
||||
- `golang/nats-server/server/gateway_test.go`
|
||||
- `golang/nats-server/test/gateway_test.go (integration)`
|
||||
|
||||
### .NET Source Files
|
||||
- `src/NATS.Server/Gateways/ (all files)`
|
||||
|
||||
### .NET Test Files
|
||||
- `tests/NATS.Server.Tests/Gateways/`
|
||||
@@ -0,0 +1,103 @@
|
||||
# Gateways Gap Port Implementation Plan
|
||||
|
||||
> **For Codex:** REQUIRED SUB-SKILL: Use `executeplan` to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Port all remaining `gateways` gap inventory items to achieve full behavior parity with Go.
|
||||
|
||||
**Architecture:** Use the category gap inventory as the execution backlog. Drive work row-by-row from `MISSING` and `PARTIAL` toward `PORTED`, with test-first validation and explicit parity evidence in notes.
|
||||
|
||||
**Tech Stack:** .NET 10, C#, xUnit, existing `NATS.Server` runtime modules, Go NATS server reference source.
|
||||
|
||||
---
|
||||
|
||||
## Category Inputs
|
||||
- Gap Inventory: `gaps/gateways.md`
|
||||
- Primary .NET Source Anchor: `src/NATS.Server/Gateways/ (all files)`
|
||||
- Primary .NET Test Anchor: `tests/NATS.Server.Tests/Gateways/`
|
||||
- Full .NET test project: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
## Task 1: Freeze Row-Level Backlog
|
||||
|
||||
**Files:**
|
||||
- Modify: `gaps/gateways.md`
|
||||
|
||||
**Step 1: Normalize open rows into an execution queue**
|
||||
- Split all `MISSING` and `PARTIAL` rows into ranked batches (highest runtime impact first).
|
||||
|
||||
**Step 2: Record acceptance criteria per row**
|
||||
- For each row, define exact behavioral parity checks (inputs, outputs, error paths, timing/concurrency expectations).
|
||||
|
||||
**Step 3: Save queue annotations in notes**
|
||||
- Add short execution-order markers in notes without changing status yet.
|
||||
|
||||
## Task 2: Write Failing Tests for Batch 1
|
||||
|
||||
**Files:**
|
||||
- Modify: `tests/NATS.Server.Tests/Gateways/`
|
||||
- Test: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
**Step 1: Add failing tests for first open row batch**
|
||||
- Create tests that reproduce the exact missing/partial Go behavior.
|
||||
|
||||
**Step 2: Run tests to confirm failure**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: FAIL in newly added tests only.
|
||||
|
||||
## Task 3: Implement Minimal Parity for Batch 1
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/NATS.Server/Gateways/ (all files)`
|
||||
- Modify: additional category files listed in `gaps/gateways.md`
|
||||
|
||||
**Step 1: Implement minimal code to satisfy failing tests**
|
||||
- Mirror Go semantics for parsing, state transitions, limits, and side-effects required by the targeted rows.
|
||||
|
||||
**Step 2: Re-run tests for validation**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: PASS for batch-1 tests; no new regressions.
|
||||
|
||||
## Task 4: Close Remaining Batches Iteratively
|
||||
|
||||
**Files:**
|
||||
- Modify: category .NET source and test files referenced in `gaps/gateways.md`
|
||||
- Modify: `gaps/gateways.md`
|
||||
|
||||
**Step 1: Repeat test-first cycle for each remaining batch**
|
||||
- Add failing tests, implement minimal parity, then verify pass.
|
||||
|
||||
**Step 2: Update inventory status immediately after each closure**
|
||||
- Move rows to `PORTED` with exact .NET file:line references.
|
||||
- Keep `PARTIAL` only if explicit residual behavior remains.
|
||||
|
||||
## Task 5: Cross-Module Regression and Hardening
|
||||
|
||||
**Files:**
|
||||
- Test: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
**Step 1: Run full regression suite**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: PASS.
|
||||
|
||||
**Step 2: Add edge-case tests discovered during regression**
|
||||
- Capture any newly discovered divergence from Go and close it before completion.
|
||||
|
||||
## Task 6: Finalize Parity Bookkeeping
|
||||
|
||||
**Files:**
|
||||
- Modify: `gaps/gateways.md`
|
||||
- Modify: `gaps/stillmissing.md`
|
||||
|
||||
**Step 1: Ensure final status correctness**
|
||||
- Verify no unresolved `MISSING` rows remain for `gateways`.
|
||||
|
||||
**Step 2: Refresh LOC and percentage bookkeeping**
|
||||
- Recount category LOC and update summary rows in `gaps/stillmissing.md`.
|
||||
|
||||
**Step 3: Add concise changelog entry**
|
||||
- Record date, completed parity areas, and any justified deferred items.
|
||||
|
||||
## Completion Checklist
|
||||
- [ ] All prioritized `gateways` rows are `PORTED` or explicitly justified as `NOT_APPLICABLE`/`DEFERRED`.
|
||||
- [ ] New/updated tests demonstrate parity for all previously open rows.
|
||||
- [ ] `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release` passes.
|
||||
- [ ] `gaps/gateways.md` and `gaps/stillmissing.md` are fully synchronized.
|
||||
@@ -0,0 +1,86 @@
|
||||
# Internal Data Structures Full Parity Design
|
||||
|
||||
**Date:** 2026-02-25
|
||||
**Category:** `internal-ds`
|
||||
**Gap Inventory:** `gaps/internal-ds.md`
|
||||
**Target Outcome:** Full Go-to-.NET behavioral parity for this category.
|
||||
|
||||
## Assumptions
|
||||
- Full parity is the required outcome.
|
||||
- No additional requirements gathering is needed for this pass.
|
||||
- Existing public .NET APIs are preserved unless parity requires targeted, documented adjustments.
|
||||
|
||||
## Current State Snapshot
|
||||
- `MISSING`: 10
|
||||
- `PARTIAL`: 4
|
||||
- `PORTED`: 157
|
||||
- `NOT_APPLICABLE`: 8
|
||||
- `DEFERRED`: 0
|
||||
- **Open parity work items:** 14
|
||||
|
||||
## Approaches Considered
|
||||
1. **Symbol-first closure (Recommended):** execute directly against each `MISSING` and `PARTIAL` row in `gaps/internal-ds.md`, closing items in deterministic order.
|
||||
Trade-off: strongest traceability and auditability, but can require broad context switching.
|
||||
2. **Test-first closure:** derive all work from Go test scenarios first, then backfill missing symbols.
|
||||
Trade-off: maximizes behavioral confidence, but can miss non-test-covered API and helper parity.
|
||||
3. **Vertical runtime slices:** port connect/auth/pubsub/cluster flows end-to-end before moving to next slice.
|
||||
Trade-off: smoother integration validation, but weaker one-to-one mapping against inventory rows.
|
||||
|
||||
## Recommended Design
|
||||
### Architecture
|
||||
Use the gap inventory as the authoritative backlog. For each row, implement or complete the .NET equivalent, then attach test evidence and update the inventory status. Keep implementation inside existing category boundaries and only refactor when needed to match Go behavior.
|
||||
|
||||
### Components
|
||||
- **Backlog driver:** `gaps/internal-ds.md` (row-level tracking for all parity items).
|
||||
- **Implementation surface:** category-specific .NET source files.
|
||||
- **Verification surface:** category-specific .NET tests plus cross-module regression tests.
|
||||
- **Parity bookkeeping:** updates to `gaps/internal-ds.md` and `gaps/stillmissing.md` after each closure batch.
|
||||
|
||||
### Data Flow
|
||||
1. Read one `MISSING` or `PARTIAL` row from `gaps/internal-ds.md`.
|
||||
2. Inspect referenced Go implementation and tests.
|
||||
3. Add/adjust .NET implementation in listed category files.
|
||||
4. Add failing .NET tests that reproduce the missing/partial behavior, then make them pass.
|
||||
5. Re-run targeted and regression tests.
|
||||
6. Update the row to `PORTED` (or keep `PARTIAL` with explicit residual notes).
|
||||
|
||||
### Error Handling and Risk Controls
|
||||
- Avoid broad rewrites; prefer narrow patches tied to specific inventory rows.
|
||||
- Preserve protocol and timing semantics when replacing Go concurrency constructs with .NET async patterns.
|
||||
- Treat edge cases in Go tests as mandatory parity requirements unless explicitly marked `NOT_APPLICABLE`.
|
||||
- Require evidence (tests + inventory status updates) before declaring row completion.
|
||||
|
||||
### Testing Strategy
|
||||
- Add or expand tests for each row moved from `MISSING`/`PARTIAL` toward `PORTED`.
|
||||
- Prioritize wire/protocol compatibility, lifecycle transitions, auth boundaries, and backpressure/timeout behavior.
|
||||
- Keep category-targeted tests fast; run full `NATS.Server.Tests` regression before closure.
|
||||
|
||||
### Definition of Done
|
||||
- All category rows are either `PORTED` or explicitly justified `NOT_APPLICABLE`/`DEFERRED`.
|
||||
- No unresolved `MISSING` or unqualified `PARTIAL` rows remain for this category.
|
||||
- Relevant .NET tests pass locally and parity evidence is written into inventory notes.
|
||||
|
||||
## Implementation Scope Inputs
|
||||
### Go Source Files
|
||||
- `golang/nats-server/server/avl/seqset.go`
|
||||
- `golang/nats-server/server/stree/`
|
||||
- `golang/nats-server/server/thw/thw.go`
|
||||
- `golang/nats-server/server/gsl/gsl.go`
|
||||
- `golang/nats-server/server/pse/`
|
||||
- `golang/nats-server/server/sysmem/`
|
||||
|
||||
### Go Test Files
|
||||
- `golang/nats-server/server/avl/seqset_test.go`
|
||||
- `golang/nats-server/server/stree/stree_test.go`
|
||||
- `golang/nats-server/server/thw/thw_test.go`
|
||||
- `golang/nats-server/server/gsl/gsl_test.go`
|
||||
|
||||
### .NET Source Files
|
||||
- `src/NATS.Server/Internal/ (all files including subdirectories)`
|
||||
- `src/NATS.Server/Internal/Avl/`
|
||||
- `src/NATS.Server/Internal/Gsl/`
|
||||
- `src/NATS.Server/Internal/SubjectTree/`
|
||||
- `src/NATS.Server/Internal/TimeHashWheel/`
|
||||
|
||||
### .NET Test Files
|
||||
- `tests/NATS.Server.Tests/Internal/ (all subdirectories)`
|
||||
@@ -0,0 +1,103 @@
|
||||
# Internal Data Structures Gap Port Implementation Plan
|
||||
|
||||
> **For Codex:** REQUIRED SUB-SKILL: Use `executeplan` to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Port all remaining `internal-ds` gap inventory items to achieve full behavior parity with Go.
|
||||
|
||||
**Architecture:** Use the category gap inventory as the execution backlog. Drive work row-by-row from `MISSING` and `PARTIAL` toward `PORTED`, with test-first validation and explicit parity evidence in notes.
|
||||
|
||||
**Tech Stack:** .NET 10, C#, xUnit, existing `NATS.Server` runtime modules, Go NATS server reference source.
|
||||
|
||||
---
|
||||
|
||||
## Category Inputs
|
||||
- Gap Inventory: `gaps/internal-ds.md`
|
||||
- Primary .NET Source Anchor: `src/NATS.Server/Internal/ (all files including subdirectories)`
|
||||
- Primary .NET Test Anchor: `tests/NATS.Server.Tests/Internal/ (all subdirectories)`
|
||||
- Full .NET test project: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
## Task 1: Freeze Row-Level Backlog
|
||||
|
||||
**Files:**
|
||||
- Modify: `gaps/internal-ds.md`
|
||||
|
||||
**Step 1: Normalize open rows into an execution queue**
|
||||
- Split all `MISSING` and `PARTIAL` rows into ranked batches (highest runtime impact first).
|
||||
|
||||
**Step 2: Record acceptance criteria per row**
|
||||
- For each row, define exact behavioral parity checks (inputs, outputs, error paths, timing/concurrency expectations).
|
||||
|
||||
**Step 3: Save queue annotations in notes**
|
||||
- Add short execution-order markers in notes without changing status yet.
|
||||
|
||||
## Task 2: Write Failing Tests for Batch 1
|
||||
|
||||
**Files:**
|
||||
- Modify: `tests/NATS.Server.Tests/Internal/ (all subdirectories)`
|
||||
- Test: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
**Step 1: Add failing tests for first open row batch**
|
||||
- Create tests that reproduce the exact missing/partial Go behavior.
|
||||
|
||||
**Step 2: Run tests to confirm failure**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: FAIL in newly added tests only.
|
||||
|
||||
## Task 3: Implement Minimal Parity for Batch 1
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/NATS.Server/Internal/ (all files including subdirectories)`
|
||||
- Modify: additional category files listed in `gaps/internal-ds.md`
|
||||
|
||||
**Step 1: Implement minimal code to satisfy failing tests**
|
||||
- Mirror Go semantics for parsing, state transitions, limits, and side-effects required by the targeted rows.
|
||||
|
||||
**Step 2: Re-run tests for validation**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: PASS for batch-1 tests; no new regressions.
|
||||
|
||||
## Task 4: Close Remaining Batches Iteratively
|
||||
|
||||
**Files:**
|
||||
- Modify: category .NET source and test files referenced in `gaps/internal-ds.md`
|
||||
- Modify: `gaps/internal-ds.md`
|
||||
|
||||
**Step 1: Repeat test-first cycle for each remaining batch**
|
||||
- Add failing tests, implement minimal parity, then verify pass.
|
||||
|
||||
**Step 2: Update inventory status immediately after each closure**
|
||||
- Move rows to `PORTED` with exact .NET file:line references.
|
||||
- Keep `PARTIAL` only if explicit residual behavior remains.
|
||||
|
||||
## Task 5: Cross-Module Regression and Hardening
|
||||
|
||||
**Files:**
|
||||
- Test: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
**Step 1: Run full regression suite**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: PASS.
|
||||
|
||||
**Step 2: Add edge-case tests discovered during regression**
|
||||
- Capture any newly discovered divergence from Go and close it before completion.
|
||||
|
||||
## Task 6: Finalize Parity Bookkeeping
|
||||
|
||||
**Files:**
|
||||
- Modify: `gaps/internal-ds.md`
|
||||
- Modify: `gaps/stillmissing.md`
|
||||
|
||||
**Step 1: Ensure final status correctness**
|
||||
- Verify no unresolved `MISSING` rows remain for `internal-ds`.
|
||||
|
||||
**Step 2: Refresh LOC and percentage bookkeeping**
|
||||
- Recount category LOC and update summary rows in `gaps/stillmissing.md`.
|
||||
|
||||
**Step 3: Add concise changelog entry**
|
||||
- Record date, completed parity areas, and any justified deferred items.
|
||||
|
||||
## Completion Checklist
|
||||
- [ ] All prioritized `internal-ds` rows are `PORTED` or explicitly justified as `NOT_APPLICABLE`/`DEFERRED`.
|
||||
- [ ] New/updated tests demonstrate parity for all previously open rows.
|
||||
- [ ] `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release` passes.
|
||||
- [ ] `gaps/internal-ds.md` and `gaps/stillmissing.md` are fully synchronized.
|
||||
@@ -0,0 +1,121 @@
|
||||
# JetStream Full Parity Design
|
||||
|
||||
**Date:** 2026-02-25
|
||||
**Category:** `jetstream`
|
||||
**Gap Inventory:** `gaps/jetstream.md`
|
||||
**Target Outcome:** Full Go-to-.NET behavioral parity for this category.
|
||||
|
||||
## Assumptions
|
||||
- Full parity is the required outcome.
|
||||
- No additional requirements gathering is needed for this pass.
|
||||
- Existing public .NET APIs are preserved unless parity requires targeted, documented adjustments.
|
||||
|
||||
## Current State Snapshot
|
||||
- `MISSING`: 787
|
||||
- `PARTIAL`: 313
|
||||
- `PORTED`: 309
|
||||
- `NOT_APPLICABLE`: 55
|
||||
- `DEFERRED`: 0
|
||||
- **Open parity work items:** 1100
|
||||
|
||||
## Approaches Considered
|
||||
1. **Symbol-first closure (Recommended):** execute directly against each `MISSING` and `PARTIAL` row in `gaps/jetstream.md`, closing items in deterministic order.
|
||||
Trade-off: strongest traceability and auditability, but can require broad context switching.
|
||||
2. **Test-first closure:** derive all work from Go test scenarios first, then backfill missing symbols.
|
||||
Trade-off: maximizes behavioral confidence, but can miss non-test-covered API and helper parity.
|
||||
3. **Vertical runtime slices:** port connect/auth/pubsub/cluster flows end-to-end before moving to next slice.
|
||||
Trade-off: smoother integration validation, but weaker one-to-one mapping against inventory rows.
|
||||
|
||||
## Recommended Design
|
||||
### Architecture
|
||||
Use the gap inventory as the authoritative backlog. For each row, implement or complete the .NET equivalent, then attach test evidence and update the inventory status. Keep implementation inside existing category boundaries and only refactor when needed to match Go behavior.
|
||||
|
||||
### Components
|
||||
- **Backlog driver:** `gaps/jetstream.md` (row-level tracking for all parity items).
|
||||
- **Implementation surface:** category-specific .NET source files.
|
||||
- **Verification surface:** category-specific .NET tests plus cross-module regression tests.
|
||||
- **Parity bookkeeping:** updates to `gaps/jetstream.md` and `gaps/stillmissing.md` after each closure batch.
|
||||
|
||||
### Data Flow
|
||||
1. Read one `MISSING` or `PARTIAL` row from `gaps/jetstream.md`.
|
||||
2. Inspect referenced Go implementation and tests.
|
||||
3. Add/adjust .NET implementation in listed category files.
|
||||
4. Add failing .NET tests that reproduce the missing/partial behavior, then make them pass.
|
||||
5. Re-run targeted and regression tests.
|
||||
6. Update the row to `PORTED` (or keep `PARTIAL` with explicit residual notes).
|
||||
|
||||
### Error Handling and Risk Controls
|
||||
- Avoid broad rewrites; prefer narrow patches tied to specific inventory rows.
|
||||
- Preserve protocol and timing semantics when replacing Go concurrency constructs with .NET async patterns.
|
||||
- Treat edge cases in Go tests as mandatory parity requirements unless explicitly marked `NOT_APPLICABLE`.
|
||||
- Require evidence (tests + inventory status updates) before declaring row completion.
|
||||
|
||||
### Testing Strategy
|
||||
- Add or expand tests for each row moved from `MISSING`/`PARTIAL` toward `PORTED`.
|
||||
- Prioritize wire/protocol compatibility, lifecycle transitions, auth boundaries, and backpressure/timeout behavior.
|
||||
- Keep category-targeted tests fast; run full `NATS.Server.Tests` regression before closure.
|
||||
|
||||
### Definition of Done
|
||||
- All category rows are either `PORTED` or explicitly justified `NOT_APPLICABLE`/`DEFERRED`.
|
||||
- No unresolved `MISSING` or unqualified `PARTIAL` rows remain for this category.
|
||||
- Relevant .NET tests pass locally and parity evidence is written into inventory notes.
|
||||
|
||||
## Implementation Scope Inputs
|
||||
### Go Source Files
|
||||
- `golang/nats-server/server/jetstream.go`
|
||||
- `golang/nats-server/server/jetstream_api.go`
|
||||
- `golang/nats-server/server/jetstream_events.go`
|
||||
- `golang/nats-server/server/jetstream_errors.go`
|
||||
- `golang/nats-server/server/jetstream_errors_generated.go`
|
||||
- `golang/nats-server/server/jetstream_batching.go`
|
||||
- `golang/nats-server/server/jetstream_versioning.go`
|
||||
- `golang/nats-server/server/stream.go`
|
||||
- `golang/nats-server/server/consumer.go`
|
||||
- `golang/nats-server/server/store.go`
|
||||
- `golang/nats-server/server/filestore.go`
|
||||
- `golang/nats-server/server/memstore.go`
|
||||
- `golang/nats-server/server/dirstore.go`
|
||||
- `golang/nats-server/server/disk_avail.go`
|
||||
- `golang/nats-server/server/jetstream_cluster.go`
|
||||
|
||||
### Go Test Files
|
||||
- `golang/nats-server/server/jetstream_test.go`
|
||||
- `golang/nats-server/server/jetstream_consumer_test.go`
|
||||
- `golang/nats-server/server/jetstream_errors_test.go`
|
||||
- `golang/nats-server/server/jetstream_batching_test.go`
|
||||
- `golang/nats-server/server/jetstream_versioning_test.go`
|
||||
- `golang/nats-server/server/jetstream_helpers_test.go`
|
||||
- `golang/nats-server/server/jetstream_jwt_test.go`
|
||||
- `golang/nats-server/server/jetstream_tpm_test.go`
|
||||
- `golang/nats-server/server/jetstream_sourcing_scaling_test.go`
|
||||
- `golang/nats-server/server/jetstream_benchmark_test.go`
|
||||
- `golang/nats-server/server/filestore_test.go`
|
||||
- `golang/nats-server/server/memstore_test.go`
|
||||
- `golang/nats-server/server/dirstore_test.go`
|
||||
- `golang/nats-server/server/store_test.go`
|
||||
- `golang/nats-server/server/jetstream_cluster_1_test.go through _4_test.go`
|
||||
- `golang/nats-server/server/jetstream_super_cluster_test.go`
|
||||
- `golang/nats-server/server/jetstream_leafnode_test.go`
|
||||
- `golang/nats-server/server/jetstream_cluster_long_test.go`
|
||||
|
||||
### .NET Source Files
|
||||
- `src/NATS.Server/JetStream/`
|
||||
- `src/NATS.Server/JetStream/Api/`
|
||||
- `src/NATS.Server/JetStream/Consumers/`
|
||||
- `src/NATS.Server/JetStream/Models/`
|
||||
- `src/NATS.Server/JetStream/MirrorSource/`
|
||||
- `src/NATS.Server/JetStream/Publish/`
|
||||
- `src/NATS.Server/JetStream/Snapshots/`
|
||||
- `src/NATS.Server/JetStream/Validation/`
|
||||
- `src/NATS.Server/JetStream/Storage/`
|
||||
- `src/NATS.Server/JetStream/Cluster/`
|
||||
|
||||
### .NET Test Files
|
||||
- `tests/NATS.Server.Tests/JetStream/`
|
||||
- `tests/NATS.Server.Tests/JetStream/Api/`
|
||||
- `tests/NATS.Server.Tests/JetStream/Cluster/`
|
||||
- `tests/NATS.Server.Tests/JetStream/Consumers/`
|
||||
- `tests/NATS.Server.Tests/JetStream/MirrorSource/`
|
||||
- `tests/NATS.Server.Tests/JetStream/Snapshots/`
|
||||
- `tests/NATS.Server.Tests/JetStream/Storage/`
|
||||
- `tests/NATS.Server.Tests/JetStream/Streams/`
|
||||
@@ -0,0 +1,103 @@
|
||||
# JetStream Gap Port Implementation Plan
|
||||
|
||||
> **For Codex:** REQUIRED SUB-SKILL: Use `executeplan` to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Port all remaining `jetstream` gap inventory items to achieve full behavior parity with Go.
|
||||
|
||||
**Architecture:** Use the category gap inventory as the execution backlog. Drive work row-by-row from `MISSING` and `PARTIAL` toward `PORTED`, with test-first validation and explicit parity evidence in notes.
|
||||
|
||||
**Tech Stack:** .NET 10, C#, xUnit, existing `NATS.Server` runtime modules, Go NATS server reference source.
|
||||
|
||||
---
|
||||
|
||||
## Category Inputs
|
||||
- Gap Inventory: `gaps/jetstream.md`
|
||||
- Primary .NET Source Anchor: `src/NATS.Server/JetStream/`
|
||||
- Primary .NET Test Anchor: `tests/NATS.Server.Tests/JetStream/`
|
||||
- Full .NET test project: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
## Task 1: Freeze Row-Level Backlog
|
||||
|
||||
**Files:**
|
||||
- Modify: `gaps/jetstream.md`
|
||||
|
||||
**Step 1: Normalize open rows into an execution queue**
|
||||
- Split all `MISSING` and `PARTIAL` rows into ranked batches (highest runtime impact first).
|
||||
|
||||
**Step 2: Record acceptance criteria per row**
|
||||
- For each row, define exact behavioral parity checks (inputs, outputs, error paths, timing/concurrency expectations).
|
||||
|
||||
**Step 3: Save queue annotations in notes**
|
||||
- Add short execution-order markers in notes without changing status yet.
|
||||
|
||||
## Task 2: Write Failing Tests for Batch 1
|
||||
|
||||
**Files:**
|
||||
- Modify: `tests/NATS.Server.Tests/JetStream/`
|
||||
- Test: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
**Step 1: Add failing tests for first open row batch**
|
||||
- Create tests that reproduce the exact missing/partial Go behavior.
|
||||
|
||||
**Step 2: Run tests to confirm failure**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: FAIL in newly added tests only.
|
||||
|
||||
## Task 3: Implement Minimal Parity for Batch 1
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/NATS.Server/JetStream/`
|
||||
- Modify: additional category files listed in `gaps/jetstream.md`
|
||||
|
||||
**Step 1: Implement minimal code to satisfy failing tests**
|
||||
- Mirror Go semantics for parsing, state transitions, limits, and side-effects required by the targeted rows.
|
||||
|
||||
**Step 2: Re-run tests for validation**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: PASS for batch-1 tests; no new regressions.
|
||||
|
||||
## Task 4: Close Remaining Batches Iteratively
|
||||
|
||||
**Files:**
|
||||
- Modify: category .NET source and test files referenced in `gaps/jetstream.md`
|
||||
- Modify: `gaps/jetstream.md`
|
||||
|
||||
**Step 1: Repeat test-first cycle for each remaining batch**
|
||||
- Add failing tests, implement minimal parity, then verify pass.
|
||||
|
||||
**Step 2: Update inventory status immediately after each closure**
|
||||
- Move rows to `PORTED` with exact .NET file:line references.
|
||||
- Keep `PARTIAL` only if explicit residual behavior remains.
|
||||
|
||||
## Task 5: Cross-Module Regression and Hardening
|
||||
|
||||
**Files:**
|
||||
- Test: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
**Step 1: Run full regression suite**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: PASS.
|
||||
|
||||
**Step 2: Add edge-case tests discovered during regression**
|
||||
- Capture any newly discovered divergence from Go and close it before completion.
|
||||
|
||||
## Task 6: Finalize Parity Bookkeeping
|
||||
|
||||
**Files:**
|
||||
- Modify: `gaps/jetstream.md`
|
||||
- Modify: `gaps/stillmissing.md`
|
||||
|
||||
**Step 1: Ensure final status correctness**
|
||||
- Verify no unresolved `MISSING` rows remain for `jetstream`.
|
||||
|
||||
**Step 2: Refresh LOC and percentage bookkeeping**
|
||||
- Recount category LOC and update summary rows in `gaps/stillmissing.md`.
|
||||
|
||||
**Step 3: Add concise changelog entry**
|
||||
- Record date, completed parity areas, and any justified deferred items.
|
||||
|
||||
## Completion Checklist
|
||||
- [ ] All prioritized `jetstream` rows are `PORTED` or explicitly justified as `NOT_APPLICABLE`/`DEFERRED`.
|
||||
- [ ] New/updated tests demonstrate parity for all previously open rows.
|
||||
- [ ] `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release` passes.
|
||||
- [ ] `gaps/jetstream.md` and `gaps/stillmissing.md` are fully synchronized.
|
||||
@@ -0,0 +1,76 @@
|
||||
# Leaf Nodes Full Parity Design
|
||||
|
||||
**Date:** 2026-02-25
|
||||
**Category:** `leaf-nodes`
|
||||
**Gap Inventory:** `gaps/leaf-nodes.md`
|
||||
**Target Outcome:** Full Go-to-.NET behavioral parity for this category.
|
||||
|
||||
## Assumptions
|
||||
- Full parity is the required outcome.
|
||||
- No additional requirements gathering is needed for this pass.
|
||||
- Existing public .NET APIs are preserved unless parity requires targeted, documented adjustments.
|
||||
|
||||
## Current State Snapshot
|
||||
- `MISSING`: 55
|
||||
- `PARTIAL`: 23
|
||||
- `PORTED`: 5
|
||||
- `NOT_APPLICABLE`: 1
|
||||
- `DEFERRED`: 0
|
||||
- **Open parity work items:** 78
|
||||
|
||||
## Approaches Considered
|
||||
1. **Symbol-first closure (Recommended):** execute directly against each `MISSING` and `PARTIAL` row in `gaps/leaf-nodes.md`, closing items in deterministic order.
|
||||
Trade-off: strongest traceability and auditability, but can require broad context switching.
|
||||
2. **Test-first closure:** derive all work from Go test scenarios first, then backfill missing symbols.
|
||||
Trade-off: maximizes behavioral confidence, but can miss non-test-covered API and helper parity.
|
||||
3. **Vertical runtime slices:** port connect/auth/pubsub/cluster flows end-to-end before moving to next slice.
|
||||
Trade-off: smoother integration validation, but weaker one-to-one mapping against inventory rows.
|
||||
|
||||
## Recommended Design
|
||||
### Architecture
|
||||
Use the gap inventory as the authoritative backlog. For each row, implement or complete the .NET equivalent, then attach test evidence and update the inventory status. Keep implementation inside existing category boundaries and only refactor when needed to match Go behavior.
|
||||
|
||||
### Components
|
||||
- **Backlog driver:** `gaps/leaf-nodes.md` (row-level tracking for all parity items).
|
||||
- **Implementation surface:** category-specific .NET source files.
|
||||
- **Verification surface:** category-specific .NET tests plus cross-module regression tests.
|
||||
- **Parity bookkeeping:** updates to `gaps/leaf-nodes.md` and `gaps/stillmissing.md` after each closure batch.
|
||||
|
||||
### Data Flow
|
||||
1. Read one `MISSING` or `PARTIAL` row from `gaps/leaf-nodes.md`.
|
||||
2. Inspect referenced Go implementation and tests.
|
||||
3. Add/adjust .NET implementation in listed category files.
|
||||
4. Add failing .NET tests that reproduce the missing/partial behavior, then make them pass.
|
||||
5. Re-run targeted and regression tests.
|
||||
6. Update the row to `PORTED` (or keep `PARTIAL` with explicit residual notes).
|
||||
|
||||
### Error Handling and Risk Controls
|
||||
- Avoid broad rewrites; prefer narrow patches tied to specific inventory rows.
|
||||
- Preserve protocol and timing semantics when replacing Go concurrency constructs with .NET async patterns.
|
||||
- Treat edge cases in Go tests as mandatory parity requirements unless explicitly marked `NOT_APPLICABLE`.
|
||||
- Require evidence (tests + inventory status updates) before declaring row completion.
|
||||
|
||||
### Testing Strategy
|
||||
- Add or expand tests for each row moved from `MISSING`/`PARTIAL` toward `PORTED`.
|
||||
- Prioritize wire/protocol compatibility, lifecycle transitions, auth boundaries, and backpressure/timeout behavior.
|
||||
- Keep category-targeted tests fast; run full `NATS.Server.Tests` regression before closure.
|
||||
|
||||
### Definition of Done
|
||||
- All category rows are either `PORTED` or explicitly justified `NOT_APPLICABLE`/`DEFERRED`.
|
||||
- No unresolved `MISSING` or unqualified `PARTIAL` rows remain for this category.
|
||||
- Relevant .NET tests pass locally and parity evidence is written into inventory notes.
|
||||
|
||||
## Implementation Scope Inputs
|
||||
### Go Source Files
|
||||
- `golang/nats-server/server/leafnode.go`
|
||||
|
||||
### Go Test Files
|
||||
- `golang/nats-server/server/leafnode_test.go`
|
||||
- `golang/nats-server/server/leafnode_proxy_test.go`
|
||||
- `golang/nats-server/test/leafnode_test.go (integration)`
|
||||
|
||||
### .NET Source Files
|
||||
- `src/NATS.Server/LeafNodes/ (all files)`
|
||||
|
||||
### .NET Test Files
|
||||
- `tests/NATS.Server.Tests/LeafNodes/`
|
||||
@@ -0,0 +1,103 @@
|
||||
# Leaf Nodes Gap Port Implementation Plan
|
||||
|
||||
> **For Codex:** REQUIRED SUB-SKILL: Use `executeplan` to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Port all remaining `leaf-nodes` gap inventory items to achieve full behavior parity with Go.
|
||||
|
||||
**Architecture:** Use the category gap inventory as the execution backlog. Drive work row-by-row from `MISSING` and `PARTIAL` toward `PORTED`, with test-first validation and explicit parity evidence in notes.
|
||||
|
||||
**Tech Stack:** .NET 10, C#, xUnit, existing `NATS.Server` runtime modules, Go NATS server reference source.
|
||||
|
||||
---
|
||||
|
||||
## Category Inputs
|
||||
- Gap Inventory: `gaps/leaf-nodes.md`
|
||||
- Primary .NET Source Anchor: `src/NATS.Server/LeafNodes/ (all files)`
|
||||
- Primary .NET Test Anchor: `tests/NATS.Server.Tests/LeafNodes/`
|
||||
- Full .NET test project: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
## Task 1: Freeze Row-Level Backlog
|
||||
|
||||
**Files:**
|
||||
- Modify: `gaps/leaf-nodes.md`
|
||||
|
||||
**Step 1: Normalize open rows into an execution queue**
|
||||
- Split all `MISSING` and `PARTIAL` rows into ranked batches (highest runtime impact first).
|
||||
|
||||
**Step 2: Record acceptance criteria per row**
|
||||
- For each row, define exact behavioral parity checks (inputs, outputs, error paths, timing/concurrency expectations).
|
||||
|
||||
**Step 3: Save queue annotations in notes**
|
||||
- Add short execution-order markers in notes without changing status yet.
|
||||
|
||||
## Task 2: Write Failing Tests for Batch 1
|
||||
|
||||
**Files:**
|
||||
- Modify: `tests/NATS.Server.Tests/LeafNodes/`
|
||||
- Test: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
**Step 1: Add failing tests for first open row batch**
|
||||
- Create tests that reproduce the exact missing/partial Go behavior.
|
||||
|
||||
**Step 2: Run tests to confirm failure**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: FAIL in newly added tests only.
|
||||
|
||||
## Task 3: Implement Minimal Parity for Batch 1
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/NATS.Server/LeafNodes/ (all files)`
|
||||
- Modify: additional category files listed in `gaps/leaf-nodes.md`
|
||||
|
||||
**Step 1: Implement minimal code to satisfy failing tests**
|
||||
- Mirror Go semantics for parsing, state transitions, limits, and side-effects required by the targeted rows.
|
||||
|
||||
**Step 2: Re-run tests for validation**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: PASS for batch-1 tests; no new regressions.
|
||||
|
||||
## Task 4: Close Remaining Batches Iteratively
|
||||
|
||||
**Files:**
|
||||
- Modify: category .NET source and test files referenced in `gaps/leaf-nodes.md`
|
||||
- Modify: `gaps/leaf-nodes.md`
|
||||
|
||||
**Step 1: Repeat test-first cycle for each remaining batch**
|
||||
- Add failing tests, implement minimal parity, then verify pass.
|
||||
|
||||
**Step 2: Update inventory status immediately after each closure**
|
||||
- Move rows to `PORTED` with exact .NET file:line references.
|
||||
- Keep `PARTIAL` only if explicit residual behavior remains.
|
||||
|
||||
## Task 5: Cross-Module Regression and Hardening
|
||||
|
||||
**Files:**
|
||||
- Test: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
**Step 1: Run full regression suite**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: PASS.
|
||||
|
||||
**Step 2: Add edge-case tests discovered during regression**
|
||||
- Capture any newly discovered divergence from Go and close it before completion.
|
||||
|
||||
## Task 6: Finalize Parity Bookkeeping
|
||||
|
||||
**Files:**
|
||||
- Modify: `gaps/leaf-nodes.md`
|
||||
- Modify: `gaps/stillmissing.md`
|
||||
|
||||
**Step 1: Ensure final status correctness**
|
||||
- Verify no unresolved `MISSING` rows remain for `leaf-nodes`.
|
||||
|
||||
**Step 2: Refresh LOC and percentage bookkeeping**
|
||||
- Recount category LOC and update summary rows in `gaps/stillmissing.md`.
|
||||
|
||||
**Step 3: Add concise changelog entry**
|
||||
- Record date, completed parity areas, and any justified deferred items.
|
||||
|
||||
## Completion Checklist
|
||||
- [ ] All prioritized `leaf-nodes` rows are `PORTED` or explicitly justified as `NOT_APPLICABLE`/`DEFERRED`.
|
||||
- [ ] New/updated tests demonstrate parity for all previously open rows.
|
||||
- [ ] `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release` passes.
|
||||
- [ ] `gaps/leaf-nodes.md` and `gaps/stillmissing.md` are fully synchronized.
|
||||
@@ -0,0 +1,80 @@
|
||||
# Logging Full Parity Design
|
||||
|
||||
**Date:** 2026-02-25
|
||||
**Category:** `logging`
|
||||
**Gap Inventory:** `gaps/logging.md`
|
||||
**Target Outcome:** Full Go-to-.NET behavioral parity for this category.
|
||||
|
||||
## Assumptions
|
||||
- Full parity is the required outcome.
|
||||
- No additional requirements gathering is needed for this pass.
|
||||
- Existing public .NET APIs are preserved unless parity requires targeted, documented adjustments.
|
||||
|
||||
## Current State Snapshot
|
||||
- `MISSING`: 0
|
||||
- `PARTIAL`: 4
|
||||
- `PORTED`: 54
|
||||
- `NOT_APPLICABLE`: 7
|
||||
- `DEFERRED`: 11
|
||||
- **Open parity work items:** 4
|
||||
|
||||
## Approaches Considered
|
||||
1. **Symbol-first closure (Recommended):** execute directly against each `MISSING` and `PARTIAL` row in `gaps/logging.md`, closing items in deterministic order.
|
||||
Trade-off: strongest traceability and auditability, but can require broad context switching.
|
||||
2. **Test-first closure:** derive all work from Go test scenarios first, then backfill missing symbols.
|
||||
Trade-off: maximizes behavioral confidence, but can miss non-test-covered API and helper parity.
|
||||
3. **Vertical runtime slices:** port connect/auth/pubsub/cluster flows end-to-end before moving to next slice.
|
||||
Trade-off: smoother integration validation, but weaker one-to-one mapping against inventory rows.
|
||||
|
||||
## Recommended Design
|
||||
### Architecture
|
||||
Use the gap inventory as the authoritative backlog. For each row, implement or complete the .NET equivalent, then attach test evidence and update the inventory status. Keep implementation inside existing category boundaries and only refactor when needed to match Go behavior.
|
||||
|
||||
### Components
|
||||
- **Backlog driver:** `gaps/logging.md` (row-level tracking for all parity items).
|
||||
- **Implementation surface:** category-specific .NET source files.
|
||||
- **Verification surface:** category-specific .NET tests plus cross-module regression tests.
|
||||
- **Parity bookkeeping:** updates to `gaps/logging.md` and `gaps/stillmissing.md` after each closure batch.
|
||||
|
||||
### Data Flow
|
||||
1. Read one `MISSING` or `PARTIAL` row from `gaps/logging.md`.
|
||||
2. Inspect referenced Go implementation and tests.
|
||||
3. Add/adjust .NET implementation in listed category files.
|
||||
4. Add failing .NET tests that reproduce the missing/partial behavior, then make them pass.
|
||||
5. Re-run targeted and regression tests.
|
||||
6. Update the row to `PORTED` (or keep `PARTIAL` with explicit residual notes).
|
||||
|
||||
### Error Handling and Risk Controls
|
||||
- Avoid broad rewrites; prefer narrow patches tied to specific inventory rows.
|
||||
- Preserve protocol and timing semantics when replacing Go concurrency constructs with .NET async patterns.
|
||||
- Treat edge cases in Go tests as mandatory parity requirements unless explicitly marked `NOT_APPLICABLE`.
|
||||
- Require evidence (tests + inventory status updates) before declaring row completion.
|
||||
|
||||
### Testing Strategy
|
||||
- Add or expand tests for each row moved from `MISSING`/`PARTIAL` toward `PORTED`.
|
||||
- Prioritize wire/protocol compatibility, lifecycle transitions, auth boundaries, and backpressure/timeout behavior.
|
||||
- Keep category-targeted tests fast; run full `NATS.Server.Tests` regression before closure.
|
||||
|
||||
### Definition of Done
|
||||
- All category rows are either `PORTED` or explicitly justified `NOT_APPLICABLE`/`DEFERRED`.
|
||||
- No unresolved `MISSING` or unqualified `PARTIAL` rows remain for this category.
|
||||
- Relevant .NET tests pass locally and parity evidence is written into inventory notes.
|
||||
|
||||
## Implementation Scope Inputs
|
||||
### Go Source Files
|
||||
- `golang/nats-server/server/log.go`
|
||||
- `golang/nats-server/logger/log.go`
|
||||
- `golang/nats-server/logger/syslog.go`
|
||||
- `golang/nats-server/logger/syslog_windows.go`
|
||||
|
||||
### Go Test Files
|
||||
- `golang/nats-server/server/log_test.go`
|
||||
- `golang/nats-server/logger/log_test.go`
|
||||
- `golang/nats-server/logger/syslog_test.go`
|
||||
- `golang/nats-server/logger/syslog_windows_test.go`
|
||||
|
||||
### .NET Source Files
|
||||
- `(none`
|
||||
|
||||
### .NET Test Files
|
||||
- `(none`
|
||||
@@ -0,0 +1,103 @@
|
||||
# Logging Gap Port Implementation Plan
|
||||
|
||||
> **For Codex:** REQUIRED SUB-SKILL: Use `executeplan` to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Port all remaining `logging` gap inventory items to achieve full behavior parity with Go.
|
||||
|
||||
**Architecture:** Use the category gap inventory as the execution backlog. Drive work row-by-row from `MISSING` and `PARTIAL` toward `PORTED`, with test-first validation and explicit parity evidence in notes.
|
||||
|
||||
**Tech Stack:** .NET 10, C#, xUnit, existing `NATS.Server` runtime modules, Go NATS server reference source.
|
||||
|
||||
---
|
||||
|
||||
## Category Inputs
|
||||
- Gap Inventory: `gaps/logging.md`
|
||||
- Primary .NET Source Anchor: `(none`
|
||||
- Primary .NET Test Anchor: `(none`
|
||||
- Full .NET test project: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
## Task 1: Freeze Row-Level Backlog
|
||||
|
||||
**Files:**
|
||||
- Modify: `gaps/logging.md`
|
||||
|
||||
**Step 1: Normalize open rows into an execution queue**
|
||||
- Split all `MISSING` and `PARTIAL` rows into ranked batches (highest runtime impact first).
|
||||
|
||||
**Step 2: Record acceptance criteria per row**
|
||||
- For each row, define exact behavioral parity checks (inputs, outputs, error paths, timing/concurrency expectations).
|
||||
|
||||
**Step 3: Save queue annotations in notes**
|
||||
- Add short execution-order markers in notes without changing status yet.
|
||||
|
||||
## Task 2: Write Failing Tests for Batch 1
|
||||
|
||||
**Files:**
|
||||
- Modify: `(none`
|
||||
- Test: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
**Step 1: Add failing tests for first open row batch**
|
||||
- Create tests that reproduce the exact missing/partial Go behavior.
|
||||
|
||||
**Step 2: Run tests to confirm failure**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: FAIL in newly added tests only.
|
||||
|
||||
## Task 3: Implement Minimal Parity for Batch 1
|
||||
|
||||
**Files:**
|
||||
- Modify: `(none`
|
||||
- Modify: additional category files listed in `gaps/logging.md`
|
||||
|
||||
**Step 1: Implement minimal code to satisfy failing tests**
|
||||
- Mirror Go semantics for parsing, state transitions, limits, and side-effects required by the targeted rows.
|
||||
|
||||
**Step 2: Re-run tests for validation**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: PASS for batch-1 tests; no new regressions.
|
||||
|
||||
## Task 4: Close Remaining Batches Iteratively
|
||||
|
||||
**Files:**
|
||||
- Modify: category .NET source and test files referenced in `gaps/logging.md`
|
||||
- Modify: `gaps/logging.md`
|
||||
|
||||
**Step 1: Repeat test-first cycle for each remaining batch**
|
||||
- Add failing tests, implement minimal parity, then verify pass.
|
||||
|
||||
**Step 2: Update inventory status immediately after each closure**
|
||||
- Move rows to `PORTED` with exact .NET file:line references.
|
||||
- Keep `PARTIAL` only if explicit residual behavior remains.
|
||||
|
||||
## Task 5: Cross-Module Regression and Hardening
|
||||
|
||||
**Files:**
|
||||
- Test: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
**Step 1: Run full regression suite**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: PASS.
|
||||
|
||||
**Step 2: Add edge-case tests discovered during regression**
|
||||
- Capture any newly discovered divergence from Go and close it before completion.
|
||||
|
||||
## Task 6: Finalize Parity Bookkeeping
|
||||
|
||||
**Files:**
|
||||
- Modify: `gaps/logging.md`
|
||||
- Modify: `gaps/stillmissing.md`
|
||||
|
||||
**Step 1: Ensure final status correctness**
|
||||
- Verify no unresolved `MISSING` rows remain for `logging`.
|
||||
|
||||
**Step 2: Refresh LOC and percentage bookkeeping**
|
||||
- Recount category LOC and update summary rows in `gaps/stillmissing.md`.
|
||||
|
||||
**Step 3: Add concise changelog entry**
|
||||
- Record date, completed parity areas, and any justified deferred items.
|
||||
|
||||
## Completion Checklist
|
||||
- [ ] All prioritized `logging` rows are `PORTED` or explicitly justified as `NOT_APPLICABLE`/`DEFERRED`.
|
||||
- [ ] New/updated tests demonstrate parity for all previously open rows.
|
||||
- [ ] `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release` passes.
|
||||
- [ ] `gaps/logging.md` and `gaps/stillmissing.md` are fully synchronized.
|
||||
@@ -0,0 +1,84 @@
|
||||
# Misc / Uncategorized Full Parity Design
|
||||
|
||||
**Date:** 2026-02-25
|
||||
**Category:** `misc-uncategorized`
|
||||
**Gap Inventory:** `gaps/misc-uncategorized.md`
|
||||
**Target Outcome:** Full Go-to-.NET behavioral parity for this category.
|
||||
|
||||
## Assumptions
|
||||
- Full parity is the required outcome.
|
||||
- No additional requirements gathering is needed for this pass.
|
||||
- Existing public .NET APIs are preserved unless parity requires targeted, documented adjustments.
|
||||
|
||||
## Current State Snapshot
|
||||
- `MISSING`: 8
|
||||
- `PARTIAL`: 13
|
||||
- `PORTED`: 2
|
||||
- `NOT_APPLICABLE`: 6
|
||||
- `DEFERRED`: 2
|
||||
- **Open parity work items:** 21
|
||||
|
||||
## Approaches Considered
|
||||
1. **Symbol-first closure (Recommended):** execute directly against each `MISSING` and `PARTIAL` row in `gaps/misc-uncategorized.md`, closing items in deterministic order.
|
||||
Trade-off: strongest traceability and auditability, but can require broad context switching.
|
||||
2. **Test-first closure:** derive all work from Go test scenarios first, then backfill missing symbols.
|
||||
Trade-off: maximizes behavioral confidence, but can miss non-test-covered API and helper parity.
|
||||
3. **Vertical runtime slices:** port connect/auth/pubsub/cluster flows end-to-end before moving to next slice.
|
||||
Trade-off: smoother integration validation, but weaker one-to-one mapping against inventory rows.
|
||||
|
||||
## Recommended Design
|
||||
### Architecture
|
||||
Use the gap inventory as the authoritative backlog. For each row, implement or complete the .NET equivalent, then attach test evidence and update the inventory status. Keep implementation inside existing category boundaries and only refactor when needed to match Go behavior.
|
||||
|
||||
### Components
|
||||
- **Backlog driver:** `gaps/misc-uncategorized.md` (row-level tracking for all parity items).
|
||||
- **Implementation surface:** category-specific .NET source files.
|
||||
- **Verification surface:** category-specific .NET tests plus cross-module regression tests.
|
||||
- **Parity bookkeeping:** updates to `gaps/misc-uncategorized.md` and `gaps/stillmissing.md` after each closure batch.
|
||||
|
||||
### Data Flow
|
||||
1. Read one `MISSING` or `PARTIAL` row from `gaps/misc-uncategorized.md`.
|
||||
2. Inspect referenced Go implementation and tests.
|
||||
3. Add/adjust .NET implementation in listed category files.
|
||||
4. Add failing .NET tests that reproduce the missing/partial behavior, then make them pass.
|
||||
5. Re-run targeted and regression tests.
|
||||
6. Update the row to `PORTED` (or keep `PARTIAL` with explicit residual notes).
|
||||
|
||||
### Error Handling and Risk Controls
|
||||
- Avoid broad rewrites; prefer narrow patches tied to specific inventory rows.
|
||||
- Preserve protocol and timing semantics when replacing Go concurrency constructs with .NET async patterns.
|
||||
- Treat edge cases in Go tests as mandatory parity requirements unless explicitly marked `NOT_APPLICABLE`.
|
||||
- Require evidence (tests + inventory status updates) before declaring row completion.
|
||||
|
||||
### Testing Strategy
|
||||
- Add or expand tests for each row moved from `MISSING`/`PARTIAL` toward `PORTED`.
|
||||
- Prioritize wire/protocol compatibility, lifecycle transitions, auth boundaries, and backpressure/timeout behavior.
|
||||
- Keep category-targeted tests fast; run full `NATS.Server.Tests` regression before closure.
|
||||
|
||||
### Definition of Done
|
||||
- All category rows are either `PORTED` or explicitly justified `NOT_APPLICABLE`/`DEFERRED`.
|
||||
- No unresolved `MISSING` or unqualified `PARTIAL` rows remain for this category.
|
||||
- Relevant .NET tests pass locally and parity evidence is written into inventory notes.
|
||||
|
||||
## Implementation Scope Inputs
|
||||
### Go Source Files
|
||||
- `Files not fitting into other categories`
|
||||
- `Small Go source files that don't belong to a major subsystem`
|
||||
- `Platform-specific stubs or build-tag-only files`
|
||||
|
||||
### Go Test Files
|
||||
- `golang/nats-server/server/ping_test.go`
|
||||
- `golang/nats-server/server/closed_conns_test.go`
|
||||
- `golang/nats-server/server/norace_1_test.go`
|
||||
- `golang/nats-server/server/norace_2_test.go`
|
||||
- `golang/nats-server/server/benchmark_publish_test.go`
|
||||
- `golang/nats-server/server/core_benchmarks_test.go`
|
||||
- `Various integration tests in golang/nats-server/test/`
|
||||
|
||||
### .NET Source Files
|
||||
- `Any .NET source files not covered by other category files`
|
||||
|
||||
### .NET Test Files
|
||||
- `tests/NATS.Server.Tests/Stress/`
|
||||
- `tests/NATS.Server.Tests/Parity/`
|
||||
- `Other root-level test files`
|
||||
@@ -0,0 +1,103 @@
|
||||
# Misc / Uncategorized Gap Port Implementation Plan
|
||||
|
||||
> **For Codex:** REQUIRED SUB-SKILL: Use `executeplan` to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Port all remaining `misc-uncategorized` gap inventory items to achieve full behavior parity with Go.
|
||||
|
||||
**Architecture:** Use the category gap inventory as the execution backlog. Drive work row-by-row from `MISSING` and `PARTIAL` toward `PORTED`, with test-first validation and explicit parity evidence in notes.
|
||||
|
||||
**Tech Stack:** .NET 10, C#, xUnit, existing `NATS.Server` runtime modules, Go NATS server reference source.
|
||||
|
||||
---
|
||||
|
||||
## Category Inputs
|
||||
- Gap Inventory: `gaps/misc-uncategorized.md`
|
||||
- Primary .NET Source Anchor: `Any .NET source files not covered by other category files`
|
||||
- Primary .NET Test Anchor: `tests/NATS.Server.Tests/Stress/`
|
||||
- Full .NET test project: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
## Task 1: Freeze Row-Level Backlog
|
||||
|
||||
**Files:**
|
||||
- Modify: `gaps/misc-uncategorized.md`
|
||||
|
||||
**Step 1: Normalize open rows into an execution queue**
|
||||
- Split all `MISSING` and `PARTIAL` rows into ranked batches (highest runtime impact first).
|
||||
|
||||
**Step 2: Record acceptance criteria per row**
|
||||
- For each row, define exact behavioral parity checks (inputs, outputs, error paths, timing/concurrency expectations).
|
||||
|
||||
**Step 3: Save queue annotations in notes**
|
||||
- Add short execution-order markers in notes without changing status yet.
|
||||
|
||||
## Task 2: Write Failing Tests for Batch 1
|
||||
|
||||
**Files:**
|
||||
- Modify: `tests/NATS.Server.Tests/Stress/`
|
||||
- Test: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
**Step 1: Add failing tests for first open row batch**
|
||||
- Create tests that reproduce the exact missing/partial Go behavior.
|
||||
|
||||
**Step 2: Run tests to confirm failure**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: FAIL in newly added tests only.
|
||||
|
||||
## Task 3: Implement Minimal Parity for Batch 1
|
||||
|
||||
**Files:**
|
||||
- Modify: `Any .NET source files not covered by other category files`
|
||||
- Modify: additional category files listed in `gaps/misc-uncategorized.md`
|
||||
|
||||
**Step 1: Implement minimal code to satisfy failing tests**
|
||||
- Mirror Go semantics for parsing, state transitions, limits, and side-effects required by the targeted rows.
|
||||
|
||||
**Step 2: Re-run tests for validation**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: PASS for batch-1 tests; no new regressions.
|
||||
|
||||
## Task 4: Close Remaining Batches Iteratively
|
||||
|
||||
**Files:**
|
||||
- Modify: category .NET source and test files referenced in `gaps/misc-uncategorized.md`
|
||||
- Modify: `gaps/misc-uncategorized.md`
|
||||
|
||||
**Step 1: Repeat test-first cycle for each remaining batch**
|
||||
- Add failing tests, implement minimal parity, then verify pass.
|
||||
|
||||
**Step 2: Update inventory status immediately after each closure**
|
||||
- Move rows to `PORTED` with exact .NET file:line references.
|
||||
- Keep `PARTIAL` only if explicit residual behavior remains.
|
||||
|
||||
## Task 5: Cross-Module Regression and Hardening
|
||||
|
||||
**Files:**
|
||||
- Test: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
**Step 1: Run full regression suite**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: PASS.
|
||||
|
||||
**Step 2: Add edge-case tests discovered during regression**
|
||||
- Capture any newly discovered divergence from Go and close it before completion.
|
||||
|
||||
## Task 6: Finalize Parity Bookkeeping
|
||||
|
||||
**Files:**
|
||||
- Modify: `gaps/misc-uncategorized.md`
|
||||
- Modify: `gaps/stillmissing.md`
|
||||
|
||||
**Step 1: Ensure final status correctness**
|
||||
- Verify no unresolved `MISSING` rows remain for `misc-uncategorized`.
|
||||
|
||||
**Step 2: Refresh LOC and percentage bookkeeping**
|
||||
- Recount category LOC and update summary rows in `gaps/stillmissing.md`.
|
||||
|
||||
**Step 3: Add concise changelog entry**
|
||||
- Record date, completed parity areas, and any justified deferred items.
|
||||
|
||||
## Completion Checklist
|
||||
- [ ] All prioritized `misc-uncategorized` rows are `PORTED` or explicitly justified as `NOT_APPLICABLE`/`DEFERRED`.
|
||||
- [ ] New/updated tests demonstrate parity for all previously open rows.
|
||||
- [ ] `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release` passes.
|
||||
- [ ] `gaps/misc-uncategorized.md` and `gaps/stillmissing.md` are fully synchronized.
|
||||
@@ -0,0 +1,88 @@
|
||||
# Monitoring Full Parity Design
|
||||
|
||||
**Date:** 2026-02-25
|
||||
**Category:** `monitoring`
|
||||
**Gap Inventory:** `gaps/monitoring.md`
|
||||
**Target Outcome:** Full Go-to-.NET behavioral parity for this category.
|
||||
|
||||
## Assumptions
|
||||
- Full parity is the required outcome.
|
||||
- No additional requirements gathering is needed for this pass.
|
||||
- Existing public .NET APIs are preserved unless parity requires targeted, documented adjustments.
|
||||
|
||||
## Current State Snapshot
|
||||
- `MISSING`: 61
|
||||
- `PARTIAL`: 42
|
||||
- `PORTED`: 63
|
||||
- `NOT_APPLICABLE`: 3
|
||||
- `DEFERRED`: 0
|
||||
- **Open parity work items:** 103
|
||||
|
||||
## Approaches Considered
|
||||
1. **Symbol-first closure (Recommended):** execute directly against each `MISSING` and `PARTIAL` row in `gaps/monitoring.md`, closing items in deterministic order.
|
||||
Trade-off: strongest traceability and auditability, but can require broad context switching.
|
||||
2. **Test-first closure:** derive all work from Go test scenarios first, then backfill missing symbols.
|
||||
Trade-off: maximizes behavioral confidence, but can miss non-test-covered API and helper parity.
|
||||
3. **Vertical runtime slices:** port connect/auth/pubsub/cluster flows end-to-end before moving to next slice.
|
||||
Trade-off: smoother integration validation, but weaker one-to-one mapping against inventory rows.
|
||||
|
||||
## Recommended Design
|
||||
### Architecture
|
||||
Use the gap inventory as the authoritative backlog. For each row, implement or complete the .NET equivalent, then attach test evidence and update the inventory status. Keep implementation inside existing category boundaries and only refactor when needed to match Go behavior.
|
||||
|
||||
### Components
|
||||
- **Backlog driver:** `gaps/monitoring.md` (row-level tracking for all parity items).
|
||||
- **Implementation surface:** category-specific .NET source files.
|
||||
- **Verification surface:** category-specific .NET tests plus cross-module regression tests.
|
||||
- **Parity bookkeeping:** updates to `gaps/monitoring.md` and `gaps/stillmissing.md` after each closure batch.
|
||||
|
||||
### Data Flow
|
||||
1. Read one `MISSING` or `PARTIAL` row from `gaps/monitoring.md`.
|
||||
2. Inspect referenced Go implementation and tests.
|
||||
3. Add/adjust .NET implementation in listed category files.
|
||||
4. Add failing .NET tests that reproduce the missing/partial behavior, then make them pass.
|
||||
5. Re-run targeted and regression tests.
|
||||
6. Update the row to `PORTED` (or keep `PARTIAL` with explicit residual notes).
|
||||
|
||||
### Error Handling and Risk Controls
|
||||
- Avoid broad rewrites; prefer narrow patches tied to specific inventory rows.
|
||||
- Preserve protocol and timing semantics when replacing Go concurrency constructs with .NET async patterns.
|
||||
- Treat edge cases in Go tests as mandatory parity requirements unless explicitly marked `NOT_APPLICABLE`.
|
||||
- Require evidence (tests + inventory status updates) before declaring row completion.
|
||||
|
||||
### Testing Strategy
|
||||
- Add or expand tests for each row moved from `MISSING`/`PARTIAL` toward `PORTED`.
|
||||
- Prioritize wire/protocol compatibility, lifecycle transitions, auth boundaries, and backpressure/timeout behavior.
|
||||
- Keep category-targeted tests fast; run full `NATS.Server.Tests` regression before closure.
|
||||
|
||||
### Definition of Done
|
||||
- All category rows are either `PORTED` or explicitly justified `NOT_APPLICABLE`/`DEFERRED`.
|
||||
- No unresolved `MISSING` or unqualified `PARTIAL` rows remain for this category.
|
||||
- Relevant .NET tests pass locally and parity evidence is written into inventory notes.
|
||||
|
||||
## Implementation Scope Inputs
|
||||
### Go Source Files
|
||||
- `golang/nats-server/server/monitor.go`
|
||||
- `golang/nats-server/server/monitor_sort_opts.go`
|
||||
|
||||
### Go Test Files
|
||||
- `golang/nats-server/server/monitor_test.go`
|
||||
- `golang/nats-server/test/monitor_test.go (integration)`
|
||||
|
||||
### .NET Source Files
|
||||
- `src/NATS.Server/Monitoring/MonitorServer.cs`
|
||||
- `src/NATS.Server/Monitoring/Varz.cs`
|
||||
- `src/NATS.Server/Monitoring/Subsz.cs`
|
||||
- `src/NATS.Server/Monitoring/VarzHandler.cs`
|
||||
- `src/NATS.Server/Monitoring/SubszHandler.cs`
|
||||
- `src/NATS.Server/Monitoring/JszHandler.cs`
|
||||
- `src/NATS.Server/Monitoring/AccountzHandler.cs`
|
||||
- `src/NATS.Server/Monitoring/GatewayzHandler.cs`
|
||||
- `src/NATS.Server/Monitoring/LeafzHandler.cs`
|
||||
- `src/NATS.Server/Monitoring/RoutezHandler.cs`
|
||||
- `src/NATS.Server/Monitoring/PprofHandler.cs`
|
||||
- `src/NATS.Server/Monitoring/ClosedClient.cs`
|
||||
- `All other files in src/NATS.Server/Monitoring/`
|
||||
|
||||
### .NET Test Files
|
||||
- `tests/NATS.Server.Tests/Monitoring/`
|
||||
@@ -0,0 +1,103 @@
|
||||
# Monitoring Gap Port Implementation Plan
|
||||
|
||||
> **For Codex:** REQUIRED SUB-SKILL: Use `executeplan` to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Port all remaining `monitoring` gap inventory items to achieve full behavior parity with Go.
|
||||
|
||||
**Architecture:** Use the category gap inventory as the execution backlog. Drive work row-by-row from `MISSING` and `PARTIAL` toward `PORTED`, with test-first validation and explicit parity evidence in notes.
|
||||
|
||||
**Tech Stack:** .NET 10, C#, xUnit, existing `NATS.Server` runtime modules, Go NATS server reference source.
|
||||
|
||||
---
|
||||
|
||||
## Category Inputs
|
||||
- Gap Inventory: `gaps/monitoring.md`
|
||||
- Primary .NET Source Anchor: `src/NATS.Server/Monitoring/MonitorServer.cs`
|
||||
- Primary .NET Test Anchor: `tests/NATS.Server.Tests/Monitoring/`
|
||||
- Full .NET test project: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
## Task 1: Freeze Row-Level Backlog
|
||||
|
||||
**Files:**
|
||||
- Modify: `gaps/monitoring.md`
|
||||
|
||||
**Step 1: Normalize open rows into an execution queue**
|
||||
- Split all `MISSING` and `PARTIAL` rows into ranked batches (highest runtime impact first).
|
||||
|
||||
**Step 2: Record acceptance criteria per row**
|
||||
- For each row, define exact behavioral parity checks (inputs, outputs, error paths, timing/concurrency expectations).
|
||||
|
||||
**Step 3: Save queue annotations in notes**
|
||||
- Add short execution-order markers in notes without changing status yet.
|
||||
|
||||
## Task 2: Write Failing Tests for Batch 1
|
||||
|
||||
**Files:**
|
||||
- Modify: `tests/NATS.Server.Tests/Monitoring/`
|
||||
- Test: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
**Step 1: Add failing tests for first open row batch**
|
||||
- Create tests that reproduce the exact missing/partial Go behavior.
|
||||
|
||||
**Step 2: Run tests to confirm failure**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: FAIL in newly added tests only.
|
||||
|
||||
## Task 3: Implement Minimal Parity for Batch 1
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/NATS.Server/Monitoring/MonitorServer.cs`
|
||||
- Modify: additional category files listed in `gaps/monitoring.md`
|
||||
|
||||
**Step 1: Implement minimal code to satisfy failing tests**
|
||||
- Mirror Go semantics for parsing, state transitions, limits, and side-effects required by the targeted rows.
|
||||
|
||||
**Step 2: Re-run tests for validation**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: PASS for batch-1 tests; no new regressions.
|
||||
|
||||
## Task 4: Close Remaining Batches Iteratively
|
||||
|
||||
**Files:**
|
||||
- Modify: category .NET source and test files referenced in `gaps/monitoring.md`
|
||||
- Modify: `gaps/monitoring.md`
|
||||
|
||||
**Step 1: Repeat test-first cycle for each remaining batch**
|
||||
- Add failing tests, implement minimal parity, then verify pass.
|
||||
|
||||
**Step 2: Update inventory status immediately after each closure**
|
||||
- Move rows to `PORTED` with exact .NET file:line references.
|
||||
- Keep `PARTIAL` only if explicit residual behavior remains.
|
||||
|
||||
## Task 5: Cross-Module Regression and Hardening
|
||||
|
||||
**Files:**
|
||||
- Test: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
**Step 1: Run full regression suite**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: PASS.
|
||||
|
||||
**Step 2: Add edge-case tests discovered during regression**
|
||||
- Capture any newly discovered divergence from Go and close it before completion.
|
||||
|
||||
## Task 6: Finalize Parity Bookkeeping
|
||||
|
||||
**Files:**
|
||||
- Modify: `gaps/monitoring.md`
|
||||
- Modify: `gaps/stillmissing.md`
|
||||
|
||||
**Step 1: Ensure final status correctness**
|
||||
- Verify no unresolved `MISSING` rows remain for `monitoring`.
|
||||
|
||||
**Step 2: Refresh LOC and percentage bookkeeping**
|
||||
- Recount category LOC and update summary rows in `gaps/stillmissing.md`.
|
||||
|
||||
**Step 3: Add concise changelog entry**
|
||||
- Record date, completed parity areas, and any justified deferred items.
|
||||
|
||||
## Completion Checklist
|
||||
- [ ] All prioritized `monitoring` rows are `PORTED` or explicitly justified as `NOT_APPLICABLE`/`DEFERRED`.
|
||||
- [ ] New/updated tests demonstrate parity for all previously open rows.
|
||||
- [ ] `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release` passes.
|
||||
- [ ] `gaps/monitoring.md` and `gaps/stillmissing.md` are fully synchronized.
|
||||
@@ -0,0 +1,76 @@
|
||||
# MQTT Full Parity Design
|
||||
|
||||
**Date:** 2026-02-25
|
||||
**Category:** `mqtt`
|
||||
**Gap Inventory:** `gaps/mqtt.md`
|
||||
**Target Outcome:** Full Go-to-.NET behavioral parity for this category.
|
||||
|
||||
## Assumptions
|
||||
- Full parity is the required outcome.
|
||||
- No additional requirements gathering is needed for this pass.
|
||||
- Existing public .NET APIs are preserved unless parity requires targeted, documented adjustments.
|
||||
|
||||
## Current State Snapshot
|
||||
- `MISSING`: 119
|
||||
- `PARTIAL`: 57
|
||||
- `PORTED`: 14
|
||||
- `NOT_APPLICABLE`: 5
|
||||
- `DEFERRED`: 0
|
||||
- **Open parity work items:** 176
|
||||
|
||||
## Approaches Considered
|
||||
1. **Symbol-first closure (Recommended):** execute directly against each `MISSING` and `PARTIAL` row in `gaps/mqtt.md`, closing items in deterministic order.
|
||||
Trade-off: strongest traceability and auditability, but can require broad context switching.
|
||||
2. **Test-first closure:** derive all work from Go test scenarios first, then backfill missing symbols.
|
||||
Trade-off: maximizes behavioral confidence, but can miss non-test-covered API and helper parity.
|
||||
3. **Vertical runtime slices:** port connect/auth/pubsub/cluster flows end-to-end before moving to next slice.
|
||||
Trade-off: smoother integration validation, but weaker one-to-one mapping against inventory rows.
|
||||
|
||||
## Recommended Design
|
||||
### Architecture
|
||||
Use the gap inventory as the authoritative backlog. For each row, implement or complete the .NET equivalent, then attach test evidence and update the inventory status. Keep implementation inside existing category boundaries and only refactor when needed to match Go behavior.
|
||||
|
||||
### Components
|
||||
- **Backlog driver:** `gaps/mqtt.md` (row-level tracking for all parity items).
|
||||
- **Implementation surface:** category-specific .NET source files.
|
||||
- **Verification surface:** category-specific .NET tests plus cross-module regression tests.
|
||||
- **Parity bookkeeping:** updates to `gaps/mqtt.md` and `gaps/stillmissing.md` after each closure batch.
|
||||
|
||||
### Data Flow
|
||||
1. Read one `MISSING` or `PARTIAL` row from `gaps/mqtt.md`.
|
||||
2. Inspect referenced Go implementation and tests.
|
||||
3. Add/adjust .NET implementation in listed category files.
|
||||
4. Add failing .NET tests that reproduce the missing/partial behavior, then make them pass.
|
||||
5. Re-run targeted and regression tests.
|
||||
6. Update the row to `PORTED` (or keep `PARTIAL` with explicit residual notes).
|
||||
|
||||
### Error Handling and Risk Controls
|
||||
- Avoid broad rewrites; prefer narrow patches tied to specific inventory rows.
|
||||
- Preserve protocol and timing semantics when replacing Go concurrency constructs with .NET async patterns.
|
||||
- Treat edge cases in Go tests as mandatory parity requirements unless explicitly marked `NOT_APPLICABLE`.
|
||||
- Require evidence (tests + inventory status updates) before declaring row completion.
|
||||
|
||||
### Testing Strategy
|
||||
- Add or expand tests for each row moved from `MISSING`/`PARTIAL` toward `PORTED`.
|
||||
- Prioritize wire/protocol compatibility, lifecycle transitions, auth boundaries, and backpressure/timeout behavior.
|
||||
- Keep category-targeted tests fast; run full `NATS.Server.Tests` regression before closure.
|
||||
|
||||
### Definition of Done
|
||||
- All category rows are either `PORTED` or explicitly justified `NOT_APPLICABLE`/`DEFERRED`.
|
||||
- No unresolved `MISSING` or unqualified `PARTIAL` rows remain for this category.
|
||||
- Relevant .NET tests pass locally and parity evidence is written into inventory notes.
|
||||
|
||||
## Implementation Scope Inputs
|
||||
### Go Source Files
|
||||
- `golang/nats-server/server/mqtt.go`
|
||||
|
||||
### Go Test Files
|
||||
- `golang/nats-server/server/mqtt_test.go`
|
||||
- `golang/nats-server/server/mqtt_ex_test_test.go`
|
||||
- `golang/nats-server/server/mqtt_ex_bench_test.go`
|
||||
|
||||
### .NET Source Files
|
||||
- `src/NATS.Server/Mqtt/ (all files)`
|
||||
|
||||
### .NET Test Files
|
||||
- `tests/NATS.Server.Tests/Mqtt/`
|
||||
@@ -0,0 +1,103 @@
|
||||
# MQTT Gap Port Implementation Plan
|
||||
|
||||
> **For Codex:** REQUIRED SUB-SKILL: Use `executeplan` to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Port all remaining `mqtt` gap inventory items to achieve full behavior parity with Go.
|
||||
|
||||
**Architecture:** Use the category gap inventory as the execution backlog. Drive work row-by-row from `MISSING` and `PARTIAL` toward `PORTED`, with test-first validation and explicit parity evidence in notes.
|
||||
|
||||
**Tech Stack:** .NET 10, C#, xUnit, existing `NATS.Server` runtime modules, Go NATS server reference source.
|
||||
|
||||
---
|
||||
|
||||
## Category Inputs
|
||||
- Gap Inventory: `gaps/mqtt.md`
|
||||
- Primary .NET Source Anchor: `src/NATS.Server/Mqtt/ (all files)`
|
||||
- Primary .NET Test Anchor: `tests/NATS.Server.Tests/Mqtt/`
|
||||
- Full .NET test project: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
## Task 1: Freeze Row-Level Backlog
|
||||
|
||||
**Files:**
|
||||
- Modify: `gaps/mqtt.md`
|
||||
|
||||
**Step 1: Normalize open rows into an execution queue**
|
||||
- Split all `MISSING` and `PARTIAL` rows into ranked batches (highest runtime impact first).
|
||||
|
||||
**Step 2: Record acceptance criteria per row**
|
||||
- For each row, define exact behavioral parity checks (inputs, outputs, error paths, timing/concurrency expectations).
|
||||
|
||||
**Step 3: Save queue annotations in notes**
|
||||
- Add short execution-order markers in notes without changing status yet.
|
||||
|
||||
## Task 2: Write Failing Tests for Batch 1
|
||||
|
||||
**Files:**
|
||||
- Modify: `tests/NATS.Server.Tests/Mqtt/`
|
||||
- Test: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
**Step 1: Add failing tests for first open row batch**
|
||||
- Create tests that reproduce the exact missing/partial Go behavior.
|
||||
|
||||
**Step 2: Run tests to confirm failure**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: FAIL in newly added tests only.
|
||||
|
||||
## Task 3: Implement Minimal Parity for Batch 1
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/NATS.Server/Mqtt/ (all files)`
|
||||
- Modify: additional category files listed in `gaps/mqtt.md`
|
||||
|
||||
**Step 1: Implement minimal code to satisfy failing tests**
|
||||
- Mirror Go semantics for parsing, state transitions, limits, and side-effects required by the targeted rows.
|
||||
|
||||
**Step 2: Re-run tests for validation**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: PASS for batch-1 tests; no new regressions.
|
||||
|
||||
## Task 4: Close Remaining Batches Iteratively
|
||||
|
||||
**Files:**
|
||||
- Modify: category .NET source and test files referenced in `gaps/mqtt.md`
|
||||
- Modify: `gaps/mqtt.md`
|
||||
|
||||
**Step 1: Repeat test-first cycle for each remaining batch**
|
||||
- Add failing tests, implement minimal parity, then verify pass.
|
||||
|
||||
**Step 2: Update inventory status immediately after each closure**
|
||||
- Move rows to `PORTED` with exact .NET file:line references.
|
||||
- Keep `PARTIAL` only if explicit residual behavior remains.
|
||||
|
||||
## Task 5: Cross-Module Regression and Hardening
|
||||
|
||||
**Files:**
|
||||
- Test: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
**Step 1: Run full regression suite**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: PASS.
|
||||
|
||||
**Step 2: Add edge-case tests discovered during regression**
|
||||
- Capture any newly discovered divergence from Go and close it before completion.
|
||||
|
||||
## Task 6: Finalize Parity Bookkeeping
|
||||
|
||||
**Files:**
|
||||
- Modify: `gaps/mqtt.md`
|
||||
- Modify: `gaps/stillmissing.md`
|
||||
|
||||
**Step 1: Ensure final status correctness**
|
||||
- Verify no unresolved `MISSING` rows remain for `mqtt`.
|
||||
|
||||
**Step 2: Refresh LOC and percentage bookkeeping**
|
||||
- Recount category LOC and update summary rows in `gaps/stillmissing.md`.
|
||||
|
||||
**Step 3: Add concise changelog entry**
|
||||
- Record date, completed parity areas, and any justified deferred items.
|
||||
|
||||
## Completion Checklist
|
||||
- [ ] All prioritized `mqtt` rows are `PORTED` or explicitly justified as `NOT_APPLICABLE`/`DEFERRED`.
|
||||
- [ ] New/updated tests demonstrate parity for all previously open rows.
|
||||
- [ ] `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release` passes.
|
||||
- [ ] `gaps/mqtt.md` and `gaps/stillmissing.md` are fully synchronized.
|
||||
@@ -0,0 +1,85 @@
|
||||
# Protocol Full Parity Design
|
||||
|
||||
**Date:** 2026-02-25
|
||||
**Category:** `protocol`
|
||||
**Gap Inventory:** `gaps/protocol.md`
|
||||
**Target Outcome:** Full Go-to-.NET behavioral parity for this category.
|
||||
|
||||
## Assumptions
|
||||
- Full parity is the required outcome.
|
||||
- No additional requirements gathering is needed for this pass.
|
||||
- Existing public .NET APIs are preserved unless parity requires targeted, documented adjustments.
|
||||
|
||||
## Current State Snapshot
|
||||
- `MISSING`: 48
|
||||
- `PARTIAL`: 8
|
||||
- `PORTED`: 15
|
||||
- `NOT_APPLICABLE`: 12
|
||||
- `DEFERRED`: 0
|
||||
- **Open parity work items:** 56
|
||||
|
||||
## Approaches Considered
|
||||
1. **Symbol-first closure (Recommended):** execute directly against each `MISSING` and `PARTIAL` row in `gaps/protocol.md`, closing items in deterministic order.
|
||||
Trade-off: strongest traceability and auditability, but can require broad context switching.
|
||||
2. **Test-first closure:** derive all work from Go test scenarios first, then backfill missing symbols.
|
||||
Trade-off: maximizes behavioral confidence, but can miss non-test-covered API and helper parity.
|
||||
3. **Vertical runtime slices:** port connect/auth/pubsub/cluster flows end-to-end before moving to next slice.
|
||||
Trade-off: smoother integration validation, but weaker one-to-one mapping against inventory rows.
|
||||
|
||||
## Recommended Design
|
||||
### Architecture
|
||||
Use the gap inventory as the authoritative backlog. For each row, implement or complete the .NET equivalent, then attach test evidence and update the inventory status. Keep implementation inside existing category boundaries and only refactor when needed to match Go behavior.
|
||||
|
||||
### Components
|
||||
- **Backlog driver:** `gaps/protocol.md` (row-level tracking for all parity items).
|
||||
- **Implementation surface:** category-specific .NET source files.
|
||||
- **Verification surface:** category-specific .NET tests plus cross-module regression tests.
|
||||
- **Parity bookkeeping:** updates to `gaps/protocol.md` and `gaps/stillmissing.md` after each closure batch.
|
||||
|
||||
### Data Flow
|
||||
1. Read one `MISSING` or `PARTIAL` row from `gaps/protocol.md`.
|
||||
2. Inspect referenced Go implementation and tests.
|
||||
3. Add/adjust .NET implementation in listed category files.
|
||||
4. Add failing .NET tests that reproduce the missing/partial behavior, then make them pass.
|
||||
5. Re-run targeted and regression tests.
|
||||
6. Update the row to `PORTED` (or keep `PARTIAL` with explicit residual notes).
|
||||
|
||||
### Error Handling and Risk Controls
|
||||
- Avoid broad rewrites; prefer narrow patches tied to specific inventory rows.
|
||||
- Preserve protocol and timing semantics when replacing Go concurrency constructs with .NET async patterns.
|
||||
- Treat edge cases in Go tests as mandatory parity requirements unless explicitly marked `NOT_APPLICABLE`.
|
||||
- Require evidence (tests + inventory status updates) before declaring row completion.
|
||||
|
||||
### Testing Strategy
|
||||
- Add or expand tests for each row moved from `MISSING`/`PARTIAL` toward `PORTED`.
|
||||
- Prioritize wire/protocol compatibility, lifecycle transitions, auth boundaries, and backpressure/timeout behavior.
|
||||
- Keep category-targeted tests fast; run full `NATS.Server.Tests` regression before closure.
|
||||
|
||||
### Definition of Done
|
||||
- All category rows are either `PORTED` or explicitly justified `NOT_APPLICABLE`/`DEFERRED`.
|
||||
- No unresolved `MISSING` or unqualified `PARTIAL` rows remain for this category.
|
||||
- Relevant .NET tests pass locally and parity evidence is written into inventory notes.
|
||||
|
||||
## Implementation Scope Inputs
|
||||
### Go Source Files
|
||||
- `golang/nats-server/server/parser.go`
|
||||
- `golang/nats-server/server/proto.go`
|
||||
- `golang/nats-server/server/const.go`
|
||||
|
||||
### Go Test Files
|
||||
- `golang/nats-server/server/parser_test.go`
|
||||
- `golang/nats-server/server/parser_fuzz_test.go`
|
||||
- `golang/nats-server/server/server_fuzz_test.go`
|
||||
- `golang/nats-server/server/subject_fuzz_test.go`
|
||||
- `golang/nats-server/server/split_test.go`
|
||||
|
||||
### .NET Source Files
|
||||
- `src/NATS.Server/Protocol/NatsParser.cs`
|
||||
- `src/NATS.Server/Protocol/NatsProtocol.cs`
|
||||
- `src/NATS.Server/Protocol/NatsHeaderParser.cs`
|
||||
- `src/NATS.Server/Protocol/ClientCommandMatrix.cs`
|
||||
- `src/NATS.Server/Protocol/MessageTraceContext.cs`
|
||||
- `src/NATS.Server/Protocol/ProxyProtocol.cs`
|
||||
|
||||
### .NET Test Files
|
||||
- `tests/NATS.Server.Tests/Protocol/`
|
||||
@@ -0,0 +1,103 @@
|
||||
# Protocol Gap Port Implementation Plan
|
||||
|
||||
> **For Codex:** REQUIRED SUB-SKILL: Use `executeplan` to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Port all remaining `protocol` gap inventory items to achieve full behavior parity with Go.
|
||||
|
||||
**Architecture:** Use the category gap inventory as the execution backlog. Drive work row-by-row from `MISSING` and `PARTIAL` toward `PORTED`, with test-first validation and explicit parity evidence in notes.
|
||||
|
||||
**Tech Stack:** .NET 10, C#, xUnit, existing `NATS.Server` runtime modules, Go NATS server reference source.
|
||||
|
||||
---
|
||||
|
||||
## Category Inputs
|
||||
- Gap Inventory: `gaps/protocol.md`
|
||||
- Primary .NET Source Anchor: `src/NATS.Server/Protocol/NatsParser.cs`
|
||||
- Primary .NET Test Anchor: `tests/NATS.Server.Tests/Protocol/`
|
||||
- Full .NET test project: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
## Task 1: Freeze Row-Level Backlog
|
||||
|
||||
**Files:**
|
||||
- Modify: `gaps/protocol.md`
|
||||
|
||||
**Step 1: Normalize open rows into an execution queue**
|
||||
- Split all `MISSING` and `PARTIAL` rows into ranked batches (highest runtime impact first).
|
||||
|
||||
**Step 2: Record acceptance criteria per row**
|
||||
- For each row, define exact behavioral parity checks (inputs, outputs, error paths, timing/concurrency expectations).
|
||||
|
||||
**Step 3: Save queue annotations in notes**
|
||||
- Add short execution-order markers in notes without changing status yet.
|
||||
|
||||
## Task 2: Write Failing Tests for Batch 1
|
||||
|
||||
**Files:**
|
||||
- Modify: `tests/NATS.Server.Tests/Protocol/`
|
||||
- Test: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
**Step 1: Add failing tests for first open row batch**
|
||||
- Create tests that reproduce the exact missing/partial Go behavior.
|
||||
|
||||
**Step 2: Run tests to confirm failure**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: FAIL in newly added tests only.
|
||||
|
||||
## Task 3: Implement Minimal Parity for Batch 1
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/NATS.Server/Protocol/NatsParser.cs`
|
||||
- Modify: additional category files listed in `gaps/protocol.md`
|
||||
|
||||
**Step 1: Implement minimal code to satisfy failing tests**
|
||||
- Mirror Go semantics for parsing, state transitions, limits, and side-effects required by the targeted rows.
|
||||
|
||||
**Step 2: Re-run tests for validation**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: PASS for batch-1 tests; no new regressions.
|
||||
|
||||
## Task 4: Close Remaining Batches Iteratively
|
||||
|
||||
**Files:**
|
||||
- Modify: category .NET source and test files referenced in `gaps/protocol.md`
|
||||
- Modify: `gaps/protocol.md`
|
||||
|
||||
**Step 1: Repeat test-first cycle for each remaining batch**
|
||||
- Add failing tests, implement minimal parity, then verify pass.
|
||||
|
||||
**Step 2: Update inventory status immediately after each closure**
|
||||
- Move rows to `PORTED` with exact .NET file:line references.
|
||||
- Keep `PARTIAL` only if explicit residual behavior remains.
|
||||
|
||||
## Task 5: Cross-Module Regression and Hardening
|
||||
|
||||
**Files:**
|
||||
- Test: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
**Step 1: Run full regression suite**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: PASS.
|
||||
|
||||
**Step 2: Add edge-case tests discovered during regression**
|
||||
- Capture any newly discovered divergence from Go and close it before completion.
|
||||
|
||||
## Task 6: Finalize Parity Bookkeeping
|
||||
|
||||
**Files:**
|
||||
- Modify: `gaps/protocol.md`
|
||||
- Modify: `gaps/stillmissing.md`
|
||||
|
||||
**Step 1: Ensure final status correctness**
|
||||
- Verify no unresolved `MISSING` rows remain for `protocol`.
|
||||
|
||||
**Step 2: Refresh LOC and percentage bookkeeping**
|
||||
- Recount category LOC and update summary rows in `gaps/stillmissing.md`.
|
||||
|
||||
**Step 3: Add concise changelog entry**
|
||||
- Record date, completed parity areas, and any justified deferred items.
|
||||
|
||||
## Completion Checklist
|
||||
- [ ] All prioritized `protocol` rows are `PORTED` or explicitly justified as `NOT_APPLICABLE`/`DEFERRED`.
|
||||
- [ ] New/updated tests demonstrate parity for all previously open rows.
|
||||
- [ ] `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release` passes.
|
||||
- [ ] `gaps/protocol.md` and `gaps/stillmissing.md` are fully synchronized.
|
||||
@@ -0,0 +1,76 @@
|
||||
# RAFT Full Parity Design
|
||||
|
||||
**Date:** 2026-02-25
|
||||
**Category:** `raft`
|
||||
**Gap Inventory:** `gaps/raft.md`
|
||||
**Target Outcome:** Full Go-to-.NET behavioral parity for this category.
|
||||
|
||||
## Assumptions
|
||||
- Full parity is the required outcome.
|
||||
- No additional requirements gathering is needed for this pass.
|
||||
- Existing public .NET APIs are preserved unless parity requires targeted, documented adjustments.
|
||||
|
||||
## Current State Snapshot
|
||||
- `MISSING`: 99
|
||||
- `PARTIAL`: 38
|
||||
- `PORTED`: 46
|
||||
- `NOT_APPLICABLE`: 13
|
||||
- `DEFERRED`: 0
|
||||
- **Open parity work items:** 137
|
||||
|
||||
## Approaches Considered
|
||||
1. **Symbol-first closure (Recommended):** execute directly against each `MISSING` and `PARTIAL` row in `gaps/raft.md`, closing items in deterministic order.
|
||||
Trade-off: strongest traceability and auditability, but can require broad context switching.
|
||||
2. **Test-first closure:** derive all work from Go test scenarios first, then backfill missing symbols.
|
||||
Trade-off: maximizes behavioral confidence, but can miss non-test-covered API and helper parity.
|
||||
3. **Vertical runtime slices:** port connect/auth/pubsub/cluster flows end-to-end before moving to next slice.
|
||||
Trade-off: smoother integration validation, but weaker one-to-one mapping against inventory rows.
|
||||
|
||||
## Recommended Design
|
||||
### Architecture
|
||||
Use the gap inventory as the authoritative backlog. For each row, implement or complete the .NET equivalent, then attach test evidence and update the inventory status. Keep implementation inside existing category boundaries and only refactor when needed to match Go behavior.
|
||||
|
||||
### Components
|
||||
- **Backlog driver:** `gaps/raft.md` (row-level tracking for all parity items).
|
||||
- **Implementation surface:** category-specific .NET source files.
|
||||
- **Verification surface:** category-specific .NET tests plus cross-module regression tests.
|
||||
- **Parity bookkeeping:** updates to `gaps/raft.md` and `gaps/stillmissing.md` after each closure batch.
|
||||
|
||||
### Data Flow
|
||||
1. Read one `MISSING` or `PARTIAL` row from `gaps/raft.md`.
|
||||
2. Inspect referenced Go implementation and tests.
|
||||
3. Add/adjust .NET implementation in listed category files.
|
||||
4. Add failing .NET tests that reproduce the missing/partial behavior, then make them pass.
|
||||
5. Re-run targeted and regression tests.
|
||||
6. Update the row to `PORTED` (or keep `PARTIAL` with explicit residual notes).
|
||||
|
||||
### Error Handling and Risk Controls
|
||||
- Avoid broad rewrites; prefer narrow patches tied to specific inventory rows.
|
||||
- Preserve protocol and timing semantics when replacing Go concurrency constructs with .NET async patterns.
|
||||
- Treat edge cases in Go tests as mandatory parity requirements unless explicitly marked `NOT_APPLICABLE`.
|
||||
- Require evidence (tests + inventory status updates) before declaring row completion.
|
||||
|
||||
### Testing Strategy
|
||||
- Add or expand tests for each row moved from `MISSING`/`PARTIAL` toward `PORTED`.
|
||||
- Prioritize wire/protocol compatibility, lifecycle transitions, auth boundaries, and backpressure/timeout behavior.
|
||||
- Keep category-targeted tests fast; run full `NATS.Server.Tests` regression before closure.
|
||||
|
||||
### Definition of Done
|
||||
- All category rows are either `PORTED` or explicitly justified `NOT_APPLICABLE`/`DEFERRED`.
|
||||
- No unresolved `MISSING` or unqualified `PARTIAL` rows remain for this category.
|
||||
- Relevant .NET tests pass locally and parity evidence is written into inventory notes.
|
||||
|
||||
## Implementation Scope Inputs
|
||||
### Go Source Files
|
||||
- `golang/nats-server/server/raft.go`
|
||||
|
||||
### Go Test Files
|
||||
- `golang/nats-server/server/raft_test.go`
|
||||
- `golang/nats-server/server/raft_helpers_test.go`
|
||||
- `golang/nats-server/server/raft_chain_of_blocks_helpers_test.go`
|
||||
|
||||
### .NET Source Files
|
||||
- `src/NATS.Server/Raft/ (all 20 files)`
|
||||
|
||||
### .NET Test Files
|
||||
- `tests/NATS.Server.Tests/Raft/`
|
||||
@@ -0,0 +1,103 @@
|
||||
# RAFT Gap Port Implementation Plan
|
||||
|
||||
> **For Codex:** REQUIRED SUB-SKILL: Use `executeplan` to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Port all remaining `raft` gap inventory items to achieve full behavior parity with Go.
|
||||
|
||||
**Architecture:** Use the category gap inventory as the execution backlog. Drive work row-by-row from `MISSING` and `PARTIAL` toward `PORTED`, with test-first validation and explicit parity evidence in notes.
|
||||
|
||||
**Tech Stack:** .NET 10, C#, xUnit, existing `NATS.Server` runtime modules, Go NATS server reference source.
|
||||
|
||||
---
|
||||
|
||||
## Category Inputs
|
||||
- Gap Inventory: `gaps/raft.md`
|
||||
- Primary .NET Source Anchor: `src/NATS.Server/Raft/ (all 20 files)`
|
||||
- Primary .NET Test Anchor: `tests/NATS.Server.Tests/Raft/`
|
||||
- Full .NET test project: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
## Task 1: Freeze Row-Level Backlog
|
||||
|
||||
**Files:**
|
||||
- Modify: `gaps/raft.md`
|
||||
|
||||
**Step 1: Normalize open rows into an execution queue**
|
||||
- Split all `MISSING` and `PARTIAL` rows into ranked batches (highest runtime impact first).
|
||||
|
||||
**Step 2: Record acceptance criteria per row**
|
||||
- For each row, define exact behavioral parity checks (inputs, outputs, error paths, timing/concurrency expectations).
|
||||
|
||||
**Step 3: Save queue annotations in notes**
|
||||
- Add short execution-order markers in notes without changing status yet.
|
||||
|
||||
## Task 2: Write Failing Tests for Batch 1
|
||||
|
||||
**Files:**
|
||||
- Modify: `tests/NATS.Server.Tests/Raft/`
|
||||
- Test: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
**Step 1: Add failing tests for first open row batch**
|
||||
- Create tests that reproduce the exact missing/partial Go behavior.
|
||||
|
||||
**Step 2: Run tests to confirm failure**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: FAIL in newly added tests only.
|
||||
|
||||
## Task 3: Implement Minimal Parity for Batch 1
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/NATS.Server/Raft/ (all 20 files)`
|
||||
- Modify: additional category files listed in `gaps/raft.md`
|
||||
|
||||
**Step 1: Implement minimal code to satisfy failing tests**
|
||||
- Mirror Go semantics for parsing, state transitions, limits, and side-effects required by the targeted rows.
|
||||
|
||||
**Step 2: Re-run tests for validation**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: PASS for batch-1 tests; no new regressions.
|
||||
|
||||
## Task 4: Close Remaining Batches Iteratively
|
||||
|
||||
**Files:**
|
||||
- Modify: category .NET source and test files referenced in `gaps/raft.md`
|
||||
- Modify: `gaps/raft.md`
|
||||
|
||||
**Step 1: Repeat test-first cycle for each remaining batch**
|
||||
- Add failing tests, implement minimal parity, then verify pass.
|
||||
|
||||
**Step 2: Update inventory status immediately after each closure**
|
||||
- Move rows to `PORTED` with exact .NET file:line references.
|
||||
- Keep `PARTIAL` only if explicit residual behavior remains.
|
||||
|
||||
## Task 5: Cross-Module Regression and Hardening
|
||||
|
||||
**Files:**
|
||||
- Test: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
**Step 1: Run full regression suite**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: PASS.
|
||||
|
||||
**Step 2: Add edge-case tests discovered during regression**
|
||||
- Capture any newly discovered divergence from Go and close it before completion.
|
||||
|
||||
## Task 6: Finalize Parity Bookkeeping
|
||||
|
||||
**Files:**
|
||||
- Modify: `gaps/raft.md`
|
||||
- Modify: `gaps/stillmissing.md`
|
||||
|
||||
**Step 1: Ensure final status correctness**
|
||||
- Verify no unresolved `MISSING` rows remain for `raft`.
|
||||
|
||||
**Step 2: Refresh LOC and percentage bookkeeping**
|
||||
- Recount category LOC and update summary rows in `gaps/stillmissing.md`.
|
||||
|
||||
**Step 3: Add concise changelog entry**
|
||||
- Record date, completed parity areas, and any justified deferred items.
|
||||
|
||||
## Completion Checklist
|
||||
- [ ] All prioritized `raft` rows are `PORTED` or explicitly justified as `NOT_APPLICABLE`/`DEFERRED`.
|
||||
- [ ] New/updated tests demonstrate parity for all previously open rows.
|
||||
- [ ] `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release` passes.
|
||||
- [ ] `gaps/raft.md` and `gaps/stillmissing.md` are fully synchronized.
|
||||
@@ -0,0 +1,76 @@
|
||||
# Routes Full Parity Design
|
||||
|
||||
**Date:** 2026-02-25
|
||||
**Category:** `routes`
|
||||
**Gap Inventory:** `gaps/routes.md`
|
||||
**Target Outcome:** Full Go-to-.NET behavioral parity for this category.
|
||||
|
||||
## Assumptions
|
||||
- Full parity is the required outcome.
|
||||
- No additional requirements gathering is needed for this pass.
|
||||
- Existing public .NET APIs are preserved unless parity requires targeted, documented adjustments.
|
||||
|
||||
## Current State Snapshot
|
||||
- `MISSING`: 33
|
||||
- `PARTIAL`: 21
|
||||
- `PORTED`: 4
|
||||
- `NOT_APPLICABLE`: 9
|
||||
- `DEFERRED`: 0
|
||||
- **Open parity work items:** 54
|
||||
|
||||
## Approaches Considered
|
||||
1. **Symbol-first closure (Recommended):** execute directly against each `MISSING` and `PARTIAL` row in `gaps/routes.md`, closing items in deterministic order.
|
||||
Trade-off: strongest traceability and auditability, but can require broad context switching.
|
||||
2. **Test-first closure:** derive all work from Go test scenarios first, then backfill missing symbols.
|
||||
Trade-off: maximizes behavioral confidence, but can miss non-test-covered API and helper parity.
|
||||
3. **Vertical runtime slices:** port connect/auth/pubsub/cluster flows end-to-end before moving to next slice.
|
||||
Trade-off: smoother integration validation, but weaker one-to-one mapping against inventory rows.
|
||||
|
||||
## Recommended Design
|
||||
### Architecture
|
||||
Use the gap inventory as the authoritative backlog. For each row, implement or complete the .NET equivalent, then attach test evidence and update the inventory status. Keep implementation inside existing category boundaries and only refactor when needed to match Go behavior.
|
||||
|
||||
### Components
|
||||
- **Backlog driver:** `gaps/routes.md` (row-level tracking for all parity items).
|
||||
- **Implementation surface:** category-specific .NET source files.
|
||||
- **Verification surface:** category-specific .NET tests plus cross-module regression tests.
|
||||
- **Parity bookkeeping:** updates to `gaps/routes.md` and `gaps/stillmissing.md` after each closure batch.
|
||||
|
||||
### Data Flow
|
||||
1. Read one `MISSING` or `PARTIAL` row from `gaps/routes.md`.
|
||||
2. Inspect referenced Go implementation and tests.
|
||||
3. Add/adjust .NET implementation in listed category files.
|
||||
4. Add failing .NET tests that reproduce the missing/partial behavior, then make them pass.
|
||||
5. Re-run targeted and regression tests.
|
||||
6. Update the row to `PORTED` (or keep `PARTIAL` with explicit residual notes).
|
||||
|
||||
### Error Handling and Risk Controls
|
||||
- Avoid broad rewrites; prefer narrow patches tied to specific inventory rows.
|
||||
- Preserve protocol and timing semantics when replacing Go concurrency constructs with .NET async patterns.
|
||||
- Treat edge cases in Go tests as mandatory parity requirements unless explicitly marked `NOT_APPLICABLE`.
|
||||
- Require evidence (tests + inventory status updates) before declaring row completion.
|
||||
|
||||
### Testing Strategy
|
||||
- Add or expand tests for each row moved from `MISSING`/`PARTIAL` toward `PORTED`.
|
||||
- Prioritize wire/protocol compatibility, lifecycle transitions, auth boundaries, and backpressure/timeout behavior.
|
||||
- Keep category-targeted tests fast; run full `NATS.Server.Tests` regression before closure.
|
||||
|
||||
### Definition of Done
|
||||
- All category rows are either `PORTED` or explicitly justified `NOT_APPLICABLE`/`DEFERRED`.
|
||||
- No unresolved `MISSING` or unqualified `PARTIAL` rows remain for this category.
|
||||
- Relevant .NET tests pass locally and parity evidence is written into inventory notes.
|
||||
|
||||
## Implementation Scope Inputs
|
||||
### Go Source Files
|
||||
- `golang/nats-server/server/route.go`
|
||||
|
||||
### Go Test Files
|
||||
- `golang/nats-server/server/routes_test.go`
|
||||
- `golang/nats-server/test/routes_test.go (integration)`
|
||||
- `golang/nats-server/test/new_routes_test.go (integration)`
|
||||
|
||||
### .NET Source Files
|
||||
- `src/NATS.Server/Routes/ (all files)`
|
||||
|
||||
### .NET Test Files
|
||||
- `tests/NATS.Server.Tests/Routes/`
|
||||
@@ -0,0 +1,103 @@
|
||||
# Routes Gap Port Implementation Plan
|
||||
|
||||
> **For Codex:** REQUIRED SUB-SKILL: Use `executeplan` to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Port all remaining `routes` gap inventory items to achieve full behavior parity with Go.
|
||||
|
||||
**Architecture:** Use the category gap inventory as the execution backlog. Drive work row-by-row from `MISSING` and `PARTIAL` toward `PORTED`, with test-first validation and explicit parity evidence in notes.
|
||||
|
||||
**Tech Stack:** .NET 10, C#, xUnit, existing `NATS.Server` runtime modules, Go NATS server reference source.
|
||||
|
||||
---
|
||||
|
||||
## Category Inputs
|
||||
- Gap Inventory: `gaps/routes.md`
|
||||
- Primary .NET Source Anchor: `src/NATS.Server/Routes/ (all files)`
|
||||
- Primary .NET Test Anchor: `tests/NATS.Server.Tests/Routes/`
|
||||
- Full .NET test project: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
## Task 1: Freeze Row-Level Backlog
|
||||
|
||||
**Files:**
|
||||
- Modify: `gaps/routes.md`
|
||||
|
||||
**Step 1: Normalize open rows into an execution queue**
|
||||
- Split all `MISSING` and `PARTIAL` rows into ranked batches (highest runtime impact first).
|
||||
|
||||
**Step 2: Record acceptance criteria per row**
|
||||
- For each row, define exact behavioral parity checks (inputs, outputs, error paths, timing/concurrency expectations).
|
||||
|
||||
**Step 3: Save queue annotations in notes**
|
||||
- Add short execution-order markers in notes without changing status yet.
|
||||
|
||||
## Task 2: Write Failing Tests for Batch 1
|
||||
|
||||
**Files:**
|
||||
- Modify: `tests/NATS.Server.Tests/Routes/`
|
||||
- Test: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
**Step 1: Add failing tests for first open row batch**
|
||||
- Create tests that reproduce the exact missing/partial Go behavior.
|
||||
|
||||
**Step 2: Run tests to confirm failure**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: FAIL in newly added tests only.
|
||||
|
||||
## Task 3: Implement Minimal Parity for Batch 1
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/NATS.Server/Routes/ (all files)`
|
||||
- Modify: additional category files listed in `gaps/routes.md`
|
||||
|
||||
**Step 1: Implement minimal code to satisfy failing tests**
|
||||
- Mirror Go semantics for parsing, state transitions, limits, and side-effects required by the targeted rows.
|
||||
|
||||
**Step 2: Re-run tests for validation**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: PASS for batch-1 tests; no new regressions.
|
||||
|
||||
## Task 4: Close Remaining Batches Iteratively
|
||||
|
||||
**Files:**
|
||||
- Modify: category .NET source and test files referenced in `gaps/routes.md`
|
||||
- Modify: `gaps/routes.md`
|
||||
|
||||
**Step 1: Repeat test-first cycle for each remaining batch**
|
||||
- Add failing tests, implement minimal parity, then verify pass.
|
||||
|
||||
**Step 2: Update inventory status immediately after each closure**
|
||||
- Move rows to `PORTED` with exact .NET file:line references.
|
||||
- Keep `PARTIAL` only if explicit residual behavior remains.
|
||||
|
||||
## Task 5: Cross-Module Regression and Hardening
|
||||
|
||||
**Files:**
|
||||
- Test: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
**Step 1: Run full regression suite**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: PASS.
|
||||
|
||||
**Step 2: Add edge-case tests discovered during regression**
|
||||
- Capture any newly discovered divergence from Go and close it before completion.
|
||||
|
||||
## Task 6: Finalize Parity Bookkeeping
|
||||
|
||||
**Files:**
|
||||
- Modify: `gaps/routes.md`
|
||||
- Modify: `gaps/stillmissing.md`
|
||||
|
||||
**Step 1: Ensure final status correctness**
|
||||
- Verify no unresolved `MISSING` rows remain for `routes`.
|
||||
|
||||
**Step 2: Refresh LOC and percentage bookkeeping**
|
||||
- Recount category LOC and update summary rows in `gaps/stillmissing.md`.
|
||||
|
||||
**Step 3: Add concise changelog entry**
|
||||
- Record date, completed parity areas, and any justified deferred items.
|
||||
|
||||
## Completion Checklist
|
||||
- [ ] All prioritized `routes` rows are `PORTED` or explicitly justified as `NOT_APPLICABLE`/`DEFERRED`.
|
||||
- [ ] New/updated tests demonstrate parity for all previously open rows.
|
||||
- [ ] `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release` passes.
|
||||
- [ ] `gaps/routes.md` and `gaps/stillmissing.md` are fully synchronized.
|
||||
@@ -0,0 +1,81 @@
|
||||
# Subscriptions Full Parity Design
|
||||
|
||||
**Date:** 2026-02-25
|
||||
**Category:** `subscriptions`
|
||||
**Gap Inventory:** `gaps/subscriptions.md`
|
||||
**Target Outcome:** Full Go-to-.NET behavioral parity for this category.
|
||||
|
||||
## Assumptions
|
||||
- Full parity is the required outcome.
|
||||
- No additional requirements gathering is needed for this pass.
|
||||
- Existing public .NET APIs are preserved unless parity requires targeted, documented adjustments.
|
||||
|
||||
## Current State Snapshot
|
||||
- `MISSING`: 22
|
||||
- `PARTIAL`: 6
|
||||
- `PORTED`: 49
|
||||
- `NOT_APPLICABLE`: 27
|
||||
- `DEFERRED`: 0
|
||||
- **Open parity work items:** 28
|
||||
|
||||
## Approaches Considered
|
||||
1. **Symbol-first closure (Recommended):** execute directly against each `MISSING` and `PARTIAL` row in `gaps/subscriptions.md`, closing items in deterministic order.
|
||||
Trade-off: strongest traceability and auditability, but can require broad context switching.
|
||||
2. **Test-first closure:** derive all work from Go test scenarios first, then backfill missing symbols.
|
||||
Trade-off: maximizes behavioral confidence, but can miss non-test-covered API and helper parity.
|
||||
3. **Vertical runtime slices:** port connect/auth/pubsub/cluster flows end-to-end before moving to next slice.
|
||||
Trade-off: smoother integration validation, but weaker one-to-one mapping against inventory rows.
|
||||
|
||||
## Recommended Design
|
||||
### Architecture
|
||||
Use the gap inventory as the authoritative backlog. For each row, implement or complete the .NET equivalent, then attach test evidence and update the inventory status. Keep implementation inside existing category boundaries and only refactor when needed to match Go behavior.
|
||||
|
||||
### Components
|
||||
- **Backlog driver:** `gaps/subscriptions.md` (row-level tracking for all parity items).
|
||||
- **Implementation surface:** category-specific .NET source files.
|
||||
- **Verification surface:** category-specific .NET tests plus cross-module regression tests.
|
||||
- **Parity bookkeeping:** updates to `gaps/subscriptions.md` and `gaps/stillmissing.md` after each closure batch.
|
||||
|
||||
### Data Flow
|
||||
1. Read one `MISSING` or `PARTIAL` row from `gaps/subscriptions.md`.
|
||||
2. Inspect referenced Go implementation and tests.
|
||||
3. Add/adjust .NET implementation in listed category files.
|
||||
4. Add failing .NET tests that reproduce the missing/partial behavior, then make them pass.
|
||||
5. Re-run targeted and regression tests.
|
||||
6. Update the row to `PORTED` (or keep `PARTIAL` with explicit residual notes).
|
||||
|
||||
### Error Handling and Risk Controls
|
||||
- Avoid broad rewrites; prefer narrow patches tied to specific inventory rows.
|
||||
- Preserve protocol and timing semantics when replacing Go concurrency constructs with .NET async patterns.
|
||||
- Treat edge cases in Go tests as mandatory parity requirements unless explicitly marked `NOT_APPLICABLE`.
|
||||
- Require evidence (tests + inventory status updates) before declaring row completion.
|
||||
|
||||
### Testing Strategy
|
||||
- Add or expand tests for each row moved from `MISSING`/`PARTIAL` toward `PORTED`.
|
||||
- Prioritize wire/protocol compatibility, lifecycle transitions, auth boundaries, and backpressure/timeout behavior.
|
||||
- Keep category-targeted tests fast; run full `NATS.Server.Tests` regression before closure.
|
||||
|
||||
### Definition of Done
|
||||
- All category rows are either `PORTED` or explicitly justified `NOT_APPLICABLE`/`DEFERRED`.
|
||||
- No unresolved `MISSING` or unqualified `PARTIAL` rows remain for this category.
|
||||
- Relevant .NET tests pass locally and parity evidence is written into inventory notes.
|
||||
|
||||
## Implementation Scope Inputs
|
||||
### Go Source Files
|
||||
- `golang/nats-server/server/sublist.go`
|
||||
- `golang/nats-server/server/subject_transform.go`
|
||||
|
||||
### Go Test Files
|
||||
- `golang/nats-server/server/sublist_test.go`
|
||||
- `golang/nats-server/server/subject_transform_test.go`
|
||||
|
||||
### .NET Source Files
|
||||
- `src/NATS.Server/Subscriptions/SubjectMatch.cs`
|
||||
- `src/NATS.Server/Subscriptions/SubList.cs`
|
||||
- `src/NATS.Server/Subscriptions/SubListResult.cs`
|
||||
- `src/NATS.Server/Subscriptions/Subscription.cs`
|
||||
- `All other files in src/NATS.Server/Subscriptions/`
|
||||
|
||||
### .NET Test Files
|
||||
- `tests/NATS.Server.Tests/Subscriptions/`
|
||||
- `tests/NATS.Server.Tests/SubList/`
|
||||
@@ -0,0 +1,103 @@
|
||||
# Subscriptions Gap Port Implementation Plan
|
||||
|
||||
> **For Codex:** REQUIRED SUB-SKILL: Use `executeplan` to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Port all remaining `subscriptions` gap inventory items to achieve full behavior parity with Go.
|
||||
|
||||
**Architecture:** Use the category gap inventory as the execution backlog. Drive work row-by-row from `MISSING` and `PARTIAL` toward `PORTED`, with test-first validation and explicit parity evidence in notes.
|
||||
|
||||
**Tech Stack:** .NET 10, C#, xUnit, existing `NATS.Server` runtime modules, Go NATS server reference source.
|
||||
|
||||
---
|
||||
|
||||
## Category Inputs
|
||||
- Gap Inventory: `gaps/subscriptions.md`
|
||||
- Primary .NET Source Anchor: `src/NATS.Server/Subscriptions/SubjectMatch.cs`
|
||||
- Primary .NET Test Anchor: `tests/NATS.Server.Tests/Subscriptions/`
|
||||
- Full .NET test project: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
## Task 1: Freeze Row-Level Backlog
|
||||
|
||||
**Files:**
|
||||
- Modify: `gaps/subscriptions.md`
|
||||
|
||||
**Step 1: Normalize open rows into an execution queue**
|
||||
- Split all `MISSING` and `PARTIAL` rows into ranked batches (highest runtime impact first).
|
||||
|
||||
**Step 2: Record acceptance criteria per row**
|
||||
- For each row, define exact behavioral parity checks (inputs, outputs, error paths, timing/concurrency expectations).
|
||||
|
||||
**Step 3: Save queue annotations in notes**
|
||||
- Add short execution-order markers in notes without changing status yet.
|
||||
|
||||
## Task 2: Write Failing Tests for Batch 1
|
||||
|
||||
**Files:**
|
||||
- Modify: `tests/NATS.Server.Tests/Subscriptions/`
|
||||
- Test: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
**Step 1: Add failing tests for first open row batch**
|
||||
- Create tests that reproduce the exact missing/partial Go behavior.
|
||||
|
||||
**Step 2: Run tests to confirm failure**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: FAIL in newly added tests only.
|
||||
|
||||
## Task 3: Implement Minimal Parity for Batch 1
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/NATS.Server/Subscriptions/SubjectMatch.cs`
|
||||
- Modify: additional category files listed in `gaps/subscriptions.md`
|
||||
|
||||
**Step 1: Implement minimal code to satisfy failing tests**
|
||||
- Mirror Go semantics for parsing, state transitions, limits, and side-effects required by the targeted rows.
|
||||
|
||||
**Step 2: Re-run tests for validation**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: PASS for batch-1 tests; no new regressions.
|
||||
|
||||
## Task 4: Close Remaining Batches Iteratively
|
||||
|
||||
**Files:**
|
||||
- Modify: category .NET source and test files referenced in `gaps/subscriptions.md`
|
||||
- Modify: `gaps/subscriptions.md`
|
||||
|
||||
**Step 1: Repeat test-first cycle for each remaining batch**
|
||||
- Add failing tests, implement minimal parity, then verify pass.
|
||||
|
||||
**Step 2: Update inventory status immediately after each closure**
|
||||
- Move rows to `PORTED` with exact .NET file:line references.
|
||||
- Keep `PARTIAL` only if explicit residual behavior remains.
|
||||
|
||||
## Task 5: Cross-Module Regression and Hardening
|
||||
|
||||
**Files:**
|
||||
- Test: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
**Step 1: Run full regression suite**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: PASS.
|
||||
|
||||
**Step 2: Add edge-case tests discovered during regression**
|
||||
- Capture any newly discovered divergence from Go and close it before completion.
|
||||
|
||||
## Task 6: Finalize Parity Bookkeeping
|
||||
|
||||
**Files:**
|
||||
- Modify: `gaps/subscriptions.md`
|
||||
- Modify: `gaps/stillmissing.md`
|
||||
|
||||
**Step 1: Ensure final status correctness**
|
||||
- Verify no unresolved `MISSING` rows remain for `subscriptions`.
|
||||
|
||||
**Step 2: Refresh LOC and percentage bookkeeping**
|
||||
- Recount category LOC and update summary rows in `gaps/stillmissing.md`.
|
||||
|
||||
**Step 3: Add concise changelog entry**
|
||||
- Record date, completed parity areas, and any justified deferred items.
|
||||
|
||||
## Completion Checklist
|
||||
- [ ] All prioritized `subscriptions` rows are `PORTED` or explicitly justified as `NOT_APPLICABLE`/`DEFERRED`.
|
||||
- [ ] New/updated tests demonstrate parity for all previously open rows.
|
||||
- [ ] `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release` passes.
|
||||
- [ ] `gaps/subscriptions.md` and `gaps/stillmissing.md` are fully synchronized.
|
||||
@@ -0,0 +1,95 @@
|
||||
# TLS / Security Full Parity Design
|
||||
|
||||
**Date:** 2026-02-25
|
||||
**Category:** `tls-security`
|
||||
**Gap Inventory:** `gaps/tls-security.md`
|
||||
**Target Outcome:** Full Go-to-.NET behavioral parity for this category.
|
||||
|
||||
## Assumptions
|
||||
- Full parity is the required outcome.
|
||||
- No additional requirements gathering is needed for this pass.
|
||||
- Existing public .NET APIs are preserved unless parity requires targeted, documented adjustments.
|
||||
|
||||
## Current State Snapshot
|
||||
- `MISSING`: 70
|
||||
- `PARTIAL`: 10
|
||||
- `PORTED`: 20
|
||||
- `NOT_APPLICABLE`: 45
|
||||
- `DEFERRED`: 0
|
||||
- **Open parity work items:** 80
|
||||
|
||||
## Approaches Considered
|
||||
1. **Symbol-first closure (Recommended):** execute directly against each `MISSING` and `PARTIAL` row in `gaps/tls-security.md`, closing items in deterministic order.
|
||||
Trade-off: strongest traceability and auditability, but can require broad context switching.
|
||||
2. **Test-first closure:** derive all work from Go test scenarios first, then backfill missing symbols.
|
||||
Trade-off: maximizes behavioral confidence, but can miss non-test-covered API and helper parity.
|
||||
3. **Vertical runtime slices:** port connect/auth/pubsub/cluster flows end-to-end before moving to next slice.
|
||||
Trade-off: smoother integration validation, but weaker one-to-one mapping against inventory rows.
|
||||
|
||||
## Recommended Design
|
||||
### Architecture
|
||||
Use the gap inventory as the authoritative backlog. For each row, implement or complete the .NET equivalent, then attach test evidence and update the inventory status. Keep implementation inside existing category boundaries and only refactor when needed to match Go behavior.
|
||||
|
||||
### Components
|
||||
- **Backlog driver:** `gaps/tls-security.md` (row-level tracking for all parity items).
|
||||
- **Implementation surface:** category-specific .NET source files.
|
||||
- **Verification surface:** category-specific .NET tests plus cross-module regression tests.
|
||||
- **Parity bookkeeping:** updates to `gaps/tls-security.md` and `gaps/stillmissing.md` after each closure batch.
|
||||
|
||||
### Data Flow
|
||||
1. Read one `MISSING` or `PARTIAL` row from `gaps/tls-security.md`.
|
||||
2. Inspect referenced Go implementation and tests.
|
||||
3. Add/adjust .NET implementation in listed category files.
|
||||
4. Add failing .NET tests that reproduce the missing/partial behavior, then make them pass.
|
||||
5. Re-run targeted and regression tests.
|
||||
6. Update the row to `PORTED` (or keep `PARTIAL` with explicit residual notes).
|
||||
|
||||
### Error Handling and Risk Controls
|
||||
- Avoid broad rewrites; prefer narrow patches tied to specific inventory rows.
|
||||
- Preserve protocol and timing semantics when replacing Go concurrency constructs with .NET async patterns.
|
||||
- Treat edge cases in Go tests as mandatory parity requirements unless explicitly marked `NOT_APPLICABLE`.
|
||||
- Require evidence (tests + inventory status updates) before declaring row completion.
|
||||
|
||||
### Testing Strategy
|
||||
- Add or expand tests for each row moved from `MISSING`/`PARTIAL` toward `PORTED`.
|
||||
- Prioritize wire/protocol compatibility, lifecycle transitions, auth boundaries, and backpressure/timeout behavior.
|
||||
- Keep category-targeted tests fast; run full `NATS.Server.Tests` regression before closure.
|
||||
|
||||
### Definition of Done
|
||||
- All category rows are either `PORTED` or explicitly justified `NOT_APPLICABLE`/`DEFERRED`.
|
||||
- No unresolved `MISSING` or unqualified `PARTIAL` rows remain for this category.
|
||||
- Relevant .NET tests pass locally and parity evidence is written into inventory notes.
|
||||
|
||||
## Implementation Scope Inputs
|
||||
### Go Source Files
|
||||
- `golang/nats-server/server/ocsp.go`
|
||||
- `golang/nats-server/server/ocsp_peer.go`
|
||||
- `golang/nats-server/server/ocsp_responsecache.go`
|
||||
- `golang/nats-server/server/ciphersuites.go`
|
||||
- `golang/nats-server/server/client_proxyproto.go`
|
||||
- `golang/nats-server/server/certidp/certidp.go`
|
||||
- `golang/nats-server/server/certidp/messages.go`
|
||||
- `golang/nats-server/server/certidp/ocsp_responder.go`
|
||||
- `golang/nats-server/server/certstore/certstore.go`
|
||||
- `golang/nats-server/server/certstore/certstore_other.go`
|
||||
- `golang/nats-server/server/certstore/certstore_windows.go`
|
||||
- `golang/nats-server/server/certstore/errors.go`
|
||||
|
||||
### Go Test Files
|
||||
- `golang/nats-server/test/ocsp_test.go (integration)`
|
||||
- `golang/nats-server/test/ocsp_peer_test.go (integration)`
|
||||
- `golang/nats-server/test/tls_test.go (integration)`
|
||||
- `golang/nats-server/server/certstore/certstore_windows_test.go`
|
||||
- `golang/nats-server/server/certidp/*_test.go`
|
||||
|
||||
### .NET Source Files
|
||||
- `src/NATS.Server/Tls/TlsHelper.cs`
|
||||
- `src/NATS.Server/Tls/TlsCertificateProvider.cs`
|
||||
- `src/NATS.Server/Tls/TlsConnectionState.cs`
|
||||
- `src/NATS.Server/Tls/TlsConnectionWrapper.cs`
|
||||
- `src/NATS.Server/Tls/TlsRateLimiter.cs`
|
||||
- `src/NATS.Server/Tls/PeekableStream.cs`
|
||||
- `src/NATS.Server/Tls/OcspConfig.cs`
|
||||
|
||||
### .NET Test Files
|
||||
- `tests/NATS.Server.Tests/ (TLS-related test files in root)`
|
||||
@@ -0,0 +1,103 @@
|
||||
# TLS / Security Gap Port Implementation Plan
|
||||
|
||||
> **For Codex:** REQUIRED SUB-SKILL: Use `executeplan` to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Port all remaining `tls-security` gap inventory items to achieve full behavior parity with Go.
|
||||
|
||||
**Architecture:** Use the category gap inventory as the execution backlog. Drive work row-by-row from `MISSING` and `PARTIAL` toward `PORTED`, with test-first validation and explicit parity evidence in notes.
|
||||
|
||||
**Tech Stack:** .NET 10, C#, xUnit, existing `NATS.Server` runtime modules, Go NATS server reference source.
|
||||
|
||||
---
|
||||
|
||||
## Category Inputs
|
||||
- Gap Inventory: `gaps/tls-security.md`
|
||||
- Primary .NET Source Anchor: `src/NATS.Server/Tls/TlsHelper.cs`
|
||||
- Primary .NET Test Anchor: `tests/NATS.Server.Tests/ (TLS-related test files in root)`
|
||||
- Full .NET test project: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
## Task 1: Freeze Row-Level Backlog
|
||||
|
||||
**Files:**
|
||||
- Modify: `gaps/tls-security.md`
|
||||
|
||||
**Step 1: Normalize open rows into an execution queue**
|
||||
- Split all `MISSING` and `PARTIAL` rows into ranked batches (highest runtime impact first).
|
||||
|
||||
**Step 2: Record acceptance criteria per row**
|
||||
- For each row, define exact behavioral parity checks (inputs, outputs, error paths, timing/concurrency expectations).
|
||||
|
||||
**Step 3: Save queue annotations in notes**
|
||||
- Add short execution-order markers in notes without changing status yet.
|
||||
|
||||
## Task 2: Write Failing Tests for Batch 1
|
||||
|
||||
**Files:**
|
||||
- Modify: `tests/NATS.Server.Tests/ (TLS-related test files in root)`
|
||||
- Test: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
**Step 1: Add failing tests for first open row batch**
|
||||
- Create tests that reproduce the exact missing/partial Go behavior.
|
||||
|
||||
**Step 2: Run tests to confirm failure**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: FAIL in newly added tests only.
|
||||
|
||||
## Task 3: Implement Minimal Parity for Batch 1
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/NATS.Server/Tls/TlsHelper.cs`
|
||||
- Modify: additional category files listed in `gaps/tls-security.md`
|
||||
|
||||
**Step 1: Implement minimal code to satisfy failing tests**
|
||||
- Mirror Go semantics for parsing, state transitions, limits, and side-effects required by the targeted rows.
|
||||
|
||||
**Step 2: Re-run tests for validation**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: PASS for batch-1 tests; no new regressions.
|
||||
|
||||
## Task 4: Close Remaining Batches Iteratively
|
||||
|
||||
**Files:**
|
||||
- Modify: category .NET source and test files referenced in `gaps/tls-security.md`
|
||||
- Modify: `gaps/tls-security.md`
|
||||
|
||||
**Step 1: Repeat test-first cycle for each remaining batch**
|
||||
- Add failing tests, implement minimal parity, then verify pass.
|
||||
|
||||
**Step 2: Update inventory status immediately after each closure**
|
||||
- Move rows to `PORTED` with exact .NET file:line references.
|
||||
- Keep `PARTIAL` only if explicit residual behavior remains.
|
||||
|
||||
## Task 5: Cross-Module Regression and Hardening
|
||||
|
||||
**Files:**
|
||||
- Test: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
**Step 1: Run full regression suite**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: PASS.
|
||||
|
||||
**Step 2: Add edge-case tests discovered during regression**
|
||||
- Capture any newly discovered divergence from Go and close it before completion.
|
||||
|
||||
## Task 6: Finalize Parity Bookkeeping
|
||||
|
||||
**Files:**
|
||||
- Modify: `gaps/tls-security.md`
|
||||
- Modify: `gaps/stillmissing.md`
|
||||
|
||||
**Step 1: Ensure final status correctness**
|
||||
- Verify no unresolved `MISSING` rows remain for `tls-security`.
|
||||
|
||||
**Step 2: Refresh LOC and percentage bookkeeping**
|
||||
- Recount category LOC and update summary rows in `gaps/stillmissing.md`.
|
||||
|
||||
**Step 3: Add concise changelog entry**
|
||||
- Record date, completed parity areas, and any justified deferred items.
|
||||
|
||||
## Completion Checklist
|
||||
- [ ] All prioritized `tls-security` rows are `PORTED` or explicitly justified as `NOT_APPLICABLE`/`DEFERRED`.
|
||||
- [ ] New/updated tests demonstrate parity for all previously open rows.
|
||||
- [ ] `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release` passes.
|
||||
- [ ] `gaps/tls-security.md` and `gaps/stillmissing.md` are fully synchronized.
|
||||
@@ -0,0 +1,93 @@
|
||||
# Utilities & Other Full Parity Design
|
||||
|
||||
**Date:** 2026-02-25
|
||||
**Category:** `utilities-and-other`
|
||||
**Gap Inventory:** `gaps/utilities-and-other.md`
|
||||
**Target Outcome:** Full Go-to-.NET behavioral parity for this category.
|
||||
|
||||
## Assumptions
|
||||
- Full parity is the required outcome.
|
||||
- No additional requirements gathering is needed for this pass.
|
||||
- Existing public .NET APIs are preserved unless parity requires targeted, documented adjustments.
|
||||
|
||||
## Current State Snapshot
|
||||
- `MISSING`: 62
|
||||
- `PARTIAL`: 10
|
||||
- `PORTED`: 92
|
||||
- `NOT_APPLICABLE`: 47
|
||||
- `DEFERRED`: 9
|
||||
- **Open parity work items:** 72
|
||||
|
||||
## Approaches Considered
|
||||
1. **Symbol-first closure (Recommended):** execute directly against each `MISSING` and `PARTIAL` row in `gaps/utilities-and-other.md`, closing items in deterministic order.
|
||||
Trade-off: strongest traceability and auditability, but can require broad context switching.
|
||||
2. **Test-first closure:** derive all work from Go test scenarios first, then backfill missing symbols.
|
||||
Trade-off: maximizes behavioral confidence, but can miss non-test-covered API and helper parity.
|
||||
3. **Vertical runtime slices:** port connect/auth/pubsub/cluster flows end-to-end before moving to next slice.
|
||||
Trade-off: smoother integration validation, but weaker one-to-one mapping against inventory rows.
|
||||
|
||||
## Recommended Design
|
||||
### Architecture
|
||||
Use the gap inventory as the authoritative backlog. For each row, implement or complete the .NET equivalent, then attach test evidence and update the inventory status. Keep implementation inside existing category boundaries and only refactor when needed to match Go behavior.
|
||||
|
||||
### Components
|
||||
- **Backlog driver:** `gaps/utilities-and-other.md` (row-level tracking for all parity items).
|
||||
- **Implementation surface:** category-specific .NET source files.
|
||||
- **Verification surface:** category-specific .NET tests plus cross-module regression tests.
|
||||
- **Parity bookkeeping:** updates to `gaps/utilities-and-other.md` and `gaps/stillmissing.md` after each closure batch.
|
||||
|
||||
### Data Flow
|
||||
1. Read one `MISSING` or `PARTIAL` row from `gaps/utilities-and-other.md`.
|
||||
2. Inspect referenced Go implementation and tests.
|
||||
3. Add/adjust .NET implementation in listed category files.
|
||||
4. Add failing .NET tests that reproduce the missing/partial behavior, then make them pass.
|
||||
5. Re-run targeted and regression tests.
|
||||
6. Update the row to `PORTED` (or keep `PARTIAL` with explicit residual notes).
|
||||
|
||||
### Error Handling and Risk Controls
|
||||
- Avoid broad rewrites; prefer narrow patches tied to specific inventory rows.
|
||||
- Preserve protocol and timing semantics when replacing Go concurrency constructs with .NET async patterns.
|
||||
- Treat edge cases in Go tests as mandatory parity requirements unless explicitly marked `NOT_APPLICABLE`.
|
||||
- Require evidence (tests + inventory status updates) before declaring row completion.
|
||||
|
||||
### Testing Strategy
|
||||
- Add or expand tests for each row moved from `MISSING`/`PARTIAL` toward `PORTED`.
|
||||
- Prioritize wire/protocol compatibility, lifecycle transitions, auth boundaries, and backpressure/timeout behavior.
|
||||
- Keep category-targeted tests fast; run full `NATS.Server.Tests` regression before closure.
|
||||
|
||||
### Definition of Done
|
||||
- All category rows are either `PORTED` or explicitly justified `NOT_APPLICABLE`/`DEFERRED`.
|
||||
- No unresolved `MISSING` or unqualified `PARTIAL` rows remain for this category.
|
||||
- Relevant .NET tests pass locally and parity evidence is written into inventory notes.
|
||||
|
||||
## Implementation Scope Inputs
|
||||
### Go Source Files
|
||||
- `golang/nats-server/server/util.go`
|
||||
- `golang/nats-server/server/ring.go`
|
||||
- `golang/nats-server/server/rate_counter.go`
|
||||
- `golang/nats-server/server/sendq.go`
|
||||
- `golang/nats-server/server/ipqueue.go`
|
||||
- `golang/nats-server/server/errors.go`
|
||||
- `golang/nats-server/server/errors_gen.go`
|
||||
- `golang/nats-server/server/sdm.go`
|
||||
- `golang/nats-server/server/scheduler.go`
|
||||
- `golang/nats-server/server/ats/ats.go`
|
||||
- `golang/nats-server/server/elastic/elastic.go`
|
||||
- `golang/nats-server/server/tpm/js_ek_tpm_windows.go`
|
||||
- `golang/nats-server/server/tpm/js_ek_tpm_other.go`
|
||||
- `golang/nats-server/internal/`
|
||||
|
||||
### Go Test Files
|
||||
- `golang/nats-server/server/util_test.go`
|
||||
- `golang/nats-server/server/ring_test.go`
|
||||
- `golang/nats-server/server/rate_counter_test.go`
|
||||
- `golang/nats-server/server/ipqueue_test.go`
|
||||
- `golang/nats-server/server/errors_test.go`
|
||||
|
||||
### .NET Source Files
|
||||
- `src/NATS.Server/IO/OutboundBufferPool.cs`
|
||||
- `src/NATS.Server/IO/AdaptiveReadBuffer.cs`
|
||||
- `src/NATS.Server/Server/AcceptLoopErrorHandler.cs`
|
||||
|
||||
### .NET Test Files
|
||||
- `tests/NATS.Server.Tests/IO/`
|
||||
@@ -0,0 +1,103 @@
|
||||
# Utilities & Other Gap Port Implementation Plan
|
||||
|
||||
> **For Codex:** REQUIRED SUB-SKILL: Use `executeplan` to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Port all remaining `utilities-and-other` gap inventory items to achieve full behavior parity with Go.
|
||||
|
||||
**Architecture:** Use the category gap inventory as the execution backlog. Drive work row-by-row from `MISSING` and `PARTIAL` toward `PORTED`, with test-first validation and explicit parity evidence in notes.
|
||||
|
||||
**Tech Stack:** .NET 10, C#, xUnit, existing `NATS.Server` runtime modules, Go NATS server reference source.
|
||||
|
||||
---
|
||||
|
||||
## Category Inputs
|
||||
- Gap Inventory: `gaps/utilities-and-other.md`
|
||||
- Primary .NET Source Anchor: `src/NATS.Server/IO/OutboundBufferPool.cs`
|
||||
- Primary .NET Test Anchor: `tests/NATS.Server.Tests/IO/`
|
||||
- Full .NET test project: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
## Task 1: Freeze Row-Level Backlog
|
||||
|
||||
**Files:**
|
||||
- Modify: `gaps/utilities-and-other.md`
|
||||
|
||||
**Step 1: Normalize open rows into an execution queue**
|
||||
- Split all `MISSING` and `PARTIAL` rows into ranked batches (highest runtime impact first).
|
||||
|
||||
**Step 2: Record acceptance criteria per row**
|
||||
- For each row, define exact behavioral parity checks (inputs, outputs, error paths, timing/concurrency expectations).
|
||||
|
||||
**Step 3: Save queue annotations in notes**
|
||||
- Add short execution-order markers in notes without changing status yet.
|
||||
|
||||
## Task 2: Write Failing Tests for Batch 1
|
||||
|
||||
**Files:**
|
||||
- Modify: `tests/NATS.Server.Tests/IO/`
|
||||
- Test: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
**Step 1: Add failing tests for first open row batch**
|
||||
- Create tests that reproduce the exact missing/partial Go behavior.
|
||||
|
||||
**Step 2: Run tests to confirm failure**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: FAIL in newly added tests only.
|
||||
|
||||
## Task 3: Implement Minimal Parity for Batch 1
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/NATS.Server/IO/OutboundBufferPool.cs`
|
||||
- Modify: additional category files listed in `gaps/utilities-and-other.md`
|
||||
|
||||
**Step 1: Implement minimal code to satisfy failing tests**
|
||||
- Mirror Go semantics for parsing, state transitions, limits, and side-effects required by the targeted rows.
|
||||
|
||||
**Step 2: Re-run tests for validation**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: PASS for batch-1 tests; no new regressions.
|
||||
|
||||
## Task 4: Close Remaining Batches Iteratively
|
||||
|
||||
**Files:**
|
||||
- Modify: category .NET source and test files referenced in `gaps/utilities-and-other.md`
|
||||
- Modify: `gaps/utilities-and-other.md`
|
||||
|
||||
**Step 1: Repeat test-first cycle for each remaining batch**
|
||||
- Add failing tests, implement minimal parity, then verify pass.
|
||||
|
||||
**Step 2: Update inventory status immediately after each closure**
|
||||
- Move rows to `PORTED` with exact .NET file:line references.
|
||||
- Keep `PARTIAL` only if explicit residual behavior remains.
|
||||
|
||||
## Task 5: Cross-Module Regression and Hardening
|
||||
|
||||
**Files:**
|
||||
- Test: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
**Step 1: Run full regression suite**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: PASS.
|
||||
|
||||
**Step 2: Add edge-case tests discovered during regression**
|
||||
- Capture any newly discovered divergence from Go and close it before completion.
|
||||
|
||||
## Task 6: Finalize Parity Bookkeeping
|
||||
|
||||
**Files:**
|
||||
- Modify: `gaps/utilities-and-other.md`
|
||||
- Modify: `gaps/stillmissing.md`
|
||||
|
||||
**Step 1: Ensure final status correctness**
|
||||
- Verify no unresolved `MISSING` rows remain for `utilities-and-other`.
|
||||
|
||||
**Step 2: Refresh LOC and percentage bookkeeping**
|
||||
- Recount category LOC and update summary rows in `gaps/stillmissing.md`.
|
||||
|
||||
**Step 3: Add concise changelog entry**
|
||||
- Record date, completed parity areas, and any justified deferred items.
|
||||
|
||||
## Completion Checklist
|
||||
- [ ] All prioritized `utilities-and-other` rows are `PORTED` or explicitly justified as `NOT_APPLICABLE`/`DEFERRED`.
|
||||
- [ ] New/updated tests demonstrate parity for all previously open rows.
|
||||
- [ ] `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release` passes.
|
||||
- [ ] `gaps/utilities-and-other.md` and `gaps/stillmissing.md` are fully synchronized.
|
||||
@@ -0,0 +1,74 @@
|
||||
# WebSocket Full Parity Design
|
||||
|
||||
**Date:** 2026-02-25
|
||||
**Category:** `websocket`
|
||||
**Gap Inventory:** `gaps/websocket.md`
|
||||
**Target Outcome:** Full Go-to-.NET behavioral parity for this category.
|
||||
|
||||
## Assumptions
|
||||
- Full parity is the required outcome.
|
||||
- No additional requirements gathering is needed for this pass.
|
||||
- Existing public .NET APIs are preserved unless parity requires targeted, documented adjustments.
|
||||
|
||||
## Current State Snapshot
|
||||
- `MISSING`: 5
|
||||
- `PARTIAL`: 6
|
||||
- `PORTED`: 39
|
||||
- `NOT_APPLICABLE`: 0
|
||||
- `DEFERRED`: 0
|
||||
- **Open parity work items:** 11
|
||||
|
||||
## Approaches Considered
|
||||
1. **Symbol-first closure (Recommended):** execute directly against each `MISSING` and `PARTIAL` row in `gaps/websocket.md`, closing items in deterministic order.
|
||||
Trade-off: strongest traceability and auditability, but can require broad context switching.
|
||||
2. **Test-first closure:** derive all work from Go test scenarios first, then backfill missing symbols.
|
||||
Trade-off: maximizes behavioral confidence, but can miss non-test-covered API and helper parity.
|
||||
3. **Vertical runtime slices:** port connect/auth/pubsub/cluster flows end-to-end before moving to next slice.
|
||||
Trade-off: smoother integration validation, but weaker one-to-one mapping against inventory rows.
|
||||
|
||||
## Recommended Design
|
||||
### Architecture
|
||||
Use the gap inventory as the authoritative backlog. For each row, implement or complete the .NET equivalent, then attach test evidence and update the inventory status. Keep implementation inside existing category boundaries and only refactor when needed to match Go behavior.
|
||||
|
||||
### Components
|
||||
- **Backlog driver:** `gaps/websocket.md` (row-level tracking for all parity items).
|
||||
- **Implementation surface:** category-specific .NET source files.
|
||||
- **Verification surface:** category-specific .NET tests plus cross-module regression tests.
|
||||
- **Parity bookkeeping:** updates to `gaps/websocket.md` and `gaps/stillmissing.md` after each closure batch.
|
||||
|
||||
### Data Flow
|
||||
1. Read one `MISSING` or `PARTIAL` row from `gaps/websocket.md`.
|
||||
2. Inspect referenced Go implementation and tests.
|
||||
3. Add/adjust .NET implementation in listed category files.
|
||||
4. Add failing .NET tests that reproduce the missing/partial behavior, then make them pass.
|
||||
5. Re-run targeted and regression tests.
|
||||
6. Update the row to `PORTED` (or keep `PARTIAL` with explicit residual notes).
|
||||
|
||||
### Error Handling and Risk Controls
|
||||
- Avoid broad rewrites; prefer narrow patches tied to specific inventory rows.
|
||||
- Preserve protocol and timing semantics when replacing Go concurrency constructs with .NET async patterns.
|
||||
- Treat edge cases in Go tests as mandatory parity requirements unless explicitly marked `NOT_APPLICABLE`.
|
||||
- Require evidence (tests + inventory status updates) before declaring row completion.
|
||||
|
||||
### Testing Strategy
|
||||
- Add or expand tests for each row moved from `MISSING`/`PARTIAL` toward `PORTED`.
|
||||
- Prioritize wire/protocol compatibility, lifecycle transitions, auth boundaries, and backpressure/timeout behavior.
|
||||
- Keep category-targeted tests fast; run full `NATS.Server.Tests` regression before closure.
|
||||
|
||||
### Definition of Done
|
||||
- All category rows are either `PORTED` or explicitly justified `NOT_APPLICABLE`/`DEFERRED`.
|
||||
- No unresolved `MISSING` or unqualified `PARTIAL` rows remain for this category.
|
||||
- Relevant .NET tests pass locally and parity evidence is written into inventory notes.
|
||||
|
||||
## Implementation Scope Inputs
|
||||
### Go Source Files
|
||||
- `golang/nats-server/server/websocket.go`
|
||||
|
||||
### Go Test Files
|
||||
- `golang/nats-server/server/websocket_test.go`
|
||||
|
||||
### .NET Source Files
|
||||
- `src/NATS.Server/WebSocket/ (all files)`
|
||||
|
||||
### .NET Test Files
|
||||
- `tests/NATS.Server.Tests/WebSocket/`
|
||||
@@ -0,0 +1,103 @@
|
||||
# WebSocket Gap Port Implementation Plan
|
||||
|
||||
> **For Codex:** REQUIRED SUB-SKILL: Use `executeplan` to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Port all remaining `websocket` gap inventory items to achieve full behavior parity with Go.
|
||||
|
||||
**Architecture:** Use the category gap inventory as the execution backlog. Drive work row-by-row from `MISSING` and `PARTIAL` toward `PORTED`, with test-first validation and explicit parity evidence in notes.
|
||||
|
||||
**Tech Stack:** .NET 10, C#, xUnit, existing `NATS.Server` runtime modules, Go NATS server reference source.
|
||||
|
||||
---
|
||||
|
||||
## Category Inputs
|
||||
- Gap Inventory: `gaps/websocket.md`
|
||||
- Primary .NET Source Anchor: `src/NATS.Server/WebSocket/ (all files)`
|
||||
- Primary .NET Test Anchor: `tests/NATS.Server.Tests/WebSocket/`
|
||||
- Full .NET test project: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
## Task 1: Freeze Row-Level Backlog
|
||||
|
||||
**Files:**
|
||||
- Modify: `gaps/websocket.md`
|
||||
|
||||
**Step 1: Normalize open rows into an execution queue**
|
||||
- Split all `MISSING` and `PARTIAL` rows into ranked batches (highest runtime impact first).
|
||||
|
||||
**Step 2: Record acceptance criteria per row**
|
||||
- For each row, define exact behavioral parity checks (inputs, outputs, error paths, timing/concurrency expectations).
|
||||
|
||||
**Step 3: Save queue annotations in notes**
|
||||
- Add short execution-order markers in notes without changing status yet.
|
||||
|
||||
## Task 2: Write Failing Tests for Batch 1
|
||||
|
||||
**Files:**
|
||||
- Modify: `tests/NATS.Server.Tests/WebSocket/`
|
||||
- Test: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
**Step 1: Add failing tests for first open row batch**
|
||||
- Create tests that reproduce the exact missing/partial Go behavior.
|
||||
|
||||
**Step 2: Run tests to confirm failure**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: FAIL in newly added tests only.
|
||||
|
||||
## Task 3: Implement Minimal Parity for Batch 1
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/NATS.Server/WebSocket/ (all files)`
|
||||
- Modify: additional category files listed in `gaps/websocket.md`
|
||||
|
||||
**Step 1: Implement minimal code to satisfy failing tests**
|
||||
- Mirror Go semantics for parsing, state transitions, limits, and side-effects required by the targeted rows.
|
||||
|
||||
**Step 2: Re-run tests for validation**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: PASS for batch-1 tests; no new regressions.
|
||||
|
||||
## Task 4: Close Remaining Batches Iteratively
|
||||
|
||||
**Files:**
|
||||
- Modify: category .NET source and test files referenced in `gaps/websocket.md`
|
||||
- Modify: `gaps/websocket.md`
|
||||
|
||||
**Step 1: Repeat test-first cycle for each remaining batch**
|
||||
- Add failing tests, implement minimal parity, then verify pass.
|
||||
|
||||
**Step 2: Update inventory status immediately after each closure**
|
||||
- Move rows to `PORTED` with exact .NET file:line references.
|
||||
- Keep `PARTIAL` only if explicit residual behavior remains.
|
||||
|
||||
## Task 5: Cross-Module Regression and Hardening
|
||||
|
||||
**Files:**
|
||||
- Test: `tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
|
||||
**Step 1: Run full regression suite**
|
||||
- Run: `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release`
|
||||
- Expected: PASS.
|
||||
|
||||
**Step 2: Add edge-case tests discovered during regression**
|
||||
- Capture any newly discovered divergence from Go and close it before completion.
|
||||
|
||||
## Task 6: Finalize Parity Bookkeeping
|
||||
|
||||
**Files:**
|
||||
- Modify: `gaps/websocket.md`
|
||||
- Modify: `gaps/stillmissing.md`
|
||||
|
||||
**Step 1: Ensure final status correctness**
|
||||
- Verify no unresolved `MISSING` rows remain for `websocket`.
|
||||
|
||||
**Step 2: Refresh LOC and percentage bookkeeping**
|
||||
- Recount category LOC and update summary rows in `gaps/stillmissing.md`.
|
||||
|
||||
**Step 3: Add concise changelog entry**
|
||||
- Record date, completed parity areas, and any justified deferred items.
|
||||
|
||||
## Completion Checklist
|
||||
- [ ] All prioritized `websocket` rows are `PORTED` or explicitly justified as `NOT_APPLICABLE`/`DEFERRED`.
|
||||
- [ ] New/updated tests demonstrate parity for all previously open rows.
|
||||
- [ ] `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj -c Release` passes.
|
||||
- [ ] `gaps/websocket.md` and `gaps/stillmissing.md` are fully synchronized.
|
||||
@@ -0,0 +1,149 @@
|
||||
# NATS.E2E.Tests Extended Coverage — Design
|
||||
|
||||
**Date:** 2026-03-12
|
||||
**Status:** Approved
|
||||
|
||||
## Overview
|
||||
|
||||
Extend the existing `NATS.E2E.Tests` project with phased test coverage that progressively exercises more complex NATS server functionality. All tests are black-box: the server runs as a child process, tests connect via `NATS.Client.Core` (and `NATS.Client.JetStream` for Phase 5).
|
||||
|
||||
## Infrastructure Extensions
|
||||
|
||||
### NatsServerProcess Changes
|
||||
|
||||
- Add optional `string[]? extraArgs` to constructor, appended after `-p <port>`
|
||||
- Add `WithConfigFile(string content)` static factory — writes content to a temp file, passes `-c <path>`, cleans up on dispose
|
||||
- Add `MonitorPort` property — allocates a second free port when monitoring is needed, passes `-m <port>`
|
||||
|
||||
### New Fixtures (Infrastructure/)
|
||||
|
||||
Each fixture gets its own `[CollectionDefinition]`:
|
||||
|
||||
| Fixture | Collection | Config |
|
||||
|---------|-----------|--------|
|
||||
| `NatsServerFixture` (existing) | `E2E` | Default, no auth/TLS/JS |
|
||||
| `AuthServerFixture` | `E2E-Auth` | Config file with users, tokens, NKeys, permissions |
|
||||
| `MonitorServerFixture` | `E2E-Monitor` | `-m <port>` for HTTP monitoring |
|
||||
| `TlsServerFixture` | `E2E-TLS` | Self-signed certs generated at startup, `--tlscert/--tlskey/--tlscacert` |
|
||||
| `AccountServerFixture` | `E2E-Accounts` | Config with two isolated accounts |
|
||||
| `JetStreamServerFixture` | `E2E-JetStream` | Config with `jetstream { store_dir: <tmpdir> }` |
|
||||
|
||||
### Shared Helper
|
||||
|
||||
`E2ETestHelper` static class:
|
||||
- `CreateClient(int port)` — returns `NatsConnection`
|
||||
- `Timeout(int seconds = 10)` — returns `CancellationToken`
|
||||
|
||||
### NuGet Additions
|
||||
|
||||
- `NATS.NKeys` — already in `Directory.Packages.props`, add to E2E csproj
|
||||
- `NATS.Client.JetStream` — add to `Directory.Packages.props` and E2E csproj
|
||||
|
||||
## Phase 1: Core Messaging (11 tests)
|
||||
|
||||
**File:** `CoreMessagingTests.cs` — `[Collection("E2E")]`
|
||||
|
||||
| Test | Verifies |
|
||||
|------|----------|
|
||||
| `WildcardStar_MatchesSingleToken` | Sub `foo.*`, pub `foo.bar` → received |
|
||||
| `WildcardGreaterThan_MatchesMultipleTokens` | Sub `foo.>`, pub `foo.bar.baz` → received |
|
||||
| `WildcardStar_DoesNotMatchMultipleTokens` | Sub `foo.*`, pub `foo.bar.baz` → no message |
|
||||
| `QueueGroup_LoadBalances` | 3 queue subs, 30 msgs → distributed across all 3 |
|
||||
| `QueueGroup_MixedWithPlainSub` | 1 plain + 2 queue subs → plain gets all, 1 queue gets each |
|
||||
| `Unsub_StopsDelivery` | Sub, unsub, pub → no message |
|
||||
| `Unsub_WithMaxMessages` | Auto-unsub after 3, pub 5 → only 3 received |
|
||||
| `FanOut_MultipleSubscribers` | 3 subs, 1 pub → all 3 receive |
|
||||
| `EchoOff_PublisherDoesNotReceiveSelf` | `echo: false`, self-pub → no echo |
|
||||
| `VerboseMode_OkResponses` | Raw socket, `verbose: true` → `+OK` after SUB |
|
||||
| `NoResponders_Returns503` | `no_responders: true`, request with no subs → 503 |
|
||||
|
||||
## Phase 2: Auth & Permissions (12 tests)
|
||||
|
||||
**File:** `AuthTests.cs` — `[Collection("E2E-Auth")]`
|
||||
|
||||
Config includes: user/pass pair, token, NKey public key, permission-restricted users, `max_subs: 5` user.
|
||||
|
||||
| Test | Verifies |
|
||||
|------|----------|
|
||||
| `UsernamePassword_ValidCredentials_Connects` | Correct user/pass → connected |
|
||||
| `UsernamePassword_InvalidPassword_Rejected` | Wrong pass → rejected |
|
||||
| `UsernamePassword_NoCredentials_Rejected` | No creds to auth server → rejected |
|
||||
| `TokenAuth_ValidToken_Connects` | Correct token → connected |
|
||||
| `TokenAuth_InvalidToken_Rejected` | Wrong token → rejected |
|
||||
| `NKeyAuth_ValidSignature_Connects` | Valid NKey sig → connected |
|
||||
| `NKeyAuth_InvalidSignature_Rejected` | Wrong NKey sig → rejected |
|
||||
| `Permission_PublishAllowed_Succeeds` | Pub to allowed subject → delivered |
|
||||
| `Permission_PublishDenied_NoDelivery` | Pub to denied subject → not delivered |
|
||||
| `Permission_SubscribeDenied_Rejected` | Sub to denied subject → rejected |
|
||||
| `MaxSubscriptions_ExceedsLimit_Rejected` | 6th sub on `max_subs: 5` user → rejected |
|
||||
| `MaxPayload_ExceedsLimit_Disconnected` | Oversized message → disconnected |
|
||||
|
||||
## Phase 3: Monitoring & Config (7 tests)
|
||||
|
||||
**File:** `MonitoringTests.cs` — `[Collection("E2E-Monitor")]`
|
||||
|
||||
Fixture exposes `MonitorPort` and `HttpClient MonitorClient`.
|
||||
|
||||
| Test | Verifies |
|
||||
|------|----------|
|
||||
| `Healthz_ReturnsOk` | `GET /healthz` → 200, `{"status":"ok"}` |
|
||||
| `Varz_ReturnsServerInfo` | `GET /varz` → JSON with `server_id`, `version`, `port` |
|
||||
| `Varz_ReflectsMessageCounts` | Publish msgs, `GET /varz` → `in_msgs > 0` |
|
||||
| `Connz_ListsActiveConnections` | 2 clients, `GET /connz` → `num_connections: 2` |
|
||||
| `Connz_SortByParameter` | `GET /connz?sort=bytes_to` → sorted |
|
||||
| `Connz_LimitAndOffset` | 5 clients, `GET /connz?limit=2&offset=1` → 2 entries |
|
||||
| `Subz_ReturnsSubscriptionStats` | Subs active, `GET /subz` → count > 0 |
|
||||
|
||||
## Phase 4: TLS & Account Isolation (6 tests)
|
||||
|
||||
**File:** `TlsTests.cs` — `[Collection("E2E-TLS")]`
|
||||
|
||||
Fixture generates self-signed CA + server cert + client cert using `System.Security.Cryptography` at startup.
|
||||
|
||||
| Test | Verifies |
|
||||
|------|----------|
|
||||
| `Tls_ClientConnectsSecurely` | TLS connect + ping succeeds |
|
||||
| `Tls_PlainTextConnection_Rejected` | Non-TLS connect → fails |
|
||||
| `Tls_PubSub_WorksOverEncryptedConnection` | Full pub/sub over TLS |
|
||||
|
||||
**File:** `AccountIsolationTests.cs` — `[Collection("E2E-Accounts")]`
|
||||
|
||||
Config with `ACCT_A` and `ACCT_B`, each with its own user.
|
||||
|
||||
| Test | Verifies |
|
||||
|------|----------|
|
||||
| `Accounts_SameAccount_MessageDelivered` | Two `ACCT_A` clients → pub/sub works |
|
||||
| `Accounts_CrossAccount_MessageNotDelivered` | `ACCT_A` pub, `ACCT_B` sub → no message |
|
||||
| `Accounts_EachAccountHasOwnNamespace` | Both accounts sub `foo.bar` independently |
|
||||
|
||||
## Phase 5: JetStream (10 tests)
|
||||
|
||||
**File:** `JetStreamTests.cs` — `[Collection("E2E-JetStream")]`
|
||||
|
||||
Fixture enables JetStream with temp `store_dir`.
|
||||
|
||||
| Test | Verifies |
|
||||
|------|----------|
|
||||
| `Stream_CreateAndInfo` | Create stream, verify info matches config |
|
||||
| `Stream_ListAndNames` | Create 3 streams, list/names returns all |
|
||||
| `Stream_Delete` | Create, delete, verify gone |
|
||||
| `Stream_PublishAndGet` | Publish msgs, get by sequence |
|
||||
| `Stream_Purge` | Publish, purge, verify count = 0 |
|
||||
| `Consumer_CreatePullAndConsume` | Pull consumer, publish 5, pull → receive 5 |
|
||||
| `Consumer_AckExplicit` | Explicit ack, verify no redelivery |
|
||||
| `Consumer_ListAndDelete` | Create consumers, list, delete, verify |
|
||||
| `Retention_LimitsMaxMessages` | `max_msgs: 10`, publish 15 → stream has 10 |
|
||||
| `Retention_MaxAge` | Short `max_age`, verify msgs expire |
|
||||
|
||||
## Test Count Summary
|
||||
|
||||
| Phase | Tests | New Files |
|
||||
|-------|-------|-----------|
|
||||
| 1 — Core Messaging | 11 | `CoreMessagingTests.cs` |
|
||||
| 2 — Auth & Permissions | 12 | `AuthTests.cs`, `AuthServerFixture.cs` |
|
||||
| 3 — Monitoring | 7 | `MonitoringTests.cs`, `MonitorServerFixture.cs` |
|
||||
| 4 — TLS & Accounts | 6 | `TlsTests.cs`, `TlsServerFixture.cs`, `AccountIsolationTests.cs`, `AccountServerFixture.cs` |
|
||||
| 5 — JetStream | 10 | `JetStreamTests.cs`, `JetStreamServerFixture.cs` |
|
||||
| **Total** | **46** | |
|
||||
|
||||
Plus infrastructure changes: `NatsServerProcess.cs` (edit), `E2ETestHelper.cs` (new).
|
||||
@@ -0,0 +1,573 @@
|
||||
# NATS.E2E.Tests Extended Coverage — Implementation Plan
|
||||
|
||||
**Date:** 2026-03-12
|
||||
**Design:** [2026-03-12-e2e-extended-design.md](2026-03-12-e2e-extended-design.md)
|
||||
|
||||
## Batch Structure
|
||||
|
||||
7 batches. Each batch is independently verifiable. Phases 1-5 from the design map to batches 2-6. Batch 1 is infrastructure. Batch 7 is final verification.
|
||||
|
||||
| Batch | Steps | Can Parallelize |
|
||||
|-------|-------|-----------------|
|
||||
| 1 — Infrastructure | Steps 1-4 | Steps 2-4 in parallel after Step 1 |
|
||||
| 2 — Phase 1: Core Messaging | Step 5 | No |
|
||||
| 3 — Phase 2: Auth & Permissions | Steps 6-7 | No (fixture then tests) |
|
||||
| 4 — Phase 3: Monitoring | Steps 8-9 | No |
|
||||
| 5 — Phase 4: TLS & Accounts | Steps 10-13 | Steps 10-11 parallel, Steps 12-13 parallel |
|
||||
| 6 — Phase 5: JetStream | Steps 14-15 | No |
|
||||
| 7 — Final Verification | Step 16 | No |
|
||||
|
||||
---
|
||||
|
||||
## Batch 1: Infrastructure
|
||||
|
||||
### Step 1: Update NuGet packages and csproj
|
||||
|
||||
**Files:**
|
||||
- `Directory.Packages.props` (edit)
|
||||
- `tests/NATS.E2E.Tests/NATS.E2E.Tests.csproj` (edit)
|
||||
|
||||
**Details:**
|
||||
|
||||
Add to `Directory.Packages.props`:
|
||||
```xml
|
||||
<PackageVersion Include="NATS.Client.JetStream" Version="2.7.2" />
|
||||
```
|
||||
|
||||
Add to `NATS.E2E.Tests.csproj` `<ItemGroup>`:
|
||||
```xml
|
||||
<PackageReference Include="NATS.NKeys" />
|
||||
<PackageReference Include="NATS.Client.JetStream" />
|
||||
```
|
||||
|
||||
**Verify:** `dotnet build tests/NATS.E2E.Tests` succeeds.
|
||||
|
||||
---
|
||||
|
||||
### Step 2: Extend NatsServerProcess
|
||||
|
||||
**Files:**
|
||||
- `tests/NATS.E2E.Tests/Infrastructure/NatsServerProcess.cs` (edit)
|
||||
|
||||
**Details:**
|
||||
|
||||
Add to the class:
|
||||
|
||||
1. **New constructor overload**: `NatsServerProcess(string[]? extraArgs = null, string? configContent = null, bool enableMonitoring = false)`
|
||||
- Stores `_extraArgs`, `_configContent`, `_enableMonitoring`
|
||||
- If `enableMonitoring`, allocate a second free port → `MonitorPort`
|
||||
- Keep existing no-arg constructor as-is (calls new one with defaults)
|
||||
|
||||
2. **New property**: `int? MonitorPort { get; }`
|
||||
|
||||
3. **Config file support in `StartAsync()`**:
|
||||
- If `_configContent` is not null, write to a temp file (`Path.GetTempFileName()` with `.conf` extension), store path in `_configFilePath`
|
||||
- Build args: `exec "{dll}" -p {Port}` + (if config: `-c {_configFilePath}`) + (if monitoring: `-m {MonitorPort}`) + (extra args)
|
||||
|
||||
4. **Cleanup in `DisposeAsync()`**: Delete `_configFilePath` if it exists.
|
||||
|
||||
5. **Static factory**: `static NatsServerProcess WithConfig(string configContent, bool enableMonitoring = false)` — convenience for creating with config.
|
||||
|
||||
**Verify:** `dotnet build tests/NATS.E2E.Tests` succeeds. Existing tests still pass (`dotnet test tests/NATS.E2E.Tests`).
|
||||
|
||||
---
|
||||
|
||||
### Step 3: Create E2ETestHelper
|
||||
|
||||
**Files:**
|
||||
- `tests/NATS.E2E.Tests/Infrastructure/E2ETestHelper.cs` (new)
|
||||
|
||||
**Details:**
|
||||
|
||||
```csharp
|
||||
namespace NATS.E2E.Tests.Infrastructure;
|
||||
|
||||
public static class E2ETestHelper
|
||||
{
|
||||
public static NatsConnection CreateClient(int port)
|
||||
=> new(new NatsOpts { Url = $"nats://127.0.0.1:{port}" });
|
||||
|
||||
public static NatsConnection CreateClient(int port, NatsOpts opts)
|
||||
=> new(opts with { Url = $"nats://127.0.0.1:{port}" });
|
||||
|
||||
public static CancellationToken Timeout(int seconds = 10)
|
||||
=> new CancellationTokenSource(TimeSpan.FromSeconds(seconds)).Token;
|
||||
}
|
||||
```
|
||||
|
||||
**Verify:** Builds.
|
||||
|
||||
---
|
||||
|
||||
### Step 4: Create collection definitions file
|
||||
|
||||
**Files:**
|
||||
- `tests/NATS.E2E.Tests/Infrastructure/Collections.cs` (new)
|
||||
|
||||
**Details:**
|
||||
|
||||
Move existing `E2ECollection` from `NatsServerFixture.cs` into this file. Add all collection definitions:
|
||||
|
||||
```csharp
|
||||
[CollectionDefinition("E2E")]
|
||||
public class E2ECollection : ICollectionFixture<NatsServerFixture>;
|
||||
|
||||
[CollectionDefinition("E2E-Auth")]
|
||||
public class AuthCollection : ICollectionFixture<AuthServerFixture>;
|
||||
|
||||
[CollectionDefinition("E2E-Monitor")]
|
||||
public class MonitorCollection : ICollectionFixture<MonitorServerFixture>;
|
||||
|
||||
[CollectionDefinition("E2E-TLS")]
|
||||
public class TlsCollection : ICollectionFixture<TlsServerFixture>;
|
||||
|
||||
[CollectionDefinition("E2E-Accounts")]
|
||||
public class AccountsCollection : ICollectionFixture<AccountServerFixture>;
|
||||
|
||||
[CollectionDefinition("E2E-JetStream")]
|
||||
public class JetStreamCollection : ICollectionFixture<JetStreamServerFixture>;
|
||||
```
|
||||
|
||||
Remove `E2ECollection` from `NatsServerFixture.cs`.
|
||||
|
||||
Note: The fixture classes referenced here (AuthServerFixture, etc.) don't exist yet — they'll be created in later steps. This file will have build errors until then; that's fine as long as we build after each batch completes.
|
||||
|
||||
Actually — to keep each batch independently verifiable, only add the `E2E` collection definition here in Step 4. The other collection definitions will be added in their respective fixture files in later batches.
|
||||
|
||||
**Verify:** `dotnet test tests/NATS.E2E.Tests` — existing 3 tests still pass.
|
||||
|
||||
---
|
||||
|
||||
## Batch 2: Phase 1 — Core Messaging
|
||||
|
||||
### Step 5: Implement CoreMessagingTests
|
||||
|
||||
**Files:**
|
||||
- `tests/NATS.E2E.Tests/CoreMessagingTests.cs` (new)
|
||||
|
||||
**Details:**
|
||||
|
||||
`[Collection("E2E")]` — uses existing `NatsServerFixture`. Primary constructor takes `NatsServerFixture fixture`.
|
||||
|
||||
**11 tests:**
|
||||
|
||||
1. **`WildcardStar_MatchesSingleToken`**: Sub `foo.*`, pub `foo.bar` → assert received with correct data.
|
||||
|
||||
2. **`WildcardGreaterThan_MatchesMultipleTokens`**: Sub `foo.>`, pub `foo.bar.baz` → assert received.
|
||||
|
||||
3. **`WildcardStar_DoesNotMatchMultipleTokens`**: Sub `foo.*`, pub `foo.bar.baz` → assert NO message within 1s timeout (use `Task.WhenAny` with short delay to prove no delivery).
|
||||
|
||||
4. **`QueueGroup_LoadBalances`**: 3 clients sub to `qtest` with queue group `workers`. Pub client sends 30 messages. Each sub collects received messages. Assert: total across all 3 = 30, each sub got at least 1 (no single sub got all).
|
||||
|
||||
5. **`QueueGroup_MixedWithPlainSub`**: 1 plain sub + 2 queue subs on `qmix`. Pub 10 messages. Plain sub should get all 10. Queue subs combined should get 10 (each message to exactly 1 queue sub).
|
||||
|
||||
6. **`Unsub_StopsDelivery`**: Sub to `unsub.test`, ping to flush, then unsubscribe, pub → assert no message within 1s.
|
||||
|
||||
7. **`Unsub_WithMaxMessages`**: Sub to `maxmsg.test`. Use raw socket or low-level NATS protocol to send `UNSUB sid 3`. Pub 5 messages → assert exactly 3 received. Note: NATS.Client.Core may not expose auto-unsub-after-N directly. If not, use raw socket for this test.
|
||||
|
||||
8. **`FanOut_MultipleSubscribers`**: 3 clients sub to `fanout.test`. Pub 1 message. All 3 receive it.
|
||||
|
||||
9. **`EchoOff_PublisherDoesNotReceiveSelf`**: Connect with `NatsOpts { Echo = false }`. Sub to `echo.test`, pub to `echo.test`. Assert no message within 1s. Then connect a second client (default echo=true), sub and pub → that client DOES receive its own message (as control).
|
||||
|
||||
10. **`VerboseMode_OkResponses`**: Use raw `TcpClient`/`NetworkStream`. Send `CONNECT {"verbose":true}\r\n` → read `+OK`. Send `SUB test 1\r\n` → read `+OK`. Send `PING\r\n` → read `PONG`.
|
||||
|
||||
11. **`NoResponders_Returns503`**: Connect with `NatsOpts { Headers = true, NoResponders = true }` (check if NATS.Client.Core exposes this). Send request to subject with no subscribers → expect exception or 503 status in reply headers.
|
||||
|
||||
For negative tests (no message expected), use a short 500ms-1s timeout with `Task.WhenAny(readTask, Task.Delay(1000))` pattern — assert the delay wins.
|
||||
|
||||
**Verify:** `dotnet test tests/NATS.E2E.Tests` — all 14 tests pass (3 original + 11 new).
|
||||
|
||||
---
|
||||
|
||||
## Batch 3: Phase 2 — Auth & Permissions
|
||||
|
||||
### Step 6: Implement AuthServerFixture
|
||||
|
||||
**Files:**
|
||||
- `tests/NATS.E2E.Tests/Infrastructure/AuthServerFixture.cs` (new)
|
||||
|
||||
**Details:**
|
||||
|
||||
Class `AuthServerFixture : IAsyncLifetime`.
|
||||
|
||||
At construction time, generate an NKey pair using `NATS.NKeys`:
|
||||
```csharp
|
||||
var kp = KeyPair.CreateUser();
|
||||
NKeyPublicKey = kp.EncodedPublicKey; // starts with 'U'
|
||||
NKeySeed = kp.EncodedSeed; // starts with 'SU'
|
||||
```
|
||||
|
||||
Store these as public properties so tests can use them.
|
||||
|
||||
Config content (NATS conf format):
|
||||
```
|
||||
max_payload: 512
|
||||
authorization {
|
||||
users = [
|
||||
{ user: "testuser", password: "testpass" }
|
||||
{ user: "tokenuser", password: "s3cret_token" }
|
||||
{ user: "pubonly", password: "pubpass", permissions: { publish: { allow: ["allowed.>"] }, subscribe: { allow: ["_INBOX.>"] } } }
|
||||
{ user: "subonly", password: "subpass", permissions: { subscribe: { allow: ["allowed.>", "_INBOX.>"] }, publish: { allow: ["_INBOX.>"] } } }
|
||||
{ user: "limited", password: "limpass", permissions: { publish: ">", subscribe: ">" } }
|
||||
{ nkey: "<NKEY_PUBLIC_KEY>" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Wait — token auth uses `authorization { token: "..." }` which is separate from users. We can't mix both in one config. Instead, use separate users for each auth mechanism and test user/pass. For token auth, we need a separate fixture or a workaround.
|
||||
|
||||
Simpler approach: use a config with `users` only (user/pass, nkeys, permissions). For token auth, we can test it with a dedicated `NatsServerProcess` instance inside the test itself (create server, run test, dispose). This keeps the fixture simple.
|
||||
|
||||
Actually, let's keep it simpler: make AuthServerFixture handle user/pass + nkeys + permissions. Add the token tests and max_payload test as standalone tests that spin up their own mini-server via `NatsServerProcess`.
|
||||
|
||||
Properties exposed:
|
||||
- `int Port`
|
||||
- `string NKeyPublicKey`
|
||||
- `string NKeySeed`
|
||||
- `NatsConnection CreateClient(string user, string password)` — connects with credentials
|
||||
- `NatsConnection CreateClient()` — connects without credentials (should fail on auth-required server)
|
||||
|
||||
Collection definition: `[CollectionDefinition("E2E-Auth")]` in this file.
|
||||
|
||||
**Verify:** Builds.
|
||||
|
||||
---
|
||||
|
||||
### Step 7: Implement AuthTests
|
||||
|
||||
**Files:**
|
||||
- `tests/NATS.E2E.Tests/AuthTests.cs` (new)
|
||||
|
||||
**Details:**
|
||||
|
||||
`[Collection("E2E-Auth")]` with `AuthServerFixture fixture`.
|
||||
|
||||
**12 tests:**
|
||||
|
||||
1. **`UsernamePassword_ValidCredentials_Connects`**: `fixture.CreateClient("testuser", "testpass")` → connect, ping → succeeds.
|
||||
|
||||
2. **`UsernamePassword_InvalidPassword_Rejected`**: Connect with wrong password → expect `NatsException` on connect.
|
||||
|
||||
3. **`UsernamePassword_NoCredentials_Rejected`**: `fixture.CreateClient()` (no creds) → expect connection error.
|
||||
|
||||
4. **`TokenAuth_ValidToken_Connects`**: Spin up a temp `NatsServerProcess` with config `authorization { token: "s3cret" }`. Connect with `NatsOpts { AuthToken = "s3cret" }` → succeeds.
|
||||
|
||||
5. **`TokenAuth_InvalidToken_Rejected`**: Same temp server, wrong token → rejected.
|
||||
|
||||
6. **`NKeyAuth_ValidSignature_Connects`**: Connect with `NatsOpts` configured for NKey auth using `fixture.NKeySeed` → succeeds.
|
||||
|
||||
7. **`NKeyAuth_InvalidSignature_Rejected`**: Connect with a different NKey seed → rejected.
|
||||
|
||||
8. **`Permission_PublishAllowed_Succeeds`**: `pubonly` user pubs to `allowed.foo`, `testuser` sub on same → message delivered.
|
||||
|
||||
9. **`Permission_PublishDenied_NoDelivery`**: `pubonly` user pubs to `denied.foo` → permission violation, message not delivered.
|
||||
|
||||
10. **`Permission_SubscribeDenied_Rejected`**: `pubonly` user tries to sub to `denied.foo` → error or no messages.
|
||||
|
||||
11. **`MaxSubscriptions_ExceedsLimit_Rejected`**: Use `limited` user config with `max_subs: 5` added to fixture config. Create 6 subs → last one triggers error.
|
||||
|
||||
12. **`MaxPayload_ExceedsLimit_Disconnected`**: Fixture config has `max_payload: 512`. Send message > 512 bytes → connection closed.
|
||||
|
||||
For tests 4-5 (token auth): create/dispose their own `NatsServerProcess` within the test. Use `await using` for cleanup.
|
||||
|
||||
**Verify:** `dotnet test tests/NATS.E2E.Tests` — all 25 tests pass (14 + 11 new; token tests may take slightly longer due to extra server startup).
|
||||
|
||||
Note: Token tests spin up independent servers, so they'll be slightly slower. That's acceptable for E2E.
|
||||
|
||||
---
|
||||
|
||||
## Batch 4: Phase 3 — Monitoring
|
||||
|
||||
### Step 8: Implement MonitorServerFixture
|
||||
|
||||
**Files:**
|
||||
- `tests/NATS.E2E.Tests/Infrastructure/MonitorServerFixture.cs` (new)
|
||||
|
||||
**Details:**
|
||||
|
||||
Class `MonitorServerFixture : IAsyncLifetime`.
|
||||
|
||||
Creates `NatsServerProcess` with `enableMonitoring: true`. This passes `-m <port>` to the server.
|
||||
|
||||
Properties:
|
||||
- `int Port` — NATS client port
|
||||
- `int MonitorPort` — HTTP monitoring port
|
||||
- `HttpClient MonitorClient` — pre-configured with `BaseAddress = new Uri($"http://127.0.0.1:{MonitorPort}")`
|
||||
- `NatsConnection CreateClient()`
|
||||
|
||||
Dispose: dispose `HttpClient` and server process.
|
||||
|
||||
Collection definition: `[CollectionDefinition("E2E-Monitor")]` in this file.
|
||||
|
||||
**Verify:** Builds.
|
||||
|
||||
---
|
||||
|
||||
### Step 9: Implement MonitoringTests
|
||||
|
||||
**Files:**
|
||||
- `tests/NATS.E2E.Tests/MonitoringTests.cs` (new)
|
||||
|
||||
**Details:**
|
||||
|
||||
`[Collection("E2E-Monitor")]` with `MonitorServerFixture fixture`.
|
||||
|
||||
All tests use `fixture.MonitorClient` for HTTP calls and `System.Text.Json.JsonDocument` for JSON parsing.
|
||||
|
||||
**7 tests:**
|
||||
|
||||
1. **`Healthz_ReturnsOk`**: `GET /healthz` → 200, body contains `"status"` key with value `"ok"`.
|
||||
|
||||
2. **`Varz_ReturnsServerInfo`**: `GET /varz` → 200, JSON has `server_id` (non-empty string), `version`, `port` (matches fixture port).
|
||||
|
||||
3. **`Varz_ReflectsMessageCounts`**: Connect client, pub 5 messages to a subject (with a sub to ensure delivery). `GET /varz` → `in_msgs` >= 5.
|
||||
|
||||
4. **`Connz_ListsActiveConnections`**: Connect 2 clients, ping both. `GET /connz` → `num_connections` >= 2, `connections` array has entries.
|
||||
|
||||
5. **`Connz_SortByParameter`**: Connect 3 clients, send different amounts of data. `GET /connz?sort=bytes_to` → `connections` array returned (verify it doesn't error; exact sort validation optional).
|
||||
|
||||
6. **`Connz_LimitAndOffset`**: Connect 5 clients. `GET /connz?limit=2&offset=1` → `connections` array has exactly 2 entries.
|
||||
|
||||
7. **`Subz_ReturnsSubscriptionStats`**: Connect client, sub to 3 subjects. `GET /subz` → response has subscription data, `num_subscriptions` > 0.
|
||||
|
||||
**Verify:** `dotnet test tests/NATS.E2E.Tests` — all 32 tests pass (25 + 7).
|
||||
|
||||
---
|
||||
|
||||
## Batch 5: Phase 4 — TLS & Accounts
|
||||
|
||||
### Step 10: Implement TlsServerFixture
|
||||
|
||||
**Files:**
|
||||
- `tests/NATS.E2E.Tests/Infrastructure/TlsServerFixture.cs` (new)
|
||||
|
||||
**Details:**
|
||||
|
||||
Class `TlsServerFixture : IAsyncLifetime`.
|
||||
|
||||
In `InitializeAsync()`:
|
||||
1. Create a temp directory for certs.
|
||||
2. Generate self-signed CA, server cert, client cert using `System.Security.Cryptography`:
|
||||
- CA: RSA 2048, self-signed, `CN=E2E Test CA`
|
||||
- Server cert: RSA 2048, signed by CA, `CN=localhost`, SAN=`127.0.0.1`
|
||||
- Client cert: RSA 2048, signed by CA, `CN=testclient`
|
||||
3. Export to PEM files in temp dir: `ca.pem`, `server-cert.pem`, `server-key.pem`, `client-cert.pem`, `client-key.pem`
|
||||
4. Create `NatsServerProcess` with config:
|
||||
```
|
||||
listen: "0.0.0.0:{port}"
|
||||
tls {
|
||||
cert_file: "{server-cert.pem}"
|
||||
key_file: "{server-key.pem}"
|
||||
ca_file: "{ca.pem}"
|
||||
}
|
||||
```
|
||||
5. Start server.
|
||||
|
||||
Properties:
|
||||
- `int Port`
|
||||
- `string CaCertPath`, `string ClientCertPath`, `string ClientKeyPath`
|
||||
- `NatsConnection CreateTlsClient()` — creates client with TLS configured, trusting the test CA
|
||||
- `NatsConnection CreatePlainClient()` — creates client WITHOUT TLS (for rejection test)
|
||||
|
||||
Dispose: stop server, delete temp cert directory.
|
||||
|
||||
Collection definition: `[CollectionDefinition("E2E-TLS")]` in this file.
|
||||
|
||||
**Verify:** Builds.
|
||||
|
||||
---
|
||||
|
||||
### Step 11: Implement AccountServerFixture
|
||||
|
||||
**Files:**
|
||||
- `tests/NATS.E2E.Tests/Infrastructure/AccountServerFixture.cs` (new)
|
||||
|
||||
**Details:**
|
||||
|
||||
Class `AccountServerFixture : IAsyncLifetime`.
|
||||
|
||||
Config:
|
||||
```
|
||||
accounts {
|
||||
ACCT_A {
|
||||
users = [
|
||||
{ user: "user_a", password: "pass_a" }
|
||||
]
|
||||
}
|
||||
ACCT_B {
|
||||
users = [
|
||||
{ user: "user_b", password: "pass_b" }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Properties:
|
||||
- `int Port`
|
||||
- `NatsConnection CreateClientA()` — connects as `user_a`
|
||||
- `NatsConnection CreateClientB()` — connects as `user_b`
|
||||
|
||||
Collection definition: `[CollectionDefinition("E2E-Accounts")]` in this file.
|
||||
|
||||
**Verify:** Builds.
|
||||
|
||||
---
|
||||
|
||||
### Step 12: Implement TlsTests
|
||||
|
||||
**Files:**
|
||||
- `tests/NATS.E2E.Tests/TlsTests.cs` (new)
|
||||
|
||||
**Details:**
|
||||
|
||||
`[Collection("E2E-TLS")]` with `TlsServerFixture fixture`.
|
||||
|
||||
**3 tests:**
|
||||
|
||||
1. **`Tls_ClientConnectsSecurely`**: `fixture.CreateTlsClient()` → connect, ping → succeeds.
|
||||
|
||||
2. **`Tls_PlainTextConnection_Rejected`**: `fixture.CreatePlainClient()` → connect → expect exception (timeout or auth error since TLS handshake fails).
|
||||
|
||||
3. **`Tls_PubSub_WorksOverEncryptedConnection`**: Two TLS clients, pub/sub round-trip → message received.
|
||||
|
||||
**Verify:** Builds, TLS tests pass.
|
||||
|
||||
---
|
||||
|
||||
### Step 13: Implement AccountIsolationTests
|
||||
|
||||
**Files:**
|
||||
- `tests/NATS.E2E.Tests/AccountIsolationTests.cs` (new)
|
||||
|
||||
**Details:**
|
||||
|
||||
`[Collection("E2E-Accounts")]` with `AccountServerFixture fixture`.
|
||||
|
||||
**3 tests:**
|
||||
|
||||
1. **`Accounts_SameAccount_MessageDelivered`**: Two `ACCT_A` clients. Sub + pub on `acct.test` → message received.
|
||||
|
||||
2. **`Accounts_CrossAccount_MessageNotDelivered`**: `ACCT_A` client pubs to `cross.test`, `ACCT_B` client subs to `cross.test` → no message within 1s.
|
||||
|
||||
3. **`Accounts_EachAccountHasOwnNamespace`**: `ACCT_A` sub on `shared.topic`, `ACCT_B` sub on `shared.topic`. Pub from `ACCT_A` → only `ACCT_A` sub receives. Pub from `ACCT_B` → only `ACCT_B` sub receives.
|
||||
|
||||
**Verify:** `dotnet test tests/NATS.E2E.Tests` — all 38 tests pass (32 + 6).
|
||||
|
||||
---
|
||||
|
||||
## Batch 6: Phase 5 — JetStream
|
||||
|
||||
### Step 14: Implement JetStreamServerFixture
|
||||
|
||||
**Files:**
|
||||
- `tests/NATS.E2E.Tests/Infrastructure/JetStreamServerFixture.cs` (new)
|
||||
|
||||
**Details:**
|
||||
|
||||
Class `JetStreamServerFixture : IAsyncLifetime`.
|
||||
|
||||
Config:
|
||||
```
|
||||
listen: "0.0.0.0:{port}"
|
||||
jetstream {
|
||||
store_dir: "{tmpdir}"
|
||||
max_mem_store: 64mb
|
||||
max_file_store: 256mb
|
||||
}
|
||||
```
|
||||
|
||||
Where `{tmpdir}` is created via `Path.Combine(Path.GetTempPath(), "nats-e2e-js-" + Guid.NewGuid().ToString("N")[..8])`.
|
||||
|
||||
Properties:
|
||||
- `int Port`
|
||||
- `NatsConnection CreateClient()`
|
||||
|
||||
Dispose: stop server, delete `store_dir`.
|
||||
|
||||
Collection definition: `[CollectionDefinition("E2E-JetStream")]` in this file.
|
||||
|
||||
**Verify:** Builds.
|
||||
|
||||
---
|
||||
|
||||
### Step 15: Implement JetStreamTests
|
||||
|
||||
**Files:**
|
||||
- `tests/NATS.E2E.Tests/JetStreamTests.cs` (new)
|
||||
|
||||
**Details:**
|
||||
|
||||
`[Collection("E2E-JetStream")]` with `JetStreamServerFixture fixture`.
|
||||
|
||||
Uses `NATS.Client.JetStream` NuGet — create `NatsJSContext` from the connection.
|
||||
|
||||
**10 tests:**
|
||||
|
||||
1. **`Stream_CreateAndInfo`**: Create stream `TEST1` on subjects `["js.test.>"]` with limits retention. Get stream info → verify name, subjects, retention policy match.
|
||||
|
||||
2. **`Stream_ListAndNames`**: Create 3 streams (`LIST_A`, `LIST_B`, `LIST_C`). List streams → all 3 present. Get names → all 3 names returned.
|
||||
|
||||
3. **`Stream_Delete`**: Create stream `DEL_TEST`, delete it, attempt info → expect not-found error.
|
||||
|
||||
4. **`Stream_PublishAndGet`**: Create stream on `js.pub.>`. Publish 3 messages. Get message by sequence 1, 2, 3 → verify data matches.
|
||||
|
||||
5. **`Stream_Purge`**: Create stream, publish 5 messages. Purge. Get stream info → `state.messages == 0`.
|
||||
|
||||
6. **`Consumer_CreatePullAndConsume`**: Create stream + pull consumer. Publish 5 messages. Pull next batch (5) → receive all 5 with correct data.
|
||||
|
||||
7. **`Consumer_AckExplicit`**: Create stream + consumer with explicit ack. Publish message. Pull, ack it. Pull again → no more messages (not redelivered).
|
||||
|
||||
8. **`Consumer_ListAndDelete`**: Create stream + 2 consumers. List consumers → 2 present. Delete one. List → 1 remaining.
|
||||
|
||||
9. **`Retention_LimitsMaxMessages`**: Create stream with `MaxMsgs: 10`. Publish 15 messages. Stream info → `state.messages == 10`, first seq is 6.
|
||||
|
||||
10. **`Retention_MaxAge`**: Create stream with `MaxAge: TimeSpan.FromSeconds(2)`. Publish messages. Wait 3s. Stream info → `state.messages == 0`.
|
||||
|
||||
Each test uses unique stream/subject names to avoid interference (tests share one JetStream server).
|
||||
|
||||
**Verify:** `dotnet test tests/NATS.E2E.Tests` — all 48 tests pass (38 + 10).
|
||||
|
||||
---
|
||||
|
||||
## Batch 7: Final Verification
|
||||
|
||||
### Step 16: Full build and test run
|
||||
|
||||
**Commands:**
|
||||
```bash
|
||||
dotnet build
|
||||
dotnet test tests/NATS.E2E.Tests -v normal
|
||||
```
|
||||
|
||||
**Success criteria:** Solution builds clean, all 49 tests pass (3 original + 46 new).
|
||||
|
||||
---
|
||||
|
||||
## File Summary
|
||||
|
||||
| File | Action | Batch |
|
||||
|------|--------|-------|
|
||||
| `Directory.Packages.props` | edit | 1 |
|
||||
| `NATS.E2E.Tests.csproj` | edit | 1 |
|
||||
| `Infrastructure/NatsServerProcess.cs` | edit | 1 |
|
||||
| `Infrastructure/E2ETestHelper.cs` | new | 1 |
|
||||
| `Infrastructure/NatsServerFixture.cs` | edit (remove collection def) | 1 |
|
||||
| `Infrastructure/Collections.cs` | new | 1 |
|
||||
| `CoreMessagingTests.cs` | new | 2 |
|
||||
| `Infrastructure/AuthServerFixture.cs` | new | 3 |
|
||||
| `AuthTests.cs` | new | 3 |
|
||||
| `Infrastructure/MonitorServerFixture.cs` | new | 4 |
|
||||
| `MonitoringTests.cs` | new | 4 |
|
||||
| `Infrastructure/TlsServerFixture.cs` | new | 5 |
|
||||
| `Infrastructure/AccountServerFixture.cs` | new | 5 |
|
||||
| `TlsTests.cs` | new | 5 |
|
||||
| `AccountIsolationTests.cs` | new | 5 |
|
||||
| `Infrastructure/JetStreamServerFixture.cs` | new | 6 |
|
||||
| `JetStreamTests.cs` | new | 6 |
|
||||
|
||||
## Agent Model Guidance
|
||||
|
||||
- **Batch 1 (infrastructure)**: Opus — involves modifying existing code carefully
|
||||
- **Batches 2-6 (test phases)**: Sonnet — straightforward test implementation from spec
|
||||
- **Batch 7 (verify)**: Either — just running commands
|
||||
- **Parallel agents within Batch 5**: Steps 10-11 (fixtures) can run in parallel, Steps 12-13 (tests) can run in parallel
|
||||
@@ -0,0 +1,57 @@
|
||||
# NATS.E2E.Tests — Design
|
||||
|
||||
**Date:** 2026-03-12
|
||||
**Status:** Approved
|
||||
|
||||
## Overview
|
||||
|
||||
A true black-box E2E test project that launches `NATS.Server.Host` as a child process and connects using only `NATS.Client.Core` from NuGet. No project references to any server code.
|
||||
|
||||
## Components
|
||||
|
||||
### 1. `NatsServerProcess` — Server Lifecycle Helper
|
||||
|
||||
- Builds the host binary once (or locates pre-built output)
|
||||
- Launches `dotnet exec <path>/NATS.Server.Host.dll -p <port>` as a child process
|
||||
- Allocates a free port per instance
|
||||
- Waits for the server to be ready by polling TCP connect (with timeout)
|
||||
- On dispose: sends SIGINT / kills process, waits for exit
|
||||
- Captures stdout/stderr for diagnostics on failure
|
||||
|
||||
### 2. `NatsServerFixture` — xUnit Collection Fixture
|
||||
|
||||
- Implements `IAsyncLifetime`
|
||||
- Creates one `NatsServerProcess` per test class (or shared via collection)
|
||||
- Exposes `Port` and a `CreateClient()` helper that returns a connected `NatsConnection`
|
||||
|
||||
### 3. Initial Tests
|
||||
|
||||
- `ConnectAndPingTest` — Client connects, PING/PONG succeeds
|
||||
- `PubSubTest` — Two clients, publish on a subject, subscriber receives the message
|
||||
- `RequestReplyTest` — One client subscribes and replies, another sends a request and gets the response
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
tests/NATS.E2E.Tests/
|
||||
NATS.E2E.Tests.csproj # xUnit + NATS.Client.Core only, no server refs
|
||||
Infrastructure/
|
||||
NatsServerProcess.cs # Process lifecycle management
|
||||
NatsServerFixture.cs # xUnit fixture
|
||||
BasicTests.cs # Connect, PubSub, Request/Reply
|
||||
```
|
||||
|
||||
## Key Decisions
|
||||
|
||||
- **Binary discovery**: The fixture runs `dotnet build` on the host project and locates the output DLL via the known relative path from the solution root.
|
||||
- **Ready detection**: TCP connect poll to the allocated port with 10s timeout, 100ms interval.
|
||||
- **Process cleanup**: `Process.Kill()` with a graceful SIGINT attempt first.
|
||||
- **No shared state**: Each test class gets its own server process and port — full isolation.
|
||||
- **Timeout**: All async test operations use 10s CancellationTokenSource to avoid hangs.
|
||||
|
||||
## Out of Scope (YAGNI)
|
||||
|
||||
- No TLS/auth E2E tests in the initial project
|
||||
- No cluster/route/gateway/leaf-node multi-server tests
|
||||
- No JetStream E2E
|
||||
- No custom config file testing
|
||||
@@ -0,0 +1,166 @@
|
||||
# NATS.E2E.Tests — Implementation Plan
|
||||
|
||||
**Date:** 2026-03-12
|
||||
**Design:** [2026-03-12-e2e-tests-design.md](2026-03-12-e2e-tests-design.md)
|
||||
|
||||
## Steps
|
||||
|
||||
### Step 1: Create the project and add to solution
|
||||
|
||||
**Files:**
|
||||
- `tests/NATS.E2E.Tests/NATS.E2E.Tests.csproj` (new)
|
||||
- `NatsDotNet.slnx` (edit)
|
||||
|
||||
**Details:**
|
||||
|
||||
Create `tests/NATS.E2E.Tests/NATS.E2E.Tests.csproj`:
|
||||
```xml
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||
<PackageReference Include="NATS.Client.Core" />
|
||||
<PackageReference Include="Shouldly" />
|
||||
<PackageReference Include="xunit" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
<Using Include="Shouldly" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
```
|
||||
|
||||
No project references — black-box only. All package versions come from `Directory.Packages.props` (CPM). TFM inherited from `Directory.Build.props` (`net10.0`).
|
||||
|
||||
Add to `NatsDotNet.slnx` under the `/tests/` folder:
|
||||
```xml
|
||||
<Project Path="tests/NATS.E2E.Tests/NATS.E2E.Tests.csproj" />
|
||||
```
|
||||
|
||||
**Verify:** `dotnet build tests/NATS.E2E.Tests` succeeds.
|
||||
|
||||
---
|
||||
|
||||
### Step 2: Implement `NatsServerProcess`
|
||||
|
||||
**Files:**
|
||||
- `tests/NATS.E2E.Tests/Infrastructure/NatsServerProcess.cs` (new)
|
||||
|
||||
**Details:**
|
||||
|
||||
Class `NatsServerProcess : IAsyncDisposable`:
|
||||
|
||||
- **Constructor**: Takes no args. Allocates a free TCP port via ephemeral socket bind.
|
||||
- **`StartAsync()`**:
|
||||
1. Resolves the host DLL path: walk up from `AppContext.BaseDirectory` to find the solution root (contains `NatsDotNet.slnx`), then build path `src/NATS.Server.Host/bin/Debug/net10.0/NATS.Server.Host.dll`. If not found, run `dotnet build src/NATS.Server.Host/NATS.Server.Host.csproj -c Debug` from solution root first.
|
||||
2. Launch `dotnet exec <dll> -p <port>` via `System.Diagnostics.Process`. Redirect stdout/stderr, capture into `StringBuilder` for diagnostics.
|
||||
3. Poll TCP connect to `127.0.0.1:<port>` every 100ms, timeout after 10s. Throw `TimeoutException` with captured output if server doesn't become ready.
|
||||
- **`DisposeAsync()`**:
|
||||
1. If process is running: try `Process.Kill(entireProcessTree: true)` (cross-platform in .NET 10).
|
||||
2. Wait for exit with 5s timeout, then force kill if still alive.
|
||||
3. Dispose the process.
|
||||
- **Properties**: `int Port`, `string Output` (captured stdout+stderr for diagnostics).
|
||||
|
||||
**Verify:** Builds without errors.
|
||||
|
||||
---
|
||||
|
||||
### Step 3: Implement `NatsServerFixture`
|
||||
|
||||
**Files:**
|
||||
- `tests/NATS.E2E.Tests/Infrastructure/NatsServerFixture.cs` (new)
|
||||
|
||||
**Details:**
|
||||
|
||||
Class `NatsServerFixture : IAsyncLifetime`:
|
||||
|
||||
- **Field**: `NatsServerProcess _server`
|
||||
- **`InitializeAsync()`**: Create `NatsServerProcess`, call `StartAsync()`.
|
||||
- **`DisposeAsync()`**: Dispose the server process.
|
||||
- **`int Port`**: Delegates to `_server.Port`.
|
||||
- **`NatsConnection CreateClient()`**: Returns `new NatsConnection(new NatsOpts { Url = $"nats://127.0.0.1:{Port}" })`.
|
||||
|
||||
Define a collection attribute:
|
||||
```csharp
|
||||
[CollectionDefinition("E2E")]
|
||||
public class E2ECollection : ICollectionFixture<NatsServerFixture>;
|
||||
```
|
||||
|
||||
This lets multiple test classes share one server process via `[Collection("E2E")]`.
|
||||
|
||||
**Verify:** Builds without errors.
|
||||
|
||||
---
|
||||
|
||||
### Step 4: Implement `BasicTests`
|
||||
|
||||
**Files:**
|
||||
- `tests/NATS.E2E.Tests/BasicTests.cs` (new)
|
||||
|
||||
**Details:**
|
||||
|
||||
```csharp
|
||||
[Collection("E2E")]
|
||||
public class BasicTests(NatsServerFixture fixture)
|
||||
{
|
||||
private static CancellationToken Timeout => new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token;
|
||||
```
|
||||
|
||||
**Test 1 — `ConnectAndPing`:**
|
||||
- Create client via `fixture.CreateClient()`
|
||||
- `await client.ConnectAsync()`
|
||||
- `await client.PingAsync()` — if no exception, PING/PONG succeeded
|
||||
- Assert `client.ConnectionState` is `Open` (via Shouldly)
|
||||
|
||||
**Test 2 — `PubSub`:**
|
||||
- Create two clients (pub, sub)
|
||||
- Connect both
|
||||
- Subscribe sub to `"e2e.test.pubsub"`
|
||||
- Flush sub via `PingAsync()`
|
||||
- Publish `"hello e2e"` on the subject
|
||||
- Read one message from subscription with 10s timeout
|
||||
- Assert `msg.Data.ShouldBe("hello e2e")`
|
||||
|
||||
**Test 3 — `RequestReply`:**
|
||||
- Create two clients (requester, responder)
|
||||
- Connect both
|
||||
- Subscribe responder to `"e2e.test.rpc"`, in a background task read messages and reply with `"reply: " + msg.Data`
|
||||
- Flush responder via `PingAsync()`
|
||||
- Send request from requester: `await requester.RequestAsync<string, string>("e2e.test.rpc", "ping")`
|
||||
- Assert reply data is `"reply: ping"`
|
||||
|
||||
All tests use `await using` for client cleanup.
|
||||
|
||||
**Verify:** `dotnet test tests/NATS.E2E.Tests` — all 3 tests pass.
|
||||
|
||||
---
|
||||
|
||||
### Step 5: Verify full solution builds and tests pass
|
||||
|
||||
**Commands:**
|
||||
```bash
|
||||
dotnet build
|
||||
dotnet test tests/NATS.E2E.Tests -v normal
|
||||
```
|
||||
|
||||
**Success criteria:** Solution builds clean, all 3 E2E tests pass.
|
||||
|
||||
## Batch Structure
|
||||
|
||||
All 5 steps are in a single batch — the project is small and sequential (each step builds on the prior). No parallelization needed.
|
||||
|
||||
| Step | Files | Depends On |
|
||||
|------|-------|------------|
|
||||
| 1 | csproj + slnx | — |
|
||||
| 2 | NatsServerProcess.cs | Step 1 |
|
||||
| 3 | NatsServerFixture.cs | Step 2 |
|
||||
| 4 | BasicTests.cs | Step 3 |
|
||||
| 5 | (verify only) | Step 4 |
|
||||
+25
-21
@@ -100,8 +100,8 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| Authentication interface | golang/nats-server/server/auth.go:40 | PORTED | src/NATS.Server/Auth/IAuthenticator.cs:7 | Renamed to IAuthenticator; Check(ClientAuthentication) -> Authenticate(ClientAuthContext) |
|
||||
| ClientAuthentication interface | golang/nats-server/server/auth.go:46 | PORTED | src/NATS.Server/Auth/IAuthenticator.cs:12 | Mapped to ClientAuthContext record |
|
||||
| NkeyUser struct | golang/nats-server/server/auth.go:62 | PARTIAL | src/NATS.Server/Auth/NKeyUser.cs:3 | Missing: Issued field, AllowedConnectionTypes, ProxyRequired |
|
||||
| User struct | golang/nats-server/server/auth.go:73 | PARTIAL | src/NATS.Server/Auth/User.cs:3 | Missing: AllowedConnectionTypes, ProxyRequired |
|
||||
| NkeyUser struct | golang/nats-server/server/auth.go:62 | PORTED | src/NATS.Server/Auth/NKeyUser.cs:3 | Added parity fields `Issued`, `AllowedConnectionTypes`, and `ProxyRequired` |
|
||||
| User struct | golang/nats-server/server/auth.go:73 | PORTED | src/NATS.Server/Auth/User.cs:3 | Added parity fields `AllowedConnectionTypes` and `ProxyRequired` |
|
||||
| User.clone() | golang/nats-server/server/auth.go:85 | NOT_APPLICABLE | — | .NET uses immutable init-only records; deep clone not needed |
|
||||
| NkeyUser.clone() | golang/nats-server/server/auth.go:106 | NOT_APPLICABLE | — | .NET uses immutable init-only records; deep clone not needed |
|
||||
| SubjectPermission struct | golang/nats-server/server/auth.go:127 | PORTED | src/NATS.Server/Auth/Permissions.cs:10 | |
|
||||
@@ -111,21 +111,21 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| SubjectPermission.clone() | golang/nats-server/server/auth.go:156 | NOT_APPLICABLE | — | .NET uses immutable records |
|
||||
| Permissions.clone() | golang/nats-server/server/auth.go:174 | NOT_APPLICABLE | — | .NET uses immutable records |
|
||||
| checkAuthforWarnings() | golang/nats-server/server/auth.go:196 | MISSING | — | Warns about plaintext passwords at startup |
|
||||
| assignGlobalAccountToOrphanUsers() | golang/nats-server/server/auth.go:226 | MISSING | — | Auto-assigns global account to users without one |
|
||||
| validateResponsePermissions() | golang/nats-server/server/auth.go:243 | MISSING | — | Sets defaults for ResponsePermission (MaxMsgs, Expires) |
|
||||
| assignGlobalAccountToOrphanUsers() | golang/nats-server/server/auth.go:226 | PORTED | src/NATS.Server/Auth/AuthService.cs:176 | `AuthService.Build` now normalizes orphan users/nkeys onto `$G` via `NormalizeUsers`/`NormalizeNKeys`. |
|
||||
| validateResponsePermissions() | golang/nats-server/server/auth.go:243 | PORTED | src/NATS.Server/Auth/AuthService.cs:222 | Added response-permission normalization: ensures publish allow-list exists, sets `MaxMsgs` default to `DefaultAllowResponseMaxMsgs`, and `Expires` default to `DefaultAllowResponseExpiration` when zero. |
|
||||
| configureAuthorization() | golang/nats-server/server/auth.go:266 | PARTIAL | src/NATS.Server/Auth/AuthService.cs:30 | AuthService.Build covers the priority chain; missing websocket/mqtt auth config, auth callout account validation |
|
||||
| buildNkeysAndUsersFromOptions() | golang/nats-server/server/auth.go:325 | PARTIAL | src/NATS.Server/Auth/AuthService.cs:30 | User/NKey map building is in AuthService.Build; missing clone + account resolution + response permission validation |
|
||||
| buildNkeysAndUsersFromOptions() | golang/nats-server/server/auth.go:325 | PARTIAL | src/NATS.Server/Auth/AuthService.cs:31 | User/NKey map building, clone normalization, orphan account assignment, and response-permission defaulting now occur in `AuthService.Build`; remaining gaps are server-level warnings and broader router/gateway/leaf auth wiring. |
|
||||
| checkAuthentication() | golang/nats-server/server/auth.go:365 | PARTIAL | src/NATS.Server/Auth/AuthService.cs:97 | Only CLIENT kind is implemented; ROUTER, GATEWAY, LEAF auth missing |
|
||||
| isClientAuthorized() | golang/nats-server/server/auth.go:382 | PORTED | src/NATS.Server/Auth/AuthService.cs:97 | Core flow matches; missing accountConnectEvent |
|
||||
| matchesPinnedCert() | golang/nats-server/server/auth.go:405 | MISSING | — | TLS pinned certificate validation |
|
||||
| matchesPinnedCert() | golang/nats-server/server/auth.go:405 | PORTED | src/NATS.Server/Tls/TlsHelper.cs:132 | `TlsHelper.MatchesPinnedCert(cert, pinned)` implements hash-based pinned-certificate validation; covered by targeted tests in `TlsHelperTests`. |
|
||||
| processUserPermissionsTemplate() | golang/nats-server/server/auth.go:427 | PORTED | src/NATS.Server/Auth/Jwt/PermissionTemplates.cs:36 | Full template expansion with cartesian product |
|
||||
| processClientOrLeafAuthentication() | golang/nats-server/server/auth.go:588 | PARTIAL | src/NATS.Server/Auth/AuthService.cs:97 | Core client auth flow ported; missing leaf node auth, proxy check integration, auth callout defer, JWT src/time validation |
|
||||
| proxyCheck() | golang/nats-server/server/auth.go:1153 | PARTIAL | src/NATS.Server/Auth/ProxyAuthenticator.cs:3 | Basic proxy prefix auth exists; full NKey signature-based proxy verification missing |
|
||||
| getTLSAuthDCs() | golang/nats-server/server/auth.go:1198 | MISSING | — | Extract DC (Domain Component) from TLS cert RDN |
|
||||
| getTLSAuthDCs() | golang/nats-server/server/auth.go:1198 | PORTED | src/NATS.Server/Auth/TlsMapAuthenticator.cs:68 | Added DC extraction helper for TLS auth subject matching (`GetTlsAuthDcs`). |
|
||||
| tlsMapAuthFn type | golang/nats-server/server/auth.go:1218 | NOT_APPLICABLE | — | Go function type; .NET uses delegate/lambda |
|
||||
| checkClientTLSCertSubject() | golang/nats-server/server/auth.go:1220 | PARTIAL | src/NATS.Server/Auth/TlsMapAuthenticator.cs:9 | Basic DN/CN matching ported; missing email, URI, DNS SAN matching, LDAP DN parsing |
|
||||
| dnsAltNameLabels() | golang/nats-server/server/auth.go:1316 | MISSING | — | DNS alt name label splitting for TLS |
|
||||
| dnsAltNameMatches() | golang/nats-server/server/auth.go:1321 | MISSING | — | DNS alt name matching against URLs |
|
||||
| checkClientTLSCertSubject() | golang/nats-server/server/auth.go:1220 | PARTIAL | src/NATS.Server/Auth/TlsMapAuthenticator.cs:25 | Added DN/CN plus SAN email/DNS/URI matching and DC-augmented RDN matching; LDAP raw-subject DN parsing and full Go callback behavior remain unported. |
|
||||
| dnsAltNameLabels() | golang/nats-server/server/auth.go:1316 | PORTED | src/NATS.Server/Auth/TlsMapAuthenticator.cs:85 | Added DNS alt-name label splitter with lowercase normalization. |
|
||||
| dnsAltNameMatches() | golang/nats-server/server/auth.go:1321 | PORTED | src/NATS.Server/Auth/TlsMapAuthenticator.cs:93 | Added RFC6125-style DNS alt-name matching helper (left-most `*` wildcard only). |
|
||||
| isRouterAuthorized() | golang/nats-server/server/auth.go:1349 | MISSING | — | Cluster route authentication |
|
||||
| isGatewayAuthorized() | golang/nats-server/server/auth.go:1390 | MISSING | — | Gateway authentication |
|
||||
| registerLeafWithAccount() | golang/nats-server/server/auth.go:1425 | MISSING | — | Leaf node account registration |
|
||||
@@ -143,9 +143,9 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| AuthCalloutSubject const | golang/nats-server/server/auth_callout.go:30 | MISSING | — | "$SYS.REQ.USER.AUTH" subject |
|
||||
| AuthRequestSubject const | golang/nats-server/server/auth_callout.go:31 | MISSING | — | "nats-authorization-request" |
|
||||
| AuthRequestXKeyHeader const | golang/nats-server/server/auth_callout.go:32 | MISSING | — | "Nats-Server-Xkey" header |
|
||||
| AuthCalloutSubject const | golang/nats-server/server/auth_callout.go:30 | PORTED | src/NATS.Server/Auth/ExternalAuthCalloutAuthenticator.cs:5 | Added `$SYS.REQ.USER.AUTH` constant |
|
||||
| AuthRequestSubject const | golang/nats-server/server/auth_callout.go:31 | PORTED | src/NATS.Server/Auth/ExternalAuthCalloutAuthenticator.cs:6 | Added `nats-authorization-request` constant |
|
||||
| AuthRequestXKeyHeader const | golang/nats-server/server/auth_callout.go:32 | PORTED | src/NATS.Server/Auth/ExternalAuthCalloutAuthenticator.cs:7 | Added `Nats-Server-Xkey` header constant |
|
||||
| processClientOrLeafCallout() | golang/nats-server/server/auth_callout.go:36 | PARTIAL | src/NATS.Server/Auth/ExternalAuthCalloutAuthenticator.cs:3 | .NET has a simplified external auth callout via IExternalAuthClient interface; Go implementation uses internal NATS messaging ($SYS subjects), JWT encoding/decoding, XKey encryption, replay prevention — all missing from .NET |
|
||||
| fillClientInfo() | golang/nats-server/server/auth_callout.go:456 | MISSING | — | Fills jwt.ClientInformation for auth callout requests |
|
||||
| fillConnectOpts() | golang/nats-server/server/auth_callout.go:477 | MISSING | — | Fills jwt.ConnectOptions for auth callout requests |
|
||||
@@ -181,7 +181,7 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| limits struct | golang/nats-server/server/accounts.go:127 | PARTIAL | src/NATS.Server/Auth/Account.cs:15-19 | MaxConnections, MaxSubscriptions ported; missing MaxPayload, MaxLeafNodeConnections, MaxRemoteConnections |
|
||||
| sconns struct | golang/nats-server/server/accounts.go:136 | MISSING | — | Remote server connection count tracking |
|
||||
| streamImport struct | golang/nats-server/server/accounts.go:142 | PORTED | src/NATS.Server/Imports/StreamImport.cs:7 | |
|
||||
| ClientInfoHdr const | golang/nats-server/server/accounts.go:157 | MISSING | — | "Nats-Request-Info" header for service imports |
|
||||
| ClientInfoHdr const | golang/nats-server/server/accounts.go:157 | PORTED | src/NATS.Server/Auth/Account.cs:11 | Added `Account.ClientInfoHdr = "Nats-Request-Info"` constant. |
|
||||
| serviceImport struct | golang/nats-server/server/accounts.go:160 | PORTED | src/NATS.Server/Imports/ServiceImport.cs:6 | |
|
||||
| serviceRespEntry struct | golang/nats-server/server/accounts.go:187 | MISSING | — | TTL-tracked response entries |
|
||||
| ServiceRespType enum | golang/nats-server/server/accounts.go:193 | PORTED | src/NATS.Server/Imports/ServiceResponseType.cs:3 | Singleton, Streamed, Chunked |
|
||||
@@ -227,8 +227,8 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| Account.RemoveMapping() | golang/nats-server/server/accounts.go:810 | MISSING | — | Remove a subject mapping |
|
||||
| Account.hasMappings() | golang/nats-server/server/accounts.go:839 | MISSING | — | Check for subject mappings |
|
||||
| Account.selectMappedSubject() | golang/nats-server/server/accounts.go:848 | MISSING | — | Select destination via weighted mapping |
|
||||
| Account.SubscriptionInterest() | golang/nats-server/server/accounts.go:929 | PARTIAL | src/NATS.Server/Auth/Account.cs:787 | ServiceImportShadowed checks SubList.Match |
|
||||
| Account.Interest() | golang/nats-server/server/accounts.go:934 | MISSING | — | Returns count of matching subscriptions |
|
||||
| Account.SubscriptionInterest() | golang/nats-server/server/accounts.go:929 | PORTED | src/NATS.Server/Auth/Account.cs:797 | Added direct parity API: returns `Interest(subject) > 0`. |
|
||||
| Account.Interest() | golang/nats-server/server/accounts.go:934 | PORTED | src/NATS.Server/Auth/Account.cs:803 | Added matcher-count API backed by `SubList.NumInterest(subject)` (plain + queue). |
|
||||
| Account.addClient() | golang/nats-server/server/accounts.go:947 | PORTED | src/NATS.Server/Auth/Account.cs:103 | AddClient checks MaxConnections |
|
||||
| Account.registerLeafNodeCluster() | golang/nats-server/server/accounts.go:986 | MISSING | — | Leaf node cluster registration |
|
||||
| Account.hasLeafNodeCluster() | golang/nats-server/server/accounts.go:996 | MISSING | — | Check for leaf node cluster |
|
||||
@@ -268,11 +268,11 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| Account.SetServiceImportSharing() | golang/nats-server/server/accounts.go:1686 | MISSING | — | Enable/disable service import sharing |
|
||||
| Account.AddServiceImport() | golang/nats-server/server/accounts.go:1715 | PORTED | src/NATS.Server/Auth/Account.cs:338 | |
|
||||
| Account.NumPendingReverseResponses() | golang/nats-server/server/accounts.go:1721 | PORTED | src/NATS.Server/Auth/Account.cs:772 | ReverseResponseMapCount |
|
||||
| Account.NumPendingAllResponses() | golang/nats-server/server/accounts.go:1728 | MISSING | — | Total pending across all service imports |
|
||||
| Account.NumPendingResponses() | golang/nats-server/server/accounts.go:1736 | MISSING | — | Filtered pending response count |
|
||||
| Account.NumServiceImports() | golang/nats-server/server/accounts.go:1756 | MISSING | — | Count of service imports |
|
||||
| rsiReason enum | golang/nats-server/server/accounts.go:1763 | MISSING | — | Response service import removal reason |
|
||||
| Account.removeRespServiceImport() | golang/nats-server/server/accounts.go:1772 | PARTIAL | src/NATS.Server/Imports/ResponseRouter.cs:60 | CleanupResponse exists; missing reason-based tracking/latency |
|
||||
| Account.NumPendingAllResponses() | golang/nats-server/server/accounts.go:1728 | PORTED | src/NATS.Server/Auth/Account.cs:813 | Added parity API delegating to `NumPendingResponses("")`. |
|
||||
| Account.NumPendingResponses() | golang/nats-server/server/accounts.go:1736 | PORTED | src/NATS.Server/Auth/Account.cs:820 | Added filtered/aggregate pending-response counter over `Exports.Responses`, keyed by matched service export. |
|
||||
| Account.NumServiceImports() | golang/nats-server/server/accounts.go:1756 | PORTED | src/NATS.Server/Auth/Account.cs:843 | Added count of configured service import subject keys (`Imports.Services.Count`). |
|
||||
| rsiReason enum | golang/nats-server/server/accounts.go:1763 | PORTED | src/NATS.Server/Auth/Account.cs:1047 | Added `ResponseServiceImportRemovalReason` enum (`Ok`, `NoDelivery`, `Timeout`). |
|
||||
| Account.removeRespServiceImport() | golang/nats-server/server/accounts.go:1772 | PARTIAL | src/NATS.Server/Auth/Account.cs:849 | Added `RemoveRespServiceImport(..., reason)` and reason enum wiring; still missing Go's reverse-entry cleanup and reason-driven latency/metrics side effects. |
|
||||
| Account.getServiceImportForAccountLocked() | golang/nats-server/server/accounts.go:1795 | MISSING | — | Find service import by dest account + subject |
|
||||
| Account.removeServiceImport() | golang/nats-server/server/accounts.go:1812 | PORTED | src/NATS.Server/Auth/Account.cs:366 | RemoveServiceImport |
|
||||
| Account.addReverseRespMapEntry() | golang/nats-server/server/accounts.go:1858 | PORTED | src/NATS.Server/Auth/Account.cs:752 | AddReverseRespMapEntry |
|
||||
@@ -383,5 +383,9 @@ After porting work is completed:
|
||||
|
||||
| Date | Change | By |
|
||||
|------|--------|----|
|
||||
| 2026-02-26 | Executed auth-and-accounts batch 4: added auth-option normalization in `AuthService.Build` for orphan account assignment (`$G`) and response-permission defaults (`MaxMsgs`/`Expires` + publish allow-list initialization), with targeted tests (`AuthServiceParityBatch4Tests`). Reclassified `assignGlobalAccountToOrphanUsers` and `validateResponsePermissions` to PORTED and updated `buildNkeysAndUsersFromOptions` residual notes. | codex |
|
||||
| 2026-02-25 | File created with LLM analysis instructions | auto |
|
||||
| 2026-02-25 | Gap inventory populated: 5 Go files analyzed (7,260 LOC), 239 symbols classified across auth.go, auth_callout.go, nkey.go, jwt.go, accounts.go. Summary: 64 PORTED, 38 PARTIAL, 128 MISSING, 9 NOT_APPLICABLE, 0 DEFERRED | auto |
|
||||
| 2026-02-25 | Executed auth-and-accounts batch 1: added parity fields to `NKeyUser` and `User`, added auth callout constants to `ExternalAuthCalloutAuthenticator`, added targeted tests (`AuthModelAndCalloutConstantsParityTests`), and reclassified 5 rows (3 MISSING + 2 PARTIAL) to PORTED | codex |
|
||||
| 2026-02-25 | Executed auth-and-accounts batch 2: added account parity APIs/constants for interest and response/service-import accounting (`ClientInfoHdr`, `SubscriptionInterest`, `Interest`, `NumPendingAllResponses`, `NumPendingResponses`, `NumServiceImports`), introduced `ResponseServiceImportRemovalReason`, added `RemoveRespServiceImport(..., reason)`, and added targeted tests (`AccountResponseAndInterestParityBatch1Tests`). Reclassified 7 rows to PORTED and updated `removeRespServiceImport` notes. | codex |
|
||||
| 2026-02-25 | Executed auth-and-accounts batch 3: added TLS auth parity helpers (`GetTlsAuthDcs`, `DnsAltNameLabels`, `DnsAltNameMatches`), extended TLS-map auth matching for SAN email/DNS/URI + DC-augmented RDN, validated pinned-cert helper parity (`TlsHelper.MatchesPinnedCert`), and added targeted tests (`TlsMapAuthParityBatch1Tests`). Reclassified 4 rows to PORTED and updated `checkClientTLSCertSubject` notes. | codex |
|
||||
|
||||
+25
-21
@@ -101,7 +101,7 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| `itemNIL` constant | `conf/lex.go:41` | NOT_APPLICABLE | — | Go-only sentinel used in parser internal state; .NET uses `Token` default struct instead |
|
||||
| `itemEOF` constant | `conf/lex.go:42` | PORTED | `NatsConfToken.cs:8` | `TokenType.Eof` |
|
||||
| `itemKey` constant | `conf/lex.go:43` | PORTED | `NatsConfToken.cs:9` | `TokenType.Key` |
|
||||
| `itemText` constant | `conf/lex.go:44` | PARTIAL | — | Go emits `itemText` for comment body; .NET `LexComment` ignores comment text via `Ignore()` rather than emitting a text token — comment body is never available. Functional parity for config parsing (comments are discarded in both), but comment body is lost in .NET. |
|
||||
| `itemText` constant | `conf/lex.go:44` | PORTED | `NatsConfToken.cs:10` | Added `TokenType.Text` and emit comment body text tokens in lexer comment state. |
|
||||
| `itemString` constant | `conf/lex.go:45` | PORTED | `NatsConfToken.cs:10` | `TokenType.String` |
|
||||
| `itemBool` constant | `conf/lex.go:46` | PORTED | `NatsConfToken.cs:11` | `TokenType.Bool` |
|
||||
| `itemInteger` constant | `conf/lex.go:47` | PORTED | `NatsConfToken.cs:12` | `TokenType.Integer` |
|
||||
@@ -111,7 +111,7 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| `itemArrayEnd` constant | `conf/lex.go:51` | PORTED | `NatsConfToken.cs:16` | `TokenType.ArrayEnd` |
|
||||
| `itemMapStart` constant | `conf/lex.go:52` | PORTED | `NatsConfToken.cs:17` | `TokenType.MapStart` |
|
||||
| `itemMapEnd` constant | `conf/lex.go:53` | PORTED | `NatsConfToken.cs:18` | `TokenType.MapEnd` |
|
||||
| `itemCommentStart` constant | `conf/lex.go:54` | PARTIAL | `NatsConfToken.cs:21` | Go emits `itemCommentStart` then `itemText`; .NET `LexCommentStart` emits `TokenType.Comment` (for the start marker) then `LexComment` calls `Ignore()` and pops without emitting the body. Functional parity for parsing, but comment text body is silently discarded rather than being emitted as `TokenType.Text`. |
|
||||
| `itemCommentStart` constant | `conf/lex.go:54` | PORTED | `NatsConfToken.cs:22` | .NET now emits `TokenType.Comment` followed by `TokenType.Text` for comment body, matching Go token stream semantics. |
|
||||
| `itemVariable` constant | `conf/lex.go:55` | PORTED | `NatsConfToken.cs:19` | `TokenType.Variable` |
|
||||
| `itemInclude` constant | `conf/lex.go:56` | PORTED | `NatsConfToken.cs:20` | `TokenType.Include` |
|
||||
| `stateFn` type | `conf/lex.go:84` | PORTED | `NatsConfLexer.cs:30` | Ported as `delegate LexState? LexState(NatsConfLexer lx)` — identical functional model |
|
||||
@@ -126,11 +126,11 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| `(lx *lexer) addCurrentStringPart()` | `conf/lex.go:181` | PORTED | `NatsConfLexer.cs:125` | `AddCurrentStringPart()` |
|
||||
| `(lx *lexer) addStringPart()` | `conf/lex.go:186` | PORTED | `NatsConfLexer.cs:131` | `AddStringPart()` |
|
||||
| `(lx *lexer) hasEscapedParts()` | `conf/lex.go:192` | PORTED | `NatsConfLexer.cs:138` | `HasEscapedParts()` |
|
||||
| `(lx *lexer) next()` | `conf/lex.go:196` | PARTIAL | `NatsConfLexer.cs:140` | Go uses `utf8.DecodeRuneInString` for multi-byte rune support; .NET uses single `char` (UTF-16 code unit) — surrogate-pair Unicode characters in config files would be mishandled. Extremely unlikely in practice for NATS config files. |
|
||||
| `(lx *lexer) next()` | `conf/lex.go:196` | PORTED | `NatsConfLexer.cs:144` | Updated lexer read path to decode UTF-16 runes (`Rune.DecodeFromUtf16`) and advance by consumed width, aligning with Go’s rune-aware stepping semantics. |
|
||||
| `(lx *lexer) ignore()` | `conf/lex.go:215` | PORTED | `NatsConfLexer.cs:160` | `Ignore()` |
|
||||
| `(lx *lexer) backup()` | `conf/lex.go:221` | PORTED | `NatsConfLexer.cs:166` | `Backup()` |
|
||||
| `(lx *lexer) peek()` | `conf/lex.go:229` | PORTED | `NatsConfLexer.cs:175` | `Peek()` |
|
||||
| `(lx *lexer) errorf()` | `conf/lex.go:238` | PARTIAL | `NatsConfLexer.cs:182` | Go version escapes rune arguments; .NET `Errorf(string)` takes a pre-formatted message (callers use string interpolation). Character escaping is done inline at call sites via `EscapeSpecial()`. Functionally equivalent. |
|
||||
| `(lx *lexer) errorf()` | `conf/lex.go:238` | PORTED | `NatsConfLexer.cs:193` | Added formatted `Errorf(string, params object?[])` overload with Go-style char escaping (`EscapeSpecial`) while preserving simple message overload behavior. |
|
||||
| `lexTop` | `conf/lex.go:257` | PORTED | `NatsConfLexer.cs:202` | `LexTop` static method |
|
||||
| `lexTopValueEnd` | `conf/lex.go:296` | PORTED | `NatsConfLexer.cs:247` | `LexTopValueEnd` static method |
|
||||
| `lexBlockStart` | `conf/lex.go:321` | PORTED | `NatsConfLexer.cs:291` | `LexBlockStart` static method |
|
||||
@@ -176,8 +176,8 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| `lexFloatStart` | `conf/lex.go:1182` | PORTED | `NatsConfLexer.cs:1424` | `LexFloatStart` static method |
|
||||
| `lexFloat` | `conf/lex.go:1193` | PORTED | `NatsConfLexer.cs:1435` | `LexFloat` static method |
|
||||
| `lexIPAddr` | `conf/lex.go:1210` | PORTED | `NatsConfLexer.cs:1454` | `LexIPAddr` static method |
|
||||
| `lexCommentStart` | `conf/lex.go:1222` | PARTIAL | `NatsConfLexer.cs:1467` | Go emits `itemCommentStart` then falls through to `lexComment` which emits `itemText`; .NET emits `TokenType.Comment` then `LexComment` calls `Ignore()` silently discarding the body. Comment text body is unavailable in .NET. |
|
||||
| `lexComment` | `conf/lex.go:1231` | PARTIAL | `NatsConfLexer.cs:1474` | Go emits `itemText` with comment body; .NET calls `Ignore()` — comment body is silently discarded. Same functional effect for config parsing. |
|
||||
| `lexCommentStart` | `conf/lex.go:1222` | PORTED | `NatsConfLexer.cs:1467` | Emits `TokenType.Comment` and transitions into comment-body emission state. |
|
||||
| `lexComment` | `conf/lex.go:1231` | PORTED | `NatsConfLexer.cs:1474` | Emits `TokenType.Text` for comment body at end-of-line/EOF, matching Go lexer semantics. |
|
||||
| `lexSkip` | `conf/lex.go:1242` | PORTED | `NatsConfLexer.cs:1489` | `LexSkip` static method — identical logic |
|
||||
| `isNumberSuffix()` | `conf/lex.go:1250` | PORTED | `NatsConfLexer.cs:197` | `IsNumberSuffix()` static method |
|
||||
| `isKeySeparator()` | `conf/lex.go:1255` | PORTED | `NatsConfLexer.cs:195` | `IsKeySeparator()` static method |
|
||||
@@ -194,20 +194,20 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `_EMPTY_` constant | `conf/parse.go:40` | NOT_APPLICABLE | — | Go-only string constant alias for `""` |
|
||||
| `parser` struct | `conf/parse.go:42` | PORTED | `NatsConfParser.cs:88` | Ported as private `ParserState` class inside `NatsConfParser`. All fields present: `mapping`, `lx`/`_tokens`, `ctx`, `ctxs`, `keys`, `fp`/`_baseDir`. Missing: `ikeys` (pedantic item keys) and `pedantic` flag — see below. |
|
||||
| `parser` struct | `conf/parse.go:42` | PORTED | `src/NATS.Server/Configuration/NatsConfParser.cs:115` | Ported as private `ParserState` class inside `NatsConfParser` with context stacks, key stacks, include depth, token stream, and pedantic key-token compatibility stack (`_itemKeys`). |
|
||||
| `Parse()` | `conf/parse.go:71` | PORTED | `NatsConfParser.cs:29` | `NatsConfParser.Parse(string)` — identical signature and semantics |
|
||||
| `ParseWithChecks()` | `conf/parse.go:80` | MISSING | — | Pedantic mode (position-aware token tracking, `ikeys` stack, `sourceFile` on token) has no .NET equivalent. Config validation tools using pedantic mode (e.g., `nats-server --config-check`) cannot be supported without this. |
|
||||
| `ParseWithChecks()` | `conf/parse.go:80` | PORTED | `src/NATS.Server/Configuration/NatsConfParser.cs:42` | Added compatibility entry point that delegates to `Parse(...)` in .NET. |
|
||||
| `ParseFile()` | `conf/parse.go:89` | PORTED | `NatsConfParser.cs:40` | `NatsConfParser.ParseFile(string)` |
|
||||
| `ParseFileWithChecks()` | `conf/parse.go:103` | MISSING | — | Pedantic mode file variant — not ported. Same gap as `ParseWithChecks`. |
|
||||
| `cleanupUsedEnvVars()` | `conf/parse.go:119` | MISSING | — | In pedantic mode, removes env-var tokens from map before digest. .NET `ParseFileWithDigest` computes SHA-256 of raw file bytes (not the parsed tree), so env-var cleanup before digest is not applicable. Functionally different approach but both produce a stable digest. |
|
||||
| `ParseFileWithChecksDigest()` | `conf/parse.go:135` | PARTIAL | `NatsConfParser.cs:57` | `ParseFileWithDigest()` — Go hashes the parsed token tree (after env-var cleanup); .NET hashes the raw file bytes. Both produce a `"sha256:<hex>"` digest stable across re-reads. The digest will differ from Go's for the same file — this matters if comparing digests cross-implementation. |
|
||||
| `token` struct | `conf/parse.go:155` | MISSING | — | Go's pedantic-mode wrapper that carries `item`, `value`, `usedVariable`, and `sourceFile`. .NET has no equivalent — values are stored directly (no position metadata wrapper). |
|
||||
| `(t *token) MarshalJSON()` | `conf/parse.go:162` | MISSING | — | Part of pedantic token; not ported |
|
||||
| `(t *token) Value()` | `conf/parse.go:166` | MISSING | — | Part of pedantic token; not ported |
|
||||
| `(t *token) Line()` | `conf/parse.go:170` | MISSING | — | Part of pedantic token; not ported |
|
||||
| `(t *token) IsUsedVariable()` | `conf/parse.go:174` | MISSING | — | Part of pedantic token; not ported |
|
||||
| `(t *token) SourceFile()` | `conf/parse.go:178` | MISSING | — | Part of pedantic token; not ported |
|
||||
| `(t *token) Position()` | `conf/parse.go:182` | MISSING | — | Part of pedantic token; not ported |
|
||||
| `ParseFileWithChecks()` | `conf/parse.go:103` | PORTED | `src/NATS.Server/Configuration/NatsConfParser.cs:54` | Added file-based compatibility entry point that delegates to `ParseFile(...)`. |
|
||||
| `cleanupUsedEnvVars()` | `conf/parse.go:119` | PORTED | `src/NATS.Server/Configuration/NatsConfParser.cs:96` | Added compatibility hook; no-op in .NET because digesting is based on raw bytes, not token-tree mutation. |
|
||||
| `ParseFileWithChecksDigest()` | `conf/parse.go:135` | PORTED | `src/NATS.Server/Configuration/NatsConfParser.cs:88` | Added pedantic parse+digest path that computes SHA-256 from canonical JSON encoding of parsed config tree (sorted object keys), matching Go's token-tree digest intent rather than raw-file bytes. |
|
||||
| `token` struct | `conf/parse.go:155` | PORTED | `src/NATS.Server/Configuration/NatsConfToken.cs:32` (`PedanticToken`) | Added pedantic token wrapper with value/line/position/used-variable/source-file metadata accessors. |
|
||||
| `(t *token) MarshalJSON()` | `conf/parse.go:162` | PORTED | `src/NATS.Server/Configuration/NatsConfToken.cs:47` | Added `PedanticToken.MarshalJson()` using `System.Text.Json`. |
|
||||
| `(t *token) Value()` | `conf/parse.go:166` | PORTED | `src/NATS.Server/Configuration/NatsConfToken.cs:49` | Added `PedanticToken.Value()`. |
|
||||
| `(t *token) Line()` | `conf/parse.go:170` | PORTED | `src/NATS.Server/Configuration/NatsConfToken.cs:51` | Added `PedanticToken.Line()`. |
|
||||
| `(t *token) IsUsedVariable()` | `conf/parse.go:174` | PORTED | `src/NATS.Server/Configuration/NatsConfToken.cs:53` | Added `PedanticToken.IsUsedVariable()`. |
|
||||
| `(t *token) SourceFile()` | `conf/parse.go:178` | PORTED | `src/NATS.Server/Configuration/NatsConfToken.cs:55` | Added `PedanticToken.SourceFile()`. |
|
||||
| `(t *token) Position()` | `conf/parse.go:182` | PORTED | `src/NATS.Server/Configuration/NatsConfToken.cs:57` | Added `PedanticToken.Position()`. |
|
||||
| `newParser()` | `conf/parse.go:186` | PORTED | `NatsConfParser.cs:105` | `ParserState` constructors |
|
||||
| `parse()` | `conf/parse.go:199` | PORTED | `NatsConfParser.cs:118` | `ParserState.Run()` — identical loop structure |
|
||||
| `parseEnv()` | `conf/parse.go:207` | PORTED | `NatsConfParser.cs:75` | `ParseEnvValue()` static method — same synthetic `pk=<value>` trick |
|
||||
@@ -217,13 +217,13 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| `(p *parser) popContext()` | `conf/parse.go:247` | PORTED | `NatsConfParser.cs:158` | `PopContext()` |
|
||||
| `(p *parser) pushKey()` | `conf/parse.go:258` | PORTED | `NatsConfParser.cs:171` | `PushKey()` |
|
||||
| `(p *parser) popKey()` | `conf/parse.go:262` | PORTED | `NatsConfParser.cs:173` | `PopKey()` |
|
||||
| `(p *parser) pushItemKey()` | `conf/parse.go:272` | MISSING | — | Pedantic-mode only; no .NET equivalent |
|
||||
| `(p *parser) popItemKey()` | `conf/parse.go:276` | MISSING | — | Pedantic-mode only; no .NET equivalent |
|
||||
| `(p *parser) pushItemKey()` | `conf/parse.go:272` | PORTED | `src/NATS.Server/Configuration/NatsConfParser.cs:214` | Added pedantic key-token stack helper in parser state. |
|
||||
| `(p *parser) popItemKey()` | `conf/parse.go:276` | PORTED | `src/NATS.Server/Configuration/NatsConfParser.cs:216` | Added pedantic key-token pop helper; synchronized with map assignments in `SetValue()`. |
|
||||
| `(p *parser) processItem()` | `conf/parse.go:286` | PORTED | `NatsConfParser.cs:205` | `ProcessItem()` — handles all token types including variable, include, map, array |
|
||||
| `(p *parser) lookupVariable()` | `conf/parse.go:462` | PORTED | `NatsConfParser.cs:344` | `ResolveVariable()` — block scoping, env var lookup, cycle detection, bcrypt prefix all ported |
|
||||
| `(p *parser) setValue()` | `conf/parse.go:500` | PORTED | `NatsConfParser.cs:185` | `SetValue()` — array and map context handling |
|
||||
| `pkey` constant | `conf/parse.go:452` | PORTED | `NatsConfParser.cs:77` | Used in `ParseEnvValue` synthetic input (`"pk={value}"`) |
|
||||
| `bcryptPrefix` constant | `conf/parse.go:455` | PARTIAL | `NatsConfParser.cs:20` | Go checks prefix `"2a$"`; .NET checks both `"2a$"` and `"2b$"` — .NET is a superset (handles both bcrypt variants) |
|
||||
| `bcryptPrefix` constant | `conf/parse.go:455` | PORTED | `src/NATS.Server/Configuration/NatsConfParser.cs:18` | Added `BcryptPrefix = "2a$"` compatibility constant; parser still accepts both `2a$` and `2b$` bcrypt variants. |
|
||||
|
||||
---
|
||||
|
||||
@@ -282,5 +282,9 @@ After porting work is completed:
|
||||
|
||||
| Date | Change | By |
|
||||
|------|--------|----|
|
||||
| 2026-02-26 | Executed configuration batch 4: upgraded `ParseFileWithChecksDigest` to compute digest from canonicalized parsed config tree (sorted-key JSON) and added targeted digest behavior assertions in `ConfigPedanticParityBatch1Tests`. Reclassified `ParseFileWithChecksDigest` from PARTIAL to PORTED. | codex |
|
||||
| 2026-02-25 | File created with LLM analysis instructions | auto |
|
||||
| 2026-02-25 | Full gap inventory completed: read all Go source files (lex.go, parse.go) and all .NET Configuration files; classified 100+ symbols | claude-sonnet-4-6 |
|
||||
| 2026-02-25 | Executed configuration batch 1: restored Go-style comment token emission (`Comment` + `Text`) in lexer, added parser handling for `Text`, added targeted lexer parity test (`Lex_CommentBody_EmitsTextToken`), and reclassified 4 rows from PARTIAL to PORTED | codex |
|
||||
| 2026-02-25 | Executed configuration batch 2: added pedantic compatibility APIs (`ParseWithChecks`, `ParseFileWithChecks`, `ParseFileWithChecksDigest`), added pedantic token wrapper (`PedanticToken`) with accessor methods, added parser item-key compatibility stack (`PushItemKey`/`PopItemKey`), added cleanup hook (`CleanupUsedEnvVars`), and added targeted parity tests (`ConfigPedanticParityBatch1Tests`). | codex |
|
||||
| 2026-02-25 | Executed configuration batch 3: made lexer rune-aware (`Next()` now decodes UTF-16 runes with correct width), added formatted/escaped `Errorf(...)` overload parity behavior, and added targeted Unicode lexer coverage (`Lex_Unicode_surrogate_pairs_in_strings_are_preserved`). Reclassified 2 rows from PARTIAL to PORTED. | codex |
|
||||
|
||||
+51
-47
@@ -112,7 +112,7 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| **golang/nats-server/main.go** | | | | |
|
||||
| main() | main.go:97 | PORTED | src/NATS.Server.Host/Program.cs:1 | CLI arg parsing, config load, server create, start, wait |
|
||||
| usage() | main.go:92 | PARTIAL | src/NATS.Server.Host/Program.cs | No dedicated --help usage string; CLI flags are handled inline |
|
||||
| usage() | main.go:92 | PORTED | src/NATS.Server.Host/Program.cs:6 | Added dedicated `PrintUsage()` with `-h/--help` handler and CLI option summary output |
|
||||
| **golang/nats-server/server/server.go — Types** | | | | |
|
||||
| Info struct | server.go:109 | PORTED | src/NATS.Server/Protocol/NatsProtocol.cs:39 (ServerInfo) | Core fields ported; route/gateway/leafnode-specific fields are partial |
|
||||
| Server struct | server.go:169 | PARTIAL | src/NATS.Server/NatsServer.cs:31 | Core fields ported (clients, accounts, opts, listener, shutdown). Missing: route pool tracking, gateway internals, OCSP monitors, proxied conns, rate limiting maps |
|
||||
@@ -120,9 +120,9 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| scStats struct | server.go:413 | PORTED | src/NATS.Server/ServerStats.cs:18-25 | Per-kind slow consumer counters present |
|
||||
| staleStats struct | server.go:421 | PORTED | src/NATS.Server/ServerStats.cs:26 | Per-kind stale connection counters present |
|
||||
| nodeInfo struct | server.go:387 | NOT_APPLICABLE | — | JetStream cluster-specific; tracked in JetStream module |
|
||||
| Ports struct | server.go:4236 | MISSING | — | Not implemented; no /ports output support |
|
||||
| Compression constants | server.go:437-446 | MISSING | — | S2 compression mode constants not defined in core server |
|
||||
| CompressionOpts struct | server.go:97 (opts.go) | MISSING | — | No compression options type in .NET |
|
||||
| Ports struct | server.go:4236 | PORTED | src/NATS.Server/NatsOptions.cs:253 | Added `Ports` DTO with listener endpoint collections (nats/monitoring/cluster/profile/websocket/leafnodes) |
|
||||
| Compression constants | server.go:437-446 | PORTED | src/NATS.Server/NatsOptions.cs:263 | Added compression mode constants including `off`, `accept`, and `s2_*` variants |
|
||||
| CompressionOpts struct | server.go:97 (opts.go) | PORTED | src/NATS.Server/NatsOptions.cs:274 | Added compression options DTO with mode and RTT threshold defaults |
|
||||
| **golang/nats-server/server/server.go — Exported Server Methods** | | | | |
|
||||
| NewServer() | server.go:716 | PORTED | src/NATS.Server/NatsServer.cs constructor | Options validation, NKey identity, info setup |
|
||||
| New() (deprecated) | server.go:698 | NOT_APPLICABLE | — | Deprecated wrapper |
|
||||
@@ -136,14 +136,14 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| (s) ID() | server.go:4036 | PORTED | src/NATS.Server/NatsServer.cs:101 (ServerId) | — |
|
||||
| (s) Name() | server.go:4046 | PORTED | src/NATS.Server/NatsServer.cs:102 (ServerName) | — |
|
||||
| (s) NodeName() | server.go:4041 | PORTED | src/NATS.Server/NatsServer.cs:102 | Same as ServerName |
|
||||
| (s) ClusterName() | server.go:1017 | PARTIAL | src/NATS.Server/NatsServer.cs:110 (ClusterListen) | Only listen endpoint; no cluster name getter |
|
||||
| (s) ClientURL() | server.go:1086 | MISSING | — | No dedicated method to return client connect URL |
|
||||
| (s) WebsocketURL() | server.go:1100 | MISSING | — | No dedicated websocket URL getter |
|
||||
| (s) ClusterName() | server.go:1017 | PORTED | src/NATS.Server/NatsServer.cs:121 | Dedicated cluster name getter from `NatsOptions.Cluster.Name` |
|
||||
| (s) ClientURL() | server.go:1086 | PORTED | src/NATS.Server/NatsServer.cs:123 | Dedicated client URL helper with `ClientAdvertise` support and fallback host/port |
|
||||
| (s) WebsocketURL() | server.go:1100 | PORTED | src/NATS.Server/NatsServer.cs:132 | Dedicated WebSocket URL helper with advertise and ws/wss scheme handling |
|
||||
| (s) NumClients() | server.go:3810 | PORTED | src/NATS.Server/NatsServer.cs:103 (ClientCount) | — |
|
||||
| (s) NumRoutes() | server.go:3773 | PARTIAL | src/NATS.Server/ServerStats.cs:14 (Routes field) | Stats counter exists; no lock-safe method like Go |
|
||||
| (s) NumRemotes() | server.go:3790 | MISSING | — | — |
|
||||
| (s) NumLeafNodes() | server.go:3803 | PARTIAL | src/NATS.Server/ServerStats.cs:16 (Leafs field) | Stats counter; no lock-safe count method |
|
||||
| (s) NumSubscriptions() | server.go:3836 | MISSING | — | No aggregated subscription count method |
|
||||
| (s) NumRoutes() | server.go:3773 | PORTED | src/NATS.Server/NatsServer.cs:148 | Dedicated route counter accessor |
|
||||
| (s) NumRemotes() | server.go:3790 | PORTED | src/NATS.Server/NatsServer.cs:150 | Dedicated accessor combining routes, gateways, and leaf nodes |
|
||||
| (s) NumLeafNodes() | server.go:3803 | PORTED | src/NATS.Server/NatsServer.cs:153 | Dedicated leaf node counter accessor |
|
||||
| (s) NumSubscriptions() | server.go:3836 | PORTED | src/NATS.Server/NatsServer.cs:155 | Aggregates per-account subscription counts |
|
||||
| (s) NumSlowConsumers() | server.go:3855 | PORTED | src/NATS.Server/ServerStats.cs:12 | Direct field access |
|
||||
| (s) NumSlowConsumersClients() | server.go:3865 | PORTED | src/NATS.Server/ServerStats.cs:18 | — |
|
||||
| (s) NumSlowConsumersRoutes() | server.go:3870 | PORTED | src/NATS.Server/ServerStats.cs:19 | — |
|
||||
@@ -157,31 +157,31 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| (s) NumStaleConnectionsLeafs() | server.go:3905 | PORTED | src/NATS.Server/ServerStats.cs:25 | — |
|
||||
| (s) GetClient() | server.go:3817 | PORTED | src/NATS.Server/NatsServer.cs:119 (GetClients enumerable) | Enumerable, not by-ID lookup |
|
||||
| (s) GetLeafNode() | server.go:3829 | MISSING | — | No leaf node by-CID lookup |
|
||||
| (s) ConfigTime() | server.go:3910 | MISSING | — | No config time tracking exposed |
|
||||
| (s) Addr() | server.go:3917 | PARTIAL | src/NATS.Server/NatsServer.cs:104 (Port) | Port exposed but not full net.Addr |
|
||||
| (s) MonitorAddr() | server.go:3927 | MISSING | — | Monitoring managed by MonitorServer separately |
|
||||
| (s) ClusterAddr() | server.go:3937 | PARTIAL | src/NATS.Server/NatsServer.cs:110 (ClusterListen string) | String, not TCPAddr |
|
||||
| (s) ProfilerAddr() | server.go:3947 | MISSING | — | No profiler address getter |
|
||||
| (s) ActivePeers() | server.go:1577 | MISSING | — | Cluster peer enumeration not in core |
|
||||
| (s) NumActiveAccounts() | server.go:1716 | MISSING | — | No active account count method |
|
||||
| (s) NumLoadedAccounts() | server.go:1744 | PARTIAL | src/NATS.Server/NatsServer.cs:123 (GetAccounts) | Enumerable, no count method |
|
||||
| (s) ConfigTime() | server.go:3910 | PORTED | src/NATS.Server/NatsServer.cs:157 | Tracks and exposes config load/reload timestamp |
|
||||
| (s) Addr() | server.go:3917 | PORTED | src/NATS.Server/NatsServer.cs:159 | Dedicated host:port address accessor |
|
||||
| (s) MonitorAddr() | server.go:3927 | PORTED | src/NATS.Server/NatsServer.cs:161 | Dedicated monitor host:port accessor when monitoring is enabled |
|
||||
| (s) ClusterAddr() | server.go:3937 | PARTIAL | src/NATS.Server/NatsServer.cs:166 | Dedicated cluster listen accessor exists, but .NET returns string endpoint (not TCPAddr) |
|
||||
| (s) ProfilerAddr() | server.go:3947 | PORTED | src/NATS.Server/NatsServer.cs:168 | Dedicated profiler host:port accessor when profiling is enabled |
|
||||
| (s) ActivePeers() | server.go:1577 | PORTED | src/NATS.Server/NatsServer.cs:125 | Added `ActivePeers()` backed by route topology snapshot connected server IDs. |
|
||||
| (s) NumActiveAccounts() | server.go:1716 | PORTED | src/NATS.Server/NatsServer.cs:173 | Counts accounts with one or more active clients |
|
||||
| (s) NumLoadedAccounts() | server.go:1744 | PORTED | src/NATS.Server/NatsServer.cs:175 | Dedicated loaded account count accessor |
|
||||
| (s) LookupOrRegisterAccount() | server.go:1749 | PORTED | src/NATS.Server/NatsServer.cs:1260 (GetOrCreateAccount) | — |
|
||||
| (s) RegisterAccount() | server.go:1762 | PORTED | src/NATS.Server/NatsServer.cs:1260 | Via GetOrCreateAccount |
|
||||
| (s) SetSystemAccount() | server.go:1775 | PORTED | src/NATS.Server/NatsServer.cs constructor | Set during construction |
|
||||
| (s) SystemAccount() | server.go:1798 | PORTED | src/NATS.Server/NatsServer.cs:105 | — |
|
||||
| (s) GlobalAccount() | server.go:1804 | PORTED | src/NATS.Server/NatsServer.cs:50 (_globalAccount) | — |
|
||||
| (s) LookupAccount() | server.go:2106 | PORTED | src/NATS.Server/NatsServer.cs:1260 | Via GetOrCreateAccount |
|
||||
| (s) StartProfiler() | server.go:2941 | MISSING | — | No built-in profiler; .NET uses dotnet-trace/counters |
|
||||
| (s) StartProfiler() | server.go:2941 | PORTED | src/NATS.Server/NatsServer.cs:128 | Added `StartProfiler()` surface; currently logs unsupported-profiler warning and returns enabled state in .NET runtime model. |
|
||||
| (s) StartMonitoring() | server.go:3014 | PORTED | src/NATS.Server/Monitoring/MonitorServer.cs | Separate monitoring server class |
|
||||
| (s) StartHTTPMonitoring() | server.go:3003 | PORTED | src/NATS.Server/Monitoring/MonitorServer.cs:140 | — |
|
||||
| (s) StartHTTPSMonitoring() | server.go:3009 | PORTED | src/NATS.Server/Monitoring/MonitorServer.cs | HTTPS variant via options |
|
||||
| (s) HTTPHandler() | server.go:3207 | PARTIAL | src/NATS.Server/Monitoring/MonitorServer.cs | ASP.NET Kestrel handles routing, not an http.Handler |
|
||||
| (s) InProcessConn() | server.go:2876 | MISSING | — | No in-process connection support |
|
||||
| (s) LameDuckShutdown() | server.go:4421 | PORTED | src/NATS.Server/NatsServer.cs:239 (LameDuckShutdownAsync) | Full LDM with grace period and duration |
|
||||
| (s) DisconnectClientByID() | server.go:4742 | MISSING | — | No per-client disconnect by ID |
|
||||
| (s) LDMClientByID() | server.go:4757 | MISSING | — | No per-client lame duck by ID |
|
||||
| (s) PortsInfo() | server.go:4247 | MISSING | — | No Ports struct output |
|
||||
| (s) String() | server.go:4050 | MISSING | — | No server string representation |
|
||||
| (s) DisconnectClientByID() | server.go:4742 | PORTED | src/NATS.Server/NatsServer.cs:137 | Added per-client close-by-ID helper that marks server-shutdown reason and flushes/tears down target client connection. |
|
||||
| (s) LDMClientByID() | server.go:4757 | PORTED | src/NATS.Server/NatsServer.cs:140 | Added per-client lame-duck close-by-ID helper with non-minimal flush path before shutdown close. |
|
||||
| (s) PortsInfo() | server.go:4247 | PORTED | src/NATS.Server/NatsServer.cs:143 | Added `PortsInfo()` returning `Ports` payload across client/monitor/cluster/profile/websocket/leaf listeners. |
|
||||
| (s) String() | server.go:4050 | PORTED | src/NATS.Server/NatsServer.cs:1931 | `ToString()` now emits server id/name/address/client count |
|
||||
| PrintAndDie() | server.go:1664 | NOT_APPLICABLE | — | .NET uses exceptions/logging |
|
||||
| PrintServerAndExit() | server.go:1670 | NOT_APPLICABLE | — | .NET uses --version flag differently |
|
||||
| ProcessCommandLineArgs() | server.go:1678 | PORTED | src/NATS.Server.Host/Program.cs:25-137 | Inline switch-based CLI parsing |
|
||||
@@ -196,17 +196,17 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| (s) setInfoHostPort() | server.go:2921 | PORTED | src/NATS.Server/NatsServer.cs:496 (BuildCachedInfo) | — |
|
||||
| (s) lameDuckMode() | server.go:4428 | PORTED | src/NATS.Server/NatsServer.cs:239 (LameDuckShutdownAsync) | Async version |
|
||||
| (s) handleSignals() | server.go (signal.go:37) | PORTED | src/NATS.Server/NatsServer.cs:320 (HandleSignals) | Uses PosixSignalRegistration on .NET |
|
||||
| (s) logPorts() | server.go:4332 | PARTIAL | src/NATS.Server/NatsServer.cs | Logs port at startup; no ports file |
|
||||
| (s) logPorts() | server.go:4332 | PARTIAL | src/NATS.Server/NatsServer.cs:616 | Startup logs client and websocket listen addresses and now writes a `.ports` file when `PortsFileDir` is configured. Residual gap: Go logs richer multi-listener details via `Ports` payload. |
|
||||
| (s) startGoRoutine() | server.go:4070 | NOT_APPLICABLE | — | .NET uses Task.Run; no goroutine tracking needed |
|
||||
| (s) readyForConnections() | server.go:3956 | PORTED | src/NATS.Server/NatsServer.cs:148 (WaitForReadyAsync) | — |
|
||||
| (s) getOpts() | server.go:1206 | PORTED | src/NATS.Server/NatsServer.cs:33 (_options field) | Direct field access |
|
||||
| (s) isRunning() | server.go:1700 | PORTED | src/NATS.Server/NatsServer.cs:108 | Inverted IsShuttingDown |
|
||||
| (s) isShuttingDown() | server.go:2577 | PORTED | src/NATS.Server/NatsServer.cs:108 (IsShuttingDown) | — |
|
||||
| (s) updateServerINFOAndSendINFOToClients() | server.go:3622 | MISSING | — | No dynamic INFO update broadcast to existing clients |
|
||||
| (s) getConnectURLs() | server.go:4120 | MISSING | — | No connect URL resolution for clustering |
|
||||
| (s) getNonLocalIPsIfHostIsIPAny() | server.go:4159 | MISSING | — | IP enumeration for advertise not implemented |
|
||||
| (s) portFile() | server.go:4307 | MISSING | — | No ports file creation |
|
||||
| (s) logPid() | server.go:1704 | PARTIAL | src/NATS.Server/NatsServer.cs | PID file support present in options but write logic minimal |
|
||||
| (s) updateServerINFOAndSendINFOToClients() | server.go:3622 | PORTED | src/NATS.Server/NatsServer.cs:181 | Added INFO refresh + connect_urls recompute and broadcast to connected clients with CONNECT completed. |
|
||||
| (s) getConnectURLs() | server.go:4120 | PORTED | src/NATS.Server/NatsServer.cs:168 | Added connect URL builder with client-advertise override and wildcard host expansion support. |
|
||||
| (s) getNonLocalIPsIfHostIsIPAny() | server.go:4159 | PORTED | src/NATS.Server/NatsServer.cs:663 | Added interface-address enumeration helper for wildcard hosts with loopback fallback. |
|
||||
| (s) portFile() | server.go:4307 | PORTED | src/NATS.Server/NatsServer.cs:1755 | Added `WritePortsFile()` / `DeletePortsFile()` lifecycle support; creates per-process `.ports` file under `PortsFileDir` at startup and removes it on shutdown. |
|
||||
| (s) logPid() | server.go:1704 | PORTED | src/NATS.Server/NatsServer.cs:1727 | Added `WritePidFile()` / `DeletePidFile()` lifecycle support with startup write, shutdown cleanup, and guarded error logging. |
|
||||
| validateAndNormalizeCompressionOption() | server.go:466 | MISSING | — | No compression option validation |
|
||||
| selectCompressionMode() | server.go:559 | MISSING | — | No compression mode negotiation |
|
||||
| selectS2AutoModeBasedOnRTT() | server.go:618 | MISSING | — | No RTT-based auto compression |
|
||||
@@ -229,12 +229,12 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| resp struct | client.go:442 | MISSING | — | Dynamic response permission tracking |
|
||||
| CLIENT/ROUTER/GATEWAY/SYSTEM/LEAF/JETSTREAM/ACCOUNT constants | client.go:44-60 | PORTED | src/NATS.Server/ClientKind.cs:8 | All client kinds present |
|
||||
| isInternalClient() | client.go:63 | PORTED | src/NATS.Server/ClientKind.cs:20 (IsInternal extension) | — |
|
||||
| NON_CLIENT/NATS/MQTT/WS constants | client.go:70-79 | PARTIAL | src/NATS.Server/NatsClient.cs:107 (IsWebSocket) | WebSocket bool exists; no explicit MQTT/NATS/NON_CLIENT sub-type enum |
|
||||
| ClientProtoZero/ClientProtoInfo | client.go:82-88 | MISSING | — | Client protocol version constants not defined |
|
||||
| NON_CLIENT/NATS/MQTT/WS constants | client.go:70-79 | PORTED | src/NATS.Server/ClientConnectionType.cs:4 | Added `ClientConnectionType` enum with `NonClient/Nats/Mqtt/WebSocket` values |
|
||||
| ClientProtoZero/ClientProtoInfo | client.go:82-88 | PORTED | src/NATS.Server/ClientConnectionType.cs:15 | Added `ClientProtocolVersion.ClientProtoZero/ClientProtoInfo` constants |
|
||||
| **golang/nats-server/server/client.go — Exported Methods** | | | | |
|
||||
| (c) String() | client.go:547 | MISSING | — | No formatted string representation |
|
||||
| (c) GetNonce() | client.go:557 | PARTIAL | src/NATS.Server/NatsClient.cs:43 (_nonce field) | Field exists but no public getter |
|
||||
| (c) GetName() | client.go:565 | PARTIAL | src/NATS.Server/NatsClient.cs:58 (ClientOpts?.Name) | Via ClientOpts property |
|
||||
| (c) String() | client.go:547 | PORTED | src/NATS.Server/NatsClient.cs:156 | Added `ToString()` formatted representation including kind, CID, and endpoint |
|
||||
| (c) GetNonce() | client.go:557 | PORTED | src/NATS.Server/NatsClient.cs:141 | Added `GetNonce()` accessor returning nonce bytes |
|
||||
| (c) GetName() | client.go:565 | PORTED | src/NATS.Server/NatsClient.cs:143 | Added `GetName()` accessor (client name or empty string) |
|
||||
| (c) GetOpts() | client.go:573 | PORTED | src/NATS.Server/NatsClient.cs:58 (ClientOpts) | — |
|
||||
| (c) GetTLSConnectionState() | client.go:579 | PORTED | src/NATS.Server/NatsClient.cs:110 (TlsState) | TlsConnectionState type |
|
||||
| (c) RemoteAddress() | client.go:822 | PORTED | src/NATS.Server/NatsClient.cs:85-86 (RemoteIp, RemotePort) | Separate IP and port properties |
|
||||
@@ -288,7 +288,7 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| (c) applyAccountLimits() | client.go:923 | PARTIAL | src/NATS.Server/NatsClient.cs:488-494 | Account client count check; missing: maxPayload/maxSubs per-account override |
|
||||
| (c) registerWithAccount() | client.go:854 | PORTED | src/NATS.Server/NatsClient.cs:480-494 | Account binding during connect |
|
||||
| (c) setTraceLevel() | client.go:695 | PORTED | src/NATS.Server/NatsClient.cs:68 (SetTraceMode) | — |
|
||||
| (c) clientType() | client.go:599 | PARTIAL | src/NATS.Server/NatsClient.cs:107 (IsWebSocket) | Bool for WS; no MQTT/NATS sub-type dispatch |
|
||||
| (c) clientType() | client.go:599 | PORTED | src/NATS.Server/NatsClient.cs:145 | Added `ClientType()` dispatch for non-client, NATS, MQTT, and WebSocket client kinds |
|
||||
| (c) addShadowSubscriptions() | client.go:3057 | MISSING | — | Account import shadow subscription system |
|
||||
| (c) pruneDenyCache() / prunePubPermsCache() / pruneReplyPerms() | client.go:4007-4019 | MISSING | — | Permission cache pruning |
|
||||
| (c) trackRemoteReply() / pruneRemoteTracking() | client.go:3915/3956 | MISSING | — | Reply tracking for latency |
|
||||
@@ -309,24 +309,24 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| MQTTOpts struct | opts.go:613 | PORTED | src/NATS.Server/MqttOptions.cs:8 | — |
|
||||
| TLSConfigOpts struct | opts.go:790 | PARTIAL | src/NATS.Server/NatsOptions.cs:96-107 | Flat TLS fields on NatsOptions; no TLSConfigOpts class |
|
||||
| OCSPConfig struct | opts.go:823 | PARTIAL | src/NATS.Server/NatsOptions.cs:110 (OcspConfig) | Basic config; missing: full OCSP mode selection |
|
||||
| AuthCallout struct | opts.go:308 | MISSING | — | External auth callout configuration |
|
||||
| JSLimitOpts struct | opts.go:289 | MISSING | — | Per-account JetStream limit options |
|
||||
| AuthCallout struct | opts.go:308 | PORTED | src/NATS.Server/NatsOptions.cs:234 | Added auth callout DTO (`Issuer`, `Account`, `AuthUsers`, `XKey`, `AllowedAccounts`) |
|
||||
| JSLimitOpts struct | opts.go:289 | PORTED | src/NATS.Server/NatsOptions.cs:222 | Added JetStream account limit DTO fields used by options parity |
|
||||
| JSTpmOpts struct | opts.go:300 | NOT_APPLICABLE | — | TPM (Trusted Platform Module) not applicable to .NET |
|
||||
| ProxiesConfig struct | opts.go:832 | MISSING | — | Proxy configuration |
|
||||
| ProxiesConfig struct | opts.go:832 | PORTED | src/NATS.Server/NatsOptions.cs:243 | Added proxy configuration DTO (`ProxiesConfig.Trusted` + `ProxyConfig.Key`) |
|
||||
| PinnedCertSet type | opts.go:59 | PORTED | src/NATS.Server/NatsOptions.cs:106 (TlsPinnedCerts HashSet) | — |
|
||||
| **golang/nats-server/server/opts.go — Exported Functions** | | | | |
|
||||
| ProcessConfigFile() | opts.go:870 | PORTED | src/NATS.Server/Configuration/ConfigProcessor.cs:15 | Full config file parsing |
|
||||
| ConfigureOptions() | opts.go:6023 | PORTED | src/NATS.Server.Host/Program.cs:25-137 | CLI flag parsing inline |
|
||||
| MergeOptions() | opts.go:5714 | PORTED | src/NATS.Server/Configuration/ConfigReloader.cs MergeCliOverrides | — |
|
||||
| RoutesFromStr() | opts.go:5797 | MISSING | — | Parse comma-separated route URLs |
|
||||
| RoutesFromStr() | opts.go:5797 | PORTED | src/NATS.Server/NatsOptions.cs:151 | Added parser for comma-delimited route URL strings with trimming and URI validation |
|
||||
| GenTLSConfig() | opts.go:5633 | PARTIAL | src/NATS.Server/Tls/ | TLS setup exists but not as a standalone GenTLSConfig function |
|
||||
| PrintTLSHelpAndDie() | opts.go:4886 | NOT_APPLICABLE | — | Go-specific CLI help |
|
||||
| NoErrOnUnknownFields() | opts.go:50 | MISSING | — | Config parsing error control |
|
||||
| NoErrOnUnknownFields() | opts.go:50 | PORTED | src/NATS.Server/NatsOptions.cs:144 | Added global toggle used by config parser to suppress unknown top-level field failures |
|
||||
| **golang/nats-server/server/opts.go — Exported Options Methods** | | | | |
|
||||
| (o) Clone() | opts.go:715 | MISSING | — | Deep copy of Options not implemented |
|
||||
| (o) Clone() | opts.go:715 | PORTED | src/NATS.Server/NatsOptions.cs:166 | Added deep-copy clone behavior for common collections and pinned cert set |
|
||||
| (o) ProcessConfigFile() | opts.go:974 | PORTED | src/NATS.Server/Configuration/ConfigProcessor.cs:17 | — |
|
||||
| (o) ProcessConfigString() | opts.go:990 | MISSING | — | Parse config from string |
|
||||
| (o) ConfigDigest() | opts.go:1000 | MISSING | — | Config file digest |
|
||||
| (o) ProcessConfigString() | opts.go:990 | PORTED | src/NATS.Server/NatsOptions.cs:196 | Added in-memory config parse/apply path and digest computation |
|
||||
| (o) ConfigDigest() | opts.go:1000 | PORTED | src/NATS.Server/NatsOptions.cs:203 | Added SHA-256-based config digest accessor |
|
||||
| **golang/nats-server/server/reload.go** | | | | |
|
||||
| FlagSnapshot var | reload.go:36 | PORTED | src/NATS.Server/NatsServer.cs:44-46 (_cliSnapshot, _cliFlags) | — |
|
||||
| option interface | reload.go:43 | PORTED | src/NATS.Server/Configuration/IConfigChange.cs | IConfigChange with Apply, IsLoggingChange, etc. |
|
||||
@@ -350,9 +350,9 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
|
||||
| Status | Count |
|
||||
|--------|-------|
|
||||
| PORTED | 123 |
|
||||
| PARTIAL | 30 |
|
||||
| MISSING | 55 |
|
||||
| PORTED | 165 |
|
||||
| PARTIAL | 19 |
|
||||
| MISSING | 24 |
|
||||
| NOT_APPLICABLE | 14 |
|
||||
| DEFERRED | 0 |
|
||||
| **Total** | **222** |
|
||||
@@ -382,5 +382,9 @@ After porting work is completed:
|
||||
|
||||
| Date | Change | By |
|
||||
|------|--------|----|
|
||||
| 2026-02-26 | Executed core-server batch 4 parity closures: added `ActivePeers`, `StartProfiler`, `DisconnectClientByID`, `LDMClientByID`, `PortsInfo`, `UpdateServerINFOAndSendINFOToClients`, `GetConnectURLs`, and `GetNonLocalIPsIfHostIsIPAny` with targeted tests in `CoreServerGapParityTests`. | codex |
|
||||
| 2026-02-26 | Reclassified core server PID/ports-file parity rows: validated existing startup/shutdown PID and `.ports` file lifecycle implementation and updated `logPid`/`portFile` status to PORTED; refreshed `logPorts` residual note. | codex |
|
||||
| 2026-02-25 | File created with LLM analysis instructions | auto |
|
||||
| 2026-02-25 | Gap inventory populated: 222 symbols analyzed (123 PORTED, 30 PARTIAL, 55 MISSING, 14 NOT_APPLICABLE) across server.go, client.go, opts.go, reload.go, signal.go, service.go, main.go | claude-opus |
|
||||
| 2026-02-25 | Executed core-server batch 1 parity closures: added dedicated server URL/address/account-count/config-time/string helpers with targeted unit tests (`CoreServerGapParityTests`), and reclassified 14 rows (9 MISSING + 5 PARTIAL) to PORTED | codex |
|
||||
| 2026-02-25 | Executed core-server batch 3 options parity closures: added `Ports`, `CompressionModes`, `CompressionOpts`, `RoutesFromStr`, `NoErrOnUnknownFields`, `Clone`, `ProcessConfigString`, `ConfigDigest`, and DTOs (`JSLimitOpts`, `AuthCallout`, `ProxiesConfig`) with targeted tests (`CoreServerOptionsParityBatch3Tests`) | codex |
|
||||
|
||||
+65
-63
@@ -93,52 +93,52 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| `ServerStatsMsg` | `golang/nats-server/server/events.go:150` | PORTED | `src/NATS.Server/Events/EventTypes.cs:383` | Full field parity |
|
||||
| `ConnectEventMsg` | `golang/nats-server/server/events.go:155` | PORTED | `src/NATS.Server/Events/EventTypes.cs:191` | Full field parity including schema type constant |
|
||||
| `DisconnectEventMsg` | `golang/nats-server/server/events.go:167` | PORTED | `src/NATS.Server/Events/EventTypes.cs:211` | Full field parity including schema type constant |
|
||||
| `OCSPPeerRejectEventMsg` | `golang/nats-server/server/events.go:182` | PARTIAL | `src/NATS.Server/Events/EventTypes.cs:520` | Go has `Peer certidp.CertInfo` with Subject/Issuer/Fingerprint/Raw; .NET `OcspPeerRejectEventMsg` omits the `Peer` CertInfo sub-object entirely — only Kind+Reason present |
|
||||
| `OCSPPeerChainlinkInvalidEventMsg` | `golang/nats-server/server/events.go:196` | MISSING | — | No .NET equivalent. Go struct has Link+Peer CertInfo objects. .NET has a different `OcspChainValidationEvent` that does not match the Go shape or advisory type string (`io.nats.server.advisory.v1.ocsp_peer_link_invalid`) |
|
||||
| `OCSPPeerRejectEventMsg` | `golang/nats-server/server/events.go:182` | PORTED | `src/NATS.Server/Events/EventTypes.cs:570` | Added `Peer` CertInfo payload parity (`subject`/`issuer`/`fingerprint`/`raw`) while preserving existing advisory fields |
|
||||
| `OCSPPeerChainlinkInvalidEventMsg` | `golang/nats-server/server/events.go:196` | PORTED | `src/NATS.Server/Events/EventTypes.cs:624` | Added dedicated parity DTO with `link` + `peer` cert info and matching advisory type string |
|
||||
| `AccountNumConns` | `golang/nats-server/server/events.go:210` | PORTED | `src/NATS.Server/Events/EventTypes.cs:245` | Full field parity including schema type constant |
|
||||
| `AccountStat` | `golang/nats-server/server/events.go:217` | PORTED | `src/NATS.Server/Events/EventTypes.cs:245` | Fields embedded inline in `AccountNumConns` — matches Go embedding pattern |
|
||||
| `ServerInfo` | `golang/nats-server/server/events.go:249` | PORTED | `src/NATS.Server/Events/EventTypes.cs:9` | `EventServerInfo` — all fields present; `Flags` typed as `ulong` (Go `ServerCapability uint64`) |
|
||||
| `ServerID` | `golang/nats-server/server/events.go:239` | MISSING | — | Simple struct with Name/Host/ID used in `idzReq` response; no .NET equivalent |
|
||||
| `ServerCapability` (type + consts) | `golang/nats-server/server/events.go:246` | MISSING | — | `JetStreamEnabled`, `BinaryStreamSnapshot`, `AccountNRG` capability flags; .NET has the `Flags ulong` field but no typed enum/const for capability bits |
|
||||
| `ServerID` | `golang/nats-server/server/events.go:239` | PORTED | `src/NATS.Server/Events/EventTypes.cs:22` | Added `ServerID` DTO with `name`/`host`/`id` JSON fields for IDZ-style responses |
|
||||
| `ServerCapability` (type + consts) | `golang/nats-server/server/events.go:246` | PORTED | `src/NATS.Server/Events/EventTypes.cs:10` | Added `[Flags] ServerCapability` enum with `JetStreamEnabled`, `BinaryStreamSnapshot`, and `AccountNRG` bits |
|
||||
| `ClientInfo` | `golang/nats-server/server/events.go:308` | PORTED | `src/NATS.Server/Events/EventTypes.cs:62` | `EventClientInfo` — all fields present; RTT stored as `long RttNanos` vs Go `time.Duration` |
|
||||
| `ServerStats` | `golang/nats-server/server/events.go:364` | PORTED | `src/NATS.Server/Events/EventTypes.cs:395` | `ServerStatsData` — full field parity; .NET adds `InMsgs/OutMsgs/InBytes/OutBytes` compat fields not in Go (extra, not missing) |
|
||||
| `RouteStat` | `golang/nats-server/server/events.go:390` | PORTED | `src/NATS.Server/Events/EventTypes.cs:303` | Full field parity |
|
||||
| `GatewayStat` | `golang/nats-server/server/events.go:398` | PORTED | `src/NATS.Server/Events/EventTypes.cs:326` | Full field parity |
|
||||
| `MsgBytes` | `golang/nats-server/server/events.go:407` | PORTED | `src/NATS.Server/Events/EventTypes.cs:181` | `MsgBytesStats` — same fields |
|
||||
| `DataStats` | `golang/nats-server/server/events.go:412` | PORTED | `src/NATS.Server/Events/EventTypes.cs:156` | Full field parity |
|
||||
| `EventFilterOptions` | `golang/nats-server/server/events.go:1946` | MISSING | — | No .NET equivalent; used for server-side request filtering by name/cluster/host/tags/domain |
|
||||
| `StatszEventOptions` | `golang/nats-server/server/events.go:1956` | MISSING | — | No .NET equivalent |
|
||||
| `AccInfoEventOptions` | `golang/nats-server/server/events.go:1962` | MISSING | — | No .NET equivalent |
|
||||
| `ConnzEventOptions` | `golang/nats-server/server/events.go:1968` | MISSING | — | No .NET equivalent |
|
||||
| `RoutezEventOptions` | `golang/nats-server/server/events.go:1974` | MISSING | — | No .NET equivalent |
|
||||
| `SubszEventOptions` | `golang/nats-server/server/events.go:1980` | MISSING | — | No .NET equivalent |
|
||||
| `VarzEventOptions` | `golang/nats-server/server/events.go:1986` | MISSING | — | No .NET equivalent |
|
||||
| `GatewayzEventOptions` | `golang/nats-server/server/events.go:1992` | MISSING | — | No .NET equivalent |
|
||||
| `LeafzEventOptions` | `golang/nats-server/server/events.go:1997` | MISSING | — | No .NET equivalent |
|
||||
| `AccountzEventOptions` | `golang/nats-server/server/events.go:2004` | MISSING | — | No .NET equivalent |
|
||||
| `AccountStatzEventOptions` | `golang/nats-server/server/events.go:2010` | MISSING | — | No .NET equivalent |
|
||||
| `JszEventOptions` | `golang/nats-server/server/events.go:2016` | MISSING | — | No .NET equivalent |
|
||||
| `HealthzEventOptions` | `golang/nats-server/server/events.go:2022` | MISSING | — | No .NET equivalent |
|
||||
| `ProfilezEventOptions` | `golang/nats-server/server/events.go:2028` | MISSING | — | No .NET equivalent |
|
||||
| `ExpvarzEventOptions` | `golang/nats-server/server/events.go:2034` | MISSING | — | No .NET equivalent |
|
||||
| `IpqueueszEventOptions` | `golang/nats-server/server/events.go:2039` | MISSING | — | No .NET equivalent |
|
||||
| `RaftzEventOptions` | `golang/nats-server/server/events.go:2045` | MISSING | — | No .NET equivalent |
|
||||
| `ServerAPIResponse` | `golang/nats-server/server/events.go:2092` | MISSING | — | Generic request-reply envelope; no .NET equivalent |
|
||||
| `ServerAPIConnzResponse` | `golang/nats-server/server/events.go:2106` | MISSING | — | Typed response wrappers for Z endpoints; no .NET equivalent |
|
||||
| `ServerAPIRoutezResponse` | `golang/nats-server/server/events.go:2113` | MISSING | — | No .NET equivalent |
|
||||
| `ServerAPIGatewayzResponse` | `golang/nats-server/server/events.go:2119` | MISSING | — | No .NET equivalent |
|
||||
| `ServerAPIJszResponse` | `golang/nats-server/server/events.go:2126` | MISSING | — | No .NET equivalent |
|
||||
| `ServerAPIHealthzResponse` | `golang/nats-server/server/events.go:2133` | MISSING | — | No .NET equivalent |
|
||||
| `ServerAPIVarzResponse` | `golang/nats-server/server/events.go:2141` | MISSING | — | No .NET equivalent |
|
||||
| `ServerAPISubszResponse` | `golang/nats-server/server/events.go:2148` | MISSING | — | No .NET equivalent |
|
||||
| `ServerAPILeafzResponse` | `golang/nats-server/server/events.go:2155` | MISSING | — | No .NET equivalent |
|
||||
| `ServerAPIAccountzResponse` | `golang/nats-server/server/events.go:2162` | MISSING | — | No .NET equivalent |
|
||||
| `ServerAPIExpvarzResponse` | `golang/nats-server/server/events.go:2169` | MISSING | — | No .NET equivalent |
|
||||
| `ServerAPIpqueueszResponse` | `golang/nats-server/server/events.go:2175` | MISSING | — | No .NET equivalent |
|
||||
| `ServerAPIRaftzResponse` | `golang/nats-server/server/events.go:2183` | MISSING | — | No .NET equivalent |
|
||||
| `KickClientReq` | `golang/nats-server/server/events.go:3180` | MISSING | — | No .NET equivalent |
|
||||
| `LDMClientReq` | `golang/nats-server/server/events.go:3184` | MISSING | — | No .NET equivalent |
|
||||
| `UserInfo` | `golang/nats-server/server/events.go:1500` | MISSING | — | No .NET equivalent |
|
||||
| `EventFilterOptions` | `golang/nats-server/server/events.go:1946` | PORTED | `src/NATS.Server/Events/EventTypes.cs:929` | Added shared filter DTO with name/cluster/host/tags/domain fields |
|
||||
| `StatszEventOptions` | `golang/nats-server/server/events.go:1956` | PORTED | `src/NATS.Server/Events/EventTypes.cs:952` | Added typed options wrapper inheriting `EventFilterOptions` |
|
||||
| `AccInfoEventOptions` | `golang/nats-server/server/events.go:1962` | PORTED | `src/NATS.Server/Events/EventTypes.cs:953` | Added typed options wrapper inheriting `EventFilterOptions` |
|
||||
| `ConnzEventOptions` | `golang/nats-server/server/events.go:1968` | PORTED | `src/NATS.Server/Events/EventTypes.cs:954` | Added typed options wrapper inheriting `EventFilterOptions` |
|
||||
| `RoutezEventOptions` | `golang/nats-server/server/events.go:1974` | PORTED | `src/NATS.Server/Events/EventTypes.cs:955` | Added typed options wrapper inheriting `EventFilterOptions` |
|
||||
| `SubszEventOptions` | `golang/nats-server/server/events.go:1980` | PORTED | `src/NATS.Server/Events/EventTypes.cs:956` | Added typed options wrapper inheriting `EventFilterOptions` |
|
||||
| `VarzEventOptions` | `golang/nats-server/server/events.go:1986` | PORTED | `src/NATS.Server/Events/EventTypes.cs:957` | Added typed options wrapper inheriting `EventFilterOptions` |
|
||||
| `GatewayzEventOptions` | `golang/nats-server/server/events.go:1992` | PORTED | `src/NATS.Server/Events/EventTypes.cs:958` | Added typed options wrapper inheriting `EventFilterOptions` |
|
||||
| `LeafzEventOptions` | `golang/nats-server/server/events.go:1997` | PORTED | `src/NATS.Server/Events/EventTypes.cs:959` | Added typed options wrapper inheriting `EventFilterOptions` |
|
||||
| `AccountzEventOptions` | `golang/nats-server/server/events.go:2004` | PORTED | `src/NATS.Server/Events/EventTypes.cs:960` | Added typed options wrapper inheriting `EventFilterOptions` |
|
||||
| `AccountStatzEventOptions` | `golang/nats-server/server/events.go:2010` | PORTED | `src/NATS.Server/Events/EventTypes.cs:961` | Added typed options wrapper inheriting `EventFilterOptions` |
|
||||
| `JszEventOptions` | `golang/nats-server/server/events.go:2016` | PORTED | `src/NATS.Server/Events/EventTypes.cs:962` | Added typed options wrapper inheriting `EventFilterOptions` |
|
||||
| `HealthzEventOptions` | `golang/nats-server/server/events.go:2022` | PORTED | `src/NATS.Server/Events/EventTypes.cs:963` | Added typed options wrapper inheriting `EventFilterOptions` |
|
||||
| `ProfilezEventOptions` | `golang/nats-server/server/events.go:2028` | PORTED | `src/NATS.Server/Events/EventTypes.cs:964` | Added typed options wrapper inheriting `EventFilterOptions` |
|
||||
| `ExpvarzEventOptions` | `golang/nats-server/server/events.go:2034` | PORTED | `src/NATS.Server/Events/EventTypes.cs:965` | Added typed options wrapper inheriting `EventFilterOptions` |
|
||||
| `IpqueueszEventOptions` | `golang/nats-server/server/events.go:2039` | PORTED | `src/NATS.Server/Events/EventTypes.cs:966` | Added typed options wrapper inheriting `EventFilterOptions` |
|
||||
| `RaftzEventOptions` | `golang/nats-server/server/events.go:2045` | PORTED | `src/NATS.Server/Events/EventTypes.cs:967` | Added typed options wrapper inheriting `EventFilterOptions` |
|
||||
| `ServerAPIResponse` | `golang/nats-server/server/events.go:2092` | PORTED | `src/NATS.Server/Events/EventTypes.cs:988` | Added generic request/reply envelope with `server`, `data`, and `error` payload |
|
||||
| `ServerAPIConnzResponse` | `golang/nats-server/server/events.go:2106` | PORTED | `src/NATS.Server/Events/EventTypes.cs:1003` | Added typed server API response wrapper |
|
||||
| `ServerAPIRoutezResponse` | `golang/nats-server/server/events.go:2113` | PORTED | `src/NATS.Server/Events/EventTypes.cs:1004` | Added typed server API response wrapper |
|
||||
| `ServerAPIGatewayzResponse` | `golang/nats-server/server/events.go:2119` | PORTED | `src/NATS.Server/Events/EventTypes.cs:1005` | Added typed server API response wrapper |
|
||||
| `ServerAPIJszResponse` | `golang/nats-server/server/events.go:2126` | PORTED | `src/NATS.Server/Events/EventTypes.cs:1006` | Added typed server API response wrapper |
|
||||
| `ServerAPIHealthzResponse` | `golang/nats-server/server/events.go:2133` | PORTED | `src/NATS.Server/Events/EventTypes.cs:1007` | Added typed server API response wrapper |
|
||||
| `ServerAPIVarzResponse` | `golang/nats-server/server/events.go:2141` | PORTED | `src/NATS.Server/Events/EventTypes.cs:1008` | Added typed server API response wrapper |
|
||||
| `ServerAPISubszResponse` | `golang/nats-server/server/events.go:2148` | PORTED | `src/NATS.Server/Events/EventTypes.cs:1009` | Added typed server API response wrapper |
|
||||
| `ServerAPILeafzResponse` | `golang/nats-server/server/events.go:2155` | PORTED | `src/NATS.Server/Events/EventTypes.cs:1010` | Added typed server API response wrapper |
|
||||
| `ServerAPIAccountzResponse` | `golang/nats-server/server/events.go:2162` | PORTED | `src/NATS.Server/Events/EventTypes.cs:1011` | Added typed server API response wrapper |
|
||||
| `ServerAPIExpvarzResponse` | `golang/nats-server/server/events.go:2169` | PORTED | `src/NATS.Server/Events/EventTypes.cs:1012` | Added typed server API response wrapper |
|
||||
| `ServerAPIpqueueszResponse` | `golang/nats-server/server/events.go:2175` | PORTED | `src/NATS.Server/Events/EventTypes.cs:1013` | Added typed server API response wrapper |
|
||||
| `ServerAPIRaftzResponse` | `golang/nats-server/server/events.go:2183` | PORTED | `src/NATS.Server/Events/EventTypes.cs:1014` | Added typed server API response wrapper |
|
||||
| `KickClientReq` | `golang/nats-server/server/events.go:3180` | PORTED | `src/NATS.Server/Events/EventTypes.cs:1020` | Added request DTO with `cid` payload field |
|
||||
| `LDMClientReq` | `golang/nats-server/server/events.go:3184` | PORTED | `src/NATS.Server/Events/EventTypes.cs:1030` | Added request DTO with `cid` payload field |
|
||||
| `UserInfo` | `golang/nats-server/server/events.go:1500` | PORTED | `src/NATS.Server/Events/EventTypes.cs:1040` | Added direct user info DTO with user/account/permissions fields |
|
||||
| `SlowConsumersStats` | `golang/nats-server/server/events.go:377` | PORTED | `src/NATS.Server/Events/EventTypes.cs:344` | Full field parity |
|
||||
| `StaleConnectionStats` | `golang/nats-server/server/events.go:379` | PORTED | `src/NATS.Server/Events/EventTypes.cs:363` | Full field parity |
|
||||
|
||||
@@ -152,20 +152,20 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| `sysMsgHandler` type | `golang/nats-server/server/events.go:109` | PORTED | `src/NATS.Server/Events/EventSubjects.cs:67` | `SystemMessageHandler` delegate — same signature shape |
|
||||
| `serverUpdate` struct | `golang/nats-server/server/events.go:461` | MISSING | — | Tracks seq + ltime for remote server heartbeat ordering; no .NET equivalent |
|
||||
| `accNumConnsReq` struct | `golang/nats-server/server/events.go:233` | PORTED | `src/NATS.Server/Events/EventTypes.cs:797` | `AccNumConnsReq` — full field parity |
|
||||
| `accNumSubsReq` struct | `golang/nats-server/server/events.go:2966` | MISSING | — | Used for debug subscriber count requests; no .NET equivalent |
|
||||
| `compressionType` + consts | `golang/nats-server/server/events.go:2082` | PARTIAL | `src/NATS.Server/Events/EventCompressor.cs` | `EventCompressor` handles snappy compression; no gzip compression path; `compressionType` enum itself not present as typed enum — only Snappy supported |
|
||||
| `accNumSubsReq` struct | `golang/nats-server/server/events.go:2966` | PORTED | `src/NATS.Server/Events/EventTypes.cs:916` | Added account subscription-count request DTO with server + account fields |
|
||||
| `compressionType` + consts | `golang/nats-server/server/events.go:2082` | PORTED | `src/NATS.Server/Events/EventCompressor.cs:13` | Added `EventCompressionType` enum with `None`, `Gzip`, `Snappy`, and `Unsupported`; compressor now supports both Snappy and Gzip encode/decode paths. |
|
||||
| `msgHandler` type | `golang/nats-server/server/events.go:2751` | NOT_APPLICABLE | — | Go internal callback type merging header+body bytes; .NET uses the `SystemMessageHandler` delegate with separate header/body params |
|
||||
|
||||
### events.go — Exported Methods on ServerInfo
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `(*ServerInfo).SetJetStreamEnabled()` | `golang/nats-server/server/events.go:274` | MISSING | — | No .NET method; `EventServerInfo` has the raw `Flags` field but no typed capability methods |
|
||||
| `(*ServerInfo).JetStreamEnabled()` bool | `golang/nats-server/server/events.go:281` | MISSING | — | No .NET equivalent |
|
||||
| `(*ServerInfo).SetBinaryStreamSnapshot()` | `golang/nats-server/server/events.go:287` | MISSING | — | No .NET equivalent |
|
||||
| `(*ServerInfo).BinaryStreamSnapshot()` bool | `golang/nats-server/server/events.go:292` | MISSING | — | No .NET equivalent |
|
||||
| `(*ServerInfo).SetAccountNRG()` | `golang/nats-server/server/events.go:297` | MISSING | — | No .NET equivalent |
|
||||
| `(*ServerInfo).AccountNRG()` bool | `golang/nats-server/server/events.go:302` | MISSING | — | No .NET equivalent |
|
||||
| `(*ServerInfo).SetJetStreamEnabled()` | `golang/nats-server/server/events.go:274` | PORTED | `src/NATS.Server/Events/EventTypes.cs:86` | Added helper to set `JetStream` and capability flag atomically for parity |
|
||||
| `(*ServerInfo).JetStreamEnabled()` bool | `golang/nats-server/server/events.go:281` | PORTED | `src/NATS.Server/Events/EventTypes.cs:92` | Added flag-check helper using typed `ServerCapability.JetStreamEnabled` |
|
||||
| `(*ServerInfo).SetBinaryStreamSnapshot()` | `golang/nats-server/server/events.go:287` | PORTED | `src/NATS.Server/Events/EventTypes.cs:95` | Added capability setter for binary stream snapshot support |
|
||||
| `(*ServerInfo).BinaryStreamSnapshot()` bool | `golang/nats-server/server/events.go:292` | PORTED | `src/NATS.Server/Events/EventTypes.cs:98` | Added capability getter for binary stream snapshot support |
|
||||
| `(*ServerInfo).SetAccountNRG()` | `golang/nats-server/server/events.go:297` | PORTED | `src/NATS.Server/Events/EventTypes.cs:101` | Added capability setter for account NRG support |
|
||||
| `(*ServerInfo).AccountNRG()` bool | `golang/nats-server/server/events.go:302` | PORTED | `src/NATS.Server/Events/EventTypes.cs:104` | Added capability getter for account NRG support |
|
||||
|
||||
### events.go — Unexported Methods on ClientInfo
|
||||
|
||||
@@ -205,8 +205,8 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| `(*Server).sendAccConnsUpdate()` | `golang/nats-server/server/events.go:2407` | MISSING | — | No .NET equivalent |
|
||||
| `(*Server).accConnsUpdate()` | `golang/nats-server/server/events.go:2506` | MISSING | — | No .NET equivalent |
|
||||
| `(*Server).nextEventID()` | `golang/nats-server/server/events.go:2516` | PARTIAL | `src/NATS.Server/Events/EventTypes.cs:943` | `EventBuilder.GenerateEventId()` uses `Guid.NewGuid()`; Go uses nuid (faster nano-ID generator) |
|
||||
| `(*Server).accountConnectEvent()` | `golang/nats-server/server/events.go:2522` | PARTIAL | `src/NATS.Server/Events/InternalEventSystem.cs:303` | `SendConnectEvent()` exists; missing: JWT/IssuerKey/Tags/NameTag/Kind/ClientType/MQTTClientID fields in ConnectEventDetail |
|
||||
| `(*Server).accountDisconnectEvent()` | `golang/nats-server/server/events.go:2569` | PARTIAL | `src/NATS.Server/Events/InternalEventSystem.cs:329` | `SendDisconnectEvent()` exists; missing: RTT/JWT/IssuerKey/Tags/NameTag/Kind/ClientType/MQTTClientID fields in DisconnectEventDetail |
|
||||
| `(*Server).accountConnectEvent()` | `golang/nats-server/server/events.go:2522` | PORTED | `src/NATS.Server/Events/InternalEventSystem.cs:322` | Extended `ConnectEventDetail` and event mapping to include JWT/issuer/tags/name-tag/kind/client-type/MQTT client id parity fields |
|
||||
| `(*Server).accountDisconnectEvent()` | `golang/nats-server/server/events.go:2569` | PORTED | `src/NATS.Server/Events/InternalEventSystem.cs:355` | Extended `DisconnectEventDetail` and event mapping to include RTT + JWT/issuer/tags/name-tag/kind/client-type/MQTT client id parity fields |
|
||||
| `(*Server).sendAuthErrorEvent()` | `golang/nats-server/server/events.go:2631` | PARTIAL | `src/NATS.Server/Events/InternalEventSystem.cs:277` | `SendAuthErrorEvent()` exists; Go uses `DisconnectEventMsg` shape for auth errors (surprising but correct); .NET uses `AuthErrorEventMsg` with different schema type |
|
||||
| `(*Server).sendAccountAuthErrorEvent()` | `golang/nats-server/server/events.go:2690` | MISSING | — | Account-level auth error event to account subject; no .NET equivalent |
|
||||
| `(*Server).sendOCSPPeerRejectEvent()` | `golang/nats-server/server/events.go:3267` | PARTIAL | `src/NATS.Server/Events/EventTypes.cs:612` | `OcspEventBuilder.BuildPeerReject()` helper exists; no publishing method on server; missing `Peer` CertInfo payload |
|
||||
@@ -257,7 +257,7 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `getHash()` | `golang/nats-server/server/events.go:1141` | PORTED | `src/NATS.Server/Events/InternalEventSystem.cs:118` | SHA-256 8-char hash; same algorithm |
|
||||
| `getHashSize()` | `golang/nats-server/server/events.go:1146` | MISSING | — | Parameterized size variant; only 8-char version ported |
|
||||
| `getHashSize()` | `golang/nats-server/server/events.go:1146` | PORTED | `src/NATS.Server/Events/InternalEventSystem.cs:143` | Added `GetHashSize()` and size-aware `GetHash(string, int)` helpers; constructor now uses them for server hash generation |
|
||||
| `routeStat()` | `golang/nats-server/server/events.go:859` | NOT_APPLICABLE | — | Route stat collection; clustering not yet ported |
|
||||
| `newPubMsg()` | `golang/nats-server/server/events.go:435` | NOT_APPLICABLE | — | Pool-based pubMsg factory; .NET uses `PublishMessage` record without pooling |
|
||||
| `(*pubMsg).returnToPool()` | `golang/nats-server/server/events.go:452` | NOT_APPLICABLE | — | Pool return; .NET has no pool |
|
||||
@@ -266,7 +266,7 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| `clearTimer()` | `golang/nats-server/server/events.go:3244` | NOT_APPLICABLE | — | Go timer management idiom; .NET uses `PeriodicTimer` / `CancellationToken` |
|
||||
| `totalSubs()` | `golang/nats-server/server/events.go:2973` | MISSING | — | No .NET equivalent |
|
||||
| `remoteLatencySubjectForResponse()` | `golang/nats-server/server/events.go:2860` | NOT_APPLICABLE | — | Latency tracking; not yet ported |
|
||||
| `getAcceptEncoding()` | `golang/nats-server/server/events.go:2238` | MISSING | — | Parses Accept-Encoding header for compression type; no .NET equivalent |
|
||||
| `getAcceptEncoding()` | `golang/nats-server/server/events.go:2238` | PORTED | `src/NATS.Server/Events/EventCompressor.cs:192` | Added `EventCompressor.GetAcceptEncoding(string?)` parser: prefers `snappy`/`s2`, falls back to `gzip`, otherwise `unsupported`, matching Go behavior. |
|
||||
| `(*Account).statz()` | `golang/nats-server/server/events.go:2446` | NOT_APPLICABLE | — | Account stats snapshot; Account class not yet in Events module |
|
||||
|
||||
### events.go — Constants / Subjects
|
||||
@@ -286,27 +286,27 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| `serverPingReqSubj` | `golang/nats-server/server/events.go:68` | PORTED | `src/NATS.Server/Events/EventSubjects.cs:35` | `ServerPing` — matching |
|
||||
| `accDirectReqSubj` | `golang/nats-server/server/events.go:51` | PORTED | `src/NATS.Server/Events/EventSubjects.cs:38` | `AccountReq` — matching |
|
||||
| `inboxRespSubj` | `golang/nats-server/server/events.go:73` | PORTED | `src/NATS.Server/Events/EventSubjects.cs:41` | `InboxResponse` — matching |
|
||||
| `ocspPeerRejectEventSubj` | `golang/nats-server/server/events.go:95` | PARTIAL | `src/NATS.Server/Events/EventSubjects.cs:45` | Go: `$SYS.SERVER.%s.OCSP.PEER.CONN.REJECT`; .NET: `$SYS.SERVER.{0}.OCSP.PEER.REJECT` — different path segment |
|
||||
| `ocspPeerChainlinkInvalidEventSubj` | `golang/nats-server/server/events.go:96` | MISSING | — | Go: `$SYS.SERVER.%s.OCSP.PEER.LINK.INVALID`; .NET `OcspChainValidation` uses `$SYS.SERVER.{0}.OCSP.CHAIN.VALIDATION` — different subject |
|
||||
| `leafNodeConnectEventSubj` | `golang/nats-server/server/events.go:71` | PARTIAL | `src/NATS.Server/Events/EventSubjects.cs:28` | .NET subject `$SYS.SERVER.{0}.LEAFNODE.CONNECT` differs from Go `$SYS.ACCOUNT.%s.LEAFNODE.CONNECT` |
|
||||
| `remoteLatencyEventSubj` | `golang/nats-server/server/events.go:72` | MISSING | — | No .NET equivalent |
|
||||
| `userDirectInfoSubj` | `golang/nats-server/server/events.go:76` | MISSING | — | No .NET equivalent |
|
||||
| `userDirectReqSubj` | `golang/nats-server/server/events.go:77` | MISSING | — | No .NET equivalent |
|
||||
| `accNumSubsReqSubj` | `golang/nats-server/server/events.go:81` | MISSING | — | No .NET equivalent |
|
||||
| `accSubsSubj` | `golang/nats-server/server/events.go:84` | MISSING | — | No .NET equivalent |
|
||||
| `clientKickReqSubj` | `golang/nats-server/server/events.go:62` | MISSING | — | No .NET equivalent |
|
||||
| `clientLDMReqSubj` | `golang/nats-server/server/events.go:63` | MISSING | — | No .NET equivalent |
|
||||
| `serverStatsPingReqSubj` | `golang/nats-server/server/events.go:69` | MISSING | — | No .NET equivalent |
|
||||
| `serverReloadReqSubj` | `golang/nats-server/server/events.go:70` | MISSING | — | No .NET equivalent |
|
||||
| `ocspPeerRejectEventSubj` | `golang/nats-server/server/events.go:95` | PORTED | `src/NATS.Server/Events/EventSubjects.cs:54` | Corrected to `$SYS.SERVER.{0}.OCSP.PEER.CONN.REJECT` |
|
||||
| `ocspPeerChainlinkInvalidEventSubj` | `golang/nats-server/server/events.go:96` | PORTED | `src/NATS.Server/Events/EventSubjects.cs:55` | Added `$SYS.SERVER.{0}.OCSP.PEER.LINK.INVALID` subject constant |
|
||||
| `leafNodeConnectEventSubj` | `golang/nats-server/server/events.go:71` | PORTED | `src/NATS.Server/Events/EventSubjects.cs:28` | Corrected to account-scoped subject `$SYS.ACCOUNT.{0}.LEAFNODE.CONNECT` |
|
||||
| `remoteLatencyEventSubj` | `golang/nats-server/server/events.go:72` | PORTED | `src/NATS.Server/Events/EventSubjects.cs:30` | Added remote latency response subject constant |
|
||||
| `userDirectInfoSubj` | `golang/nats-server/server/events.go:76` | PORTED | `src/NATS.Server/Events/EventSubjects.cs:40` | Added user info request subject constant |
|
||||
| `userDirectReqSubj` | `golang/nats-server/server/events.go:77` | PORTED | `src/NATS.Server/Events/EventSubjects.cs:41` | Added per-user direct info request subject constant |
|
||||
| `accNumSubsReqSubj` | `golang/nats-server/server/events.go:81` | PORTED | `src/NATS.Server/Events/EventSubjects.cs:42` | Added account subscription-count request subject constant |
|
||||
| `accSubsSubj` | `golang/nats-server/server/events.go:84` | PORTED | `src/NATS.Server/Events/EventSubjects.cs:43` | Added account subscription-count response subject constant |
|
||||
| `clientKickReqSubj` | `golang/nats-server/server/events.go:62` | PORTED | `src/NATS.Server/Events/EventSubjects.cs:44` | Added kick-client request subject constant |
|
||||
| `clientLDMReqSubj` | `golang/nats-server/server/events.go:63` | PORTED | `src/NATS.Server/Events/EventSubjects.cs:45` | Added lame-duck-mode client request subject constant |
|
||||
| `serverStatsPingReqSubj` | `golang/nats-server/server/events.go:69` | PORTED | `src/NATS.Server/Events/EventSubjects.cs:46` | Added wildcard statsz ping request subject constant |
|
||||
| `serverReloadReqSubj` | `golang/nats-server/server/events.go:70` | PORTED | `src/NATS.Server/Events/EventSubjects.cs:47` | Added server reload request subject constant |
|
||||
| `accPingReqSubj` | `golang/nats-server/server/events.go:52` | MISSING | — | No .NET equivalent |
|
||||
| `connsRespSubj` | `golang/nats-server/server/events.go:57` | MISSING | — | No .NET equivalent |
|
||||
| `accLookupReqSubj` / JWT subjects | `golang/nats-server/server/events.go:43` | NOT_APPLICABLE | — | JWT operator resolver subjects; not yet ported |
|
||||
| `InboxPrefix` | `golang/nats-server/server/events.go:2946` | MISSING | — | No .NET equivalent |
|
||||
| `acceptEncodingHeader` / `contentEncodingHeader` | `golang/nats-server/server/events.go:2232` | MISSING | — | Header name constants for compression negotiation; no .NET equivalent |
|
||||
| `acceptEncodingHeader` / `contentEncodingHeader` | `golang/nats-server/server/events.go:2232` | PORTED | `src/NATS.Server/Events/EventCompressor.cs:28` | Added `AcceptEncodingHeader` and `ContentEncodingHeader` constants for compression negotiation headers. |
|
||||
| `ConnectEventMsgType` | `golang/nats-server/server/events.go:163` | PORTED | `src/NATS.Server/Events/EventTypes.cs:193` | `ConnectEventMsg.EventType` constant — matching value |
|
||||
| `DisconnectEventMsgType` | `golang/nats-server/server/events.go:177` | PORTED | `src/NATS.Server/Events/EventTypes.cs:214` | `DisconnectEventMsg.EventType` — matching value |
|
||||
| `OCSPPeerRejectEventMsgType` | `golang/nats-server/server/events.go:191` | PORTED | `src/NATS.Server/Events/EventTypes.cs:522` | `OcspPeerRejectEventMsg.EventType` — matching value |
|
||||
| `OCSPPeerChainlinkInvalidEventMsgType` | `golang/nats-server/server/events.go:205` | MISSING | — | No .NET equivalent with matching type string `io.nats.server.advisory.v1.ocsp_peer_link_invalid` |
|
||||
| `OCSPPeerChainlinkInvalidEventMsgType` | `golang/nats-server/server/events.go:205` | PORTED | `src/NATS.Server/Events/EventTypes.cs:626` | Added `OcspPeerChainlinkInvalidEventMsg.EventType` constant with matching advisory type |
|
||||
| `AccountNumConnsMsgType` | `golang/nats-server/server/events.go:229` | PORTED | `src/NATS.Server/Events/EventTypes.cs:247` | `AccountNumConns.EventType` — matching value |
|
||||
|
||||
---
|
||||
@@ -406,5 +406,7 @@ After porting work is completed:
|
||||
|
||||
| Date | Change | By |
|
||||
|------|--------|----|
|
||||
| 2026-02-26 | Ported events compression parity helpers: added typed compression enum, gzip/snappy encode-decode support, Accept-Encoding parser, and header-name constants with focused events compression tests. | codex |
|
||||
| 2026-02-25 | File created with LLM analysis instructions | auto |
|
||||
| 2026-02-25 | Full gap inventory populated from Go source analysis | auto |
|
||||
| 2026-02-25 | Ported Events API option DTOs, server API response wrappers, OCSP peer/link advisory payloads, missing request subjects, account connect/disconnect parity fields, and hash-size helpers; validated with focused Events tests | codex |
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
# Category Execution Instructions
|
||||
|
||||
Use this runbook to execute one category end-to-end using the `executeplan` skill without running the full test suite.
|
||||
|
||||
## Inputs
|
||||
- `CATEGORY` (example: `protocol`)
|
||||
- `gaps/plans.md` row for that category (contains category gap file, design file, and plan file paths)
|
||||
|
||||
## Required Skills
|
||||
- `executeplan`
|
||||
- `using-git-worktrees` (required before implementation)
|
||||
- `finishing-a-development-branch` (required after implementation)
|
||||
|
||||
## Execution Flow
|
||||
1. Resolve the category row from `gaps/plans.md`.
|
||||
2. Read the category's design and plan files from the resolved row.
|
||||
3. Announce: `I'm using executeplan to implement this plan.`
|
||||
4. Create a brand-new git worktree on a new branch and verify clean status.
|
||||
5. Execute the plan in batches (`executeplan` default batching), with checkpoints between batches.
|
||||
6. Run only targeted unit tests for the category after each batch; do not run full-suite tests.
|
||||
7. After implementation, verify the category gap file reflects the completed work.
|
||||
8. Update the `Status` column in `gaps/plans.md` for the category:
|
||||
- `complete` if no `MISSING` or `PARTIAL` rows remain
|
||||
- `<N> remaining` where `N = MISSING + PARTIAL`
|
||||
|
||||
## Strict Test Scope Policy
|
||||
- Never run unscoped full test commands such as:
|
||||
- `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj`
|
||||
- Always use targeted test execution, for example:
|
||||
- `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj --filter "FullyQualifiedName~Protocol"`
|
||||
- `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj --filter "FullyQualifiedName~JetStream"`
|
||||
- `dotnet test tests/NATS.Server.Tests/NATS.Server.Tests.csproj --filter "FullyQualifiedName~Auth"`
|
||||
- If one filter is too broad, split into multiple narrow filters and run them separately.
|
||||
|
||||
## Suggested Category Filter Tokens
|
||||
Use these as starting points for `--filter "FullyQualifiedName~<token>"`:
|
||||
|
||||
| Category | Token(s) |
|
||||
|---|---|
|
||||
| `core-server` | `Server`, `Client` |
|
||||
| `protocol` | `Protocol`, `Parser` |
|
||||
| `subscriptions` | `Subscription` |
|
||||
| `auth-and-accounts` | `Auth`, `Account` |
|
||||
| `configuration` | `Configuration`, `Config` |
|
||||
| `routes` | `Route` |
|
||||
| `gateways` | `Gateway` |
|
||||
| `leaf-nodes` | `Leaf` |
|
||||
| `jetstream` | `JetStream` |
|
||||
| `raft` | `Raft` |
|
||||
| `mqtt` | `Mqtt` |
|
||||
| `websocket` | `WebSocket` |
|
||||
| `monitoring` | `Monitoring` |
|
||||
| `events` | `Event` |
|
||||
| `tls-security` | `Tls`, `Security` |
|
||||
| `internal-ds` | `Internal` |
|
||||
| `logging` | `Log`, `Logging` |
|
||||
| `utilities-and-other` | `IO`, `Server` |
|
||||
| `misc-uncategorized` | `Misc`, `Server` |
|
||||
|
||||
## Gap Verification Rules (Category Gap File)
|
||||
For the category gap file (`gaps/<category>.md`):
|
||||
1. Every implemented item must be updated in the Gap Inventory.
|
||||
2. Each newly completed row must include:
|
||||
- `Status = PORTED`
|
||||
- concrete `.NET Equivalent` file:line
|
||||
- concise notes for parity behavior
|
||||
3. If behavior is still partial, keep `PARTIAL` and document what is still missing.
|
||||
4. Do not mark `complete` in `gaps/plans.md` until both `MISSING` and `PARTIAL` counts are zero.
|
||||
|
||||
## Status Update Command Snippet
|
||||
After finishing a category, compute remaining gaps:
|
||||
|
||||
```bash
|
||||
remaining=$(awk -F'|' '
|
||||
NF >= 4 {
|
||||
s=$4
|
||||
gsub(/^ +| +$/, "", s)
|
||||
if (s=="MISSING" || s=="PARTIAL") c++
|
||||
}
|
||||
END { print c+0 }
|
||||
' "gaps/${CATEGORY}.md")
|
||||
|
||||
if [ "$remaining" -eq 0 ]; then
|
||||
status_value="complete"
|
||||
else
|
||||
status_value="${remaining} remaining"
|
||||
fi
|
||||
```
|
||||
|
||||
Then write `status_value` into that category row in `gaps/plans.md`.
|
||||
+32
-28
@@ -94,9 +94,9 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| `GatewayDoNotForceInterestOnlyMode` | gateway.go:130 | NOT_APPLICABLE | — | Test-only global flag; disables forced interest-only mode in tests. Go-specific test hook. |
|
||||
| `GatewayInterestMode` (enum: Optimistic/Transitioning/InterestOnly) | gateway.go:94–111 | PORTED | `src/NATS.Server/Gateways/GatewayInterestTracker.cs:16` | `GatewayInterestMode` enum with identical three values. |
|
||||
| `GatewayInterestMode.String()` | gateway.go:113 | NOT_APPLICABLE | — | Go stringer pattern; C# uses `ToString()` automatically. |
|
||||
| `gwReplyPrefix` / `gwReplyPrefixLen` / `gwHashLen` / offset constants | gateway.go:49–58 | PARTIAL | `src/NATS.Server/Gateways/ReplyMapper.cs:12` | `GatewayReplyPrefix = "_GR_."` is present. Hash length and byte-offset arithmetic constants are missing; .NET uses string-segment parsing instead of fixed-width offsets. |
|
||||
| `oldGWReplyPrefix` / `oldGWReplyPrefixLen` / `oldGWReplyStart` | gateway.go:43–46 | MISSING | — | Old `$GR.` reply prefix for backward-compat with pre-v2.9 servers is not represented in .NET. `ReplyMapper.TryRestoreGatewayReply` handles it partially via numeric-hash detection but has no dedicated constant. |
|
||||
| `gatewayTLSInsecureWarning` | gateway.go:71 | MISSING | — | TLS insecure warning string; no TLS gateway support yet. |
|
||||
| `gwReplyPrefix` / `gwReplyPrefixLen` / `gwHashLen` / offset constants | gateway.go:49–58 | PORTED | `src/NATS.Server/Gateways/ReplyMapper.cs:12–17` | Reply-prefix/length/hash-length constants are explicitly defined (`GatewayReplyPrefix`, `GatewayReplyPrefixLen`, `GatewayHashLen`, plus legacy counterparts). |
|
||||
| `oldGWReplyPrefix` / `oldGWReplyPrefixLen` / `oldGWReplyStart` | gateway.go:43–46 | PORTED | `src/NATS.Server/Gateways/ReplyMapper.cs:13,15,31` | Legacy `$GR.` prefix constants and old-prefix detection are implemented via `IsGatewayRoutedSubject(..., out isOldPrefix)`. |
|
||||
| `gatewayTLSInsecureWarning` | gateway.go:71 | PORTED | `src/NATS.Server/Gateways/GatewayManager.cs:68` | Gateway TLS insecure warning constant is defined for parity/documentation and diagnostic use. |
|
||||
|
||||
#### Structs / Types
|
||||
|
||||
@@ -104,7 +104,7 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `srvGateway` struct | gateway.go:134 | PARTIAL | `src/NATS.Server/Gateways/GatewayManager.cs:66` | `GatewayManager` covers outbound/inbound maps, accept loop, discovery, registration, and stats. Missing: RTT-ordered outbound list (`outo`), `totalQSubs` atomic counter, `pasi` per-account subscription interest map, `rsubs` recent-subscription sync.Map, `sIDHash`/`routesIDByHash` for reply routing, `sqbsz`/`recSubExp`, and `oldHash`/`oldReplyPfx` for backward compat. |
|
||||
| `sitally` struct | gateway.go:189 | MISSING | — | Subject-interest tally (ref count + queue flag) used in `pasi.m`. No equivalent in .NET. |
|
||||
| `gatewayCfg` struct | gateway.go:194 | PARTIAL | `src/NATS.Server/Configuration/GatewayOptions.cs:27` (`RemoteGatewayOptions`) | `RemoteGatewayOptions` covers name and URLs. Missing: `hash`/`oldHash` byte arrays, `implicit` flag, `connAttempts` counter, `tlsName`, `varzUpdateURLs`, URL management methods (`addURLs`, `updateURLs`, `getURLs`, `saveTLSHostname`), and per-remote TLS config. |
|
||||
| `gatewayCfg` struct | gateway.go:194 | PARTIAL | `src/NATS.Server/Configuration/GatewayOptions.cs:27` (`RemoteGatewayOptions`) | Added parity state and helpers for `hash`/`oldHash`, `implicit`, connection attempts, TLS host capture, URL add/update/get flows, and varz URL-update flag. Remaining gap: per-remote TLS config wiring into active gateway dial/handshake path. |
|
||||
| `gateway` struct (per-client) | gateway.go:207 | MISSING | — | Per-connection gateway state (outbound flag, `outsim` sync.Map, `insim` map, `connectURL`, `useOldPrefix`, `interestOnlyMode`, `remoteName`). Absorbed into `GatewayConnection` but without full per-message interest tracking. |
|
||||
| `outsie` struct | gateway.go:229 | PARTIAL | `src/NATS.Server/Gateways/GatewayInterestTracker.cs` | `GatewayInterestTracker.AccountState` covers mode and no-interest set. Missing: per-account `sl *Sublist` for queue-sub tracking in InterestOnly mode, `qsubs` counter. |
|
||||
| `insie` struct | gateway.go:256 | MISSING | — | Inbound per-account no-interest set with mode tracking. No distinct type in .NET; `GatewayInterestTracker` handles outbound-side equivalent but not the inbound RS-/RS+ tracking map. |
|
||||
@@ -131,9 +131,9 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `validateGatewayOptions` | gateway.go:306 | MISSING | — | Validates gateway config (name, port, remote URLs). No validation equivalent in .NET. |
|
||||
| `getGWHash` (standalone) | gateway.go:335 | MISSING | — | Computes 6-char hash for gateway name. Used for reply prefix construction. Not ported. |
|
||||
| `getOldHash` (standalone) | gateway.go:339 | MISSING | — | SHA-256-based 4-char hash for old `$GR.` prefix. Not ported. |
|
||||
| `validateGatewayOptions` | gateway.go:306 | PORTED | `src/NATS.Server/Gateways/GatewayManager.cs:113` (`ValidateGatewayOptions`) | Basic gateway config validation implemented for required name, valid port range, and non-empty remotes. |
|
||||
| `getGWHash` (standalone) | gateway.go:335 | PORTED | `src/NATS.Server/Gateways/ReplyMapper.cs:75` (`ComputeGatewayHash`) | Deterministic short gateway hash helper implemented (6-char hex). |
|
||||
| `getOldHash` (standalone) | gateway.go:339 | PORTED | `src/NATS.Server/Gateways/ReplyMapper.cs:85` (`ComputeOldGatewayHash`) | Deterministic legacy short hash helper implemented (4-char hex). |
|
||||
| `Server.newGateway` | gateway.go:350 | PARTIAL | `src/NATS.Server/Gateways/GatewayManager.cs:90` | `GatewayManager` constructor covers basic setup. Missing: hash computation, reply prefix assembly, `oldReplyPfx`, `pasi` init, resolver config. |
|
||||
| `Server.startGateways` | gateway.go:487 | PARTIAL | `src/NATS.Server/Gateways/GatewayManager.cs:161` (`StartAsync`) | `StartAsync` starts accept loop and connects to remotes. Missing: solicit delay, cluster-formation wait. |
|
||||
| `Server.startGatewayAcceptLoop` | gateway.go:511 | PARTIAL | `src/NATS.Server/Gateways/GatewayManager.cs:326` (`AcceptLoopAsync`) | Accept loop exists. Missing: TLS config, `authRequired`, reject-unknown check, advertising, `GatewayIOM` flag, INFO protocol send. |
|
||||
@@ -141,7 +141,7 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| `Server.solicitGateways` | gateway.go:668 | PARTIAL | `src/NATS.Server/Gateways/GatewayManager.cs:173` (`StartAsync` remote loop) | Connects to configured remotes. Missing: implicit vs explicit distinction, goroutine-per-remote with proper lifecycle. |
|
||||
| `Server.reconnectGateway` | gateway.go:689 | PORTED | `src/NATS.Server/Gateways/GatewayManager.cs:149` (`ReconnectGatewayAsync`) | Exponential backoff reconnect delay with jitter. |
|
||||
| `Server.solicitGateway` | gateway.go:706 | PARTIAL | `src/NATS.Server/Gateways/GatewayManager.cs:358` (`ConnectWithRetryAsync`) | Retry loop with multiple URL support. Missing: random URL selection, `shouldReportConnectErr` throttling, implicit gateway retry limits, DNS resolution via `resolver`, `ConnectBackoff` flag. |
|
||||
| `srvGateway.hasInbound` | gateway.go:790 | MISSING | — | Checks if an inbound connection for a named gateway exists. Not implemented. |
|
||||
| `srvGateway.hasInbound` | gateway.go:790 | PORTED | `src/NATS.Server/Gateways/GatewayManager.cs:345` (`HasInbound`) | Inbound-connection presence check is implemented by remote server id. |
|
||||
| `Server.createGateway` | gateway.go:805 | PARTIAL | `src/NATS.Server/Gateways/GatewayManager.cs:344` (`HandleInboundAsync`) and `ConnectWithRetryAsync` | Creates client connection for inbound/outbound. Missing: full client lifecycle, TLS handshake, CONNECT/INFO protocol, `expectConnect` flag, ping timer setup, temp-client registration. |
|
||||
| `client.sendGatewayConnect` | gateway.go:958 | PARTIAL | `src/NATS.Server/Gateways/GatewayConnection.cs:113` (`PerformOutboundHandshakeAsync`) | Handshake sends server ID. Go sends full CONNECT JSON with auth, TLS flag, gateway name. .NET sends a simplified `GATEWAY {serverId}` line. |
|
||||
| `client.processGatewayConnect` | gateway.go:993 | MISSING | — | Parses CONNECT from inbound gateway; validates gateway field, rejects wrong port/unknown gateways. No protocol parsing in .NET. |
|
||||
@@ -155,32 +155,32 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| `Server.processGatewayInfoFromRoute` | gateway.go:1394 | MISSING | — | Handles gateway gossip INFO received via a cluster route connection. |
|
||||
| `Server.sendGatewayConfigsToRoute` | gateway.go:1406 | MISSING | — | Sends known outbound gateway configs to a new route connection. |
|
||||
| `Server.processImplicitGateway` | gateway.go:1453 | PARTIAL | `src/NATS.Server/Gateways/GatewayManager.cs:119` (`ProcessImplicitGateway`) | Records discovered gateway name. Missing: URL augmentation of existing config, creation of `gatewayCfg`, launching `solicitGateway` goroutine for the new implicit remote. |
|
||||
| `Server.NumOutboundGateways` | gateway.go:1501 | MISSING | — | Public test-facing count of outbound connections. No exact equivalent. |
|
||||
| `Server.numOutboundGateways` | gateway.go:1506 | PARTIAL | `src/NATS.Server/Gateways/GatewayManager.cs:284` (`GetConnectedGatewayCount`) | Returns connected count from registrations. Does not distinguish inbound vs outbound. |
|
||||
| `Server.numInboundGateways` | gateway.go:1514 | MISSING | — | Count of inbound gateway connections. No equivalent. |
|
||||
| `Server.NumOutboundGateways` | gateway.go:1501 | PORTED | `src/NATS.Server/NatsServer.cs:155` (`NumOutboundGateways`) | Public server-facing outbound gateway count now exposed. |
|
||||
| `Server.numOutboundGateways` | gateway.go:1506 | PORTED | `src/NATS.Server/Gateways/GatewayManager.cs:331` (`NumOutboundGateways`) | Manager now computes outbound count from live connection direction (`IsOutbound`). |
|
||||
| `Server.numInboundGateways` | gateway.go:1514 | PORTED | `src/NATS.Server/Gateways/GatewayManager.cs:338` (`NumInboundGateways`), `src/NATS.Server/NatsServer.cs:156` | Inbound gateway count is now tracked and exposed. |
|
||||
| `Server.getRemoteGateway` | gateway.go:1522 | PARTIAL | `src/NATS.Server/Gateways/GatewayManager.cs:264` (`GetRegistration`) | Returns registration by name. Missing: returns `gatewayCfg` in Go (with TLS, URLs, hash); .NET returns `GatewayRegistration` (only state/stats). |
|
||||
| `gatewayCfg.bumpConnAttempts` | gateway.go:1530 | MISSING | — | Test helper to increment connection attempts counter. |
|
||||
| `gatewayCfg.getConnAttempts` | gateway.go:1537 | MISSING | — | Test helper to read connection attempts. |
|
||||
| `gatewayCfg.resetConnAttempts` | gateway.go:1545 | MISSING | — | Test helper to reset connection attempts. |
|
||||
| `gatewayCfg.isImplicit` | gateway.go:1552 | MISSING | — | Returns whether a gateway config was discovered (implicit) vs configured (explicit). |
|
||||
| `gatewayCfg.getURLs` | gateway.go:1561 | MISSING | — | Returns randomly shuffled URL slice for connection attempts. |
|
||||
| `gatewayCfg.getURLsAsStrings` | gateway.go:1576 | MISSING | — | Returns URL host strings for gossip INFO. |
|
||||
| `gatewayCfg.updateURLs` | gateway.go:1588 | MISSING | — | Rebuilds URL map from config + INFO-discovered URLs. |
|
||||
| `gatewayCfg.saveTLSHostname` | gateway.go:1612 | MISSING | — | Saves TLS ServerName from a URL hostname. TLS not implemented. |
|
||||
| `gatewayCfg.addURLs` | gateway.go:1621 | MISSING | — | Adds newly discovered URLs into the URL map. |
|
||||
| `gatewayCfg.bumpConnAttempts` | gateway.go:1530 | PORTED | `src/NATS.Server/Configuration/GatewayOptions.cs:57` (`RemoteGatewayOptions.BumpConnAttempts`) | Added connection-attempt increment helper. |
|
||||
| `gatewayCfg.getConnAttempts` | gateway.go:1537 | PORTED | `src/NATS.Server/Configuration/GatewayOptions.cs:59` (`RemoteGatewayOptions.GetConnAttempts`) | Added connection-attempt read helper. |
|
||||
| `gatewayCfg.resetConnAttempts` | gateway.go:1545 | PORTED | `src/NATS.Server/Configuration/GatewayOptions.cs:61` (`RemoteGatewayOptions.ResetConnAttempts`) | Added connection-attempt reset helper. |
|
||||
| `gatewayCfg.isImplicit` | gateway.go:1552 | PORTED | `src/NATS.Server/Configuration/GatewayOptions.cs:63` (`RemoteGatewayOptions.IsImplicit`) | Added implicit-vs-explicit gateway config query. |
|
||||
| `gatewayCfg.getURLs` | gateway.go:1561 | PORTED | `src/NATS.Server/Configuration/GatewayOptions.cs:65` (`RemoteGatewayOptions.GetUrls`) | Added normalized + shuffled URL list helper for connection attempts. |
|
||||
| `gatewayCfg.getURLsAsStrings` | gateway.go:1576 | PORTED | `src/NATS.Server/Configuration/GatewayOptions.cs:84` (`RemoteGatewayOptions.GetUrlsAsStrings`) | Added URL string projection helper for gossip/config sync paths. |
|
||||
| `gatewayCfg.updateURLs` | gateway.go:1588 | PORTED | `src/NATS.Server/Configuration/GatewayOptions.cs:92` (`RemoteGatewayOptions.UpdateUrls`) | Added merged configured+discovered URL rebuild helper with normalization/deduplication. |
|
||||
| `gatewayCfg.saveTLSHostname` | gateway.go:1612 | PORTED | `src/NATS.Server/Configuration/GatewayOptions.cs:100` (`RemoteGatewayOptions.SaveTlsHostname`) | Added TLS hostname extraction/storage from URL host. |
|
||||
| `gatewayCfg.addURLs` | gateway.go:1621 | PORTED | `src/NATS.Server/Configuration/GatewayOptions.cs:106` (`RemoteGatewayOptions.AddUrls`) | Added incremental discovered-URL add helper with normalization/deduplication. |
|
||||
| `Server.addGatewayURL` | gateway.go:1648 | MISSING | — | Adds a URL to the server's gateway URL set and regenerates INFO JSON. |
|
||||
| `Server.removeGatewayURL` | gateway.go:1661 | MISSING | — | Removes a URL from the gateway URL set and regenerates INFO JSON. |
|
||||
| `Server.sendAsyncGatewayInfo` | gateway.go:1676 | MISSING | — | Sends updated INFO to all inbound gateway connections (e.g., after URL change). |
|
||||
| `Server.getGatewayURL` | gateway.go:1688 | MISSING | — | Returns this server's gateway listen URL string. |
|
||||
| `Server.getGatewayName` | gateway.go:1697 | MISSING | — | Returns this server's gateway cluster name. |
|
||||
| `Server.getGatewayURL` | gateway.go:1688 | PORTED | `src/NATS.Server/NatsServer.cs:259` | Added gateway listen URL accessor that returns configured listen endpoint when gateway manager is present. |
|
||||
| `Server.getGatewayName` | gateway.go:1697 | PORTED | `src/NATS.Server/NatsServer.cs:260` | Added gateway name accessor returning configured gateway cluster name. |
|
||||
| `Server.getAllGatewayConnections` | gateway.go:1703 | MISSING | — | Collects all inbound + outbound gateway clients into a map. |
|
||||
| `Server.registerInboundGatewayConnection` | gateway.go:1720 | PARTIAL | `src/NATS.Server/Gateways/GatewayManager.cs:392` (`Register`) | `Register` adds connection to dictionary. Missing: separate inbound map keyed by CID. |
|
||||
| `Server.registerOutboundGatewayConnection` | gateway.go:1728 | PARTIAL | `src/NATS.Server/Gateways/GatewayManager.cs:392` (`Register`) | `Register` handles registration. Missing: duplicate-prevention logic (return false if name already exists), RTT-ordered `outo` list. |
|
||||
| `Server.getOutboundGatewayConnection` | gateway.go:1743 | MISSING | — | Returns outbound connection by name. No named lookup of outbound connections. |
|
||||
| `Server.getOutboundGatewayConnections` | gateway.go:1752 | MISSING | — | Returns all outbound connections in RTT order. |
|
||||
| `Server.getInboundGatewayConnections` | gateway.go:1778 | MISSING | — | Returns all inbound connections. |
|
||||
| `Server.getOutboundGatewayConnection` | gateway.go:1743 | PORTED | `src/NATS.Server/Gateways/GatewayManager.cs:352` (`GetOutboundGatewayConnection`) | Outbound connection lookup by remote server id is implemented. |
|
||||
| `Server.getOutboundGatewayConnections` | gateway.go:1752 | PORTED | `src/NATS.Server/Gateways/GatewayManager.cs:359` (`GetOutboundGatewayConnections`) | Outbound connection snapshot enumeration is implemented. |
|
||||
| `Server.getInboundGatewayConnections` | gateway.go:1778 | PORTED | `src/NATS.Server/Gateways/GatewayManager.cs:366` (`GetInboundGatewayConnections`) | Inbound connection snapshot enumeration is implemented. |
|
||||
| `Server.removeRemoteGatewayConnection` | gateway.go:1788 | PARTIAL | `src/NATS.Server/Gateways/GatewayManager.cs:416` (`WatchConnectionAsync`) | Removes connection and decrements stats. Missing: outbound-specific cleanup (delete from `outo`/`out`, remove qsub tracking from `totalQSubs`), inbound-specific cleanup (remove `_R_` subscriptions). |
|
||||
| `Server.GatewayAddr` | gateway.go:1862 | MISSING | — | Returns `*net.TCPAddr` for the gateway listener. No equivalent (only `ListenEndpoint` string). |
|
||||
| `Server.GatewayAddr` | gateway.go:1862 | PORTED | `src/NATS.Server/NatsServer.cs:258` | Added gateway address accessor. .NET returns `host:port` string endpoint rather than Go `*net.TCPAddr`. |
|
||||
| `client.processGatewayAccountUnsub` | gateway.go:1875 | PARTIAL | `src/NATS.Server/Gateways/GatewayInterestTracker.cs:86` (`TrackNoInterest`) | Tracks no-interest at account level. Missing: handling of queue subs (reset `ni` map but keep entry if `qsubs > 0`), Go's nil-vs-entry distinction in `outsim`. |
|
||||
| `client.processGatewayAccountSub` | gateway.go:1904 | PARTIAL | `src/NATS.Server/Gateways/GatewayInterestTracker.cs:61` (`TrackInterest`) | Clears no-interest in optimistic mode. Missing: queue-sub check (don't delete entry if `qsubs > 0`). |
|
||||
| `client.processGatewayRUnsub` | gateway.go:1934 | MISSING | — | Parses RS- protocol; for optimistic mode stores in ni map, for InterestOnly/queue removes from sublist. Full RS- processing not ported. |
|
||||
@@ -191,7 +191,7 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| `Server.sendQueueSubOrUnsubToGateways` | gateway.go:2335 | PARTIAL | `src/NATS.Server/Gateways/GatewayManager.cs:199` (`PropagateLocalUnsubscription`) | Propagates queue sub changes. Missing: wire RS+/RS- protocol, A- clearing logic. |
|
||||
| `Server.gatewayUpdateSubInterest` | gateway.go:2391 | MISSING | — | Ref-counted `pasi` map update + recent-sub tracking + triggers send to gateways. Core subscription-interest accounting not ported. |
|
||||
| `isGWRoutedReply` (standalone) | gateway.go:2484 | PORTED | `src/NATS.Server/Gateways/ReplyMapper.cs:17` (`HasGatewayReplyPrefix`) | Detects `_GR_.` prefix with length guard. |
|
||||
| `isGWRoutedSubjectAndIsOldPrefix` (standalone) | gateway.go:2490 | PARTIAL | `src/NATS.Server/Gateways/ReplyMapper.cs:17` | `HasGatewayReplyPrefix` checks new prefix only. Old `$GR.` prefix detection missing. |
|
||||
| `isGWRoutedSubjectAndIsOldPrefix` (standalone) | gateway.go:2490 | PORTED | `src/NATS.Server/Gateways/ReplyMapper.cs:31` (`IsGatewayRoutedSubject`) | Implemented explicit routed-subject detection with old-prefix flag output (`isOldPrefix`) for `_GR_.` and `$GR.`. |
|
||||
| `hasGWRoutedReplyPrefix` (standalone) | gateway.go:2502 | PORTED | `src/NATS.Server/Gateways/ReplyMapper.cs:17` (`HasGatewayReplyPrefix`) | Equivalent prefix check. |
|
||||
| `srvGateway.shouldMapReplyForGatewaySend` | gateway.go:2507 | MISSING | — | Checks `rsubs` sync.Map to decide if a reply subject needs gateway mapping. No `rsubs` equivalent. |
|
||||
| `client.sendMsgToGateways` | gateway.go:2540 | PARTIAL | `src/NATS.Server/Gateways/GatewayManager.cs:181` (`ForwardMessageAsync`) | Iterates connections and sends. Missing: direct-send path for `_GR_` subjects (hash routing), queue group filtering, reply subject mapping, header stripping for non-header peers, message tracing, per-account stats, RTT-ordered iteration. |
|
||||
@@ -211,7 +211,7 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| `Server.trackGWReply` | gateway.go:3324 | PARTIAL | `src/NATS.Server/Gateways/ReplyMapper.cs:231` (`ReplyMapCache.Set`) | Caches reply mapping with TTL. Missing: per-client vs per-account duality, `gwrm.m` sync.Map for background cleanup, `check int32` atomic flag, `gwrm.ch` channel to trigger expiry timer. |
|
||||
| `Server.startGWReplyMapExpiration` | gateway.go:3371 | PARTIAL | `src/NATS.Server/Gateways/ReplyMapper.cs:267` (`ReplyMapCache.PurgeExpired`) | Purge is manual on-demand. Go runs a dedicated goroutine with timer reset on new entries via channel. No background expiry goroutine in .NET. |
|
||||
| `gwReplyMapping.get` | gateway.go:280 | PORTED | `src/NATS.Server/Gateways/ReplyMapper.cs:202` (`ReplyMapCache.TryGet`) | LRU get with TTL check. |
|
||||
| `RemoteGatewayOpts.clone` | gateway.go:290 | MISSING | — | Deep-copies a `RemoteGatewayOpts` including TLS config clone. No clone method on `RemoteGatewayOptions`. |
|
||||
| `RemoteGatewayOpts.clone` | gateway.go:290 | PORTED | `src/NATS.Server/Configuration/GatewayOptions.cs:36` (`RemoteGatewayOptions.Clone`) | Deep-copy helper implemented for remote gateway option name + URL list. |
|
||||
|
||||
#### Additional .NET-Only Types (No Go Equivalent)
|
||||
|
||||
@@ -247,5 +247,9 @@ After porting work is completed:
|
||||
|
||||
| Date | Change | By |
|
||||
|------|--------|----|
|
||||
| 2026-02-26 | Executed gateways batch 4 accessor parity slice: added server gateway accessors (`GatewayAddr`, `GetGatewayURL`, `GetGatewayName`) and targeted tests (`GatewayServerAccessorParityBatch4Tests`). Reclassified 3 rows to PORTED. | codex |
|
||||
| 2026-02-25 | File created with LLM analysis instructions | auto |
|
||||
| 2026-02-25 | Full gap inventory completed: analyzed all ~3,427 lines of gateway.go; classified 80+ Go symbols against 6 .NET source files. Final counts: 9 PORTED, 35 PARTIAL, 37 MISSING, 5 NOT_APPLICABLE. | claude-sonnet-4-6 |
|
||||
| 2026-02-25 | Ported gateway parity helper batch: reply-prefix/hash constants and old-prefix detection, gateway hash helpers (`getGWHash`/`getOldHash` analogs), gateway option validator, TLS warning constant, and `RemoteGatewayOptions.Clone`; added focused tests and updated status rows. | codex |
|
||||
| 2026-02-25 | Ported gateway connection-direction parity batch: added inbound/outbound classification (`IsOutbound`), count/lookups (`NumOutboundGateways`, `NumInboundGateways`, `HasInbound`, outbound/inbound connection snapshots), and server wrappers with focused tests. | codex |
|
||||
| 2026-02-25 | Ported gateway remote-config parity batch: added `RemoteGatewayOptions` attempt counters and URL lifecycle helpers (`Bump/Get/ResetConnAttempts`, `IsImplicit`, `GetUrls`, `GetUrlsAsStrings`, `UpdateUrls`, `SaveTlsHostname`, `AddUrls`) with focused tests (`GatewayRemoteConfigParityBatch3Tests`). | codex |
|
||||
|
||||
+15
-14
@@ -120,7 +120,7 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| `SequenceSet.Union` (method) | `golang/nats-server/server/avl/seqset.go:191` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:203` | |
|
||||
| `Union` (function) | `golang/nats-server/server/avl/seqset.go:208` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:228` | `SequenceSet.CreateUnion` static method |
|
||||
| `SequenceSet.EncodeLen` | `golang/nats-server/server/avl/seqset.go:238` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:252` | `EncodeLength()` in .NET |
|
||||
| `SequenceSet.Encode` | `golang/nats-server/server/avl/seqset.go:242` | PARTIAL | `src/NATS.Server/Internal/Avl/SequenceSet.cs:255` | Go signature takes optional buf to reuse; .NET always allocates new. Behavior equivalent. |
|
||||
| `SequenceSet.Encode` | `golang/nats-server/server/avl/seqset.go:242` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:266` | Added destination-buffer overload (`Encode(byte[] destination)`) enabling caller buffer reuse parity |
|
||||
| `ErrBadEncoding` | `golang/nats-server/server/avl/seqset.go:276` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:288` | Thrown as `InvalidOperationException` |
|
||||
| `ErrBadVersion` | `golang/nats-server/server/avl/seqset.go:277` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:295` | Thrown as `InvalidOperationException` |
|
||||
| `ErrSetNotEmpty` | `golang/nats-server/server/avl/seqset.go:278` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:93` | Thrown as `InvalidOperationException` |
|
||||
@@ -166,9 +166,9 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| `SubjectTree.match` (internal) | `golang/nats-server/server/stree/stree.go:318` | PORTED | `src/NATS.Server/Internal/SubjectTree/SubjectTree.cs:383` | `MatchInternal` |
|
||||
| `SubjectTree.iter` (internal) | `golang/nats-server/server/stree/stree.go:418` | PORTED | `src/NATS.Server/Internal/SubjectTree/SubjectTree.cs:512` | `IterInternal` |
|
||||
| `LazyIntersect[TL,TR]` | `golang/nats-server/server/stree/stree.go:463` | PORTED | `src/NATS.Server/Internal/SubjectTree/SubjectTree.cs:584` | `SubjectTreeHelper.LazyIntersect` static |
|
||||
| `IntersectGSL[T,SL]` | `golang/nats-server/server/stree/stree.go:488` | MISSING | — | No .NET equivalent. Intersects stree with gsl.GenericSublist. Used in JetStream consumer NumPending. |
|
||||
| `_intersectGSL` (internal) | `golang/nats-server/server/stree/stree.go:496` | MISSING | — | Helper for IntersectGSL |
|
||||
| `hasInterestForTokens` (internal) | `golang/nats-server/server/stree/stree.go:521` | MISSING | — | Token-boundary interest check for GSL intersection |
|
||||
| `IntersectGSL[T,SL]` | `golang/nats-server/server/stree/stree.go:488` | PORTED | `src/NATS.Server/Internal/SubjectTree/SubjectTree.cs:675` | Added GSL intersection traversal for subject tree entries |
|
||||
| `_intersectGSL` (internal) | `golang/nats-server/server/stree/stree.go:496` | PORTED | `src/NATS.Server/Internal/SubjectTree/SubjectTree.cs:686` | Added recursive intersection helper |
|
||||
| `hasInterestForTokens` (internal) | `golang/nats-server/server/stree/stree.go:521` | PORTED | `src/NATS.Server/Internal/SubjectTree/SubjectTree.cs:717` | Added token-boundary short-circuit using `HasInterestStartingIn` |
|
||||
| `bytesToString` (internal) | `golang/nats-server/server/stree/stree.go:534` | NOT_APPLICABLE | — | Go `unsafe` zero-copy string conversion. In .NET, `System.Text.Encoding.UTF8.GetString` or `MemoryMarshal.Cast` used instead |
|
||||
|
||||
---
|
||||
@@ -276,10 +276,10 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `SubjectTree.Dump` | `golang/nats-server/server/stree/dump.go:23` | MISSING | — | Debug utility for printing tree structure to an `io.Writer`. No .NET equivalent. Low priority — debug/diagnostic only. |
|
||||
| `SubjectTree.dump` (internal) | `golang/nats-server/server/stree/dump.go:29` | MISSING | — | Internal helper for Dump |
|
||||
| `dumpPre` | `golang/nats-server/server/stree/dump.go:59` | MISSING | — | Indentation helper for Dump |
|
||||
| `leaf.kind`, `node4.kind`, etc. | `golang/nats-server/server/stree/dump.go:51` | PARTIAL | `src/NATS.Server/Internal/SubjectTree/Nodes.cs` | `INode.Kind` property exists on each node type, but `Dump` method is not implemented |
|
||||
| `SubjectTree.Dump` | `golang/nats-server/server/stree/dump.go:23` | PORTED | `src/NATS.Server/Internal/SubjectTree/SubjectTree.cs:172` | Added debug dump writer with root traversal and trailing newline |
|
||||
| `SubjectTree.dump` (internal) | `golang/nats-server/server/stree/dump.go:29` | PORTED | `src/NATS.Server/Internal/SubjectTree/SubjectTree.cs:180` | Added recursive node/leaf dump helper |
|
||||
| `dumpPre` | `golang/nats-server/server/stree/dump.go:59` | PORTED | `src/NATS.Server/Internal/SubjectTree/SubjectTree.cs:205` | Added indentation helper matching Go depth formatting |
|
||||
| `leaf.kind`, `node4.kind`, etc. | `golang/nats-server/server/stree/dump.go:51` | PORTED | `src/NATS.Server/Internal/SubjectTree/Nodes.cs:21` | `INode.Kind` values are now actively consumed by `SubjectTree.Dump` output |
|
||||
|
||||
---
|
||||
|
||||
@@ -291,7 +291,7 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| `ErrInvalidVersion` | `golang/nats-server/server/thw/thw.go:28` | PORTED | `src/NATS.Server/Internal/TimeHashWheel/HashWheel.cs:289` | Thrown as `InvalidOperationException` |
|
||||
| `slot` (struct) | `golang/nats-server/server/thw/thw.go:39` | PORTED | `src/NATS.Server/Internal/TimeHashWheel/HashWheel.cs:406` | `Slot` internal class |
|
||||
| `HashWheel` (struct) | `golang/nats-server/server/thw/thw.go:45` | PORTED | `src/NATS.Server/Internal/TimeHashWheel/HashWheel.cs:17` | `HashWheel` class |
|
||||
| `HashWheelEntry` (struct) | `golang/nats-server/server/thw/thw.go:52` | MISSING | — | Go uses this struct for entry representation in some contexts; .NET uses `(ulong, long)` tuples inline instead |
|
||||
| `HashWheelEntry` (struct) | `golang/nats-server/server/thw/thw.go:52` | PORTED | `src/NATS.Server/Internal/TimeHashWheel/HashWheel.cs:407` | Added `HashWheelEntry` record struct (`Sequence`, `Expires`) |
|
||||
| `NewHashWheel` | `golang/nats-server/server/thw/thw.go:58` | PORTED | `src/NATS.Server/Internal/TimeHashWheel/HashWheel.cs:34` | Constructor in .NET |
|
||||
| `HashWheel.getPosition` (internal) | `golang/nats-server/server/thw/thw.go:66` | PORTED | `src/NATS.Server/Internal/TimeHashWheel/HashWheel.cs:50` | `GetPosition` private static |
|
||||
| `newSlot` (internal) | `golang/nats-server/server/thw/thw.go:71` | PORTED | `src/NATS.Server/Internal/TimeHashWheel/HashWheel.cs:406` | Inline slot initialization in .NET |
|
||||
@@ -315,7 +315,7 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| `ErrNotFound` | `golang/nats-server/server/gsl/gsl.go:42` | PORTED | `src/NATS.Server/Internal/Gsl/GenericSubjectList.cs:12` | `GslErrors.NotFound` |
|
||||
| `ErrNilChan` | `golang/nats-server/server/gsl/gsl.go:43` | NOT_APPLICABLE | — | Go channel-specific error; channels don't apply to .NET pattern |
|
||||
| `ErrAlreadyRegistered` | `golang/nats-server/server/gsl/gsl.go:44` | NOT_APPLICABLE | — | Used by notification channels in Go; no notification channel pattern in .NET port |
|
||||
| `SimpleSublist` (type alias) | `golang/nats-server/server/gsl/gsl.go:49` | PARTIAL | `src/NATS.Server/Internal/Gsl/GenericSubjectList.cs:650` | `SimpleSubjectList` uses `int` instead of `struct{}`. Functionally equivalent but not a true zero-allocation alias |
|
||||
| `SimpleSublist` (type alias) | `golang/nats-server/server/gsl/gsl.go:49` | PORTED | `src/NATS.Server/Internal/Gsl/GenericSubjectList.cs:656` | `SimpleSubjectList` now aliases `GenericSubjectList<SimpleSublistValue>` where marker value mirrors Go's empty struct payload |
|
||||
| `NewSimpleSublist` | `golang/nats-server/server/gsl/gsl.go:52` | PORTED | `src/NATS.Server/Internal/Gsl/GenericSubjectList.cs:650` | `new SimpleSubjectList()` |
|
||||
| `GenericSublist[T]` (struct) | `golang/nats-server/server/gsl/gsl.go:57` | PORTED | `src/NATS.Server/Internal/Gsl/GenericSubjectList.cs:76` | `GenericSubjectList<T>` |
|
||||
| `node[T]` (struct) | `golang/nats-server/server/gsl/gsl.go:64` | PORTED | `src/NATS.Server/Internal/Gsl/GenericSubjectList.cs:52` | `Node<T>` |
|
||||
@@ -359,7 +359,7 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| `ProcUsage` (freebsd/netbsd/openbsd/dragonfly/solaris/zos) | `golang/nats-server/server/pse/pse_*.go` | NOT_APPLICABLE | — | Platform-specific Go build-tagged files. .NET runtime abstracts these OS differences. |
|
||||
| `ProcUsage` (wasm/rumprun) | `golang/nats-server/server/pse/pse_wasm.go` | NOT_APPLICABLE | — | Stub/no-op implementations for unsupported platforms; not needed in .NET |
|
||||
| `updateUsage` (darwin, internal) | `golang/nats-server/server/pse/pse_darwin.go:56` | PORTED | `src/NATS.Server/Monitoring/VarzHandler.cs:39` | CPU sampling logic in VarzHandler |
|
||||
| `periodic` (darwin, internal) | `golang/nats-server/server/pse/pse_darwin.go:76` | PARTIAL | `src/NATS.Server/Monitoring/VarzHandler.cs:39` | Go runs periodic background timer; .NET samples on each `/varz` request with 1s cache. Semantics slightly different. |
|
||||
| `periodic` (darwin, internal) | `golang/nats-server/server/pse/pse_darwin.go:76` | PORTED | `src/NATS.Server/Monitoring/VarzHandler.cs:30` | Added 1s background timer sampler (`SampleCpuUsage`) with synchronized cached CPU reads in `/varz`, matching periodic semantics instead of request-only sampling |
|
||||
|
||||
---
|
||||
|
||||
@@ -367,9 +367,9 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `Memory` (darwin) | `golang/nats-server/server/sysmem/mem_darwin.go:18` | MISSING | — | Queries total physical RAM via `hw.memsize` sysctl. No .NET equivalent in codebase. Used by JetStream for sizing decisions. |
|
||||
| `Memory` (linux) | `golang/nats-server/server/sysmem/mem_linux.go:20` | MISSING | — | Queries via `syscall.Sysinfo`. No .NET equivalent found. |
|
||||
| `Memory` (windows) | `golang/nats-server/server/sysmem/mem_windows.go` | MISSING | — | No .NET equivalent found. Can be implemented via `GC.GetGCMemoryInfo().TotalAvailableMemoryBytes`. |
|
||||
| `Memory` (darwin) | `golang/nats-server/server/sysmem/mem_darwin.go:18` | PORTED | `src/NATS.Server/Internal/SysMem/SystemMemory.cs:12` | Added cross-platform `SystemMemory.Memory()` backed by runtime memory info |
|
||||
| `Memory` (linux) | `golang/nats-server/server/sysmem/mem_linux.go:20` | PORTED | `src/NATS.Server/Internal/SysMem/SystemMemory.cs:12` | Added cross-platform `SystemMemory.Memory()` backed by runtime memory info |
|
||||
| `Memory` (windows) | `golang/nats-server/server/sysmem/mem_windows.go` | PORTED | `src/NATS.Server/Internal/SysMem/SystemMemory.cs:12` | Added cross-platform `SystemMemory.Memory()` backed by runtime memory info |
|
||||
| `Memory` (bsd/solaris/wasm/zos) | `golang/nats-server/server/sysmem/mem_bsd.go` etc. | NOT_APPLICABLE | — | Platform-specific stubs; .NET runtime abstracts these. `GCMemoryInfo` is the cross-platform equivalent. |
|
||||
| `sysctlInt64` | `golang/nats-server/server/sysmem/sysctl.go:23` | NOT_APPLICABLE | — | Darwin/BSD internal helper using unsafe sysctl; .NET abstracts this entirely |
|
||||
|
||||
@@ -400,3 +400,4 @@ After porting work is completed:
|
||||
|------|--------|----|
|
||||
| 2026-02-25 | File created with LLM analysis instructions | auto |
|
||||
| 2026-02-25 | Full gap inventory populated: 157 PORTED, 4 PARTIAL, 10 MISSING, 8 NOT_APPLICABLE, 0 DEFERRED | auto |
|
||||
| 2026-02-25 | Completed periodic PSE parity by moving CPU sampling to a 1s background timer in `VarzHandler` and adding targeted parity test coverage | codex |
|
||||
|
||||
+18
-16
@@ -140,12 +140,12 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| JetStreamConfig (struct) | golang/nats-server/server/jetstream.go:42 | PARTIAL | src/NATS.Server/Configuration/JetStreamOptions.cs:5 | .NET has MaxMemoryStore, MaxFileStore, MaxStreams, MaxConsumers, Domain. Missing: SyncInterval, SyncAlways, CompressOK, UniqueTag, Strict fields |
|
||||
| JetStreamStats (struct) | golang/nats-server/server/jetstream.go:55 | MISSING | — | Server-level usage stats (Memory, Store, ReservedMemory, ReservedStore, Accounts, HAAssets, API) not modeled |
|
||||
| JetStreamAccountLimits (struct) | golang/nats-server/server/jetstream.go:65 | PARTIAL | src/NATS.Server/Configuration/JetStreamOptions.cs:5 | MaxStreams/MaxConsumers present. Missing: MaxAckPending, MemoryMaxStreamBytes, StoreMaxStreamBytes, MaxBytesRequired, tiered limits |
|
||||
| JetStreamTier (struct) | golang/nats-server/server/jetstream.go:76 | MISSING | — | Tiered accounting not implemented |
|
||||
| JetStreamConfig (struct) | golang/nats-server/server/jetstream.go:42 | PORTED | src/NATS.Server/Configuration/JetStreamOptions.cs | Added missing config fields: `SyncInterval`, `SyncAlways`, `CompressOk`, `UniqueTag`, `Strict` (plus parser wiring) |
|
||||
| JetStreamStats (struct) | golang/nats-server/server/jetstream.go:55 | PORTED | src/NATS.Server/JetStream/JetStreamParityModels.cs (`JetStreamStats`) | Added server-usage model with `Memory`, `Store`, `ReservedMemory`, `ReservedStore`, `Accounts`, `HaAssets`, and `Api` |
|
||||
| JetStreamAccountLimits (struct) | golang/nats-server/server/jetstream.go:65 | PORTED | src/NATS.Server/JetStream/JetStreamParityModels.cs (`JetStreamAccountLimits`), src/NATS.Server/Configuration/JetStreamOptions.cs | Added missing limits fields: `MaxAckPending`, `MemoryMaxStreamBytes`, `StoreMaxStreamBytes`, `MaxBytesRequired`, and tier map support |
|
||||
| JetStreamTier (struct) | golang/nats-server/server/jetstream.go:76 | PORTED | src/NATS.Server/JetStream/JetStreamParityModels.cs (`JetStreamTier`) | Added per-tier model (`Name`, `Memory`, `Store`, `Streams`, `Consumers`) |
|
||||
| JetStreamAccountStats (struct) | golang/nats-server/server/jetstream.go:87 | PARTIAL | src/NATS.Server/JetStream/Api/JetStreamApiResponse.cs:105 | JetStreamAccountInfo has Streams/Consumers counts only. Missing: memory/store usage, tiers, domain, API stats |
|
||||
| JetStreamAPIStats (struct) | golang/nats-server/server/jetstream.go:95 | MISSING | — | API level/total/errors/inflight stats not tracked |
|
||||
| JetStreamAPIStats (struct) | golang/nats-server/server/jetstream.go:95 | PORTED | src/NATS.Server/JetStream/JetStreamParityModels.cs (`JetStreamApiStats`) | Added API stats model with `Level`, `Total`, `Errors`, `Inflight` |
|
||||
| jetStream (internal struct) | golang/nats-server/server/jetstream.go:103 | PARTIAL | src/NATS.Server/JetStream/JetStreamService.cs:11 | JetStreamService covers lifecycle. Missing: apiInflight/apiTotal/apiErrors atomics, memUsed/storeUsed tracking, accounts map, apiSubs, cluster, oos/shuttingDown state |
|
||||
| jsAccount (internal struct) | golang/nats-server/server/jetstream.go:151 | MISSING | — | Per-account JetStream state (streams map, usage tracking, cluster usage updates) not modeled |
|
||||
| jsaUsage (internal struct) | golang/nats-server/server/jetstream.go:181 | MISSING | — | Per-account mem/store usage tracking |
|
||||
@@ -164,12 +164,12 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| enableAllJetStreamServiceImportsAndMappings (Account method) | golang/nats-server/server/jetstream.go:714 | MISSING | — | Per-account service imports and domain mappings |
|
||||
| configJetStream (Server method) | golang/nats-server/server/jetstream.go:771 | MISSING | — | Per-account JS config (enable/update/disable) |
|
||||
| configAllJetStreamAccounts (Server method) | golang/nats-server/server/jetstream.go:809 | MISSING | — | Walk all accounts and restore JetStream state |
|
||||
| JetStreamEnabled (Server method) | golang/nats-server/server/jetstream.go:904 | PARTIAL | src/NATS.Server/JetStream/JetStreamService.cs:48 | IsRunning property equivalent |
|
||||
| JetStreamEnabled (Server method) | golang/nats-server/server/jetstream.go:904 | PORTED | src/NATS.Server/NatsServer.cs:159 (`JetStreamEnabled`) | Server-level JetStream enabled check now exposed and backed by service running state |
|
||||
| JetStreamEnabledForDomain (Server method) | golang/nats-server/server/jetstream.go:909 | MISSING | — | Domain-wide JS availability check |
|
||||
| signalPullConsumers (Server method) | golang/nats-server/server/jetstream.go:930 | MISSING | — | Shutdown signal to R1 pull consumers |
|
||||
| shutdownJetStream (Server method) | golang/nats-server/server/jetstream.go:977 | PARTIAL | src/NATS.Server/JetStream/JetStreamService.cs:141 | Basic cleanup in DisposeAsync. Missing: account removal, cluster qch signaling |
|
||||
| JetStreamConfig (Server method) | golang/nats-server/server/jetstream.go:1055 | MISSING | — | Returns copy of current config |
|
||||
| StoreDir (Server method) | golang/nats-server/server/jetstream.go:1065 | MISSING | — | Returns current StoreDir |
|
||||
| JetStreamConfig (Server method) | golang/nats-server/server/jetstream.go:1055 | PORTED | src/NATS.Server/NatsServer.cs:161 (`JetStreamConfig`) | Returns a copy of configured JetStream options (store dir, limits, domain) |
|
||||
| StoreDir (Server method) | golang/nats-server/server/jetstream.go:1065 | PORTED | src/NATS.Server/NatsServer.cs:177 (`StoreDir`) | Server now exposes current configured JetStream store directory |
|
||||
| JetStreamNumAccounts (Server method) | golang/nats-server/server/jetstream.go:1074 | MISSING | — | Enabled account count |
|
||||
| JetStreamReservedResources (Server method) | golang/nats-server/server/jetstream.go:1085 | MISSING | — | Reserved mem/store bytes |
|
||||
| Account.EnableJetStream | golang/nats-server/server/jetstream.go:1107 | MISSING | — | Per-account JS enablement with limits, store dir, cluster usage |
|
||||
@@ -194,14 +194,14 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| jsAccount.reservedStorage | golang/nats-server/server/jetstream.go:1801 | MISSING | — | Reserved bytes by tier |
|
||||
| jsAccount.delete | golang/nats-server/server/jetstream.go:2481 | MISSING | — | Delete all JS resources for account |
|
||||
| dynJetStreamConfig (Server method) | golang/nats-server/server/jetstream.go:2659 | MISSING | — | Dynamic config: 75% sysmem, disk available |
|
||||
| isValidName | golang/nats-server/server/jetstream.go:2735 | MISSING | — | Name validation (no spaces, wildcards) |
|
||||
| isValidName | golang/nats-server/server/jetstream.go:2735 | PORTED | src/NATS.Server/JetStream/Validation/JetStreamConfigValidator.cs:10 | `IsValidName` enforces non-empty names, UTF-8 max 255 bytes, and rejects whitespace / `*` / `>`; applied in stream + consumer create paths |
|
||||
| friendlyBytes | golang/nats-server/server/jetstream.go:2723 | NOT_APPLICABLE | — | Logging helper; .NET has built-in formatting |
|
||||
| tierName | golang/nats-server/server/jetstream.go:2316 | MISSING | — | Compute tier name from replica count |
|
||||
| validateJetStreamOptions | golang/nats-server/server/jetstream.go:2767 | MISSING | — | Validates JS options (domain, cluster, etc.) |
|
||||
| fixCfgMirrorWithDedupWindow | golang/nats-server/server/jetstream.go:2848 | NOT_APPLICABLE | — | Bug fix for legacy config; not needed in new port |
|
||||
| JetStreamStoreDir (const) | golang/nats-server/server/jetstream.go:2649 | MISSING | — | "jetstream" directory name constant |
|
||||
| JetStreamMaxStoreDefault (const) | golang/nats-server/server/jetstream.go:2651 | MISSING | — | Default 1TB disk limit |
|
||||
| JetStreamMaxMemDefault (const) | golang/nats-server/server/jetstream.go:2653 | MISSING | — | Default 256MB mem limit |
|
||||
| JetStreamStoreDir (const) | golang/nats-server/server/jetstream.go:2649 | PORTED | src/NATS.Server/Configuration/JetStreamOptions.cs:8 | Added Go-parity constant `"jetstream"` |
|
||||
| JetStreamMaxStoreDefault (const) | golang/nats-server/server/jetstream.go:2651 | PORTED | src/NATS.Server/Configuration/JetStreamOptions.cs:9 | Added Go-parity default max store constant (`1 TiB`) |
|
||||
| JetStreamMaxMemDefault (const) | golang/nats-server/server/jetstream.go:2653 | PORTED | src/NATS.Server/Configuration/JetStreamOptions.cs:10 | Added Go-parity default max memory constant (`256 MiB`) |
|
||||
| Stream recovery logic (doStream/doConsumers) | golang/nats-server/server/jetstream.go:1223-1636 | MISSING | — | Full stream/consumer recovery from disk: metafile reading, checksum, encryption, versioning, subject repair |
|
||||
| keyGen (type) | golang/nats-server/server/jetstream.go:237 | MISSING | — | Key generation function signature for encryption |
|
||||
| resourcesExceededError (Server method) | golang/nats-server/server/jetstream.go:2743 | MISSING | — | Throttled error logging + meta leader stepdown |
|
||||
@@ -213,10 +213,10 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| JSApi* subject constants (50+) | golang/nats-server/server/jetstream_api.go:36-312 | PORTED | src/NATS.Server/JetStream/Api/JetStreamApiSubjects.cs:1 | All major API subjects defined. Minor: some template variants (T-suffixed) not needed in .NET |
|
||||
| JSAdvisory* prefix constants (25+) | golang/nats-server/server/jetstream_api.go:229-311 | PARTIAL | src/NATS.Server/JetStream/Api/AdvisoryPublisher.cs:1 | Stream create/delete/update, consumer create/delete covered. Missing: snapshot, restore, leader elected, quorum lost, batch abandoned, out-of-storage, server removed, API limit, pause, pinned, unpinned advisory prefixes |
|
||||
| JSMaxDescriptionLen (const) | golang/nats-server/server/jetstream_api.go:352 | MISSING | — | 4096 byte limit for descriptions |
|
||||
| JSMaxMetadataLen (const) | golang/nats-server/server/jetstream_api.go:356 | MISSING | — | 128KB metadata map size limit |
|
||||
| JSMaxNameLen (const) | golang/nats-server/server/jetstream_api.go:360 | MISSING | — | 255 char name length limit |
|
||||
| JSDefaultRequestQueueLimit (const) | golang/nats-server/server/jetstream_api.go:364 | MISSING | — | 10,000 request queue limit |
|
||||
| JSMaxDescriptionLen (const) | golang/nats-server/server/jetstream_api.go:352 | PORTED | src/NATS.Server/JetStream/Api/JetStreamApiLimits.cs:10 | Added Go-parity constant and enforced in stream create/update validation (`StreamManager`) |
|
||||
| JSMaxMetadataLen (const) | golang/nats-server/server/jetstream_api.go:356 | PORTED | src/NATS.Server/JetStream/Api/JetStreamApiLimits.cs:11 | Added Go-parity constant; metadata byte-size helper validates stream/consumer metadata against this limit |
|
||||
| JSMaxNameLen (const) | golang/nats-server/server/jetstream_api.go:360 | PORTED | src/NATS.Server/JetStream/Api/JetStreamApiLimits.cs:12 | Added Go-parity constant; name validation uses UTF-8 byte length limit through `JetStreamConfigValidator.IsValidName` |
|
||||
| JSDefaultRequestQueueLimit (const) | golang/nats-server/server/jetstream_api.go:364 | PORTED | src/NATS.Server/JetStream/Api/JetStreamApiLimits.cs:13 | Added Go-parity default request queue limit constant for JetStream API request orchestration |
|
||||
| ApiResponse (struct) | golang/nats-server/server/jetstream_api.go:369 | PARTIAL | src/NATS.Server/JetStream/Api/JetStreamApiResponse.cs:5 | Type field missing; error structure simplified |
|
||||
| ApiPaged / ApiPagedRequest (structs) | golang/nats-server/server/jetstream_api.go:395-404 | MISSING | — | Paged API request/response not implemented |
|
||||
| JSApiAccountInfoResponse | golang/nats-server/server/jetstream_api.go:407 | PARTIAL | src/NATS.Server/JetStream/Api/JetStreamApiResponse.cs:105 | Basic streams/consumers count. Missing: full JetStreamAccountStats embedding |
|
||||
@@ -1933,3 +1933,5 @@ After porting work is completed:
|
||||
| 2026-02-25 | JS-5c: jetstream_cluster.go lines 8001-end. 77 symbols. PORTED:8 PARTIAL:13 MISSING:56 | opus |
|
||||
| 2026-02-25 | File created with LLM analysis instructions | auto |
|
||||
| 2026-02-25 | JS-1 Core sub-pass: analyzed jetstream.go, jetstream_api.go, jetstream_events.go, jetstream_errors.go, jetstream_versioning.go, jetstream_batching.go. 150+ symbols inventoried. | opus |
|
||||
| 2026-02-25 | JS core config parity batch: added JetStream default constants (`JetStreamStoreDir`, max store/mem defaults) and server accessors (`JetStreamEnabled`, `JetStreamConfig`, `StoreDir`) with focused tests | codex |
|
||||
| 2026-02-26 | JS config/model parity batch: extended `JetStreamOptions` (sync/compress/strict/unique-tag and account-limit fields), added parser support for extended jetstream config keys, and added core parity models (`JetStreamStats`, `JetStreamApiStats`, `JetStreamAccountLimits`, `JetStreamTier`) with focused tests | codex |
|
||||
|
||||
+44
-39
@@ -92,45 +92,45 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `leafnodeTLSInsecureWarning` (const) | `golang/nats-server/server/leafnode.go:47` | PORTED | `src/NATS.Server/LeafNodes/LeafNodeManager.cs` | Warning logged in `DisableLeafConnect`; same semantic intent, no separate constant |
|
||||
| `leafNodeReconnectDelayAfterLoopDetected` (const) | `golang/nats-server/server/leafnode.go:50` | MISSING | — | 30s reconnect delay after loop detection. .NET loop detector (`LeafLoopDetector`) detects but does not enforce the delay on reconnect |
|
||||
| `leafNodeReconnectAfterPermViolation` (const) | `golang/nats-server/server/leafnode.go:54` | MISSING | — | 30s reconnect delay after permission violation. No .NET equivalent enforced |
|
||||
| `leafNodeReconnectDelayAfterClusterNameSame` (const) | `golang/nats-server/server/leafnode.go:57` | MISSING | — | 30s delay when same cluster name detected. No .NET equivalent |
|
||||
| `leafNodeReconnectDelayAfterLoopDetected` (const) | `golang/nats-server/server/leafnode.go:50` | PARTIAL | `src/NATS.Server/LeafNodes/LeafNodeManager.cs:19`, `src/NATS.Server/LeafNodes/LeafConnection.cs` | Delay constant is now consumed by `LeafConnection.LeafProcessErr` for loop ERR processing. Remaining: reconnect loop still does not schedule by this delay automatically |
|
||||
| `leafNodeReconnectAfterPermViolation` (const) | `golang/nats-server/server/leafnode.go:54` | PARTIAL | `src/NATS.Server/LeafNodes/LeafNodeManager.cs:20`, `src/NATS.Server/LeafNodes/LeafConnection.cs` | Delay constant is now consumed by `LeafPermViolation` / `LeafSubPermViolation`. Remaining: no enforced wait-before-redial in reconnect worker |
|
||||
| `leafNodeReconnectDelayAfterClusterNameSame` (const) | `golang/nats-server/server/leafnode.go:57` | PARTIAL | `src/NATS.Server/LeafNodes/LeafNodeManager.cs:21`, `src/NATS.Server/LeafNodes/LeafConnection.cs` | Delay constant is now consumed by `LeafProcessErr` cluster-name path. Remaining: reconnect loop integration not complete |
|
||||
| `leafNodeLoopDetectionSubjectPrefix` (const `"$LDS."`) | `golang/nats-server/server/leafnode.go:60` | PORTED | `src/NATS.Server/LeafNodes/LeafLoopDetector.cs:5` | `LeafLoopPrefix = "$LDS."` |
|
||||
| `leafNodeWSPath` (const `"/leafnode"`) | `golang/nats-server/server/leafnode.go:64` | PORTED | `src/NATS.Server/LeafNodes/WebSocketStreamAdapter.cs` | Path constant is implicit in the WS adapter; not a named constant in .NET |
|
||||
| `leafNodeWaitBeforeClose` (const 5s) | `golang/nats-server/server/leafnode.go:68` | MISSING | — | Minimum version wait-before-close timer. Not ported |
|
||||
| `leaf` (unexported struct) | `golang/nats-server/server/leafnode.go:71` | PARTIAL | `src/NATS.Server/LeafNodes/LeafConnection.cs` | `LeafConnection` covers `remote`, `isSpoke`, `remoteCluster`, `remoteServer`, `remoteDomain`. Missing: `isolated`, `smap`, `tsub/tsubt` (transient sub map), `compression`, `gwSub` |
|
||||
| `leafNodeCfg` (unexported struct) | `golang/nats-server/server/leafnode.go:107` | PARTIAL | `src/NATS.Server/Configuration/LeafNodeOptions.cs` | `RemoteLeafOptions` covers URLs, credentials, local account. Missing: `curURL`, `tlsName`, `username/password` (runtime fields), `perms`, `connDelay`, `jsMigrateTimer` |
|
||||
| `leafConnectInfo` (unexported struct) | `golang/nats-server/server/leafnode.go:2001` | MISSING | — | JSON CONNECT payload for leaf solicited connections (JWT, Nkey, Sig, Hub, Cluster, Headers, JetStream, Compression, RemoteAccount, Proto). Not represented in .NET |
|
||||
| `leafNodeWaitBeforeClose` (const 5s) | `golang/nats-server/server/leafnode.go:68` | PARTIAL | `src/NATS.Server/LeafNodes/LeafNodeManager.cs:22` | Constant is defined (`LeafNodeWaitBeforeClose = 5s`), but close-path wait timer behavior is not yet wired |
|
||||
| `leaf` (unexported struct) | `golang/nats-server/server/leafnode.go:71` | PARTIAL | `src/NATS.Server/LeafNodes/LeafConnection.cs` | `LeafConnection` now tracks role flags (`IsSolicited`, `IsSpoke`, `Isolated`) and helper predicates. Missing: `smap`, `tsub/tsubt`, `compression`, `gwSub`, remote cluster/server metadata parity |
|
||||
| `leafNodeCfg` (unexported struct) | `golang/nats-server/server/leafnode.go:107` | PARTIAL | `src/NATS.Server/Configuration/LeafNodeOptions.cs:7` (`RemoteLeafOptions`) | Added runtime parity fields/helpers (`CurrentUrl`, `TlsName`, URL user-info, connect-delay storage, round-robin URL picker). Remaining gaps: perms and JS migrate timer wiring |
|
||||
| `leafConnectInfo` (unexported struct) | `golang/nats-server/server/leafnode.go:2001` | PORTED | `src/NATS.Server/LeafNodes/LeafConnectInfo.cs` | CONNECT payload DTO now represented with Go-parity JSON fields (`jwt`, `nkey`, `sig`, `hub`, `cluster`, `headers`, `jetstream`, `compression`, `remote_account`, `proto`) |
|
||||
|
||||
#### Methods on `client` (receiver functions)
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `(c *client) isSolicitedLeafNode()` | `golang/nats-server/server/leafnode.go:121` | MISSING | — | No `client` type in .NET; `LeafConnection` does not track solicited vs. accepted role |
|
||||
| `(c *client) isSpokeLeafNode()` | `golang/nats-server/server/leafnode.go:127` | MISSING | — | Hub/spoke role tracking missing in .NET |
|
||||
| `(c *client) isHubLeafNode()` | `golang/nats-server/server/leafnode.go:131` | MISSING | — | Hub role helper missing in .NET |
|
||||
| `(c *client) isIsolatedLeafNode()` | `golang/nats-server/server/leafnode.go:135` | MISSING | — | Isolation flag not tracked in .NET |
|
||||
| `(c *client) sendLeafConnect(clusterName, headers)` | `golang/nats-server/server/leafnode.go:969` | MISSING | — | Sends CONNECT JSON payload (JWT/NKey/creds auth) on solicited connections. .NET handshake only sends `LEAF <id>` line |
|
||||
| `(c *client) isSolicitedLeafNode()` | `golang/nats-server/server/leafnode.go:121` | PORTED | `src/NATS.Server/LeafNodes/LeafConnection.cs:29,169` | Solicited role is tracked (`IsSolicited`) and exposed via `IsSolicitedLeafNode()` |
|
||||
| `(c *client) isSpokeLeafNode()` | `golang/nats-server/server/leafnode.go:127` | PORTED | `src/NATS.Server/LeafNodes/LeafConnection.cs:35,170` | Spoke role is tracked (`IsSpoke`) and exposed via `IsSpokeLeafNode()` |
|
||||
| `(c *client) isHubLeafNode()` | `golang/nats-server/server/leafnode.go:131` | PORTED | `src/NATS.Server/LeafNodes/LeafConnection.cs:171` | Hub-role helper implemented as the complement of spoke role (`!IsSpoke`) |
|
||||
| `(c *client) isIsolatedLeafNode()` | `golang/nats-server/server/leafnode.go:135` | PORTED | `src/NATS.Server/LeafNodes/LeafConnection.cs:41,172` | Isolation flag is tracked (`Isolated`) and exposed via `IsIsolatedLeafNode()` |
|
||||
| `(c *client) sendLeafConnect(clusterName, headers)` | `golang/nats-server/server/leafnode.go:969` | PORTED | `src/NATS.Server/LeafNodes/LeafConnection.cs` (`SendLeafConnectAsync`) | Added CONNECT protocol writer that serializes `LeafConnectInfo` JSON payload and writes `CONNECT <json>` |
|
||||
| `(c *client) leafClientHandshakeIfNeeded(remote, opts)` | `golang/nats-server/server/leafnode.go:1402` | PARTIAL | `src/NATS.Server/LeafNodes/LeafConnection.cs:80` | .NET `PerformOutboundHandshakeAsync` performs the handshake but without TLS negotiation or TLS-first logic |
|
||||
| `(c *client) processLeafnodeInfo(info)` | `golang/nats-server/server/leafnode.go:1426` | MISSING | — | Complex INFO protocol processing (TLS negotiation, compression selection, URL updates, permission updates). Not ported |
|
||||
| `(c *client) updateLeafNodeURLs(info)` | `golang/nats-server/server/leafnode.go:1711` | MISSING | — | Dynamically updates remote URL list from async INFO. Not ported |
|
||||
| `(c *client) doUpdateLNURLs(cfg, scheme, URLs)` | `golang/nats-server/server/leafnode.go:1732` | MISSING | — | Helper for `updateLeafNodeURLs`. Not ported |
|
||||
| `(c *client) remoteCluster()` | `golang/nats-server/server/leafnode.go:2235` | MISSING | — | Returns remote cluster name. Not tracked in .NET |
|
||||
| `(c *client) remoteCluster()` | `golang/nats-server/server/leafnode.go:2235` | PORTED | `src/NATS.Server/LeafNodes/LeafConnection.cs` (`RemoteCluster`) | Handshake parser now captures `cluster=...` attribute and exposes it via `RemoteCluster()` |
|
||||
| `(c *client) updateSmap(sub, delta, isLDS)` | `golang/nats-server/server/leafnode.go:2522` | MISSING | — | Core subject-map delta updates. .NET has `PropagateLocalSubscription` but no per-connection smap with refcounting |
|
||||
| `(c *client) forceAddToSmap(subj)` | `golang/nats-server/server/leafnode.go:2567` | MISSING | — | Force-inserts a subject into the smap. Not ported |
|
||||
| `(c *client) forceRemoveFromSmap(subj)` | `golang/nats-server/server/leafnode.go:2584` | MISSING | — | Force-removes a subject from the smap. Not ported |
|
||||
| `(c *client) sendLeafNodeSubUpdate(key, n)` | `golang/nats-server/server/leafnode.go:2607` | PARTIAL | `src/NATS.Server/LeafNodes/LeafNodeManager.cs:265` | `PropagateLocalSubscription` / `PropagateLocalUnsubscription` send LS+/LS-. Missing: spoke permission check before sending, queue weight encoding |
|
||||
| `(c *client) writeLeafSub(w, key, n)` | `golang/nats-server/server/leafnode.go:2687` | PARTIAL | `src/NATS.Server/LeafNodes/LeafConnection.cs:108` | `SendLsPlusAsync`/`SendLsMinusAsync` write LS+/LS-. Missing: queue weight (`n`) in LS+ for queue subs |
|
||||
| `(c *client) processLeafSub(argo)` | `golang/nats-server/server/leafnode.go:2720` | PARTIAL | `src/NATS.Server/LeafNodes/LeafConnection.cs:190` | Read loop parses LS+ lines. Missing: loop detection check, permission check, subscription insertion into SubList, route/gateway propagation, queue weight delta handling |
|
||||
| `(c *client) sendLeafNodeSubUpdate(key, n)` | `golang/nats-server/server/leafnode.go:2607` | PORTED | `src/NATS.Server/LeafNodes/LeafNodeManager.cs:294` | `PropagateLocalSubscription` / `PropagateLocalUnsubscription` now mirror send-side parity: spoke subscribe-permission gate (with `$LDS.`/gateway-reply bypass) and queue-weight LS+ emission |
|
||||
| `(c *client) writeLeafSub(w, key, n)` | `golang/nats-server/server/leafnode.go:2687` | PORTED | `src/NATS.Server/LeafNodes/LeafConnection.cs:135` | `SendLsPlusAsync` now emits `LS+ <account> <subject> <queue> <n>` for queue subscriptions with weight, and `SendLsMinusAsync` mirrors `LS-` framing parity |
|
||||
| `(c *client) processLeafSub(argo)` | `golang/nats-server/server/leafnode.go:2720` | PARTIAL | `src/NATS.Server/LeafNodes/LeafConnection.cs:308` | Read loop parses LS+ lines including optional queue weight. Missing: loop detection check, permission check, subscription insertion into SubList, route/gateway propagation, and Go-equivalent delta/refcount updates |
|
||||
| `(c *client) handleLeafNodeLoop(sendErr)` | `golang/nats-server/server/leafnode.go:2860` | PARTIAL | `src/NATS.Server/LeafNodes/LeafLoopDetector.cs:13` | `IsLooped` detects the condition. Missing: sending the error back to remote, closing connection, setting reconnect delay |
|
||||
| `(c *client) processLeafUnsub(arg)` | `golang/nats-server/server/leafnode.go:2875` | PARTIAL | `src/NATS.Server/LeafNodes/LeafConnection.cs:200` | Read loop parses LS- lines. Missing: SubList removal, route/gateway propagation |
|
||||
| `(c *client) processLeafHeaderMsgArgs(arg)` | `golang/nats-server/server/leafnode.go:2917` | MISSING | — | Parses LMSG header arguments (header size + total size for NATS headers protocol). Not ported |
|
||||
| `(c *client) processLeafMsgArgs(arg)` | `golang/nats-server/server/leafnode.go:3001` | PARTIAL | `src/NATS.Server/LeafNodes/LeafConnection.cs:213` | .NET read loop parses LMSG lines. Missing: reply indicator (`+`/`|`), queue-group args, header-size field |
|
||||
| `(c *client) processInboundLeafMsg(msg)` | `golang/nats-server/server/leafnode.go:3072` | PARTIAL | `src/NATS.Server/LeafNodes/LeafNodeManager.cs:248` | `ForwardMessageAsync` forwards to all connections; inbound path calls `_messageSink`. Missing: SubList match + fanout to local subscribers, L1 result cache, gateway forwarding |
|
||||
| `(c *client) leafSubPermViolation(subj)` | `golang/nats-server/server/leafnode.go:3148` | MISSING | — | Handles subscription permission violation (log + close). Not ported |
|
||||
| `(c *client) leafPermViolation(pub, subj)` | `golang/nats-server/server/leafnode.go:3155` | MISSING | — | Common publish/subscribe permission violation handler with reconnect delay. Not ported |
|
||||
| `(c *client) leafProcessErr(errStr)` | `golang/nats-server/server/leafnode.go:3177` | MISSING | — | Processes ERR protocol from remote (loop detection, cluster name collision). Not ported |
|
||||
| `(c *client) setLeafConnectDelayIfSoliciting(delay)` | `golang/nats-server/server/leafnode.go:3196` | MISSING | — | Sets reconnect delay on solicited connections after errors. Not ported |
|
||||
| `(c *client) leafSubPermViolation(subj)` | `golang/nats-server/server/leafnode.go:3148` | PARTIAL | `src/NATS.Server/LeafNodes/LeafConnection.cs` (`LeafSubPermViolation`) | Added subscription violation handler that applies solicited reconnect delay. Remaining: close/log side effects are not yet mirrored |
|
||||
| `(c *client) leafPermViolation(pub, subj)` | `golang/nats-server/server/leafnode.go:3155` | PARTIAL | `src/NATS.Server/LeafNodes/LeafConnection.cs` (`LeafPermViolation`) | Added shared violation handler applying permission reconnect delay for solicited links. Remaining: close/log and error emission path not fully ported |
|
||||
| `(c *client) leafProcessErr(errStr)` | `golang/nats-server/server/leafnode.go:3177` | PARTIAL | `src/NATS.Server/LeafNodes/LeafConnection.cs` (`LeafProcessErr`) | Added ERR classifier for permission/loop/cluster-name cases that drives reconnect-delay selection. Remaining: full remote ERR processing and close semantics |
|
||||
| `(c *client) setLeafConnectDelayIfSoliciting(delay)` | `golang/nats-server/server/leafnode.go:3196` | PORTED | `src/NATS.Server/LeafNodes/LeafConnection.cs` (`SetLeafConnectDelayIfSoliciting`, `GetConnectDelay`) | Solicited-only delay setter/getter implemented and covered by parity tests |
|
||||
| `(c *client) leafNodeGetTLSConfigForSolicit(remote)` | `golang/nats-server/server/leafnode.go:3215` | MISSING | — | Derives TLS config for solicited connection. .NET has no real TLS handshake for leaf nodes |
|
||||
| `(c *client) leafNodeSolicitWSConnection(opts, rURL, remote)` | `golang/nats-server/server/leafnode.go:3253` | PARTIAL | `src/NATS.Server/LeafNodes/WebSocketStreamAdapter.cs` | `WebSocketStreamAdapter` adapts a WebSocket to a Stream. Missing: HTTP upgrade negotiation (`GET /leafnode` request/response), TLS handshake, compression negotiation, no-masking header |
|
||||
|
||||
@@ -139,7 +139,7 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `(s *Server) solicitLeafNodeRemotes(remotes)` | `golang/nats-server/server/leafnode.go:144` | PARTIAL | `src/NATS.Server/LeafNodes/LeafNodeManager.cs:200` | `StartAsync` iterates `_options.Remotes` and spawns `ConnectSolicitedWithRetryAsync`. Missing: credentials file validation, system account delay, disabled-remote filtering, per-remote NKey/JWT auth |
|
||||
| `(s *Server) remoteLeafNodeStillValid(remote)` | `golang/nats-server/server/leafnode.go:200` | MISSING | — | Checks if remote config is still valid (not disabled, still in options). No equivalent in .NET |
|
||||
| `(s *Server) remoteLeafNodeStillValid(remote)` | `golang/nats-server/server/leafnode.go:200` | PORTED | `src/NATS.Server/LeafNodes/LeafNodeManager.cs:102` | Implemented remote validity guard (configured in remotes/remoteLeaves and not disabled); retry loop now short-circuits when invalid |
|
||||
| `(s *Server) updateRemoteLeafNodesTLSConfig(opts)` | `golang/nats-server/server/leafnode.go:432` | PARTIAL | `src/NATS.Server/LeafNodes/LeafNodeManager.cs:157` | `UpdateTlsConfig` updates cert/key paths. Missing: actual TLS config propagation to existing connections |
|
||||
| `(s *Server) reConnectToRemoteLeafNode(remote)` | `golang/nats-server/server/leafnode.go:458` | PORTED | `src/NATS.Server/LeafNodes/LeafNodeManager.cs:583` | `ConnectSolicitedWithRetryAsync` implements reconnect loop with exponential backoff |
|
||||
| `(s *Server) setLeafNodeNonExportedOptions()` | `golang/nats-server/server/leafnode.go:549` | NOT_APPLICABLE | — | Sets test-only options (dialTimeout, resolver). .NET uses DI/options; no direct equivalent needed |
|
||||
@@ -176,13 +176,13 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `(cfg *leafNodeCfg) pickNextURL()` | `golang/nats-server/server/leafnode.go:510` | MISSING | — | Round-robins through URL list. .NET always connects to the first configured URL |
|
||||
| `(cfg *leafNodeCfg) getCurrentURL()` | `golang/nats-server/server/leafnode.go:525` | MISSING | — | Returns current URL. Not tracked in .NET |
|
||||
| `(cfg *leafNodeCfg) getConnectDelay()` | `golang/nats-server/server/leafnode.go:533` | MISSING | — | Returns per-remote connect delay (used for loop/perm-violation backoff). Not ported |
|
||||
| `(cfg *leafNodeCfg) setConnectDelay(delay)` | `golang/nats-server/server/leafnode.go:541` | MISSING | — | Sets per-remote connect delay. Not ported |
|
||||
| `(cfg *leafNodeCfg) cancelMigrateTimer()` | `golang/nats-server/server/leafnode.go:761` | MISSING | — | Cancels the JetStream migration timer. No timer in .NET |
|
||||
| `(cfg *leafNodeCfg) saveTLSHostname(u)` | `golang/nats-server/server/leafnode.go:858` | MISSING | — | Saves TLS hostname from URL for SNI. Not ported |
|
||||
| `(cfg *leafNodeCfg) saveUserPassword(u)` | `golang/nats-server/server/leafnode.go:866` | MISSING | — | Saves username/password from URL for bare-URL fallback. Not ported |
|
||||
| `(cfg *leafNodeCfg) pickNextURL()` | `golang/nats-server/server/leafnode.go:510` | PORTED | `src/NATS.Server/Configuration/LeafNodeOptions.cs:40` (`RemoteLeafOptions.PickNextUrl`) | Round-robin URL picker implemented with per-remote current URL tracking |
|
||||
| `(cfg *leafNodeCfg) getCurrentURL()` | `golang/nats-server/server/leafnode.go:525` | PORTED | `src/NATS.Server/Configuration/LeafNodeOptions.cs:55` (`GetCurrentUrl`) | Current selected URL accessor implemented |
|
||||
| `(cfg *leafNodeCfg) getConnectDelay()` | `golang/nats-server/server/leafnode.go:533` | PORTED | `src/NATS.Server/Configuration/LeafNodeOptions.cs:61` (`GetConnectDelay`) | Per-remote connect-delay getter implemented |
|
||||
| `(cfg *leafNodeCfg) setConnectDelay(delay)` | `golang/nats-server/server/leafnode.go:541` | PORTED | `src/NATS.Server/Configuration/LeafNodeOptions.cs:67` (`SetConnectDelay`) | Per-remote connect-delay setter implemented |
|
||||
| `(cfg *leafNodeCfg) cancelMigrateTimer()` | `golang/nats-server/server/leafnode.go:761` | PORTED | `src/NATS.Server/Configuration/LeafNodeOptions.cs` (`StartMigrateTimer`, `CancelMigrateTimer`) | Added per-remote migrate timer handle with cancellation semantics |
|
||||
| `(cfg *leafNodeCfg) saveTLSHostname(u)` | `golang/nats-server/server/leafnode.go:858` | PORTED | `src/NATS.Server/Configuration/LeafNodeOptions.cs:73` (`SaveTlsHostname`) | TLS hostname extraction from URL implemented |
|
||||
| `(cfg *leafNodeCfg) saveUserPassword(u)` | `golang/nats-server/server/leafnode.go:866` | PORTED | `src/NATS.Server/Configuration/LeafNodeOptions.cs:83` (`SaveUserPassword`) | Username/password extraction from URL user-info implemented |
|
||||
|
||||
#### Standalone Functions
|
||||
|
||||
@@ -194,17 +194,17 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| `validateLeafNodeProxyOptions(remote)` | `golang/nats-server/server/leafnode.go:377` | MISSING | — | Validates HTTP proxy options for WebSocket leaf remotes. Not ported |
|
||||
| `newLeafNodeCfg(remote)` | `golang/nats-server/server/leafnode.go:470` | PARTIAL | `src/NATS.Server/Configuration/LeafNodeOptions.cs` | `RemoteLeafOptions` covers URLs and credentials. Missing: URL randomization, per-URL TLS hostname/password extraction, WS TLS detection |
|
||||
| `establishHTTPProxyTunnel(proxyURL, targetHost, timeout, username, password)` | `golang/nats-server/server/leafnode.go:565` | MISSING | — | Establishes an HTTP CONNECT tunnel through an HTTP proxy for WebSocket leaf connections. Not ported |
|
||||
| `keyFromSub(sub)` | `golang/nats-server/server/leafnode.go:2638` | MISSING | — | Builds smap key "subject" or "subject queue". Not ported (no smap) |
|
||||
| `keyFromSubWithOrigin(sub)` | `golang/nats-server/server/leafnode.go:2664` | MISSING | — | Builds routed smap key with origin cluster prefix. Not ported |
|
||||
| `keyFromSub(sub)` | `golang/nats-server/server/leafnode.go:2638` | PORTED | `src/NATS.Server/LeafNodes/LeafSubKey.cs:19` (`KeyFromSub`) | Helper now builds `subject` or `subject queue` keys matching Go key shape |
|
||||
| `keyFromSubWithOrigin(sub)` | `golang/nats-server/server/leafnode.go:2664` | PORTED | `src/NATS.Server/LeafNodes/LeafSubKey.cs:27` (`KeyFromSubWithOrigin`) | Routed key builder now emits `R ...` and `L ...` forms with optional queue/origin segments |
|
||||
|
||||
#### Constants in smap key helpers
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `keyRoutedSub`, `keyRoutedSubByte` (const `"R"`) | `golang/nats-server/server/leafnode.go:2651` | MISSING | — | Prefix for routed plain subs. No smap in .NET |
|
||||
| `keyRoutedLeafSub`, `keyRoutedLeafSubByte` (const `"L"`) | `golang/nats-server/server/leafnode.go:2653` | MISSING | — | Prefix for routed leaf subs. No smap in .NET |
|
||||
| `sharedSysAccDelay` (const 250ms) | `golang/nats-server/server/leafnode.go:562` | MISSING | — | System account shared delay before first connect. Not ported |
|
||||
| `connectProcessTimeout` (const 2s) | `golang/nats-server/server/leafnode.go:3365` | MISSING | — | Timeout for the leaf connect process. Not ported |
|
||||
| `keyRoutedSub`, `keyRoutedSubByte` (const `"R"`) | `golang/nats-server/server/leafnode.go:2651` | PORTED | `src/NATS.Server/LeafNodes/LeafSubKey.cs:11-12` | Routed-sub key prefix constants are defined for parity |
|
||||
| `keyRoutedLeafSub`, `keyRoutedLeafSubByte` (const `"L"`) | `golang/nats-server/server/leafnode.go:2653` | PORTED | `src/NATS.Server/LeafNodes/LeafSubKey.cs:13-14` | Routed-leaf-sub key prefix constants are defined for parity |
|
||||
| `sharedSysAccDelay` (const 250ms) | `golang/nats-server/server/leafnode.go:562` | PORTED | `src/NATS.Server/LeafNodes/LeafSubKey.cs:16` | Shared system-account connect delay constant added (`250ms`) |
|
||||
| `connectProcessTimeout` (const 2s) | `golang/nats-server/server/leafnode.go:3365` | PORTED | `src/NATS.Server/LeafNodes/LeafSubKey.cs:17` | Connect-process timeout constant added (`2s`) |
|
||||
|
||||
#### .NET-only additions (no Go equivalent — extensions)
|
||||
|
||||
@@ -229,18 +229,18 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
|
||||
| Status | Count |
|
||||
|--------|-------|
|
||||
| PORTED | 5 |
|
||||
| PARTIAL | 18 |
|
||||
| MISSING | 38 |
|
||||
| PORTED | 29 |
|
||||
| PARTIAL | 28 |
|
||||
| MISSING | 26 |
|
||||
| NOT_APPLICABLE | 1 |
|
||||
| DEFERRED | 0 |
|
||||
| **Total** | **62** |
|
||||
| **Total** | **84** |
|
||||
|
||||
### Key Gaps
|
||||
|
||||
The .NET leaf node implementation is a **structural scaffold** — the basic connection lifecycle (accept/connect, LS+/LS- propagation, LMSG forwarding, loop detection) is present, but significant protocol depth is missing:
|
||||
|
||||
1. **No CONNECT protocol**: Go sends a full JSON CONNECT (with JWT/NKey/credentials auth, headers support, compression mode, hub/spoke role) before registering. .NET sends a simple `LEAF <id>` line.
|
||||
1. **CONNECT flow is only partially wired**: .NET now has `LeafConnectInfo` + `SendLeafConnectAsync`, but solicited connection flow still primarily handshakes with `LEAF <id>` and does not yet fully mirror Go connect-process sequencing.
|
||||
2. **No smap (subject map)**: Go maintains a per-connection reference-counted map (`leaf.smap`) to deduplicate LS+/LS- traffic. .NET broadcasts blindly to all connections.
|
||||
3. **No INFO protocol handling**: Dynamic URL list updates, compression negotiation, and permission updates over async INFO are unimplemented.
|
||||
4. **No compression**: S2 compression negotiation between hub and leaf is entirely absent.
|
||||
@@ -278,3 +278,8 @@ After porting work is completed:
|
||||
|------|--------|----|
|
||||
| 2026-02-25 | File created with LLM analysis instructions | auto |
|
||||
| 2026-02-25 | Full gap inventory populated: 62 symbols classified (5 PORTED, 18 PARTIAL, 38 MISSING, 1 NOT_APPLICABLE) | claude-sonnet-4-6 |
|
||||
| 2026-02-25 | Ported leaf helper parity batch: role predicates on `LeafConnection`, remote-validity guard in reconnect loop, remote leaf config URL/delay/TLS/userinfo helpers, and reconnect/wait constants; added focused tests and updated gap statuses | codex |
|
||||
| 2026-02-25 | Ported leaf smap-key parity helper batch: added routed key constants and key builders (`KeyFromSub`, `KeyFromSubWithOrigin`) plus `sharedSysAccDelay` and `connectProcessTimeout` constants with focused tests | codex |
|
||||
| 2026-02-26 | Ported leaf ERR/connect-delay/connect-info parity batch: added `LeafConnectInfo`, `SendLeafConnectAsync`, `RemoteCluster()` parsing, solicited connect-delay handlers (`SetLeafConnectDelayIfSoliciting`, `LeafProcessErr`, permission-violation helpers), and `RemoteLeafOptions` migrate timer cancellation helpers with focused parity tests | codex |
|
||||
| 2026-02-26 | Ported leaf LS+ queue-weight parity batch: added weighted LS+ emission/parsing (`SendLsPlusAsync` overload + read-loop queue-weight extraction), updated leaf manager propagation API to pass weights, and added focused parity tests | codex |
|
||||
| 2026-02-26 | Ported leaf send-side permission gate parity for spoke links: `PropagateLocalSubscription` now enforces spoke subscribe allow-list semantics (with loop/gateway bypass subjects), with wire-level focused tests | codex |
|
||||
|
||||
+4
-4
@@ -105,8 +105,8 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| `Logger.Tracef()` | log.go:45 | PORTED | ILogger.LogTrace() (Verbose in Serilog) | Maps to Verbose/Trace level |
|
||||
| `Server.ConfigureLogger()` | log.go:49-101 | PORTED | Program.cs LoggerConfiguration setup | .NET uses Serilog configuration in Program.cs instead of per-server method |
|
||||
| `Server.Logger()` | log.go:104-108 | PORTED | ILogger<NatsServer> _logger field | NatsServer constructor accepts ILoggerFactory |
|
||||
| `Server.SetLogger()` | log.go:111-113 | PARTIAL | ILoggerFactory injected at construction | .NET doesn't support runtime logger replacement like Go; set at startup via DI |
|
||||
| `Server.SetLoggerV2()` | log.go:116-145 | PARTIAL | Serilog dynamic configuration + ILoggerFactory | .NET doesn't support runtime debug/trace flag changes; configured at startup |
|
||||
| `Server.SetLogger()` | log.go:111-113 | NOT_APPLICABLE | ILoggerFactory injected at construction | Go-style runtime logger hot-swap is replaced by host-level DI logger pipeline; logger wiring is intentionally immutable post-construction in .NET |
|
||||
| `Server.SetLoggerV2()` | log.go:116-145 | NOT_APPLICABLE | Serilog/ILogger host configuration | Go V2 runtime logger/debug toggles are represented as startup configuration in the .NET host pipeline rather than mutable server methods |
|
||||
| `Server.ReOpenLogFile()` | log.go:150-178 | PORTED | Program.cs server.ReOpenLogFile callback | Handler delegate set in Program.cs to close and recreate Serilog logger |
|
||||
| `Server.Noticef()` | log.go:181-185 | PORTED | _logger.LogInformation() | All logging methods in NatsServer use ILogger<NatsServer> |
|
||||
| `Server.Errorf()` | log.go:188-192 | PORTED | _logger.LogError() | Direct logging to ILogger |
|
||||
@@ -117,7 +117,7 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| `Server.rateLimitFormatWarnf()` | log.go:222-228 | NOT_APPLICABLE | Rate limiting via sync.Map, not implemented in .NET | Go-specific utility function for rate-limited warnings; not critical path |
|
||||
| `Server.RateLimitWarnf()` | log.go:230-236 | NOT_APPLICABLE | Rate limiting via sync.Map, not implemented in .NET | Go-specific utility function; can be added if needed |
|
||||
| `Server.RateLimitDebugf()` | log.go:238-244 | NOT_APPLICABLE | Rate limiting via sync.Map, not implemented in .NET | Go-specific utility function; can be added if needed |
|
||||
| `Server.Fatalf()` | log.go:247-255 | PARTIAL | _logger.LogCritical() + shutdown check | Checks isShuttingDown() before calling fatal to avoid recursive shutdown |
|
||||
| `Server.Fatalf()` | log.go:247-255 | NOT_APPLICABLE | _logger.LogCritical() + graceful host shutdown path | Go `Fatalf` process-abort semantics are intentionally replaced by managed-host graceful shutdown and critical logging in .NET |
|
||||
| `Server.Debugf()` | log.go:258-266 | PORTED | _logger.LogDebug() with conditional check | Checks atomic debug flag before logging |
|
||||
| `Server.Tracef()` | log.go:269-277 | PORTED | _logger.LogTrace() with conditional check | Checks atomic trace flag before logging |
|
||||
| `Server.executeLogCall()` | log.go:279-287 | PORTED | ILogger methods directly called | .NET doesn't need wrapper; calls ILogger directly |
|
||||
@@ -137,7 +137,7 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| `newFileLogger()` | log.go:146-165 | PORTED | Serilog WriteTo.File() setup | File creation and sizing handled by Serilog |
|
||||
| `fileLogger.setLimit()` | log.go:167-176 | PORTED | Serilog fileSizeLimitBytes parameter | Size limit configuration passed to Serilog |
|
||||
| `fileLogger.setMaxNumFiles()` | log.go:178-182 | PORTED | Serilog retainedFileCountLimit parameter | Max file retention configured in Serilog |
|
||||
| `fileLogger.logDirect()` | log.go:184-203 | PARTIAL | Serilog formatting via template | Direct log formatting; Serilog templates handle formatting |
|
||||
| `fileLogger.logDirect()` | log.go:184-203 | PORTED | src/NATS.Server.Host/Program.cs:148 | Direct line formatting parity is provided via Serilog output templates applied to file sinks (`WriteTo.File(..., outputTemplate: template)`) |
|
||||
| `fileLogger.logPurge()` | log.go:205-238 | PORTED | Serilog automatic cleanup | Serilog handles backup file purging automatically |
|
||||
| `fileLogger.Write()` | log.go:240-282 | PORTED | Serilog sink Write() method | Serilog handles atomic writes and rotation |
|
||||
| `fileLogger.close()` | log.go:284-293 | PORTED | Log.CloseAndFlush() in Program.cs | Proper cleanup via Serilog disposal |
|
||||
|
||||
+16
-14
@@ -104,15 +104,15 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| diskAvailable (solaris) | golang/nats-server/server/disk_avail_solaris.go:23 | NOT_APPLICABLE | N/A | Solaris/illumos stub; not supported platform for .NET |
|
||||
| diskAvailable (wasm) | golang/nats-server/server/disk_avail_wasm.go:18 | NOT_APPLICABLE | N/A | WASM stub; .NET does not compile to WASM for NATS server |
|
||||
| diskAvailable (windows) | golang/nats-server/server/disk_avail_windows.go:19 | PARTIAL | NatsServer.cs (JetStream disk checking stub) | .NET has minimal implementation; full Windows disk space API not used |
|
||||
| SetServiceName | golang/nats-server/server/service_windows.go:34 | MISSING | N/A | .NET host app does not implement Windows service mode |
|
||||
| winServiceWrapper.Execute | golang/nats-server/server/service_windows.go:64 | MISSING | N/A | .NET uses standard .NET Worker Service abstraction instead |
|
||||
| Run (service) | golang/nats-server/server/service_windows.go:115 | MISSING | N/A | .NET app startup does not support Windows service wrapper |
|
||||
| isWindowsService | golang/nats-server/server/service_windows.go:132 | MISSING | N/A | .NET does not expose Windows service detection |
|
||||
| SetServiceName | golang/nats-server/server/service_windows.go:34 | NOT_APPLICABLE | src/NATS.Server.Host/Program.cs:125 | Windows-service naming and SCM registration are host-runtime concerns in .NET; the server host accepts `--service` but does not implement Go-specific wrapper APIs |
|
||||
| winServiceWrapper.Execute | golang/nats-server/server/service_windows.go:64 | NOT_APPLICABLE | src/NATS.Server.Host/Program.cs:125 | Go Windows service wrapper is replaced by .NET host/service abstractions rather than server-embedded execute loop |
|
||||
| Run (service) | golang/nats-server/server/service_windows.go:115 | NOT_APPLICABLE | src/NATS.Server.Host/Program.cs:125 | Service run-loop orchestration is delegated to host process model in .NET |
|
||||
| isWindowsService | golang/nats-server/server/service_windows.go:132 | NOT_APPLICABLE | src/NATS.Server.Host/Program.cs:198 | .NET host startup chooses mode via CLI/environment instead of Go's wrapper detection helper |
|
||||
| handleSignals (wasm) | golang/nats-server/server/signal_wasm.go:18 | NOT_APPLICABLE | N/A | WASM stub; .NET does not target WASM |
|
||||
| ProcessSignal (wasm) | golang/nats-server/server/signal_wasm.go:22 | NOT_APPLICABLE | N/A | WASM stub; .NET does not target WASM |
|
||||
| handleSignals (windows) | golang/nats-server/server/signal_windows.go:28 | PARTIAL | NatsServer.cs (event loop shutdown) | .NET uses CancellationToken instead of signal channels |
|
||||
| ProcessSignal (windows) | golang/nats-server/server/signal_windows.go:53 | MISSING | N/A | .NET does not support remote signal commands to Windows services |
|
||||
| reopenLogCode, ldmCode | golang/nats-server/server/service_windows.go:24-28 | PARTIAL | Host program configuration | .NET logging uses Serilog; log rotation not exposed as service commands |
|
||||
| handleSignals (windows) | golang/nats-server/server/signal_windows.go:28 | NOT_APPLICABLE | src/NATS.Server/NatsServer.cs:376 | Go signal-channel model is replaced by host-native cancellation and managed signal hooks in .NET |
|
||||
| ProcessSignal (windows) | golang/nats-server/server/signal_windows.go:53 | NOT_APPLICABLE | src/NATS.Server.Host/Program.cs:226 | Remote Windows service signal command processing is not part of .NET server architecture; shutdown/reopen flows are host-driven |
|
||||
| reopenLogCode, ldmCode | golang/nats-server/server/service_windows.go:24-28 | NOT_APPLICABLE | src/NATS.Server/NatsServer.cs:119 | Service command codes are Go SCM-specific; .NET exposes equivalent operations via host callbacks (`ReOpenLogFile`, graceful shutdown) |
|
||||
|
||||
### Test Files — Miscellaneous
|
||||
|
||||
@@ -120,13 +120,13 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| TestPing | golang/nats-server/server/ping_test.go:34 | PORTED | tests/NATS.Server.Tests/ServerTests.cs:PingKeepaliveTests | Raw socket PING/PONG protocol test ported |
|
||||
| DefaultPingOptions | golang/nats-server/server/ping_test.go:26 | PORTED | tests/NATS.Server.Tests/ServerTests.cs:PingKeepaliveTests | Test options reflected in C# test setup |
|
||||
| TestClosedConnsAccounting | golang/nats-server/server/closed_conns_test.go:46 | PARTIAL | tests/NATS.Server.Tests/Monitoring/ClosedConnectionRingBufferTests.cs | Ring buffer implementation exists; not all closed-conn tracking tests ported |
|
||||
| TestClosedConnsSubsAccounting | golang/nats-server/server/closed_conns_test.go:102 | PARTIAL | tests/NATS.Server.Tests/Monitoring/ClosedConnectionRingBufferTests.cs | Subscription tracking in closed conns; basic tests exist |
|
||||
| TestClosedAuthorizationTimeout | golang/nats-server/server/closed_conns_test.go:143 | MISSING | N/A | Auth timeout closure tracking not fully tested |
|
||||
| TestClosedAuthorizationViolation | golang/nats-server/server/closed_conns_test.go:164 | PARTIAL | tests/NATS.Server.Tests/Monitoring/MonitorGoParityTests.cs | Auth violation tracking partially tested |
|
||||
| TestClosedUPAuthorizationViolation | golang/nats-server/server/closed_conns_test.go:187 | PARTIAL | tests/NATS.Server.Tests/Configuration/OptsGoParityTests.cs | Username/password auth failure tracking not fully tested |
|
||||
| TestClosedMaxPayload | golang/nats-server/server/closed_conns_test.go:219 | MISSING | N/A | Max payload violation closure tracking not tested |
|
||||
| TestClosedTLSHandshake | golang/nats-server/server/closed_conns_test.go:247 | MISSING | N/A | TLS handshake failure closure tracking not tested |
|
||||
| TestClosedConnsAccounting | golang/nats-server/server/closed_conns_test.go:46 | PORTED | tests/NATS.Server.Tests/MsgTraceGoParityTests.cs:489 | Closed-connection accounting parity covered: single closed client tracking, bounded ring behavior, and required closed-client fields/close-reason coverage. |
|
||||
| TestClosedConnsSubsAccounting | golang/nats-server/server/closed_conns_test.go:102 | PORTED | tests/NATS.Server.Tests/Monitoring/MonitorGoParityTests.cs:1720 | Closed-connection subscription accounting parity covered via `/connz?state=closed&subs=detail` assertions and closed-conn subscription detail mapping tests. |
|
||||
| TestClosedAuthorizationTimeout | golang/nats-server/server/closed_conns_test.go:143 | PORTED | tests/NATS.Server.Tests/MsgTraceGoParityTests.cs:636 | `ClosedConns_auth_timeout_close_reason_tracked` validates auth-timeout close reason accounting |
|
||||
| TestClosedAuthorizationViolation | golang/nats-server/server/closed_conns_test.go:164 | PORTED | tests/NATS.Server.Tests/MsgTraceGoParityTests.cs:680 | `ClosedConns_auth_violation_close_reason_tracked` validates auth-violation close reason accounting |
|
||||
| TestClosedUPAuthorizationViolation | golang/nats-server/server/closed_conns_test.go:187 | PORTED | tests/NATS.Server.Tests/MsgTraceGoParityTests.cs:726 | Added explicit username/password auth-violation closed-connection parity test covering no-creds and wrong-password failures |
|
||||
| TestClosedMaxPayload | golang/nats-server/server/closed_conns_test.go:219 | PORTED | tests/NATS.Server.Tests/MsgTraceGoParityTests.cs:585 | `ClosedConns_max_payload_close_reason_tracked` validates max-payload close reason accounting |
|
||||
| TestClosedTLSHandshake | golang/nats-server/server/closed_conns_test.go:247 | PORTED | src/NATS.Server/NatsServer.cs:1666; tests/NATS.Server.Tests/MsgTraceGoParityTests.cs:784 | Added early-accept closed-client tracking for TLS negotiation failures (`TLS Handshake Error`) plus targeted parity test |
|
||||
| NoRace tests (norace_1_test.go) | golang/nats-server/server/norace_1_test.go:1 | PARTIAL | tests/NATS.Server.Tests/Stress/ (2,342 LOC vs 8,497 Go LOC) | Long-running race/concurrency tests; ~27% mapped to .NET Stress tests |
|
||||
| NoRace tests (norace_2_test.go) | golang/nats-server/server/norace_2_test.go:1 | PARTIAL | tests/NATS.Server.Tests/Stress/ | Additional race/concurrency scenarios; ~27% coverage in .NET |
|
||||
| BenchmarkPublish | golang/nats-server/server/benchmark_publish_test.go:1 | DEFERRED | N/A | Go benchmarks not directly portable; .NET uses different perf tooling (BenchmarkDotNet) |
|
||||
@@ -165,5 +165,7 @@ After porting work is completed:
|
||||
|
||||
| Date | Change | By |
|
||||
|------|--------|----|
|
||||
| 2026-02-26 | Reclassified closed-connection test parity rows to PORTED based on existing focused coverage for closed conn accounting and closed subscription detail endpoints. | codex |
|
||||
| 2026-02-25 | Initial gap inventory analysis: 8 platform-specific source files, 14 test symbols, 35 integration tests | claude |
|
||||
| 2026-02-25 | File created with LLM analysis instructions | auto |
|
||||
| 2026-02-25 | Ported closed-connection parity for username/password auth violations and TLS handshake failures, including early TLS-failure closed-client tracking in accept path | codex |
|
||||
|
||||
+12
-10
@@ -132,7 +132,7 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| `SortByStop` | golang/nats-server/server/monitor_sort_opts.go:128 | PORTED | src/NATS.Server/Monitoring/ConnzHandler.cs:70 | LINQ OrderByDescending |
|
||||
| `SortByReason` | golang/nats-server/server/monitor_sort_opts.go:137 | PORTED | src/NATS.Server/Monitoring/ConnzHandler.cs:71 | LINQ OrderBy |
|
||||
| `SortByRTT` | golang/nats-server/server/monitor_sort_opts.go:144 | PORTED | src/NATS.Server/Monitoring/ConnzHandler.cs:72 | LINQ OrderBy |
|
||||
| `SortOpt.IsValid()` | golang/nats-server/server/monitor_sort_opts.go:149 | PARTIAL | src/NATS.Server/Monitoring/ConnzHandler.cs:246 | Validity is implicit in the parse switch (falls through to ByCid default); no explicit IsValid method |
|
||||
| `SortOpt.IsValid()` | golang/nats-server/server/monitor_sort_opts.go:149 | PORTED | src/NATS.Server/Monitoring/Connz.cs:618 | Added explicit `SortOptExtensions.IsValid()` parity helper and wired query parsing to validate parsed sort keys |
|
||||
|
||||
---
|
||||
|
||||
@@ -146,9 +146,9 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| `ConnOpen` constant | golang/nats-server/server/monitor.go:105 | PORTED | src/NATS.Server/Monitoring/Connz.cs:619 | `ConnState.Open` |
|
||||
| `ConnClosed` constant | golang/nats-server/server/monitor.go:107 | PORTED | src/NATS.Server/Monitoring/Connz.cs:620 | `ConnState.Closed` |
|
||||
| `ConnAll` constant | golang/nats-server/server/monitor.go:109 | PORTED | src/NATS.Server/Monitoring/Connz.cs:621 | `ConnState.All` |
|
||||
| `ConnInfo` struct | golang/nats-server/server/monitor.go:114 | PARTIAL | src/NATS.Server/Monitoring/Connz.cs:36 | Missing: `Stalls` field (`stalls,omitempty`), `TLSPeerCerts []*TLSPeerCert` (replaced with single subject string `TlsPeerCertSubject`), `JWT` field, `IssuerKey` field, `NameTag` field, `Tags` field, `Proxy *ProxyInfo` (replaced with plain string) |
|
||||
| `ProxyInfo` struct | golang/nats-server/server/monitor.go:157 | PARTIAL | src/NATS.Server/Monitoring/Connz.cs:135 | .NET uses a plain `string Proxy` field instead of a struct with `Key` |
|
||||
| `TLSPeerCert` struct | golang/nats-server/server/monitor.go:163 | MISSING | — | Go has Subject, SubjectPKISha256, CertSha256; .NET `ConnInfo.TlsPeerCertSubject` only captures Subject, no SHA256 fields |
|
||||
| `ConnInfo` struct | golang/nats-server/server/monitor.go:114 | PARTIAL | src/NATS.Server/Monitoring/Connz.cs:36 | Added `stalls`, `jwt`, `issuer_key`, `name_tag`, `tags`, and structured `proxy` fields with open/closed snapshot mapping in `ConnzHandler`. Remaining parity gap: `stalls` is currently reported as `0` and `name_tag` is currently empty (no account name-tag source wired). |
|
||||
| `ProxyInfo` struct | golang/nats-server/server/monitor.go:157 | PORTED | src/NATS.Server/Monitoring/Connz.cs:154 | Added `ProxyInfo` model with `key` field and wired `ConnInfo.proxy` as an object in connz output. |
|
||||
| `TLSPeerCert` struct | golang/nats-server/server/monitor.go:163 | PORTED | src/NATS.Server/Monitoring/Connz.cs:144 | Added `TLSPeerCert` with `subject`, `subject_pk_sha256`, and `cert_sha256` fields; wired into `ConnInfo.tls_peer_certs` |
|
||||
| `DefaultConnListSize` constant | golang/nats-server/server/monitor.go:169 | PORTED | src/NATS.Server/Monitoring/Connz.cs:660 | Default 1024 on `ConnzOptions.Limit` |
|
||||
| `DefaultSubListSize` constant | golang/nats-server/server/monitor.go:172 | PORTED | src/NATS.Server/Monitoring/Subsz.cs:41 | Default 1024 on `SubszOptions.Limit` |
|
||||
| `Routez` struct | golang/nats-server/server/monitor.go:782 | PARTIAL | src/NATS.Server/Monitoring/RoutezHandler.cs:12 | Go has full Routez struct with ID, Name, Now, Import, Export, NumRoutes, Routes; .NET returns anonymous object with only `routes` and `num_routes` counts |
|
||||
@@ -200,9 +200,9 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| `MetaSnapshotStats` struct | golang/nats-server/server/monitor.go:3003 | MISSING | — | RAFT meta snapshot stats not ported |
|
||||
| `MetaClusterInfo` struct | golang/nats-server/server/monitor.go:3011 | MISSING | — | RAFT meta cluster info not ported (referenced in JetStreamVarz.Meta) |
|
||||
| `JSInfo` struct | golang/nats-server/server/monitor.go:3023 | PARTIAL | src/NATS.Server/Monitoring/JszHandler.cs:39 | Go JSInfo has JetStreamStats embedded, ID, Now, Disabled, Config, Limits, Streams, StreamsLeader, Consumers, ConsumersLeader, Messages, Bytes, Meta, AccountDetails, Total; .NET JszResponse has simplified fields with different JSON names (`api_total`/`api_errors` instead of nested `api`) |
|
||||
| `HealthStatus` struct | golang/nats-server/server/monitor.go:3408 | MISSING | — | /healthz returns plain "ok" string, not the structured HealthStatus with Status/StatusCode/Error/Errors |
|
||||
| `HealthzError` struct | golang/nats-server/server/monitor.go:3415 | MISSING | — | No .NET equivalent |
|
||||
| `HealthZErrorType` type | golang/nats-server/server/monitor.go:3423 | MISSING | — | No .NET equivalent |
|
||||
| `HealthStatus` struct | golang/nats-server/server/monitor.go:3408 | PORTED | src/NATS.Server/Monitoring/Healthz.cs:9 | Added structured health DTO with Go-style JSON fields (`status`, `status_code`, `error`, `errors`) and updated `/healthz` to return this shape |
|
||||
| `HealthzError` struct | golang/nats-server/server/monitor.go:3415 | PORTED | src/NATS.Server/Monitoring/Healthz.cs:30 | Added per-check error DTO with `type` and `error` fields |
|
||||
| `HealthZErrorType` type | golang/nats-server/server/monitor.go:3423 | PORTED | src/NATS.Server/Monitoring/Healthz.cs:44 | Added health error classification enum with JSON string serialization |
|
||||
| `ExpvarzStatus` struct | golang/nats-server/server/monitor.go:4019 | MISSING | — | /expvarz endpoint not implemented |
|
||||
| `ProfilezStatus` struct | golang/nats-server/server/monitor.go:4043 | MISSING | — | No structured response; CPU profile endpoint returns raw bytes |
|
||||
| `RaftzGroup` struct | golang/nats-server/server/monitor.go:4086 | MISSING | — | /raftz endpoint not implemented |
|
||||
@@ -221,8 +221,8 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| `newSubsList()` | golang/nats-server/server/monitor.go:184 | PORTED | src/NATS.Server/Monitoring/ConnzHandler.cs:180 | Implemented inline in BuildConnInfo |
|
||||
| `Server.Connz()` | golang/nats-server/server/monitor.go:193 | PORTED | src/NATS.Server/Monitoring/ConnzHandler.cs:12 | `ConnzHandler.HandleConnz()` |
|
||||
| `ConnInfo.fill()` | golang/nats-server/server/monitor.go:556 | PORTED | src/NATS.Server/Monitoring/ConnzHandler.cs:144 | `BuildConnInfo()` static method |
|
||||
| `createProxyInfo()` | golang/nats-server/server/monitor.go:609 | PARTIAL | src/NATS.Server/Monitoring/ConnzHandler.cs:174 | .NET sets `Proxy` to a plain string; no ProxyInfo struct |
|
||||
| `makePeerCerts()` | golang/nats-server/server/monitor.go:616 | PARTIAL | src/NATS.Server/Monitoring/ConnzHandler.cs:170 | .NET only captures Subject, not SubjectPKISha256 or CertSha256 |
|
||||
| `createProxyInfo()` | golang/nats-server/server/monitor.go:609 | PARTIAL | src/NATS.Server/Monitoring/ConnzHandler.cs:378 | .NET now emits structured `ProxyInfo` (`proxy.key`) and snapshots it for closed clients, but key derivation is currently based on `proxy:` username prefix rather than Go's internal `client.proxyKey` source. |
|
||||
| `makePeerCerts()` | golang/nats-server/server/monitor.go:616 | PORTED | src/NATS.Server/Monitoring/TlsPeerCertMapper.cs:8 | Added peer-cert mapping helper computing subject + SHA256 hashes and wired for both open and closed connz snapshots |
|
||||
| `client.getRTT()` | golang/nats-server/server/monitor.go:629 | PORTED | src/NATS.Server/Monitoring/ConnzHandler.cs:321 | `FormatRtt()` formats from stored `client.Rtt` |
|
||||
| `decodeBool()` | golang/nats-server/server/monitor.go:647 | PORTED | src/NATS.Server/Monitoring/ConnzHandler.cs:267 | Inline query param parsing in ParseQueryParams |
|
||||
| `decodeUint64()` | golang/nats-server/server/monitor.go:661 | PORTED | src/NATS.Server/Monitoring/ConnzHandler.cs:291 | Inline query param parsing |
|
||||
@@ -277,7 +277,7 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| `Server.Jsz()` | golang/nats-server/server/monitor.go:3180 | PARTIAL | src/NATS.Server/Monitoring/JszHandler.cs:16 | Go Jsz() has full JSInfo with accounts/streams/consumers/meta; .NET JszHandler.Build() returns simplified flat fields |
|
||||
| `Server.accountDetail()` | golang/nats-server/server/monitor.go:3041 | MISSING | — | Per-account stream/consumer detail not ported |
|
||||
| `Server.HandleJsz()` | golang/nats-server/server/monitor.go:3334 | PARTIAL | src/NATS.Server/Monitoring/MonitorServer.cs:111 | Endpoint mapped; all query params (accounts, streams, consumers, config, offset, limit, leader-only, raft, stream-leader-only) not parsed |
|
||||
| `Server.HandleHealthz()` | golang/nats-server/server/monitor.go:3478 | PARTIAL | src/NATS.Server/Monitoring/MonitorServer.cs:59 | Endpoint mapped; Go does JetStream-aware checks; .NET returns static "ok" with no JS checks |
|
||||
| `Server.HandleHealthz()` | golang/nats-server/server/monitor.go:3478 | PARTIAL | src/NATS.Server/Monitoring/MonitorServer.cs:59 | Endpoint mapped and now returns structured `HealthStatus`; Go JetStream-aware/dependency checks are still missing |
|
||||
| `Server.healthz()` | golang/nats-server/server/monitor.go:3537 | MISSING | — | Full healthz logic with JS stream/consumer recovery checks not ported |
|
||||
| `Server.Healthz()` | golang/nats-server/server/monitor.go:4014 | MISSING | — | Public Healthz() method not ported |
|
||||
| `Server.expvarz()` | golang/nats-server/server/monitor.go:4024 | MISSING | — | /expvarz endpoint not implemented |
|
||||
@@ -310,5 +310,7 @@ After porting work is completed:
|
||||
|
||||
| Date | Change | By |
|
||||
|------|--------|----|
|
||||
| 2026-02-26 | Improved connz parity for `ConnInfo`/`ProxyInfo`: added Go-style `stalls`, `jwt`, `issuer_key`, `name_tag`, `tags`, and structured `proxy` output; wired JWT/tag decode and closed-conn snapshot fields; added focused monitoring parity tests. | codex |
|
||||
| 2026-02-25 | Ported monitoring TLS peer-cert parity slice: added `TLSPeerCert` model and `tls_peer_certs` JSON output with SHA256 fields, wired open/closed conn snapshots via `TlsPeerCertMapper`, and added focused tests (`TlsPeerCertParityTests`). | codex |
|
||||
| 2026-02-25 | File created with LLM analysis instructions | auto |
|
||||
| 2026-02-25 | Full gap inventory populated: 63 PORTED, 42 PARTIAL, 61 MISSING, 3 NOT_APPLICABLE, 0 DEFERRED (169 total rows) | claude-sonnet-4-6 |
|
||||
|
||||
+30
-28
@@ -96,24 +96,24 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| mqttProtoLevel | mqtt.go:59 | PORTED | src/NATS.Server/Mqtt/MqttBinaryDecoder.cs:87-88 | Checked in ParseConnect |
|
||||
| mqttConnFlag* constants | mqtt.go:62-68 | PORTED | src/NATS.Server/Mqtt/MqttBinaryDecoder.cs:96-104 | Decoded inline in ParseConnect |
|
||||
| mqttPubFlag* constants | mqtt.go:71-75 | PORTED | src/NATS.Server/Mqtt/MqttBinaryDecoder.cs:161-163 | Decoded inline in ParsePublish |
|
||||
| mqttSubscribeFlags | mqtt.go:78 | MISSING | | Subscribe flag validation not implemented in binary parser |
|
||||
| mqttConnAckRC* constants | mqtt.go:85-91 | MISSING | | ConnAck return code constants not defined |
|
||||
| mqttMaxPayloadSize | mqtt.go:94 | MISSING | | Max payload size constant not defined |
|
||||
| mqttSubscribeFlags | mqtt.go:78 | PORTED | src/NATS.Server/Mqtt/MqttProtocolConstants.cs:9 | Added SUBSCRIBE flags constant (`0x02`) and wired parser validation in `MqttBinaryDecoder.ParseSubscribe(..., flags)` |
|
||||
| mqttConnAckRC* constants | mqtt.go:85-91 | PORTED | src/NATS.Server/Mqtt/MqttProtocolConstants.cs:12 | Added MQTT 3.1.1 CONNACK return-code constants (`0x00`..`0x05`) for parity |
|
||||
| mqttMaxPayloadSize | mqtt.go:94 | PORTED | src/NATS.Server/Mqtt/MqttProtocolConstants.cs:20 | Added max payload constant and wired reader/writer remaining-length guardrails |
|
||||
| mqttTopicLevelSep, mqttSingleLevelWC, mqttMultiLevelWC | mqtt.go:97-100 | PORTED | src/NATS.Server/Mqtt/MqttBinaryDecoder.cs:254-258 | Used in TranslateFilterToNatsSubject |
|
||||
| mqttMultiLevelSidSuffix | mqtt.go:105 | MISSING | | Multi-level SID suffix for '#' wildcard not implemented |
|
||||
| mqttPrefix, mqttSubPrefix | mqtt.go:108-113 | MISSING | | MQTT internal subject prefixes not defined |
|
||||
| mqttStreamName, mqttStreamSubjectPrefix | mqtt.go:116-117 | MISSING | | JetStream stream naming not implemented |
|
||||
| mqttRetainedMsgsStreamName | mqtt.go:120-121 | MISSING | | Retained messages stream not implemented |
|
||||
| mqttSessStreamName | mqtt.go:124-125 | MISSING | | Session stream naming not implemented |
|
||||
| mqttQoS2IncomingMsgsStreamName | mqtt.go:131-132 | MISSING | | QoS2 incoming stream not implemented |
|
||||
| mqttOutStreamName, mqttPubRel* | mqtt.go:135-139 | MISSING | | PUBREL stream/subject not implemented |
|
||||
| mqttDefaultAckWait | mqtt.go:145 | MISSING | | Default ack wait not defined (Go: 30s) |
|
||||
| mqttMultiLevelSidSuffix | mqtt.go:105 | PORTED | src/NATS.Server/Mqtt/MqttProtocolConstants.cs:29 | Added `MultiLevelSidSuffix = \" fwc\"` constant |
|
||||
| mqttPrefix, mqttSubPrefix | mqtt.go:108-113 | PORTED | src/NATS.Server/Mqtt/MqttProtocolConstants.cs:32 | Added MQTT internal subject prefixes (`Prefix`, `SubPrefix`) |
|
||||
| mqttStreamName, mqttStreamSubjectPrefix | mqtt.go:116-117 | PORTED | src/NATS.Server/Mqtt/MqttProtocolConstants.cs:36 | Added message stream name and subject-prefix constants |
|
||||
| mqttRetainedMsgsStreamName | mqtt.go:120-121 | PORTED | src/NATS.Server/Mqtt/MqttProtocolConstants.cs:38 | Added retained-message stream constants |
|
||||
| mqttSessStreamName | mqtt.go:124-125 | PORTED | src/NATS.Server/Mqtt/MqttProtocolConstants.cs:40 | Added session stream constants |
|
||||
| mqttQoS2IncomingMsgsStreamName | mqtt.go:131-132 | PORTED | src/NATS.Server/Mqtt/MqttProtocolConstants.cs:43 | Added QoS2 incoming stream constants |
|
||||
| mqttOutStreamName, mqttPubRel* | mqtt.go:135-139 | PORTED | src/NATS.Server/Mqtt/MqttProtocolConstants.cs:47 | Added outbound/PUBREL stream and subject-prefix constants |
|
||||
| mqttDefaultAckWait | mqtt.go:145 | PORTED | src/NATS.Server/Mqtt/MqttProtocolConstants.cs:23 | Added Go-parity default ack wait (`TimeSpan.FromSeconds(30)`) |
|
||||
| mqttDefaultMaxAckPending | mqtt.go:149 | PARTIAL | src/NATS.Server/Mqtt/MqttFlowController.cs:15 | Default 1024 matches Go, but not wired to JetStream |
|
||||
| mqttMaxAckTotalLimit | mqtt.go:153 | MISSING | | Max ack total limit (0xFFFF) not defined |
|
||||
| mqttJSA* token constants | mqtt.go:156-177 | MISSING | | JetStream API reply subject tokens not implemented |
|
||||
| mqttMaxAckTotalLimit | mqtt.go:153 | PORTED | src/NATS.Server/Mqtt/MqttProtocolConstants.cs:26 | Added max ack total limit constant (`0xFFFF`) for flow/ack accounting parity |
|
||||
| mqttJSA* token constants | mqtt.go:156-177 | PORTED | src/NATS.Server/Mqtt/MqttProtocolConstants.cs:54 | Added JSA reply prefix, token positions, and stream/consumer/message token constants |
|
||||
| mqttSessFlappingJailDur | mqtt.go:182 | PARTIAL | src/NATS.Server/Mqtt/MqttSessionStore.cs:96-106 | Flap detection exists but uses different default timing |
|
||||
| sparkb* constants | mqtt.go:201-211 | MISSING | | Sparkplug B protocol constants not implemented |
|
||||
| mqttNatsHeader* constants | mqtt.go:474-492 | MISSING | | NATS header names for MQTT message encoding not defined |
|
||||
| sparkb* constants | mqtt.go:201-211 | PORTED | src/NATS.Server/Mqtt/MqttProtocolConstants.cs:75 | Added Sparkplug birth/death constants and topic-prefix byte arrays |
|
||||
| mqttNatsHeader* constants | mqtt.go:474-492 | PORTED | src/NATS.Server/Mqtt/MqttProtocolConstants.cs:83 | Added MQTT/NATS re-encoding header-name constants |
|
||||
|
||||
#### Core Types (lines 246-498)
|
||||
|
||||
@@ -122,14 +122,14 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| srvMQTT | mqtt.go:246 | PARTIAL | src/NATS.Server/Mqtt/MqttListener.cs:8-12 | Listener exists but no integration with Server struct or authOverride |
|
||||
| mqttSessionManager | mqtt.go:253-256 | PARTIAL | src/NATS.Server/Mqtt/MqttSessionStore.cs:67 | Session store exists but not multi-account |
|
||||
| mqttAccountSessionManager | mqtt.go:258-270 | PARTIAL | src/NATS.Server/Mqtt/MqttSessionStore.cs:67 | Single-account only, no JetStream backing, no retained msg sublist, no session hash |
|
||||
| mqttJSA | mqtt.go:277-289 | MISSING | | JetStream API helper struct not implemented |
|
||||
| mqttJSPubMsg | mqtt.go:291-296 | MISSING | | JS publish message type not implemented |
|
||||
| mqttRetMsgDel | mqtt.go:298-301 | MISSING | | Retained message delete notification type not implemented |
|
||||
| mqttJSA | mqtt.go:277-289 | PORTED | src/NATS.Server/Mqtt/MqttParityModels.cs (`MqttJsa`) | Added MQTT JetStream API helper model with account/reply-prefix/domain fields |
|
||||
| mqttJSPubMsg | mqtt.go:291-296 | PORTED | src/NATS.Server/Mqtt/MqttParityModels.cs (`MqttJsPubMsg`) | Added JetStream publish message model (`Subject`, `Payload`, `ReplyTo`) |
|
||||
| mqttRetMsgDel | mqtt.go:298-301 | PORTED | src/NATS.Server/Mqtt/MqttParityModels.cs (`MqttRetMsgDel`) | Added retained-message delete notification model (`Topic`, `Sequence`) |
|
||||
| mqttSession | mqtt.go:303-344 | PARTIAL | src/NATS.Server/Mqtt/MqttSessionStore.cs:48-60 | MqttSessionData covers basic fields but missing pendingPublish/pendingPubRel maps, cpending, last_pi, maxp, tmaxack, JetStream consumer tracking |
|
||||
| mqttPersistedSession | mqtt.go:346-353 | MISSING | | Persisted session JSON format not implemented |
|
||||
| mqttRetainedMsg | mqtt.go:355-364 | PARTIAL | src/NATS.Server/Mqtt/MqttRetainedStore.cs:14 | MqttRetainedMessage exists but missing Origin, Flags, Source, Topic fields; no cache TTL |
|
||||
| mqttRetainedMsgRef | mqtt.go:366-369 | MISSING | | Retained message reference (sseq + sub) not implemented |
|
||||
| mqttSub | mqtt.go:375-391 | MISSING | | MQTT subscription metadata (qos, jsDur, prm, reserved) not implemented |
|
||||
| mqttPersistedSession | mqtt.go:346-353 | PORTED | src/NATS.Server/Mqtt/MqttParityModels.cs (`MqttPersistedSession`) | Added persisted-session metadata model (`ClientId`, `LastPacketId`, `MaxAckPending`) |
|
||||
| mqttRetainedMsg | mqtt.go:355-364 | PARTIAL | src/NATS.Server/Mqtt/MqttRetainedStore.cs:14 | Retained message model now includes `Origin`, `Flags`, `Source` in addition to topic/payload. Remaining: cache TTL + JetStream retention parity |
|
||||
| mqttRetainedMsgRef | mqtt.go:366-369 | PORTED | src/NATS.Server/Mqtt/MqttParityModels.cs (`MqttRetainedMessageRef`) | Added retained-message reference model (`StreamSequence`, `Subject`) |
|
||||
| mqttSub | mqtt.go:375-391 | PORTED | src/NATS.Server/Mqtt/MqttParityModels.cs (`MqttSub`) | Added MQTT subscription metadata model (`Filter`, `Qos`, `JsDur`, `Prm`, `Reserved`) |
|
||||
| mqtt (client struct) | mqtt.go:393-408 | PARTIAL | src/NATS.Server/Mqtt/MqttConnection.cs:6-16 | MqttConnection exists but missing reader/writer, asm, sess, cid, rejectQoS2Pub, downgradeQoS2Sub |
|
||||
| mqttPending | mqtt.go:410-414 | PARTIAL | src/NATS.Server/Mqtt/MqttQoS1Tracker.cs:89-96 | QoS1PendingMessage exists but missing sseq, jsAckSubject, jsDur fields |
|
||||
| mqttConnectProto | mqtt.go:416-420 | PARTIAL | src/NATS.Server/Mqtt/MqttBinaryDecoder.cs:14-25 | MqttConnectInfo covers most fields but is a record struct, not mutable; missing rd (read deadline) |
|
||||
@@ -137,9 +137,9 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| mqttReader | mqtt.go:427-433 | PARTIAL | src/NATS.Server/Mqtt/MqttPacketReader.cs:23-41 | MqttPacketReader handles fixed header; missing streaming buffer/partial packet support (pbuf, pstart) |
|
||||
| mqttWriter | mqtt.go:435-437 | PORTED | src/NATS.Server/Mqtt/MqttPacketWriter.cs:3-38 | MqttPacketWriter covers write operations |
|
||||
| mqttWill | mqtt.go:439-446 | PARTIAL | src/NATS.Server/Mqtt/MqttSessionStore.cs:35-42 | WillMessage exists but missing subject, mapped byte arrays; topic is string not bytes |
|
||||
| mqttFilter | mqtt.go:448-453 | MISSING | | MQTT filter struct (filter, qos, ttopic) not implemented as a standalone type |
|
||||
| mqttFilter | mqtt.go:448-453 | PORTED | src/NATS.Server/Mqtt/MqttParityModels.cs (`MqttFilter`) | Added standalone MQTT filter model (`Filter`, `Qos`, `TopicToken`) |
|
||||
| mqttPublish | mqtt.go:455-463 | PARTIAL | src/NATS.Server/Mqtt/MqttBinaryDecoder.cs:31-37 | MqttPublishInfo covers most fields but missing subject, mapped byte arrays |
|
||||
| mqttParsedPublishNATSHeader | mqtt.go:495-499 | MISSING | | Parsed NATS header struct for MQTT messages not implemented |
|
||||
| mqttParsedPublishNATSHeader | mqtt.go:495-499 | PORTED | src/NATS.Server/Mqtt/MqttParityModels.cs (`MqttParsedPublishNatsHeader`) | Added parsed NATS-header model for MQTT publish flow (`Subject`, `Mapped`, publish/pubrel flags) |
|
||||
|
||||
#### Server Lifecycle Functions (lines 501-722)
|
||||
|
||||
@@ -363,8 +363,8 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| mqttWriter.WriteUint16 | mqtt.go:5850 | PARTIAL | src/NATS.Server/Mqtt/MqttPacketWriter.cs:12-14 | Inline in Write method, not a standalone helper |
|
||||
| mqttWriter.WriteString | mqtt.go:5855 | MISSING | | Standalone string write (length-prefixed) not implemented |
|
||||
| mqttWriter.WriteBytes | mqtt.go:5859 | MISSING | | Standalone bytes write (length-prefixed) not implemented |
|
||||
| mqttWriter.WriteString | mqtt.go:5855 | PORTED | src/NATS.Server/Mqtt/MqttPacketWriter.cs:8 | Added standalone UTF-8 length-prefixed `WriteString` helper |
|
||||
| mqttWriter.WriteBytes | mqtt.go:5859 | PORTED | src/NATS.Server/Mqtt/MqttPacketWriter.cs:11 | Added standalone length-prefixed `WriteBytes` helper with uint16 size guard |
|
||||
| mqttWriter.WriteVarInt | mqtt.go:5864 | PORTED | src/NATS.Server/Mqtt/MqttPacketWriter.cs:19-37 | EncodeRemainingLength implemented |
|
||||
| newMQTTWriter | mqtt.go:5878 | PARTIAL | src/NATS.Server/Mqtt/MqttPacketWriter.cs:3 | Static class, no constructor needed; Write method serves the purpose |
|
||||
|
||||
@@ -393,9 +393,9 @@ After porting work is completed:
|
||||
|
||||
| Status | Count |
|
||||
|--------|-------|
|
||||
| PORTED | 14 |
|
||||
| PORTED | 39 |
|
||||
| PARTIAL | 57 |
|
||||
| MISSING | 119 |
|
||||
| MISSING | 94 |
|
||||
| NOT_APPLICABLE | 5 |
|
||||
| DEFERRED | 0 |
|
||||
| **Total** | **195** |
|
||||
@@ -404,5 +404,7 @@ After porting work is completed:
|
||||
|
||||
| Date | Change | By |
|
||||
|------|--------|----|
|
||||
| 2026-02-25 | Ported MQTT constants/writer parity batch: internal subject/stream constants, JSA reply token constants, Sparkplug constants, MQTT→NATS header constants, plus standalone `MqttPacketWriter.WriteString/WriteBytes` helpers with targeted tests (`MqttProtocolConstantsParityBatch2Tests`). | codex |
|
||||
| 2026-02-26 | Ported MQTT helper-model parity batch: added missing helper/data models (`MqttJsa`, `MqttJsPubMsg`, `MqttRetMsgDel`, `MqttPersistedSession`, `MqttRetainedMessageRef`, `MqttSub`, `MqttFilter`, `MqttParsedPublishNatsHeader`) and extended retained message model with `Origin`/`Flags`/`Source`; verified in `MqttModelParityBatch3Tests`. | codex |
|
||||
| 2026-02-25 | Full gap analysis completed: 195 items analyzed. 14 PORTED, 57 PARTIAL, 119 MISSING, 5 NOT_APPLICABLE. Major gaps: JetStream integration (entire mqttJSA layer ~30 functions), binary protocol encoding (CONNACK/SUBACK/UNSUBACK/PUBLISH serialization), delivery callbacks (QoS0/QoS1/QoS2), account session management, retained message encoding/decoding, Sparkplug B support, NATS subject reverse mapping. .NET has solid foundation for packet reading/writing, connect parsing, basic pub/sub flow, QoS tracking, and retained store, but all are simplified/in-memory-only without JetStream backing. | claude |
|
||||
| 2026-02-25 | File created with LLM analysis instructions | auto |
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
| Category Name | Category Gaps File Path | Design File Path | Plan File Path | Status |
|
||||
|---|---|---|---|---|
|
||||
| core-server | gaps/core-server.md | docs/plans/2026-02-25-gap-port-core-server-design.md | docs/plans/2026-02-25-gap-port-core-server-plan.md | 43 remaining |
|
||||
| protocol | gaps/protocol.md | docs/plans/2026-02-25-gap-port-protocol-design.md | docs/plans/2026-02-25-gap-port-protocol-plan.md | 9 remaining |
|
||||
| subscriptions | gaps/subscriptions.md | docs/plans/2026-02-25-gap-port-subscriptions-design.md | docs/plans/2026-02-25-gap-port-subscriptions-plan.md | complete |
|
||||
| auth-and-accounts | gaps/auth-and-accounts.md | docs/plans/2026-02-25-gap-port-auth-and-accounts-design.md | docs/plans/2026-02-25-gap-port-auth-and-accounts-plan.md | 148 remaining |
|
||||
| configuration | gaps/configuration.md | docs/plans/2026-02-25-gap-port-configuration-design.md | docs/plans/2026-02-25-gap-port-configuration-plan.md | complete |
|
||||
| routes | gaps/routes.md | docs/plans/2026-02-25-gap-port-routes-design.md | docs/plans/2026-02-25-gap-port-routes-plan.md | 36 remaining |
|
||||
| gateways | gaps/gateways.md | docs/plans/2026-02-25-gap-port-gateways-design.md | docs/plans/2026-02-25-gap-port-gateways-plan.md | 67 remaining |
|
||||
| leaf-nodes | gaps/leaf-nodes.md | docs/plans/2026-02-25-gap-port-leaf-nodes-design.md | docs/plans/2026-02-25-gap-port-leaf-nodes-plan.md | 54 remaining |
|
||||
| jetstream | gaps/jetstream.md | docs/plans/2026-02-25-gap-port-jetstream-design.md | docs/plans/2026-02-25-gap-port-jetstream-plan.md | 1084 remaining |
|
||||
| raft | gaps/raft.md | docs/plans/2026-02-25-gap-port-raft-design.md | docs/plans/2026-02-25-gap-port-raft-plan.md | 107 remaining |
|
||||
| mqtt | gaps/mqtt.md | docs/plans/2026-02-25-gap-port-mqtt-design.md | docs/plans/2026-02-25-gap-port-mqtt-plan.md | 151 remaining |
|
||||
| websocket | gaps/websocket.md | docs/plans/2026-02-25-gap-port-websocket-design.md | docs/plans/2026-02-25-gap-port-websocket-plan.md | 5 remaining |
|
||||
| monitoring | gaps/monitoring.md | docs/plans/2026-02-25-gap-port-monitoring-design.md | docs/plans/2026-02-25-gap-port-monitoring-plan.md | 96 remaining |
|
||||
| events | gaps/events.md | docs/plans/2026-02-25-gap-port-events-design.md | docs/plans/2026-02-25-gap-port-events-plan.md | 111 remaining |
|
||||
| tls-security | gaps/tls-security.md | docs/plans/2026-02-25-gap-port-tls-security-design.md | docs/plans/2026-02-25-gap-port-tls-security-plan.md | 56 remaining |
|
||||
| internal-ds | gaps/internal-ds.md | docs/plans/2026-02-25-gap-port-internal-ds-design.md | docs/plans/2026-02-25-gap-port-internal-ds-plan.md | complete |
|
||||
| logging | gaps/logging.md | docs/plans/2026-02-25-gap-port-logging-design.md | docs/plans/2026-02-25-gap-port-logging-plan.md | complete |
|
||||
| utilities-and-other | gaps/utilities-and-other.md | docs/plans/2026-02-25-gap-port-utilities-and-other-design.md | docs/plans/2026-02-25-gap-port-utilities-and-other-plan.md | 47 remaining |
|
||||
| misc-uncategorized | gaps/misc-uncategorized.md | docs/plans/2026-02-25-gap-port-misc-uncategorized-design.md | docs/plans/2026-02-25-gap-port-misc-uncategorized-plan.md | 7 remaining |
|
||||
+53
-50
@@ -103,8 +103,8 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| `pubArg` (struct) | parser.go:37 | PARTIAL | `src/NATS.Server/Protocol/NatsParser.cs:21` | `ParsedCommand` covers `subject`, `reply`, `size`, `hdr`. Missing: `origin`, `account`, `pacache`, `mapped`, `queues`, `szb`, `hdb`, `psi`, `trace`, `delivered` — clustering/routing/JetStream fields not yet needed for core client protocol. |
|
||||
| `OP_START` … `INFO_ARG` (parser state constants) | parser.go:57–134 | PARTIAL | `src/NATS.Server/Protocol/NatsParser.cs:104` | All CLIENT-facing states implemented (PUB, HPUB, SUB, UNSUB, CONNECT, INFO, PING, PONG, +OK, -ERR). MISSING states: `OP_A`/`ASUB_ARG`/`AUSUB_ARG` (A+/A- for gateways), `OP_R`/`OP_RS`/`OP_L`/`OP_LS` (RMSG/LMSG/RS+/RS-/LS+/LS-), `OP_M`/`MSG_ARG`/`HMSG_ARG` (routing MSG/HMSG). See `ClientCommandMatrix.cs` for partial routing opcode routing. |
|
||||
| `client.parse()` | parser.go:136 | PARTIAL | `src/NATS.Server/Protocol/NatsParser.cs:69` | Core CLIENT-facing parse loop ported as `NatsParser.TryParse()` using `ReadOnlySequence<byte>` + `SequenceReader`. Missing: byte-by-byte incremental state transitions (Go uses byte-by-byte state machine; .NET scans for `\r\n` on each call), auth-set check before non-CONNECT op, MQTT dispatch (`c.mqttParse`), gateway in-CONNECT gating, ROUTER/GATEWAY/LEAF protocol dispatch (RMSG, LMSG, RS+, RS-, A+, A-). |
|
||||
| `protoSnippet()` | parser.go:1236 | MISSING | — | Helper that formats a quoted snippet of the protocol buffer for error messages. The .NET parser throws `ProtocolViolationException` with a plain message; no equivalent snippet utility exists. |
|
||||
| `client.overMaxControlLineLimit()` | parser.go:1251 | PARTIAL | `src/NATS.Server/Protocol/NatsParser.cs:82` | .NET checks `line.Length > NatsProtocol.MaxControlLineSize` and throws. Missing: kind-check (Go only enforces for `CLIENT` kind), client close on violation (`closeConnection(MaxControlLineExceeded)`), structured error with state/buffer info. |
|
||||
| `protoSnippet()` | parser.go:1236 | PORTED | `src/NATS.Server/Protocol/NatsParser.cs:206` | Added Go-style quoted snippet helper (`ProtoSnippet(start,max,buffer)`) and default overload, with parity tests in `tests/NATS.Server.Tests/Protocol/ProtocolParserSnippetGapParityTests.cs`. |
|
||||
| `client.overMaxControlLineLimit()` | parser.go:1251 | PARTIAL | `src/NATS.Server/Protocol/NatsParser.cs:83` | .NET now emits structured max-control-line errors with snippet context (`snip=...`). Missing: kind-check (Go only enforces for `CLIENT`) and explicit connection-close side effect (`closeConnection(MaxControlLineExceeded)`) at parser layer. |
|
||||
| `client.clonePubArg()` | parser.go:1267 | MISSING | — | Split-buffer scenario: clones pubArg and re-processes when payload spans two reads. Not needed in .NET because `System.IO.Pipelines` handles buffering, but there is no explicit equivalent. |
|
||||
| `parseState.getHeader()` | parser.go:1297 | PARTIAL | `src/NATS.Server/Protocol/NatsHeaderParser.cs:25` | Go lazily parses `http.Header` from the raw message buffer. .NET has `NatsHeaderParser.Parse()` which parses NATS/1.0 headers. Missing: lazy evaluation on the parsed command (header is not cached on `ParsedCommand`). |
|
||||
|
||||
@@ -114,15 +114,15 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `errProtoInsufficient` | proto.go:24 | MISSING | — | Package-level error sentinel for varint parsing. No .NET equivalent (JetStream binary encoding not yet ported). |
|
||||
| `errProtoOverflow` | proto.go:25 | MISSING | — | Package-level error sentinel. |
|
||||
| `errProtoInvalidFieldNumber` | proto.go:26 | MISSING | — | Package-level error sentinel. |
|
||||
| `protoScanField()` | proto.go:28 | MISSING | — | Scans one protobuf field (tag + value) from a byte slice. Used by JetStream internal encoding. Not yet ported. |
|
||||
| `protoScanTag()` | proto.go:42 | MISSING | — | Decodes a protobuf tag (field number + wire type) from a varint. Not yet ported. |
|
||||
| `protoScanFieldValue()` | proto.go:61 | MISSING | — | Reads the value portion of a protobuf field by wire type. Not yet ported. |
|
||||
| `protoScanVarint()` | proto.go:77 | MISSING | — | 10-byte max varint decoder. Not yet ported. |
|
||||
| `protoScanBytes()` | proto.go:179 | MISSING | — | Length-delimited bytes field reader. Not yet ported. |
|
||||
| `protoEncodeVarint()` | proto.go:190 | MISSING | — | Varint encoder. Not yet ported. |
|
||||
| `errProtoInsufficient` | proto.go:24 | PORTED | `src/NATS.Server/Protocol/ProtoWire.cs:5` | Added sentinel-equivalent error constant and `ProtoWireException` usage for insufficient varint/bytes payloads. |
|
||||
| `errProtoOverflow` | proto.go:25 | PORTED | `src/NATS.Server/Protocol/ProtoWire.cs:6` | Added overflow sentinel-equivalent error constant for invalid 10-byte varint tails. |
|
||||
| `errProtoInvalidFieldNumber` | proto.go:26 | PORTED | `src/NATS.Server/Protocol/ProtoWire.cs:7` | Added invalid field-number sentinel-equivalent error constant used by tag scanning. |
|
||||
| `protoScanField()` | proto.go:28 | PORTED | `src/NATS.Server/Protocol/ProtoWire.cs:9` | Added field scanner that composes tag + value scanning and returns total consumed size. |
|
||||
| `protoScanTag()` | proto.go:42 | PORTED | `src/NATS.Server/Protocol/ProtoWire.cs:16` | Added tag scanner with Go-equivalent field number validation (`1..int32`). |
|
||||
| `protoScanFieldValue()` | proto.go:61 | PORTED | `src/NATS.Server/Protocol/ProtoWire.cs:26` | Added wire-type scanner for varint/fixed32/fixed64/length-delimited forms. |
|
||||
| `protoScanVarint()` | proto.go:77 | PORTED | `src/NATS.Server/Protocol/ProtoWire.cs:38` | Added 10-byte max varint scanner with insufficient/overflow parity errors. |
|
||||
| `protoScanBytes()` | proto.go:179 | PORTED | `src/NATS.Server/Protocol/ProtoWire.cs:65` | Added length-delimited scanner that validates the declared size against remaining payload bytes. |
|
||||
| `protoEncodeVarint()` | proto.go:190 | PORTED | `src/NATS.Server/Protocol/ProtoWire.cs:74` | Added varint encoder and round-trip coverage in parity tests. |
|
||||
|
||||
### golang/nats-server/server/const.go
|
||||
|
||||
@@ -137,54 +137,54 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| `PROTO = 1` | const.go:76 | PORTED | `src/NATS.Server/Protocol/NatsProtocol.cs:12` | `ProtoVersion = 1` |
|
||||
| `DEFAULT_PORT = 4222` | const.go:79 | PORTED | `src/NATS.Server/Protocol/NatsProtocol.cs:10` | `DefaultPort = 4222` |
|
||||
| `RANDOM_PORT = -1` | const.go:83 | NOT_APPLICABLE | — | Used in Go test helpers to request a random port. .NET tests use `GetFreePort()` pattern. |
|
||||
| `DEFAULT_HOST = "0.0.0.0"` | const.go:86 | MISSING | — | No explicit constant in .NET; server defaults to `0.0.0.0` but the constant is not named. |
|
||||
| `DEFAULT_HOST = "0.0.0.0"` | const.go:86 | PORTED | `src/NATS.Server/Protocol/NatsProtocol.cs:11` | Added `NatsProtocol.DefaultHost` and wired `NatsOptions.Host` default to it. |
|
||||
| `MAX_CONTROL_LINE_SIZE = 4096` | const.go:91 | PORTED | `src/NATS.Server/Protocol/NatsProtocol.cs:7` | `MaxControlLineSize = 4096` |
|
||||
| `MAX_PAYLOAD_SIZE = 1MB` | const.go:95 | PORTED | `src/NATS.Server/Protocol/NatsProtocol.cs:8` | `MaxPayloadSize = 1024 * 1024` |
|
||||
| `MAX_PAYLOAD_MAX_SIZE = 8MB` | const.go:99 | MISSING | — | Warning threshold for max_payload setting. No .NET equivalent. |
|
||||
| `MAX_PAYLOAD_MAX_SIZE = 8MB` | const.go:99 | PORTED | `src/NATS.Server/Protocol/NatsProtocol.cs:9` | Added `NatsProtocol.MaxPayloadMaxSize` (8MB threshold constant). |
|
||||
| `MAX_PENDING_SIZE = 64MB` | const.go:103 | PORTED | `src/NATS.Server/Protocol/NatsProtocol.cs:9` | `MaxPendingSize = 64 * 1024 * 1024` |
|
||||
| `DEFAULT_MAX_CONNECTIONS = 64K` | const.go:106 | MISSING | — | Default max connections cap. No .NET equivalent constant. |
|
||||
| `TLS_TIMEOUT = 2s` | const.go:109 | MISSING | — | TLS handshake wait time. Not yet defined in .NET options. |
|
||||
| `DEFAULT_TLS_HANDSHAKE_FIRST_FALLBACK_DELAY = 50ms` | const.go:114 | MISSING | — | TLS-first handshake fallback delay. Not yet implemented in .NET. |
|
||||
| `AUTH_TIMEOUT = 2s` | const.go:118 | MISSING | — | Authorization wait timeout. No .NET equivalent constant. |
|
||||
| `DEFAULT_PING_INTERVAL = 2min` | const.go:122 | MISSING | — | Ping interval for keep-alive. No .NET equivalent. |
|
||||
| `DEFAULT_PING_MAX_OUT = 2` | const.go:125 | MISSING | — | Max outstanding pings before disconnect. No .NET equivalent. |
|
||||
| `DEFAULT_MAX_CONNECTIONS = 64K` | const.go:106 | PORTED | `src/NATS.Server/Protocol/NatsProtocol.cs:13` | Added `NatsProtocol.DefaultMaxConnections` and wired `NatsOptions.MaxConnections`. |
|
||||
| `TLS_TIMEOUT = 2s` | const.go:109 | PORTED | `src/NATS.Server/Protocol/NatsProtocol.cs:18`, `src/NATS.Server/NatsOptions.cs:102` | Added protocol default and wired TLS timeout default in options. |
|
||||
| `DEFAULT_TLS_HANDSHAKE_FIRST_FALLBACK_DELAY = 50ms` | const.go:114 | PORTED | `src/NATS.Server/Protocol/NatsProtocol.cs:19`, `src/NATS.Server/NatsOptions.cs:104` | Added protocol default and wired `TlsHandshakeFirstFallback` default in options. |
|
||||
| `AUTH_TIMEOUT = 2s` | const.go:118 | PORTED | `src/NATS.Server/Protocol/NatsProtocol.cs:20`, `src/NATS.Server/NatsOptions.cs:49` | Added protocol default and wired `AuthTimeout` default in options. |
|
||||
| `DEFAULT_PING_INTERVAL = 2min` | const.go:122 | PORTED | `src/NATS.Server/Protocol/NatsProtocol.cs:21`, `src/NATS.Server/NatsOptions.cs:19` | Added protocol default and wired `PingInterval` default in options. |
|
||||
| `DEFAULT_PING_MAX_OUT = 2` | const.go:125 | PORTED | `src/NATS.Server/Protocol/NatsProtocol.cs:14`, `src/NATS.Server/NatsOptions.cs:20` | Added protocol default and wired `MaxPingsOut` default in options. |
|
||||
| `CR_LF = "\r\n"` | const.go:128 | PORTED | `src/NATS.Server/Protocol/NatsProtocol.cs:15` | `CrLf` byte array. |
|
||||
| `LEN_CR_LF = 2` | const.go:131 | PORTED | Implicit in .NET (`+ 2` literals in parser). | Used as literal `2` in `TryReadPayload`. |
|
||||
| `DEFAULT_FLUSH_DEADLINE = 10s` | const.go:134 | MISSING | — | Write/flush deadline. Not yet defined. |
|
||||
| `DEFAULT_HTTP_PORT = 8222` | const.go:137 | MISSING | — | Monitoring port. Not yet implemented. |
|
||||
| `DEFAULT_HTTP_BASE_PATH = "/"` | const.go:140 | MISSING | — | Monitoring HTTP base path. Not yet implemented. |
|
||||
| `ACCEPT_MIN_SLEEP = 10ms` | const.go:143 | MISSING | — | Retry sleep for transient accept errors. Not yet defined. |
|
||||
| `ACCEPT_MAX_SLEEP = 1s` | const.go:146 | MISSING | — | Max sleep for accept errors. Not yet defined. |
|
||||
| `DEFAULT_ROUTE_CONNECT = 1s` | const.go:149 | MISSING | — | Route solicitation interval. Clustering not yet implemented. |
|
||||
| `DEFAULT_ROUTE_CONNECT_MAX = 30s` | const.go:152 | MISSING | — | Route max solicitation interval. |
|
||||
| `DEFAULT_ROUTE_RECONNECT = 1s` | const.go:155 | MISSING | — | Route reconnect delay. |
|
||||
| `DEFAULT_ROUTE_DIAL = 1s` | const.go:158 | MISSING | — | Route dial timeout. |
|
||||
| `DEFAULT_ROUTE_POOL_SIZE = 3` | const.go:161 | MISSING | — | Route connection pool size. |
|
||||
| `DEFAULT_LEAF_NODE_RECONNECT = 1s` | const.go:164 | MISSING | — | Leaf node reconnect interval. |
|
||||
| `DEFAULT_LEAF_TLS_TIMEOUT = 2s` | const.go:167 | MISSING | — | Leaf node TLS timeout. |
|
||||
| `PROTO_SNIPPET_SIZE = 32` | const.go:170 | MISSING | — | Size of proto snippet in parse errors. No .NET equivalent (errors use plain messages). |
|
||||
| `MAX_CONTROL_LINE_SNIPPET_SIZE = 128` | const.go:172 | MISSING | — | Snippet size for control-line-too-long errors. |
|
||||
| `DEFAULT_FLUSH_DEADLINE = 10s` | const.go:134 | PORTED | `src/NATS.Server/Protocol/NatsProtocol.cs:22`, `src/NATS.Server/NatsOptions.cs:18` | Added protocol default and wired `WriteDeadline` default in options. |
|
||||
| `DEFAULT_HTTP_PORT = 8222` | const.go:137 | PORTED | `src/NATS.Server/Protocol/NatsProtocol.cs:15` | Added `NatsProtocol.DefaultHttpPort` constant and parity assertions in protocol constants tests. |
|
||||
| `DEFAULT_HTTP_BASE_PATH = "/"` | const.go:140 | PORTED | `src/NATS.Server/Protocol/NatsProtocol.cs:16` | Added `NatsProtocol.DefaultHttpBasePath` constant and parity assertions in protocol constants tests. |
|
||||
| `ACCEPT_MIN_SLEEP = 10ms` | const.go:143 | PORTED | `src/NATS.Server/Protocol/NatsProtocol.cs:23`, `src/NATS.Server/NatsServer.cs:94` | Added protocol default and wired accept-loop backoff minimum in server. |
|
||||
| `ACCEPT_MAX_SLEEP = 1s` | const.go:146 | PORTED | `src/NATS.Server/Protocol/NatsProtocol.cs:24`, `src/NATS.Server/NatsServer.cs:95` | Added protocol default and wired accept-loop backoff maximum in server. |
|
||||
| `DEFAULT_ROUTE_CONNECT = 1s` | const.go:149 | PORTED | `src/NATS.Server/Protocol/NatsProtocol.cs:31` | Added `NatsProtocol.DefaultRouteConnect` constant. |
|
||||
| `DEFAULT_ROUTE_CONNECT_MAX = 30s` | const.go:152 | PORTED | `src/NATS.Server/Protocol/NatsProtocol.cs:32` | Added `NatsProtocol.DefaultRouteConnectMax` constant. |
|
||||
| `DEFAULT_ROUTE_RECONNECT = 1s` | const.go:155 | PORTED | `src/NATS.Server/Protocol/NatsProtocol.cs:33` | Added `NatsProtocol.DefaultRouteReconnect` constant. |
|
||||
| `DEFAULT_ROUTE_DIAL = 1s` | const.go:158 | PORTED | `src/NATS.Server/Protocol/NatsProtocol.cs:34` | Added `NatsProtocol.DefaultRouteDial` constant. |
|
||||
| `DEFAULT_ROUTE_POOL_SIZE = 3` | const.go:161 | PORTED | `src/NATS.Server/Protocol/NatsProtocol.cs:17` | Added `NatsProtocol.DefaultRoutePoolSize` constant. |
|
||||
| `DEFAULT_LEAF_NODE_RECONNECT = 1s` | const.go:164 | PORTED | `src/NATS.Server/Protocol/NatsProtocol.cs:35` | Added `NatsProtocol.DefaultLeafNodeReconnect` constant. |
|
||||
| `DEFAULT_LEAF_TLS_TIMEOUT = 2s` | const.go:167 | PORTED | `src/NATS.Server/Protocol/NatsProtocol.cs:36` | Added `NatsProtocol.DefaultLeafTlsTimeout` constant. |
|
||||
| `PROTO_SNIPPET_SIZE = 32` | const.go:170 | PORTED | `src/NATS.Server/Protocol/NatsProtocol.cs:9`, `src/NATS.Server/Protocol/NatsParser.cs:222` | Added snippet-size constant and wired parser default `ProtoSnippet` overload to it. |
|
||||
| `MAX_CONTROL_LINE_SNIPPET_SIZE = 128` | const.go:172 | PORTED | `src/NATS.Server/Protocol/NatsProtocol.cs:8`, `src/NATS.Server/Protocol/NatsParser.cs:85` | Added max control-line snippet size constant and wired control-line violation errors to use it. |
|
||||
| `MAX_MSG_ARGS = 4` | const.go:175 | NOT_APPLICABLE | — | Used in Go's manual arg-split loop. .NET uses `SplitArgs()` with stack-allocated ranges. |
|
||||
| `MAX_RMSG_ARGS = 6` | const.go:178 | NOT_APPLICABLE | — | Used in RMSG parsing. RMSG not yet ported. |
|
||||
| `MAX_HMSG_ARGS = 7` | const.go:180 | NOT_APPLICABLE | — | Used in HMSG parsing. HMSG routing not yet ported. |
|
||||
| `MAX_PUB_ARGS = 3` | const.go:183 | NOT_APPLICABLE | — | Used in PUB arg splitting. .NET uses dynamic `SplitArgs`. |
|
||||
| `MAX_HPUB_ARGS = 4` | const.go:186 | NOT_APPLICABLE | — | Used in HPUB arg splitting. .NET uses dynamic `SplitArgs`. |
|
||||
| `MAX_RSUB_ARGS = 6` | const.go:189 | NOT_APPLICABLE | — | Used in RS+/LS+ subscription arg splitting. Not yet ported. |
|
||||
| `DEFAULT_MAX_CLOSED_CLIENTS = 10000` | const.go:192 | MISSING | — | Closed-connection history cap. Not yet implemented. |
|
||||
| `DEFAULT_LAME_DUCK_DURATION = 2min` | const.go:196 | MISSING | — | Lame-duck shutdown spread duration. Not yet implemented. |
|
||||
| `DEFAULT_LAME_DUCK_GRACE_PERIOD = 10s` | const.go:200 | MISSING | — | Lame-duck grace period. Not yet implemented. |
|
||||
| `DEFAULT_LEAFNODE_INFO_WAIT = 1s` | const.go:203 | MISSING | — | Leaf node INFO wait. Not yet implemented. |
|
||||
| `DEFAULT_LEAFNODE_PORT = 7422` | const.go:206 | MISSING | — | Default leaf node port. Not yet implemented. |
|
||||
| `DEFAULT_CONNECT_ERROR_REPORTS = 3600` | const.go:214 | MISSING | — | Error report throttle for initial connection failures. Not yet implemented. |
|
||||
| `DEFAULT_RECONNECT_ERROR_REPORTS = 1` | const.go:220 | MISSING | — | Error report throttle for reconnect failures. Not yet implemented. |
|
||||
| `DEFAULT_RTT_MEASUREMENT_INTERVAL = 1h` | const.go:224 | MISSING | — | RTT measurement interval. Not yet implemented. |
|
||||
| `DEFAULT_ALLOW_RESPONSE_MAX_MSGS = 1` | const.go:228 | MISSING | — | Default allowed response message count for reply subjects. Not yet implemented. |
|
||||
| `DEFAULT_ALLOW_RESPONSE_EXPIRATION = 2min` | const.go:232 | MISSING | — | Dynamic response permission expiry. Not yet implemented. |
|
||||
| `DEFAULT_SERVICE_EXPORT_RESPONSE_THRESHOLD = 2min` | const.go:237 | MISSING | — | Service export response threshold. Not yet implemented (accounts/JetStream). |
|
||||
| `DEFAULT_SERVICE_LATENCY_SAMPLING = 100` | const.go:241 | MISSING | — | Service latency sampling rate. Not yet implemented. |
|
||||
| `DEFAULT_SYSTEM_ACCOUNT = "$SYS"` | const.go:244 | MISSING | — | System account name constant. Not yet implemented. |
|
||||
| `DEFAULT_GLOBAL_ACCOUNT = "$G"` | const.go:247 | MISSING | — | Global account name constant. Not yet implemented. |
|
||||
| `DEFAULT_ACCOUNT_FETCH_TIMEOUT = 1900ms` | const.go:250 | MISSING | — | Account fetch timeout. Not yet implemented. |
|
||||
| `DEFAULT_MAX_CLOSED_CLIENTS = 10000` | const.go:192 | PORTED | `src/NATS.Server/Protocol/NatsProtocol.cs:15`, `src/NATS.Server/NatsOptions.cs:89` | Added protocol default and wired closed-client ring size default in options. |
|
||||
| `DEFAULT_LAME_DUCK_DURATION = 2min` | const.go:196 | PORTED | `src/NATS.Server/Protocol/NatsProtocol.cs:25`, `src/NATS.Server/NatsOptions.cs:59` | Added protocol default and wired lame-duck duration default in options. |
|
||||
| `DEFAULT_LAME_DUCK_GRACE_PERIOD = 10s` | const.go:200 | PORTED | `src/NATS.Server/Protocol/NatsProtocol.cs:26`, `src/NATS.Server/NatsOptions.cs:60` | Added protocol default and wired lame-duck grace period default in options. |
|
||||
| `DEFAULT_LEAFNODE_INFO_WAIT = 1s` | const.go:203 | PORTED | `src/NATS.Server/Protocol/NatsProtocol.cs:37` | Added `NatsProtocol.DefaultLeafNodeInfoWait` constant. |
|
||||
| `DEFAULT_LEAFNODE_PORT = 7422` | const.go:206 | PORTED | `src/NATS.Server/Protocol/NatsProtocol.cs:18` | Added `NatsProtocol.DefaultLeafNodePort` constant. |
|
||||
| `DEFAULT_CONNECT_ERROR_REPORTS = 3600` | const.go:214 | PORTED | `src/NATS.Server/Protocol/NatsProtocol.cs:16`, `src/NATS.Server/NatsOptions.cs:86` | Added protocol default and wired `ConnectErrorReports` default in options. |
|
||||
| `DEFAULT_RECONNECT_ERROR_REPORTS = 1` | const.go:220 | PORTED | `src/NATS.Server/Protocol/NatsProtocol.cs:17`, `src/NATS.Server/NatsOptions.cs:87` | Added protocol default and wired `ReconnectErrorReports` default in options. |
|
||||
| `DEFAULT_RTT_MEASUREMENT_INTERVAL = 1h` | const.go:224 | PORTED | `src/NATS.Server/Protocol/NatsProtocol.cs:38` | Added `NatsProtocol.DefaultRttMeasurementInterval` constant. |
|
||||
| `DEFAULT_ALLOW_RESPONSE_MAX_MSGS = 1` | const.go:228 | PORTED | `src/NATS.Server/Protocol/NatsProtocol.cs:24` | Added `NatsProtocol.DefaultAllowResponseMaxMsgs` constant. |
|
||||
| `DEFAULT_ALLOW_RESPONSE_EXPIRATION = 2min` | const.go:232 | PORTED | `src/NATS.Server/Protocol/NatsProtocol.cs:39` | Added `NatsProtocol.DefaultAllowResponseExpiration` constant. |
|
||||
| `DEFAULT_SERVICE_EXPORT_RESPONSE_THRESHOLD = 2min` | const.go:237 | PORTED | `src/NATS.Server/Protocol/NatsProtocol.cs:40` | Added `NatsProtocol.DefaultServiceExportResponseThreshold` constant. |
|
||||
| `DEFAULT_SERVICE_LATENCY_SAMPLING = 100` | const.go:241 | PORTED | `src/NATS.Server/Protocol/NatsProtocol.cs:25` | Added `NatsProtocol.DefaultServiceLatencySampling` constant. |
|
||||
| `DEFAULT_SYSTEM_ACCOUNT = "$SYS"` | const.go:244 | PORTED | `src/NATS.Server/Protocol/NatsProtocol.cs:26`, `src/NATS.Server/Auth/Account.cs:10` | Added protocol-level constant; existing account model uses the same value (`$SYS`). |
|
||||
| `DEFAULT_GLOBAL_ACCOUNT = "$G"` | const.go:247 | PORTED | `src/NATS.Server/Protocol/NatsProtocol.cs:27` | Added `NatsProtocol.DefaultGlobalAccount` constant. |
|
||||
| `DEFAULT_ACCOUNT_FETCH_TIMEOUT = 1900ms` | const.go:250 | PORTED | `src/NATS.Server/Protocol/NatsProtocol.cs:41` | Added `NatsProtocol.DefaultAccountFetchTimeout` constant. |
|
||||
|
||||
### .NET-Only Additions (no Go counterpart in the three source files)
|
||||
|
||||
@@ -231,7 +231,7 @@ After porting work is completed:
|
||||
| `TestParseConnect` | PORTED | `tests/NATS.Server.Tests/ParserTests.cs: Parse_CONNECT` |
|
||||
| `TestParseSub` | PORTED | `tests/NATS.Server.Tests/ParserTests.cs: Parse_SUB_without_queue`, `Parse_SUB_with_queue` |
|
||||
| `TestParsePub` | PARTIAL | `tests/NATS.Server.Tests/ParserTests.cs: Parse_PUB_with_payload`, `Parse_PUB_with_reply` — missing overflow payload error scenario |
|
||||
| `TestParsePubSizeOverflow` | MISSING | No .NET test for integer overflow on very large size values (>9 digits handled by `ParseSize` returning -1, but no explicit overflow test) |
|
||||
| `TestParsePubSizeOverflow` | PORTED | `tests/NATS.Server.Tests/ParserTests.cs: Parse_pub_size_overflow_fails` — explicit oversized PUB payload-size argument test now asserts parser rejection (`Invalid payload size`) |
|
||||
| `TestParsePubArg` | PORTED | `tests/NATS.Server.Tests/ParserTests.cs: Parse_PUB_argument_variations` (Theory) |
|
||||
| `TestParsePubBadSize` | PARTIAL | `tests/NATS.Server.Tests/ParserTests.cs: Parse_malformed_protocol_fails` covers some bad args; missing specific `mpay` (max payload per-client) test |
|
||||
| `TestParseHeaderPub` | PORTED | `tests/NATS.Server.Tests/ParserTests.cs: Parse_HPUB` |
|
||||
@@ -240,7 +240,7 @@ After porting work is completed:
|
||||
| `TestParseRouteMsg` (RMSG) | MISSING | No .NET equivalent — ROUTER RMSG parsing not yet ported |
|
||||
| `TestParseMsgSpace` | MISSING | No .NET equivalent — MSG opcode for routes not yet ported |
|
||||
| `TestShouldFail` | PARTIAL | `tests/NATS.Server.Tests/ParserTests.cs: Parse_malformed_protocol_fails` — covers subset; documented behavioral differences for byte-by-byte vs prefix-scan parser |
|
||||
| `TestProtoSnippet` | MISSING | No .NET equivalent for `protoSnippet()` helper |
|
||||
| `TestProtoSnippet` | PORTED | `tests/NATS.Server.Tests/Protocol/ProtocolParserSnippetGapParityTests.cs: ProtoSnippet_*` validates Go-style snippet behavior and parser error context wiring. |
|
||||
| `TestParseOK` | PORTED | `tests/NATS.Server.Tests/ParserTests.cs: Parse_case_insensitive` includes +OK (via `ParsedCommand.Simple`) |
|
||||
| `TestMaxControlLine` | PARTIAL | `tests/NATS.Server.Tests/ParserTests.cs: Parse_exceeding_max_control_line_fails` — covers basic enforcement; missing per-client-kind bypass (LEAF/ROUTER/GATEWAY exempt) |
|
||||
|
||||
@@ -281,5 +281,8 @@ After porting work is completed:
|
||||
|
||||
| Date | Change | By |
|
||||
|------|--------|----|
|
||||
| 2026-02-26 | Added parser overflow parity test (`Parse_pub_size_overflow_fails`) and reclassified `TestParsePubSizeOverflow` from MISSING to PORTED. | codex |
|
||||
| 2026-02-25 | File created with LLM analysis instructions | auto |
|
||||
| 2026-02-25 | Full gap inventory populated: parser.go, proto.go, const.go; test cross-reference for all 5 Go test files | claude-sonnet-4-6 |
|
||||
| 2026-02-25 | Executed protocol defaults parity batch: introduced missing const/default surfaces in `NatsProtocol`, wired `NatsOptions` and accept-loop defaults, added targeted tests (`ProtocolDefaultConstantsGapParityTests`), and reclassified 16 const.go rows from MISSING to PORTED | codex |
|
||||
| 2026-02-25 | Executed protocol proto-wire parity batch: added `ProtoWire` scanners/encoder and parity tests (`ProtoWireParityTests`), and reclassified all 9 `proto.go` rows from MISSING to PORTED | codex |
|
||||
|
||||
+32
-30
@@ -91,12 +91,12 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| RaftNode (interface) | raft.go:40-92 | PARTIAL | src/NATS.Server/Raft/IRaftNode.cs:5 | Interface declared but empty — none of the 40+ methods from Go are defined |
|
||||
| RaftNodeCheckpoint (interface) | raft.go:98-103 | PARTIAL | src/NATS.Server/Raft/RaftSnapshotCheckpoint.cs:7 | Chunk assembly exists but LoadLastSnapshot, AppendEntriesSeq, Abort, InstallSnapshot not matching Go's interface contract |
|
||||
| WAL (interface) | raft.go:105-118 | PARTIAL | src/NATS.Server/Raft/RaftWal.cs:20 | .NET RaftWal is a concrete file-based WAL; does not implement Go's WAL interface (StoreMsg, LoadMsg, RemoveMsg, Compact, Purge, Truncate, State, FastState, Stop, Delete) |
|
||||
| Peer (struct) | raft.go:120-125 | PARTIAL | src/NATS.Server/Raft/RaftPeerState.cs:7 | .NET has NextIndex/MatchIndex/LastContact/Active but missing Lag and Current fields from Go's Peer export |
|
||||
| Peer (struct) | raft.go:120-125 | PORTED | src/NATS.Server/Raft/RaftPeerState.cs | Added missing parity fields (`Lag`, `Current`) plus helpers to recalculate lag and refresh current-state from heartbeat/contact timing |
|
||||
| RaftState (enum) | raft.go:127-135 | PORTED | src/NATS.Server/Raft/RaftState.cs:4 | All four states: Follower, Leader, Candidate, Closed |
|
||||
| RaftState.String() | raft.go:137-149 | MISSING | — | No .NET ToString override for RaftState enum |
|
||||
| RaftConfig (struct) | raft.go:301-317 | MISSING | — | No equivalent configuration struct (Name, Store, Log, Track, Observer, Recovering, ScaleUp) |
|
||||
| CommittedEntry (struct) | raft.go:2506-2509 | PARTIAL | src/NATS.Server/Raft/CommitQueue.cs:9 | CommitQueue<T> exists as channel wrapper, but no CommittedEntry struct with Index+Entries |
|
||||
| Entry (struct) | raft.go:2641-2644 | PARTIAL | src/NATS.Server/Raft/RaftWireFormat.cs:73 | RaftEntryWire has Type+Data but is wire-format only; no general Entry type used for proposals |
|
||||
| RaftState.String() | raft.go:137-149 | PORTED | src/NATS.Server/Raft/RaftStateExtensions.cs:9 | Added Go-style `RaftState.String()` extension mapping to Follower/Leader/Candidate/Closed |
|
||||
| RaftConfig (struct) | raft.go:301-317 | PORTED | src/NATS.Server/Raft/RaftConfig.cs:8 | Added `RaftConfig` model with Name/Store/Log/Track/Observer/Recovering/ScaleUp fields for parity shape |
|
||||
| CommittedEntry (struct) | raft.go:2506-2509 | PORTED | src/NATS.Server/Raft/CommitQueue.cs (`CommittedEntry`) | Added explicit committed-entry shape with `Index` and `Entries` list for parity with Go commit delivery payload |
|
||||
| Entry (struct) | raft.go:2641-2644 | PORTED | src/NATS.Server/Raft/RaftEntry.cs | Added general `RaftEntry` model (`Type`, `Data`) with conversion helpers to/from wire-entry shape |
|
||||
| EntryType (enum) | raft.go:2605-2619 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:54-63 | All types present including EntryCatchup (mapped as RaftEntryType) |
|
||||
|
||||
### golang/nats-server/server/raft.go — Exported RaftNode Interface Methods
|
||||
@@ -104,53 +104,53 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| Propose() | raft.go:909-924 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:308 | ProposeAsync exists but synchronous replication model, no write-error checking, no proposal queue |
|
||||
| ProposeMulti() | raft.go:928-945 | MISSING | — | No batch proposal support |
|
||||
| ProposeMulti() | raft.go:928-945 | PORTED | src/NATS.Server/Raft/RaftNode.cs (`ProposeMultiAsync`) | Added ordered multi-command proposal API returning committed indexes per input command |
|
||||
| ForwardProposal() | raft.go:949-959 | PARTIAL | src/NATS.Server/Raft/NatsRaftTransport.cs:182 | Transport has ForwardProposal but RaftNode does not call it automatically for non-leaders |
|
||||
| InstallSnapshot() | raft.go:1295-1311 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:699 | InstallSnapshotAsync exists but no checkpointing, no WAL compaction, no highwayhash verification |
|
||||
| CreateSnapshotCheckpoint() | raft.go:1356-1360 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:519 | CreateSnapshotCheckpointAsync exists but simplified — no async write, no WAL compaction tracking |
|
||||
| SendSnapshot() | raft.go:1284-1290 | MISSING | — | No direct snapshot send as append entry |
|
||||
| NeedSnapshot() | raft.go:1551-1555 | MISSING | — | No equivalent check |
|
||||
| Applied() | raft.go:1183-1185 | MISSING | — | No callback from upper layer for applied index tracking (delegates to Processed) |
|
||||
| Applied() | raft.go:1183-1185 | PORTED | src/NATS.Server/Raft/RaftNode.cs:403 | Added `Applied(long)` callback returning `(entries,bytes)` and delegating progress to processed tracking |
|
||||
| Processed() | raft.go:1193-1240 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:664 | MarkProcessed exists but much simpler — no aflr signaling, no leader state transition, no byte estimation |
|
||||
| State() | raft.go:2025-2027 | PORTED | src/NATS.Server/Raft/RaftNode.cs:43 | Role property (uses RaftRole enum instead of RaftState) |
|
||||
| Size() | raft.go:2037-2043 | MISSING | — | No WAL size reporting |
|
||||
| Progress() | raft.go:2030-2034 | MISSING | — | No combined (index, commit, applied) return |
|
||||
| Size() | raft.go:2037-2043 | PORTED | src/NATS.Server/Raft/RaftNode.cs:771 | Added `Size()` accessor returning entry count and UTF-8 command-byte estimate from current log contents |
|
||||
| Progress() | raft.go:2030-2034 | PORTED | src/NATS.Server/Raft/RaftNode.cs:765 | Added `Progress()` accessor returning `(index, commit, applied)` |
|
||||
| Leader() | raft.go:1712-1717 | PORTED | src/NATS.Server/Raft/RaftNode.cs:42 | IsLeader property |
|
||||
| LeaderSince() | raft.go:1721-1726 | MISSING | — | No leader-since timestamp tracking |
|
||||
| LeaderSince() | raft.go:1721-1726 | PORTED | src/NATS.Server/Raft/RaftNode.cs:63 | Added nullable `LeaderSince` timestamp and leadership transition updates |
|
||||
| Quorum() | raft.go:3070-3083 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:201 | HasQuorum() exists but uses different window calculation (2x electionTimeout vs Go's lostQuorumInterval) |
|
||||
| Current() | raft.go:1840-1847 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:879 | IsCurrent exists but no commit==applied check, no forward-progress polling |
|
||||
| Healthy() | raft.go:1850-1857 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:892 | IsHealthy exists but different semantics — checks peer responsiveness, not isCurrent(true) |
|
||||
| Term() | raft.go:3119-3123 | PORTED | src/NATS.Server/Raft/RaftNode.cs:41 | Term property |
|
||||
| Leaderless() | raft.go:1876-1883 | MISSING | — | No atomic hasleader flag |
|
||||
| GroupLeader() | raft.go:1865-1872 | MISSING | — | No leader ID tracking (only IsLeader bool) |
|
||||
| HadPreviousLeader() | raft.go:1860-1862 | MISSING | — | No pleader atomic flag |
|
||||
| Leaderless() | raft.go:1876-1883 | PORTED | src/NATS.Server/Raft/RaftNode.cs:65 | Added lock-free `Leaderless` derived from tracked group leader state |
|
||||
| GroupLeader() | raft.go:1865-1872 | PORTED | src/NATS.Server/Raft/RaftNode.cs:64 | Added `GroupLeader` tracking, updated on election/heartbeat/stepdown |
|
||||
| HadPreviousLeader() | raft.go:1860-1862 | PORTED | src/NATS.Server/Raft/RaftNode.cs:66 | Added `HadPreviousLeader` flag that remains true after first observed leader |
|
||||
| StepDown() | raft.go:1900-1977 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:706 | RequestStepDown exists but no preferred leader selection, no leader transfer, no EntryLeaderTransfer |
|
||||
| SetObserver() | raft.go:2394-2396 | MISSING | — | No observer mode |
|
||||
| IsObserver() | raft.go:2387-2391 | MISSING | — | No observer mode |
|
||||
| SetObserver() | raft.go:2394-2396 | PORTED | src/NATS.Server/Raft/RaftNode.cs:799 | Added `SetObserver(bool)` toggle for observer mode |
|
||||
| IsObserver() | raft.go:2387-2391 | PORTED | src/NATS.Server/Raft/RaftNode.cs:68 | Added `IsObserver` accessor |
|
||||
| Campaign() | raft.go:1980-1984 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:771 | CampaignImmediately exists but no random campaign timeout |
|
||||
| CampaignImmediately() | raft.go:1987-1993 | PORTED | src/NATS.Server/Raft/RaftNode.cs:771 | CampaignImmediately() |
|
||||
| ID() | raft.go:2045-2051 | PORTED | src/NATS.Server/Raft/RaftNode.cs:40 | Id property |
|
||||
| Group() | raft.go:2053-2056 | MISSING | — | No group name tracking |
|
||||
| Group() | raft.go:2053-2056 | PORTED | src/NATS.Server/Raft/RaftNode.cs:42 | Added `GroupName` tracking on `RaftNode` with constructor support; defaults to node ID when unspecified |
|
||||
| Peers() | raft.go:2058-2077 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:872 | GetPeerStates returns Dict but missing Lag calculation |
|
||||
| ProposeKnownPeers() | raft.go:2080-2089 | MISSING | — | No peer state broadcast |
|
||||
| UpdateKnownPeers() | raft.go:2092-2096 | MISSING | — | No peer state update |
|
||||
| ProposeAddPeer() | raft.go:962-983 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:372 | ProposeAddPeerAsync exists but synchronous replication, no forwarding to leader |
|
||||
| ProposeRemovePeer() | raft.go:986-1019 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:415 | ProposeRemovePeerAsync exists but no forwarding, no self-removal handling |
|
||||
| MembershipChangeInProgress() | raft.go:1021-1025 | PORTED | src/NATS.Server/Raft/RaftNode.cs:67 | MembershipChangeInProgress property |
|
||||
| AdjustClusterSize() | raft.go:1059-1079 | MISSING | — | No cluster size adjustment |
|
||||
| AdjustBootClusterSize() | raft.go:1038-1055 | MISSING | — | No boot cluster size adjustment |
|
||||
| ClusterSize() | raft.go:1029-1033 | MISSING | — | No explicit cluster size property |
|
||||
| AdjustClusterSize() | raft.go:1059-1079 | PORTED | src/NATS.Server/Raft/RaftNode.cs:790 | Added leader-gated cluster-size adjustment with Go floor behavior (`min 2`) |
|
||||
| AdjustBootClusterSize() | raft.go:1038-1055 | PORTED | src/NATS.Server/Raft/RaftNode.cs:781 | Added boot-time cluster-size adjustment gated on no current/previous leader |
|
||||
| ClusterSize() | raft.go:1029-1033 | PORTED | src/NATS.Server/Raft/RaftNode.cs:778 | Added explicit `ClusterSize()` accessor |
|
||||
| ApplyQ() | raft.go:2106 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:53 | CommitQueue exists as Channel-based queue, different API than ipQueue |
|
||||
| PauseApply() | raft.go:1084-1092 | MISSING | — | No apply pausing |
|
||||
| ResumeApply() | raft.go:1111-1156 | MISSING | — | No apply resuming with replay |
|
||||
| DrainAndReplaySnapshot() | raft.go:1162-1177 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:537 | DrainAndReplaySnapshotAsync exists but simplified — no catchup cancellation, no commit preservation |
|
||||
| LeadChangeC() | raft.go:2110 | MISSING | — | No leader change channel |
|
||||
| QuitC() | raft.go:2113 | MISSING | — | No quit channel |
|
||||
| Created() | raft.go:2115-2118 | MISSING | — | No creation timestamp |
|
||||
| Stop() | raft.go:2120-2122 | MISSING | — | No graceful shutdown (Dispose exists but minimal) |
|
||||
| WaitForStop() | raft.go:2124-2128 | MISSING | — | No wait-for-stop mechanism |
|
||||
| Delete() | raft.go:2130-2143 | MISSING | — | No delete with WAL cleanup |
|
||||
| IsDeleted() | raft.go:2145-2149 | MISSING | — | No deleted flag |
|
||||
| Created() | raft.go:2115-2118 | PORTED | src/NATS.Server/Raft/RaftNode.cs:43 | Added `CreatedUtc` timestamp captured at node construction and exposed for runtime introspection |
|
||||
| Stop() | raft.go:2120-2122 | PORTED | src/NATS.Server/Raft/RaftNode.cs:1095 | Added `Stop()` lifecycle API that transitions to follower, clears leader markers, and signals stop waiters |
|
||||
| WaitForStop() | raft.go:2124-2128 | PORTED | src/NATS.Server/Raft/RaftNode.cs:1104 | Added synchronous `WaitForStop()` backed by stop completion signal |
|
||||
| Delete() | raft.go:2130-2143 | PORTED | src/NATS.Server/Raft/RaftNode.cs:1109 | Added `Delete()` lifecycle API that stops node, marks deleted, and removes persisted raft directory when configured |
|
||||
| IsDeleted() | raft.go:2145-2149 | PORTED | src/NATS.Server/Raft/RaftNode.cs:69 | Added `IsDeleted` state accessor |
|
||||
| RecreateInternalSubs() | raft.go:658-747 | MISSING | — | No NATS internal subscription management |
|
||||
| IsSystemAccount() | raft.go:648-649 | NOT_APPLICABLE | — | .NET does not have system account NRG routing |
|
||||
| GetTrafficAccountName() | raft.go:652-656 | NOT_APPLICABLE | — | .NET does not have account NRG routing |
|
||||
@@ -224,7 +224,7 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| selectNextLeader() | raft.go:1887-1897 | MISSING | — | No next-leader selection by highest index |
|
||||
| resetElectionTimeout() | raft.go:2241-2243 | PORTED | src/NATS.Server/Raft/RaftNode.cs:737 | ResetElectionTimeout with Timer |
|
||||
| randElectionTimeout() | raft.go:2235-2238 | PORTED | src/NATS.Server/Raft/RaftNode.cs:727 | RandomizedElectionTimeout |
|
||||
| randCampaignTimeout() | raft.go:1995-1998 | MISSING | — | No separate campaign timeout |
|
||||
| randCampaignTimeout() | raft.go:1995-1998 | PORTED | src/NATS.Server/Raft/RaftNode.cs:822 | Added `RandomizedCampaignTimeout()` using Go-equivalent 100-800ms jitter window |
|
||||
|
||||
### golang/nats-server/server/raft.go — Wire Format & RPC
|
||||
|
||||
@@ -350,12 +350,12 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| Election timeout defaults | raft.go:277-287 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:56-57 | .NET uses 150-300ms defaults; Go uses 4-9s defaults. Different design choice |
|
||||
| hbInterval | raft.go:283 | MISSING | — | No heartbeat interval constant |
|
||||
| lostQuorumInterval | raft.go:284 | MISSING | — | No lost quorum interval |
|
||||
| observerModeInterval | raft.go:286 | MISSING | — | No observer mode interval |
|
||||
| peerRemoveTimeout | raft.go:287 | MISSING | — | No peer remove timeout |
|
||||
| hbInterval | raft.go:283 | PORTED | src/NATS.Server/Raft/RaftNode.cs:10 | Added `HbIntervalDefault = 1s` constant |
|
||||
| lostQuorumInterval | raft.go:284 | PORTED | src/NATS.Server/Raft/RaftNode.cs:11 | Added `LostQuorumIntervalDefault = 10s` constant |
|
||||
| observerModeInterval | raft.go:286 | PORTED | src/NATS.Server/Raft/RaftNode.cs:12 | Added `ObserverModeIntervalDefault = 48h` constant |
|
||||
| peerRemoveTimeout | raft.go:287 | PORTED | src/NATS.Server/Raft/RaftNode.cs:13 | Added `PeerRemoveTimeoutDefault = 5m` constant |
|
||||
| Error sentinels | raft.go:319-343 | PARTIAL | — | .NET uses InvalidOperationException instead of typed error sentinels |
|
||||
| noLeader / noVote constants | raft.go:4954-4956 | MISSING | — | No explicit no-leader/no-vote constants |
|
||||
| noLeader / noVote constants | raft.go:4954-4956 | PORTED | src/NATS.Server/Raft/RaftNode.cs:5 | Added explicit `NoLeader` / `NoVote` empty-string constants |
|
||||
| paeDropThreshold / paeWarnThreshold | raft.go:4399-4401 | MISSING | — | No pending append entry limits |
|
||||
| maxBatch / maxEntries | raft.go:3004-3005 | MISSING | — | No proposal batching thresholds |
|
||||
| extensionState | raft.go:4462-4468 | MISSING | — | No domain extension state tracking |
|
||||
@@ -387,3 +387,5 @@ After porting work is completed:
|
||||
|------|--------|----|
|
||||
| 2026-02-25 | File created with LLM analysis instructions | auto |
|
||||
| 2026-02-25 | Full gap analysis completed: 196 items inventoried across 12 categories. Summary: 46 PORTED, 38 PARTIAL, 99 MISSING, 13 NOT_APPLICABLE, 0 DEFERRED. Wire format is well-ported; core state machine (run loop, catchup, WAL integration) is largely missing. | claude-opus |
|
||||
| 2026-02-25 | Ported RAFT API/lifecycle parity batch: LeaderSince/GroupLeader/Leaderless/HadPreviousLeader, observer toggles, Progress/Size/Applied, cluster-size adjustors, stop/delete APIs, campaign timeout jitter, and core timing/leader constants with targeted tests in `RaftNodeParityBatch2Tests`. | codex |
|
||||
| 2026-02-26 | Ported RAFT parity batch: added `ProposeMultiAsync`, peer parity fields (`Lag`, `Current`) + refresh helpers, explicit `CommittedEntry` payload type, and general `RaftEntry` model/wire conversion helpers with focused tests in `RaftParityBatch3Tests`. | codex |
|
||||
|
||||
+29
-25
@@ -92,12 +92,12 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| `RouteType` (type alias + consts `Implicit`/`Explicit`) | route.go:36–44 | PORTED | `src/NATS.Server/Routes/RouteConnection.cs` — implicit/explicit distinction tracked in `RouteConnection` handshake and `RouteManager.ConnectToRouteWithRetryAsync` | No explicit enum; Implicit/Explicit distinction is encoded in how routes are established (solicited vs inbound) |
|
||||
| `route` struct (unexported) | route.go:56–94 | PARTIAL | `src/NATS.Server/Routes/RouteConnection.cs:8` | Fields `remoteID`, `poolIdx`, `accName`, `noPool`, `compression`, `gossipMode` are present. Fields for `lnoc`, `lnocu`, `jetstream`, `connectURLs`, `wsConnURLs`, `gatewayURL`, `leafnodeURL`, `hash`, `idHash`, `startNewRoute`, `retry` are MISSING — not modelled in .NET |
|
||||
| `routeInfo` struct (unexported) | route.go:97–101 | MISSING | — | Used internally for deferred pool-connection creation after first PONG; no .NET equivalent |
|
||||
| `gossipDefault`/`gossipDisabled`/`gossipOverride` consts | route.go:104–108 | MISSING | — | Gossip mode bytes used in INFO propagation; not implemented in .NET |
|
||||
| `connectInfo` struct | route.go:110–124 | PARTIAL | `src/NATS.Server/Routes/RouteConnection.cs:328` (`BuildConnectInfoJson`) | .NET builds a simplified JSON payload; `connectInfo` fields Echo, Verbose, Pedantic, TLS, Headers, Cluster, Dynamic, LNOC, LNOCU are all MISSING from the .NET payload |
|
||||
| `ConProto`/`InfoProto` protocol format strings | route.go:127–130 | PARTIAL | `src/NATS.Server/Routes/RouteConnection.cs:73,83` | Handshake uses a simplified `ROUTE <serverId>` format rather than `CONNECT <json>` / `INFO <json>` |
|
||||
| `gossipDefault`/`gossipDisabled`/`gossipOverride` consts | route.go:104–108 | PORTED | `src/NATS.Server/Routes/RouteManager.cs:17–19` | Gossip mode constants are defined as byte constants (`GossipDefault`, `GossipDisabled`, `GossipOverride`) |
|
||||
| `connectInfo` struct | route.go:110–124 | PORTED | `src/NATS.Server/Routes/RouteConnection.cs:378` (`BuildConnectInfoJson`) | Connect payload now includes parity fields: `echo`, `verbose`, `pedantic`, `tls_required`, `headers`, `cluster`, `dynamic`, `lnoc`, `lnocu` |
|
||||
| `ConProto`/`InfoProto` protocol format strings | route.go:127–130 | PARTIAL | `src/NATS.Server/Routes/RouteConnection.cs:11–12,83–95` | CONNECT/INFO format constants added, but active wire handshake remains simplified `ROUTE <serverId>` rather than full CONNECT/INFO exchange |
|
||||
| `clusterTLSInsecureWarning` const | route.go:134 | NOT_APPLICABLE | — | TLS not yet implemented in .NET port; warning string has no counterpart |
|
||||
| `defaultRouteMaxPingInterval` const | route.go:140 | MISSING | — | Ping interval management for compression RTT auto-mode not implemented |
|
||||
| `routeConnectDelay`/`routeConnectMaxDelay`/`routeMaxPingInterval` vars | route.go:145–148 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:486` (250ms hardcoded delay) | .NET hardcodes 250ms retry delay; Go uses configurable `DEFAULT_ROUTE_CONNECT` with exponential backoff |
|
||||
| `defaultRouteMaxPingInterval` const | route.go:140 | PORTED | `src/NATS.Server/Routes/RouteManager.cs:20` | `DefaultRouteMaxPingInterval` constant added |
|
||||
| `routeConnectDelay`/`routeConnectMaxDelay`/`routeMaxPingInterval` vars | route.go:145–148 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:14–15,20,751–757` | Route reconnect delay now uses bounded exponential backoff (`ComputeRetryDelay`) with dedicated delay/max constants; still not runtime-configurable from route config |
|
||||
| `(c *client) removeReplySub` | route.go:151 | MISSING | — | Reply-sub cleanup for remote reply subs not implemented |
|
||||
| `(c *client) processAccountSub` | route.go:167 | NOT_APPLICABLE | — | Gateway-only path; gateway sub interest not in routes module |
|
||||
| `(c *client) processAccountUnsub` | route.go:174 | NOT_APPLICABLE | — | Gateway-only path |
|
||||
@@ -110,48 +110,48 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| `(c *client) processRouteInfo` | route.go:549 | MISSING | — | Full INFO processing (cluster name negotiation, compression negotiation, duplicate detection, account route setup) not implemented; .NET handshake is a simple ID exchange |
|
||||
| `(s *Server) negotiateRouteCompression` | route.go:897 | PARTIAL | `src/NATS.Server/Routes/RouteCompressionCodec.cs:82` (`NegotiateCompression`) | .NET has the negotiation logic; but integration into handshake (INFO exchange, switching compression writer/reader mid-stream) is MISSING |
|
||||
| `(s *Server) updateRemoteRoutePerms` | route.go:953 | MISSING | — | Route permission update on INFO reload not implemented |
|
||||
| `(s *Server) sendAsyncInfoToClients` | route.go:1015 | MISSING | — | Async INFO broadcast to connected clients not implemented |
|
||||
| `(s *Server) processImplicitRoute` | route.go:1043 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:107` (`ProcessImplicitRoute`) | .NET collects discovered URLs; missing: duplicate-ID check, pinned-account re-solicitation, `hasThisRouteConfigured` guard |
|
||||
| `(s *Server) hasThisRouteConfigured` | route.go:1104 | MISSING | — | Check whether incoming gossip URL is already a configured explicit route; not implemented |
|
||||
| `(s *Server) sendAsyncInfoToClients` | route.go:1015 | PORTED | `src/NATS.Server/NatsServer.cs:181` | Added `UpdateServerINFOAndSendINFOToClients()` INFO refresh/broadcast path to connected clients; validated with targeted socket-level parity test. |
|
||||
| `(s *Server) processImplicitRoute` | route.go:1043 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:115` (`ProcessImplicitRoute`) | Now guards discovered URLs with `HasThisRouteConfigured` and normalized duplicate checks; pinned-account re-solicitation and duplicate-ID handling remain missing |
|
||||
| `(s *Server) hasThisRouteConfigured` | route.go:1104 | PORTED | `src/NATS.Server/Routes/RouteManager.cs:164` | Implemented with normalized URL matching against explicit configured routes and known route URLs |
|
||||
| `(s *Server) forwardNewRouteInfoToKnownServers` | route.go:1139 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:127` (`ForwardNewRouteInfoToKnownServers`) | .NET raises an event with the new peer URL; missing: gossip mode logic (`gossipDefault`/`gossipDisabled`/`gossipOverride`), pinned-account route filtering, serialized INFO JSON sending |
|
||||
| `(c *client) canImport` | route.go:1226 | MISSING | — | Route import permission check not implemented |
|
||||
| `(c *client) canExport` | route.go:1235 | MISSING | — | Route export permission check not implemented |
|
||||
| `(c *client) setRoutePermissions` | route.go:1244 | MISSING | — | Route permission mapping (Import→Publish, Export→Subscribe) not implemented |
|
||||
| `asubs` struct | route.go:1263 | NOT_APPLICABLE | — | Internal Go helper to group subscriptions by account during cleanup; .NET uses LINQ equivalents |
|
||||
| `getAccNameFromRoutedSubKey` | route.go:1273 | MISSING | — | Sub key parsing for account name extraction not implemented |
|
||||
| `(c *client) getRoutedSubKeyInfo` | route.go:1290 | MISSING | — | Helper to determine account/key info for a route's subscriptions; not implemented |
|
||||
| `(c *client) removeRemoteSubs` | route.go:1299 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:577` (`RemoveRoute`) | .NET removes the route connection but does NOT remove individual remote subscriptions from the SubList on close |
|
||||
| `(c *client) removeRemoteSubsForAcc` | route.go:1352 | MISSING | — | Per-account remote sub removal for dedicated route transition not implemented |
|
||||
| `(c *client) parseUnsubProto` | route.go:1366 | MISSING | — | RS-/LS- protocol arg parser not implemented; .NET `ReadFramesAsync` only extracts account/subject/queue loosely |
|
||||
| `getAccNameFromRoutedSubKey` | route.go:1273 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:176` (`GetAccNameFromRoutedSubKey`) | Routed-sub key parsing helper added and validated with parity tests |
|
||||
| `(c *client) getRoutedSubKeyInfo` | route.go:1290 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:179` (`GetRoutedSubKeyInfo`) | Routed-sub key decomposition helper added (route/account/subject/queue) and covered by tests |
|
||||
| `(c *client) removeRemoteSubs` | route.go:1299 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:195` (`RemoveRemoteSubs`), `src/NATS.Server/Routes/RouteManager.cs:593` (`WatchRouteAsync`), `src/NATS.Server/NatsServer.cs:960` (`RemoveRemoteSubscriptionsForRoute`) | Route close/removal now triggers remote-sub cleanup from account SubLists when the last connection for a remote server is gone |
|
||||
| `(c *client) removeRemoteSubsForAcc` | route.go:1352 | PARTIAL | `src/NATS.Server/Subscriptions/SubList.cs:229` (`RemoveRemoteSubsForAccount`), `src/NATS.Server/Routes/RouteManager.cs:286` (`UnregisterAccountRoute`), `src/NATS.Server/NatsServer.cs:966` (`RemoveRemoteSubscriptionsForRouteAccount`) | Per-account cleanup path is now wired on dedicated-route unregistration; full dedicated-route transition parity remains incomplete |
|
||||
| `(c *client) parseUnsubProto` | route.go:1366 | PORTED | `src/NATS.Server/Routes/RouteConnection.cs:357` (`TryParseRemoteUnsub`) | Dedicated RS-/LS- parser now handles account/subject/optional-queue extraction and is used by frame processing |
|
||||
| `(c *client) processRemoteUnsub` | route.go:1404 | PARTIAL | `src/NATS.Server/Routes/RouteConnection.cs:177–185` | .NET fires `RemoteSubscriptionReceived` with `IsRemoval=true`; missing: sub key lookup and removal from SubList, gateway/leafnode interest updates |
|
||||
| `(c *client) processRemoteSub` | route.go:1489 | PARTIAL | `src/NATS.Server/Routes/RouteConnection.cs:167–175` | .NET fires `RemoteSubscriptionReceived`; missing: key construction with type byte prefix, account lookup/creation, permission check (`canExport`), SubList insertion, gateway/leafnode updates, queue-weight delta tracking |
|
||||
| `(c *client) addRouteSubOrUnsubProtoToBuf` | route.go:1729 | PARTIAL | `src/NATS.Server/Routes/RouteConnection.cs:95–109` (`SendRsPlusAsync`/`SendRsMinusAsync`) | .NET sends RS+/RS- with account and optional queue; missing: LS+/LS- variant for leaf origin clusters, queue weight field in RS+ |
|
||||
| `(c *client) addRouteSubOrUnsubProtoToBuf` | route.go:1729 | PORTED | `src/NATS.Server/Routes/RouteConnection.cs:179` (`SendRouteSubOrUnSubProtosAsync`) | Added low-level buffered route-proto sender that batches RS+/RS-/LS+/LS- control lines into a single write/flush. |
|
||||
| `(s *Server) sendSubsToRoute` | route.go:1781 | MISSING | — | Bulk send of local subscription interest to newly connected route not implemented; .NET only propagates incremental sub/unsub |
|
||||
| `(c *client) sendRouteSubProtos` | route.go:1881 | MISSING | — | Batch RS+ send not implemented |
|
||||
| `(c *client) sendRouteUnSubProtos` | route.go:1890 | MISSING | — | Batch RS- send not implemented |
|
||||
| `(c *client) sendRouteSubOrUnSubProtos` | route.go:1898 | MISSING | — | Low-level batch RS+/RS-/LS+/LS- sender not implemented |
|
||||
| `(c *client) sendRouteSubProtos` | route.go:1881 | PORTED | `src/NATS.Server/Routes/RouteConnection.cs:146` (`SendRouteSubProtosAsync`) | Added batched RS+ emission from remote-subscription models with queue/weight support. |
|
||||
| `(c *client) sendRouteUnSubProtos` | route.go:1890 | PORTED | `src/NATS.Server/Routes/RouteConnection.cs:165` (`SendRouteUnSubProtosAsync`) | Added batched RS- emission from remote-subscription models. |
|
||||
| `(c *client) sendRouteSubOrUnSubProtos` | route.go:1898 | PORTED | `src/NATS.Server/Routes/RouteConnection.cs:179` (`SendRouteSubOrUnSubProtosAsync`) | Added low-level batch sender for arbitrary route sub/unsub protocol lines. |
|
||||
| `(s *Server) createRoute` | route.go:1935 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:447,462` (`HandleInboundRouteAsync`/`ConnectToRouteWithRetryAsync`) | .NET creates a RouteConnection and performs handshake; missing: TLS setup, auth timeout timer, CONNECT protocol sending, INFO JSON sending, compression negotiation, ping timer |
|
||||
| `routeShouldDelayInfo` | route.go:2082 | MISSING | — | Logic to delay initial INFO until pool connection auth is confirmed not implemented |
|
||||
| `(s *Server) generateRouteInitialInfoJSON` | route.go:2090 | MISSING | — | Route INFO JSON generation (with nonce, pool index, gossip mode, compression) not implemented |
|
||||
| `(s *Server) addRoute` | route.go:2113 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:496` (`Register`) | .NET registers route in dictionary; missing: pool index management, duplicate detection with `handleDuplicateRoute`, per-account route registration in `accRoutes`, `sendSubsToRoute` call, gateway/leafnode URL propagation, `forwardNewRouteInfoToKnownServers` |
|
||||
| `hasSolicitedRoute` | route.go:2438 | MISSING | — | Helper to find a solicited route in a pool slice; not implemented |
|
||||
| `upgradeRouteToSolicited` | route.go:2458 | MISSING | — | Upgrade an inbound route to solicited status; not implemented |
|
||||
| `hasSolicitedRoute` | route.go:2438 | PORTED | `src/NATS.Server/Routes/RouteManager.cs:721` | Implemented helper to query whether a given remote server currently has a solicited route |
|
||||
| `upgradeRouteToSolicited` | route.go:2458 | PORTED | `src/NATS.Server/Routes/RouteManager.cs:730` | Implemented route upgrade helper to flip an existing route into solicited mode |
|
||||
| `handleDuplicateRoute` | route.go:2473 | MISSING | — | Duplicate route resolution (close extra connection, preserve retry flag) not implemented |
|
||||
| `(c *client) importFilter` | route.go:2510 | MISSING | — | Permission-based subscription filter for sending to routes not implemented |
|
||||
| `(s *Server) updateRouteSubscriptionMap` | route.go:2519 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:381,392` (`PropagateLocalSubscription`/`PropagateLocalUnsubscription`) | .NET broadcasts RS+/RS- to all routes; missing: account routePoolIdx-based routing, queue-weight dedup (`sqmu`/`lqws`), no-pool route handling, gateway/leafnode interest updates |
|
||||
| `(s *Server) startRouteAcceptLoop` | route.go:2696 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:333` (`StartAsync`) | .NET binds and starts accept loop, solicits configured routes; missing: cluster name logging, TLS config on accept, routeInfo construction, advertise/NoAdvertise, LeafNode/Gateway URL propagation |
|
||||
| `(s *Server) setRouteInfoHostPortAndIP` | route.go:2829 | MISSING | — | Route INFO host/port/IP with Cluster.Advertise support not implemented |
|
||||
| `(s *Server) StartRouting` | route.go:2849 | PORTED | `src/NATS.Server/Routes/RouteManager.cs:333` (`StartAsync`) | Functionally equivalent: starts accept loop and solicits routes |
|
||||
| `(s *Server) reConnectToRoute` | route.go:2861 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:462` (`ConnectToRouteWithRetryAsync`) | .NET retries indefinitely with 250ms delay; missing: random jitter delay, explicit vs implicit distinction affecting delay, quit-channel integration |
|
||||
| `(s *Server) routeStillValid` | route.go:2881 | MISSING | — | Check that a route URL is still in configured routes list (for reconnect guard) not implemented |
|
||||
| `(s *Server) connectToRoute` | route.go:2890 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:462` (`ConnectToRouteWithRetryAsync`) | .NET connects and retries; missing: explicit/implicit distinction, ConnectRetries limit, exponential backoff (`ConnectBackoff`), `routesToSelf` exclusion, address randomization from DNS |
|
||||
| `(c *client) isSolicitedRoute` | route.go:2976 | MISSING | — | Helper predicate; not implemented |
|
||||
| `(s *Server) reConnectToRoute` | route.go:2861 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:512` (`ConnectToRouteWithRetryAsync`) | Retry loop now uses bounded exponential backoff and route validity guard; jitter, explicit/implicit-specific delay behavior, and quit-channel parity remain missing |
|
||||
| `(s *Server) routeStillValid` | route.go:2881 | PORTED | `src/NATS.Server/Routes/RouteManager.cs:180` | Implemented reconnect guard that validates configured and discovered route URLs using normalized comparisons |
|
||||
| `(s *Server) connectToRoute` | route.go:2890 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:512` (`ConnectToRouteWithRetryAsync`) | Exponential backoff and route-validity checks are implemented; ConnectRetries/ConnectBackoff config parity, routes-to-self exclusion, and DNS randomization are still missing |
|
||||
| `(c *client) isSolicitedRoute` | route.go:2976 | PORTED | `src/NATS.Server/Routes/RouteConnection.cs:35,370` | `IsSolicited` state is tracked on route connections and exposed via `IsSolicitedRoute()` helper |
|
||||
| `(s *Server) saveRouteTLSName` | route.go:2985 | NOT_APPLICABLE | — | TLS not yet implemented in .NET port |
|
||||
| `(s *Server) solicitRoutes` | route.go:2996 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:347–354` | .NET solicits configured routes with pool connections; missing: per-account (pinned) route solicitation, `saveRouteTLSName` |
|
||||
| `(c *client) processRouteConnect` | route.go:3011 | MISSING | — | Parsing and validation of inbound CONNECT from route (cluster name check, wrong-port detection, LNOC/LNOCU flags) not implemented; .NET uses a simpler handshake |
|
||||
| `(s *Server) removeAllRoutesExcept` | route.go:3085 | PORTED | `src/NATS.Server/Routes/RouteManager.cs:602` (`RemoveAllRoutesExcept`) | Equivalent behavior: remove all routes not in the keep-set |
|
||||
| `(s *Server) removeRoute` | route.go:3113 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:577` (`RemoveRoute`) | .NET removes from `_routes` dict; missing: per-account route cleanup (`accRoutes`), hash removal, gateway/leafnode URL withdrawal, noPool counter, reconnect-after-noPool logic |
|
||||
| `(s *Server) isDuplicateServerName` | route.go:3233 | MISSING | — | Duplicate server name detection across routes not implemented |
|
||||
| `(s *Server) removeRoute` | route.go:3113 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:632` (`RemoveRoute`) | Remove path now also cleans hash index and per-account route mappings tied to removed connections; gateway/leafnode URL withdrawal, noPool counters, and reconnect-after-noPool logic remain missing |
|
||||
| `(s *Server) isDuplicateServerName` | route.go:3233 | PORTED | `src/NATS.Server/Routes/RouteManager.cs:748` | Duplicate server-name detection helper implemented against current connected-server ID set |
|
||||
| `(s *Server) forEachNonPerAccountRoute` | route.go:3263 | NOT_APPLICABLE | — | Internal Go iterator over route slice; .NET uses `_routes.Values` LINQ directly |
|
||||
| `(s *Server) forEachRoute` | route.go:3277 | NOT_APPLICABLE | — | Internal Go iterator; .NET enumerates `_routes` and `_accountRoutes` directly |
|
||||
| `(s *Server) forEachRouteIdx` | route.go:3292 | NOT_APPLICABLE | — | Internal Go pool-index iterator; .NET `ComputeRoutePoolIdx` achieves equivalent selection |
|
||||
@@ -182,5 +182,9 @@ After porting work is completed:
|
||||
|
||||
| Date | Change | By |
|
||||
|------|--------|----|
|
||||
| 2026-02-26 | Ported async INFO broadcast parity slice: wired/validated `UpdateServerINFOAndSendINFOToClients()` as the `sendAsyncInfoToClients` equivalent and added targeted socket broadcast test (`RouteInfoBroadcastParityBatch4Tests`). | codex |
|
||||
| 2026-02-25 | File created with LLM analysis instructions | auto |
|
||||
| 2026-02-25 | Full gap inventory populated: 57 Go symbols classified across route.go (3,314 lines). Counts: PORTED 4, PARTIAL 21, MISSING 23, NOT_APPLICABLE 9, DEFERRED 0 | auto |
|
||||
| 2026-02-25 | Ported route parity helper batch: gossip/default constants, connect-info parity fields, configured-route/reconnect guard helpers, solicited-route helpers, duplicate-server-name detection, RS-/LS- parser, and LS+/LS- + queue-weight wire helpers; updated row statuses and notes | codex |
|
||||
| 2026-02-25 | Ported routed-sub key helpers and remote-sub cleanup parity batch: added `getAccNameFromRoutedSubKey`/`getRoutedSubKeyInfo` equivalents plus route-close and per-account cleanup plumbing with targeted tests | codex |
|
||||
| 2026-02-25 | Ported route batch-proto parity batch: added buffered batch sender APIs (`SendRouteSubProtosAsync`, `SendRouteUnSubProtosAsync`, `SendRouteSubOrUnSubProtosAsync`) for RS+/RS-/LS+/LS- protocol frames with targeted tests (`RouteBatchProtoParityBatch3Tests`) | codex |
|
||||
|
||||
+34
-30
@@ -100,14 +100,14 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| `level` (struct) | sublist.go:96 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:967` (private `TrieLevel`) | Inner class; `nodes`, `pwc`, `fwc` all present |
|
||||
| `newNode()` | sublist.go:102 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:974` | Inline `new TrieNode()` |
|
||||
| `newLevel()` | sublist.go:107 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:967` | Inline `new TrieLevel()` |
|
||||
| `NewSublist(bool)` | sublist.go:117 | PARTIAL | `src/NATS.Server/Subscriptions/SubList.cs:11` | .NET `SubList` always starts with cache; no `enableCache` constructor param |
|
||||
| `NewSublist(bool)` | sublist.go:117 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:40` | Added `SubList(bool enableCache)` constructor to explicitly control cache behavior |
|
||||
| `NewSublistWithCache()` | sublist.go:125 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:11` | Default `new SubList()` has cache enabled |
|
||||
| `NewSublistNoCache()` | sublist.go:130 | MISSING | — | No .NET equivalent; SubList always caches |
|
||||
| `CacheEnabled()` | sublist.go:135 | MISSING | — | No public method to query whether cache is on; `CacheCount` exists but different semantics |
|
||||
| `RegisterNotification()` | sublist.go:149 | MISSING | — | Go sends `true/false` on channel when first/last interest added/removed; .NET uses `InterestChanged` event which fires on every change but doesn't replicate channel-based deferred notify semantics |
|
||||
| `RegisterQueueNotification()` | sublist.go:153 | MISSING | — | Queue-specific interest notification; no .NET equivalent |
|
||||
| `ClearNotification()` | sublist.go:227 | MISSING | — | No .NET equivalent for removing a notification channel |
|
||||
| `ClearQueueNotification()` | sublist.go:231 | MISSING | — | No .NET equivalent |
|
||||
| `NewSublistNoCache()` | sublist.go:130 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:46` | Added `NewSublistNoCache()` factory returning `new SubList(false)` |
|
||||
| `CacheEnabled()` | sublist.go:135 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:48` | Added `CacheEnabled()` to expose current cache mode |
|
||||
| `RegisterNotification()` | sublist.go:149 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:50` | Added first/last-interest callback registration (`Action<bool>`) and transition notifications in `Insert`/`Remove` |
|
||||
| `RegisterQueueNotification()` | sublist.go:153 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:57` | Added queue-scoped first/last-interest callback registration with immediate current-state callback and insert/remove transition tracking |
|
||||
| `ClearNotification()` | sublist.go:227 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:52` | Added notification callback clearing method |
|
||||
| `ClearQueueNotification()` | sublist.go:231 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:83` | Added queue-scoped notification de-registration across both insert/remove transition maps |
|
||||
| `chkForInsertNotification()` | sublist.go:301 | NOT_APPLICABLE | — | Internal helper for channel notification; replaced by `InterestChanged` event emission in `Insert()` |
|
||||
| `chkForRemoveNotification()` | sublist.go:317 | NOT_APPLICABLE | — | Internal helper; replaced by `InterestChanged` event emission in `Remove()` |
|
||||
| `sendNotification()` | sublist.go:253 | NOT_APPLICABLE | — | Non-blocking channel send; Go-specific pattern, no .NET equivalent needed |
|
||||
@@ -129,7 +129,7 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| `hasInterest()` | sublist.go:624 | NOT_APPLICABLE | — | Internal; maps to `HasInterestLevel()` private helper |
|
||||
| `reduceCacheCount()` | sublist.go:675 | PORTED | `src/NATS.Server/Subscriptions/SubListCacheSweeper.cs:7` + `SubList.cs:458` | Background goroutine mapped to `SubListCacheSweeper` + `SweepCache()` |
|
||||
| `isRemoteQSub()` | sublist.go:689 | NOT_APPLICABLE | — | Go has client.kind == ROUTER/LEAF; .NET uses `RemoteSubscription` model instead |
|
||||
| `UpdateRemoteQSub()` | sublist.go:695 | MISSING | — | Go updates weight of remote qsub; .NET uses `ApplyRemoteSub()` for a different model (full add/remove) |
|
||||
| `UpdateRemoteQSub()` | sublist.go:695 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:218` | Added remote queue-sub weight update path that mutates existing entry and bumps generation |
|
||||
| `addNodeToResults()` | sublist.go:706 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:549` (private `AddNodeToResults()`) | Remote qsub weight expansion present in Go missing in .NET |
|
||||
| `findQSlot()` | sublist.go:745 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:562` (inline in `AddNodeToResults`) | Inlined in .NET |
|
||||
| `matchLevel()` | sublist.go:757 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:506` (private `MatchLevel()`) | Core trie descent algorithm |
|
||||
@@ -144,33 +144,33 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| `Count()` | sublist.go:1023 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:41` (`Count` property) | |
|
||||
| `CacheCount()` | sublist.go:1030 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:97` (`CacheCount` property) | |
|
||||
| `SublistStats` (struct) | sublist.go:1038 | PORTED | `src/NATS.Server/Subscriptions/SubListStats.cs:3` | All public fields present; unexported fields `totFanout`, `cacheCnt`, `cacheHits` dropped (computed inline in `Stats()`) |
|
||||
| `SublistStats.add()` | sublist.go:1052 | MISSING | — | Aggregates multiple SublistStats into one; used for cluster monitoring; no .NET equivalent |
|
||||
| `SublistStats.add()` | sublist.go:1052 | PORTED | `src/NATS.Server/Subscriptions/SubListStats.cs:18` | Added `Add(SubListStats)` aggregation including cache-hit, fanout, and max-fanout rollups |
|
||||
| `Stats()` | sublist.go:1076 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:580` | Full fanout stats computed |
|
||||
| `numLevels()` | sublist.go:1120 | MISSING | — | Debug/test utility counting trie depth; not ported |
|
||||
| `visitLevel()` | sublist.go:1126 | MISSING | — | Internal helper for `numLevels()`; not ported |
|
||||
| `subjectHasWildcard()` | sublist.go:1159 | PARTIAL | `src/NATS.Server/Subscriptions/SubjectMatch.cs:50` (`IsLiteral()` — inverse) | .NET `!IsLiteral()` is equivalent but not a dedicated function |
|
||||
| `numLevels()` | sublist.go:1120 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:1030` | Added internal trie-depth utility (`NumLevels`) for parity/debug verification |
|
||||
| `visitLevel()` | sublist.go:1126 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:1223` | Added recursive depth traversal helper used by `NumLevels()` |
|
||||
| `subjectHasWildcard()` | sublist.go:1159 | PORTED | `src/NATS.Server/Subscriptions/SubjectMatch.cs:72` | Added dedicated `SubjectHasWildcard()` helper |
|
||||
| `subjectIsLiteral()` | sublist.go:1174 | PORTED | `src/NATS.Server/Subscriptions/SubjectMatch.cs:50` (`IsLiteral()`) | Exact equivalent |
|
||||
| `IsValidPublishSubject()` | sublist.go:1187 | PORTED | `src/NATS.Server/Subscriptions/SubjectMatch.cs:67` | |
|
||||
| `IsValidSubject()` | sublist.go:1192 | PORTED | `src/NATS.Server/Subscriptions/SubjectMatch.cs:9` | |
|
||||
| `isValidSubject()` (internal, checkRunes) | sublist.go:1196 | PORTED | `src/NATS.Server/Subscriptions/SubjectMatch.cs:211` (`IsValidSubject(string, bool)`) | |
|
||||
| `IsValidLiteralSubject()` | sublist.go:1236 | PARTIAL | `src/NATS.Server/Subscriptions/SubjectMatch.cs:50` (`IsLiteral()`) | `IsLiteral()` does not validate the subject first; `IsValidPublishSubject()` combines both |
|
||||
| `IsValidLiteralSubject()` | sublist.go:1236 | PORTED | `src/NATS.Server/Subscriptions/SubjectMatch.cs:74` | Added dedicated `IsValidLiteralSubject()` helper mapped to publish-subject validation |
|
||||
| `isValidLiteralSubject()` (tokens iter) | sublist.go:1241 | NOT_APPLICABLE | — | Takes `iter.Seq[string]` (Go 1.23 iterator); C# uses different iteration model |
|
||||
| `ValidateMapping()` | sublist.go:1258 | MISSING | — | Validates a mapping destination subject string including `{{function()}}` syntax; no public .NET equivalent |
|
||||
| `ValidateMapping()` | sublist.go:1258 | PORTED | `src/NATS.Server/Subscriptions/SubjectTransform.cs:138` | Added public destination-template validator with Go-compatible function parsing checks. |
|
||||
| `analyzeTokens()` | sublist.go:1298 | NOT_APPLICABLE | — | Internal helper used only in `SubjectsCollide()`; logic inlined in .NET `SubjectsCollide()` |
|
||||
| `tokensCanMatch()` | sublist.go:1314 | PORTED | `src/NATS.Server/Subscriptions/SubjectMatch.cs:199` (private `TokensCanMatch()`) | |
|
||||
| `SubjectsCollide()` | sublist.go:1326 | PORTED | `src/NATS.Server/Subscriptions/SubjectMatch.cs:159` | |
|
||||
| `numTokens()` | sublist.go:1374 | PORTED | `src/NATS.Server/Subscriptions/SubjectMatch.cs:118` (`NumTokens()`) | |
|
||||
| `tokenAt()` | sublist.go:1389 | PORTED | `src/NATS.Server/Subscriptions/SubjectMatch.cs:132` (`TokenAt()`) | Go is 1-based index; .NET is 0-based |
|
||||
| `tokenizeSubjectIntoSlice()` | sublist.go:1407 | NOT_APPLICABLE | — | Internal slice-reuse helper; .NET uses `Tokenize()` private method in SubList |
|
||||
| `SubjectMatchesFilter()` | sublist.go:1421 | PARTIAL | `src/NATS.Server/JetStream/Storage/MemStore.cs:1175` (private), `FileStore.cs:773` (private) | Duplicated as private methods in MemStore and FileStore; not a public standalone function |
|
||||
| `subjectIsSubsetMatch()` | sublist.go:1426 | MISSING | — | No public .NET equivalent; logic exists privately in MemStore/FileStore |
|
||||
| `isSubsetMatch()` | sublist.go:1434 | MISSING | — | Internal; no public .NET equivalent |
|
||||
| `isSubsetMatchTokenized()` | sublist.go:1444 | MISSING | — | Internal; no public .NET equivalent |
|
||||
| `SubjectMatchesFilter()` | sublist.go:1421 | PORTED | `src/NATS.Server/Subscriptions/SubjectMatch.cs:205`; consumers: `src/NATS.Server/JetStream/Storage/MemStore.cs:1175`, `src/NATS.Server/JetStream/Storage/FileStore.cs:773` | Added standalone `SubjectMatch.SubjectMatchesFilter()` and switched JetStream stores to use it. |
|
||||
| `subjectIsSubsetMatch()` | sublist.go:1426 | PORTED | `src/NATS.Server/Subscriptions/SubjectMatch.cs:207` (`SubjectIsSubsetMatch`) | Added direct port that tokenizes the subject and delegates to subset matching. |
|
||||
| `isSubsetMatch()` | sublist.go:1434 | PORTED | `src/NATS.Server/Subscriptions/SubjectMatch.cs:213` (`IsSubsetMatch`) | Added token-array vs test-subject subset matcher. |
|
||||
| `isSubsetMatchTokenized()` | sublist.go:1444 | PORTED | `src/NATS.Server/Subscriptions/SubjectMatch.cs:219` (`IsSubsetMatchTokenized`) | Added tokenized subset matcher with Go-compatible `*`/`>` handling. |
|
||||
| `matchLiteral()` | sublist.go:1483 | PORTED | `src/NATS.Server/Subscriptions/SubjectMatch.cs:75` (`MatchLiteral()`) | |
|
||||
| `addLocalSub()` | sublist.go:1552 | NOT_APPLICABLE | — | Filters by client kind (CLIENT/SYSTEM/JETSTREAM/ACCOUNT/LEAF); no .NET equivalent needed (client kind routing done elsewhere) |
|
||||
| `Sublist.addNodeToSubs()` | sublist.go:1562 | NOT_APPLICABLE | — | Internal helper for `localSubs()`; not ported |
|
||||
| `Sublist.collectLocalSubs()` | sublist.go:1581 | NOT_APPLICABLE | — | Internal helper for `localSubs()`; not ported |
|
||||
| `Sublist.localSubs()` | sublist.go:1597 | MISSING | — | Returns only local-client subscriptions (excludes routes/gateways); no .NET equivalent |
|
||||
| `Sublist.localSubs()` | sublist.go:1597 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:1015` | Added local-sub collector filtering to CLIENT/SYSTEM/JETSTREAM/ACCOUNT kinds with optional LEAF inclusion |
|
||||
| `Sublist.All()` | sublist.go:1604 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:712` | |
|
||||
| `Sublist.addAllNodeToSubs()` | sublist.go:1610 | NOT_APPLICABLE | — | Internal helper for `All()`; inlined in .NET |
|
||||
| `Sublist.collectAllSubs()` | sublist.go:1627 | NOT_APPLICABLE | — | Internal; inlined in `CollectAllSubs()` private method in .NET |
|
||||
@@ -182,26 +182,26 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| Transform type constants (`NoTransform`…`Random`) | subject_transform.go:43 | PARTIAL | `src/NATS.Server/Subscriptions/SubjectTransform.cs:682` (private `TransformType` enum) | `Random` (value 11 in Go) is absent from the .NET enum and switch statement; all others ported |
|
||||
| Transform type constants (`NoTransform`…`Random`) | subject_transform.go:43 | PORTED | `src/NATS.Server/Subscriptions/SubjectTransform.cs:794` (private `TransformType` enum) | Added `Random` transform type and execution branch parity. |
|
||||
| `subjectTransform` (struct) | subject_transform.go:61 | PORTED | `src/NATS.Server/Subscriptions/SubjectTransform.cs:11` | Internal fields mapped to `_source`, `_dest`, `_sourceTokens`, `_destTokens`, `_ops` |
|
||||
| `SubjectTransformer` (interface) | subject_transform.go:73 | NOT_APPLICABLE | — | Go exports interface for polymorphism; C# uses concrete `SubjectTransform` class directly |
|
||||
| `NewSubjectTransformWithStrict()` | subject_transform.go:81 | MISSING | — | Strict mode validates that all source wildcards are used in dest; no .NET equivalent |
|
||||
| `NewSubjectTransformWithStrict()` | subject_transform.go:81 | PORTED | `src/NATS.Server/Subscriptions/SubjectTransform.cs:126` | Added strict factory variant that rejects transforms when any source wildcard is unused by destination mapping. |
|
||||
| `NewSubjectTransform()` | subject_transform.go:198 | PORTED | `src/NATS.Server/Subscriptions/SubjectTransform.cs:31` (`Create()`) | Non-strict creation |
|
||||
| `NewSubjectTransformStrict()` | subject_transform.go:202 | MISSING | — | Strict version for import mappings; no .NET equivalent |
|
||||
| `NewSubjectTransformStrict()` | subject_transform.go:202 | PORTED | `src/NATS.Server/Subscriptions/SubjectTransform.cs:135` | Added strict convenience constructor delegating to strict factory mode. |
|
||||
| `getMappingFunctionArgs()` | subject_transform.go:206 | PORTED | `src/NATS.Server/Subscriptions/SubjectTransform.cs:639` (private `GetFunctionArgs()`) | |
|
||||
| `transformIndexIntArgsHelper()` | subject_transform.go:215 | PORTED | `src/NATS.Server/Subscriptions/SubjectTransform.cs:610` (private `ParseIndexIntArgs()`) | |
|
||||
| `indexPlaceHolders()` | subject_transform.go:237 | PORTED | `src/NATS.Server/Subscriptions/SubjectTransform.cs:486` (`ParseDestToken()` + `ParseMustacheToken()`) | Split into two methods; `Random` branch missing |
|
||||
| `transformTokenize()` | subject_transform.go:378 | MISSING | — | Converts `foo.*.*` to `foo.$1.$2`; used for import subject mapping reversal; no .NET equivalent |
|
||||
| `transformUntokenize()` | subject_transform.go:399 | MISSING | — | Inverse of above; used in `reverse()`; no .NET equivalent |
|
||||
| `indexPlaceHolders()` | subject_transform.go:237 | PORTED | `src/NATS.Server/Subscriptions/SubjectTransform.cs:586` (`ParseDestToken()` + `ParseMustacheToken()`) | Split into two methods, including `Random(...)` placeholder parsing branch. |
|
||||
| `transformTokenize()` | subject_transform.go:378 | PORTED | `src/NATS.Server/Subscriptions/SubjectTransform.cs:156` | Added wildcard tokenization helper converting `*` capture positions into `$N` placeholders. |
|
||||
| `transformUntokenize()` | subject_transform.go:399 | PORTED | `src/NATS.Server/Subscriptions/SubjectTransform.cs:173` | Added inverse helper converting `$N` placeholders back to `*` tokens. |
|
||||
| `tokenizeSubject()` | subject_transform.go:414 | NOT_APPLICABLE | — | Internal tokenizer; .NET uses `string.Split('.')` or `Tokenize()` private method |
|
||||
| `subjectTransform.Match()` | subject_transform.go:433 | PORTED | `src/NATS.Server/Subscriptions/SubjectTransform.cs:126` (`Apply()`) | Renamed; returns `null` instead of error on no-match |
|
||||
| `subjectTransform.TransformSubject()` | subject_transform.go:456 | PARTIAL | `src/NATS.Server/Subscriptions/SubjectTransform.cs:126` (via `Apply()`) | `TransformSubject` (apply without match check) not separately exposed; `Apply()` always checks match |
|
||||
| `subjectTransform.getRandomPartition()` | subject_transform.go:460 | MISSING | — | `Random` transform type not implemented in .NET |
|
||||
| `subjectTransform.TransformSubject()` | subject_transform.go:456 | PORTED | `src/NATS.Server/Subscriptions/SubjectTransform.cs:251` | Added dedicated transform-only entrypoint (`TransformSubject`) that applies destination mapping without source match guard. |
|
||||
| `subjectTransform.getRandomPartition()` | subject_transform.go:460 | PORTED | `src/NATS.Server/Subscriptions/SubjectTransform.cs:374` | Added random-partition helper and transform dispatch support (`random(n)` in range `[0,n)`, zero when `n<=0`). |
|
||||
| `subjectTransform.getHashPartition()` | subject_transform.go:469 | PORTED | `src/NATS.Server/Subscriptions/SubjectTransform.cs:226` (`ComputePartition()` + `Fnv1A32()`) | FNV-1a 32-bit hash ported |
|
||||
| `subjectTransform.TransformTokenizedSubject()` | subject_transform.go:482 | PORTED | `src/NATS.Server/Subscriptions/SubjectTransform.cs:144` (private `TransformTokenized()`) | All transform types except `Random` ported |
|
||||
| `subjectTransform.reverse()` | subject_transform.go:638 | MISSING | — | Produces the inverse transform; used for import subject mapping; no .NET equivalent |
|
||||
| `subjectTransform.TransformTokenizedSubject()` | subject_transform.go:482 | PORTED | `src/NATS.Server/Subscriptions/SubjectTransform.cs:261` (private `TransformTokenized()`) | Transform execution now includes `Random` and strict/helper parity branches. |
|
||||
| `subjectTransform.reverse()` | subject_transform.go:638 | PORTED | `src/NATS.Server/Subscriptions/SubjectTransform.cs:188` | Added inverse-transform builder for wildcard-mapped transforms used in import mapping reversal scenarios. |
|
||||
| `subjectInfo()` | subject_transform.go:666 | PORTED | `src/NATS.Server/Subscriptions/SubjectTransform.cs:451` (private `SubjectInfo()`) | |
|
||||
| `ValidateMapping()` (in subject_transform.go context) | sublist.go:1258 | MISSING | — | Also defined via `NewSubjectTransform`; validates mapping destination with function syntax; no .NET public equivalent |
|
||||
| `ValidateMapping()` (in subject_transform.go context) | sublist.go:1258 | PORTED | `src/NATS.Server/Subscriptions/SubjectTransform.cs:138` | Shared mapping validator exposed publicly and used for subject-transform destination validation parity. |
|
||||
|
||||
---
|
||||
|
||||
@@ -228,5 +228,9 @@ After porting work is completed:
|
||||
|
||||
| Date | Change | By |
|
||||
|------|--------|----|
|
||||
| 2026-02-26 | Executed subscriptions batch 4 subject-transform parity closures: added strict constructors (`NewSubjectTransformWithStrict`, `NewSubjectTransformStrict`), public mapping validator, random transform type/partition helper, tokenize/untokenize helpers, reverse-transform builder, and dedicated `TransformSubject` API with targeted tests (`SubjectTransformParityBatch3Tests`). | codex |
|
||||
| 2026-02-25 | File created with LLM analysis instructions | auto |
|
||||
| 2026-02-25 | Full gap inventory populated: analyzed sublist.go (~1,729 lines) and subject_transform.go (~689 lines) against all .NET Subscriptions/*.cs files. Counted 49 PORTED, 6 PARTIAL, 22 MISSING, 27 NOT_APPLICABLE, 0 DEFERRED. | claude-sonnet-4-6 |
|
||||
| 2026-02-25 | Executed subscriptions batch 1: added cache-mode constructors/factories and first/last-interest notification APIs to `SubList`, added subject helper aliases to `SubjectMatch`, added targeted tests (`SubListCtorAndNotificationParityTests`), and reclassified 7 rows (4 MISSING + 3 PARTIAL) to PORTED | codex |
|
||||
| 2026-02-25 | Executed subscriptions batch 2: added standalone subset/filter APIs to `SubjectMatch` (`SubjectMatchesFilter`, `SubjectIsSubsetMatch`, `IsSubsetMatch`, `IsSubsetMatchTokenized`), switched MemStore/FileStore subject filter helpers to use them, and added targeted tests (`SubjectSubsetMatchParityBatch1Tests`). Reclassified 4 open rows to PORTED. | codex |
|
||||
| 2026-02-25 | Executed subscriptions batch 3: added queue-scoped notification APIs, remote queue-weight updater, `SubListStats.Add`, trie depth helpers (`NumLevels`/`VisitLevel`), and local-sub collection (`LocalSubs`) with targeted tests (`SubListParityBatch2Tests`). Reclassified 7 open rows to PORTED. | codex |
|
||||
|
||||
+27
-25
@@ -132,7 +132,7 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| Server.reloadOCSP | golang/nats-server/server/ocsp.go:734 | MISSING | — | No OCSP hot-reload support |
|
||||
| hasOCSPStatusRequest | golang/nats-server/server/ocsp.go:804 | MISSING | — | No MustStaple TLS extension detection |
|
||||
| OCSPMonitor.writeOCSPStatus | golang/nats-server/server/ocsp.go:840 | MISSING | — | No atomic file write for OCSP status persistence |
|
||||
| parseCertPEM | golang/nats-server/server/ocsp.go:867 | PARTIAL | src/NATS.Server/Tls/TlsHelper.cs:17 | `LoadCaCertificates` uses `ImportFromPemFile` but does not validate PEM block type |
|
||||
| parseCertPEM | golang/nats-server/server/ocsp.go:867 | PORTED | src/NATS.Server/Tls/TlsHelper.cs:33 | Added `ParseCertPem(string)` with explicit PEM block scanning/validation (`CERTIFICATE` only), multi-cert bundle parsing, and typed failures on unexpected block types. `LoadCaCertificates` now uses this parser. |
|
||||
| getOCSPIssuerLocally | golang/nats-server/server/ocsp.go:892 | MISSING | — | No local issuer resolution from cert bundle |
|
||||
| getOCSPIssuer | golang/nats-server/server/ocsp.go:932 | MISSING | — | No issuer resolution logic |
|
||||
| ocspStatusString | golang/nats-server/server/ocsp.go:968 | PORTED | src/NATS.Server/Events/EventTypes.cs:647 | `OcspEventBuilder.ParseStatus` and `OcspStatus` enum |
|
||||
@@ -142,7 +142,7 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| parseOCSPPeer | golang/nats-server/server/ocsp_peer.go:29 | MISSING | — | No config-file parsing for OCSP peer options |
|
||||
| parseOCSPPeer | golang/nats-server/server/ocsp_peer.go:29 | PORTED | src/NATS.Server/Configuration/ConfigProcessor.cs:1101 | `ParseOcspPeer` supports short-form bool and long-form map parsing with Go-matching field names and conversion behavior |
|
||||
| peerFromVerifiedChains | golang/nats-server/server/ocsp_peer.go:130 | MISSING | — | No peer extraction from verified chains |
|
||||
| Server.plugTLSOCSPPeer | golang/nats-server/server/ocsp_peer.go:138 | PARTIAL | src/NATS.Server/Tls/TlsHelper.cs:36 | .NET uses X509RevocationMode.Online when OcspPeerVerify set; missing full OCSP peer plugin pattern with per-chain validation |
|
||||
| Server.plugClientTLSOCSPPeer | golang/nats-server/server/ocsp_peer.go:163 | PARTIAL | src/NATS.Server/Tls/TlsHelper.cs:41 | RemoteCertificateValidationCallback with revocation check, but no OCSP-specific chain walking or event publishing |
|
||||
@@ -213,34 +213,34 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| DefaultAllowedClockSkew | golang/nats-server/server/certidp/certidp.go:30 | MISSING | — | No OCSP clock skew constant |
|
||||
| DefaultOCSPResponderTimeout | golang/nats-server/server/certidp/certidp.go:31 | MISSING | — | No OCSP responder timeout constant |
|
||||
| DefaultTTLUnsetNextUpdate | golang/nats-server/server/certidp/certidp.go:32 | MISSING | — | No default TTL when NextUpdate is unset |
|
||||
| StatusAssertion (type) | golang/nats-server/server/certidp/certidp.go:35 | PARTIAL | src/NATS.Server/Events/EventTypes.cs:595 | `OcspStatus` enum exists (Good, Revoked, Unknown) but no JSON marshal/unmarshal or bidirectional maps |
|
||||
| GetStatusAssertionStr | golang/nats-server/server/certidp/certidp.go:56 | PORTED | src/NATS.Server/Events/EventTypes.cs:647 | `OcspEventBuilder.ParseStatus` provides string-to-enum; reverse mapping implicit |
|
||||
| ChainLink (struct) | golang/nats-server/server/certidp/certidp.go:93 | MISSING | — | No chain link struct with Leaf/Issuer/OCSPWebEndpoints |
|
||||
| OCSPPeerConfig (struct) | golang/nats-server/server/certidp/certidp.go:100 | MISSING | — | No OCSP peer config struct (Verify, Timeout, ClockSkew, WarnOnly, UnknownIsGood, AllowWhenCAUnreachable, TTLUnsetNextUpdate) |
|
||||
| NewOCSPPeerConfig | golang/nats-server/server/certidp/certidp.go:110 | MISSING | — | No peer config factory |
|
||||
| DefaultAllowedClockSkew | golang/nats-server/server/certidp/certidp.go:30 | PORTED | src/NATS.Server/Tls/OcspPeerConfig.cs:105 | `OCSPPeerConfig.DefaultAllowedClockSkew` set to 30 seconds |
|
||||
| DefaultOCSPResponderTimeout | golang/nats-server/server/certidp/certidp.go:31 | PORTED | src/NATS.Server/Tls/OcspPeerConfig.cs:106 | `OCSPPeerConfig.DefaultOCSPResponderTimeout` set to 2 seconds |
|
||||
| DefaultTTLUnsetNextUpdate | golang/nats-server/server/certidp/certidp.go:32 | PORTED | src/NATS.Server/Tls/OcspPeerConfig.cs:107 | `OCSPPeerConfig.DefaultTTLUnsetNextUpdate` set to 1 hour |
|
||||
| StatusAssertion (type) | golang/nats-server/server/certidp/certidp.go:35 | PORTED | src/NATS.Server/Tls/OcspPeerConfig.cs:9 | Added `StatusAssertion` enum with JSON converter and bidirectional string/int maps |
|
||||
| GetStatusAssertionStr | golang/nats-server/server/certidp/certidp.go:56 | PORTED | src/NATS.Server/Tls/OcspPeerConfig.cs:40 | `StatusAssertionMaps.GetStatusAssertionStr(int)` with unknown fallback |
|
||||
| ChainLink (struct) | golang/nats-server/server/certidp/certidp.go:93 | PORTED | src/NATS.Server/Tls/OcspPeerConfig.cs:81 | Added `ChainLink` type with `Leaf`, `Issuer`, and `OCSPWebEndpoints` |
|
||||
| OCSPPeerConfig (struct) | golang/nats-server/server/certidp/certidp.go:100 | PORTED | src/NATS.Server/Tls/OcspPeerConfig.cs:103 | Added `OCSPPeerConfig` with matching fields and defaults |
|
||||
| NewOCSPPeerConfig | golang/nats-server/server/certidp/certidp.go:110 | PORTED | src/NATS.Server/Tls/OcspPeerConfig.cs:117 | Added `OCSPPeerConfig.NewOCSPPeerConfig()` factory |
|
||||
| Log (struct) | golang/nats-server/server/certidp/certidp.go:123 | NOT_APPLICABLE | — | .NET uses ILogger<T> injection; no need for function-pointer log struct |
|
||||
| CertInfo (struct) | golang/nats-server/server/certidp/certidp.go:131 | MISSING | — | No cert info DTO for events |
|
||||
| GenerateFingerprint | golang/nats-server/server/certidp/certidp.go:179 | PARTIAL | src/NATS.Server/Tls/TlsHelper.cs:88 | `GetCertificateHash` uses SHA256 on SPKI (not raw cert as Go does); different hash input |
|
||||
| getWebEndpoints | golang/nats-server/server/certidp/certidp.go:184 | MISSING | — | No OCSP endpoint URL extraction/filtering |
|
||||
| GetSubjectDNForm | golang/nats-server/server/certidp/certidp.go:203 | MISSING | — | No subject RDN sequence formatting |
|
||||
| GetIssuerDNForm | golang/nats-server/server/certidp/certidp.go:212 | MISSING | — | No issuer RDN sequence formatting |
|
||||
| CertOCSPEligible | golang/nats-server/server/certidp/certidp.go:221 | MISSING | — | No OCSP eligibility check based on AIA extension |
|
||||
| GetLeafIssuerCert | golang/nats-server/server/certidp/certidp.go:237 | MISSING | — | No positional issuer extraction from chain |
|
||||
| OCSPResponseCurrent | golang/nats-server/server/certidp/certidp.go:250 | MISSING | — | No OCSP response currency check with clock skew and TTL fallback |
|
||||
| ValidDelegationCheck | golang/nats-server/server/certidp/certidp.go:288 | MISSING | — | No OCSP response delegation validation per RFC 6960 section 4.2.2.2 |
|
||||
| CertInfo (struct) | golang/nats-server/server/certidp/certidp.go:131 | PORTED | src/NATS.Server/Tls/OcspPeerConfig.cs:88 | Added `CertInfo` DTO with subject, issuer, fingerprint, and raw fields |
|
||||
| GenerateFingerprint | golang/nats-server/server/certidp/certidp.go:179 | PORTED | src/NATS.Server/Tls/TlsHelper.cs:95 | Added `GenerateFingerprint` using SHA-256 of raw certificate bytes and base64 encoding |
|
||||
| getWebEndpoints | golang/nats-server/server/certidp/certidp.go:184 | PORTED | src/NATS.Server/Tls/TlsHelper.cs:101 | Added `GetWebEndpoints` filtering to valid absolute HTTP/HTTPS URIs |
|
||||
| GetSubjectDNForm | golang/nats-server/server/certidp/certidp.go:203 | PORTED | src/NATS.Server/Tls/TlsHelper.cs:117 | Added subject DN helper returning empty string for null cert |
|
||||
| GetIssuerDNForm | golang/nats-server/server/certidp/certidp.go:212 | PORTED | src/NATS.Server/Tls/TlsHelper.cs:122 | Added issuer DN helper returning empty string for null cert |
|
||||
| CertOCSPEligible | golang/nats-server/server/certidp/certidp.go:221 | PORTED | src/NATS.Server/Tls/TlsHelper.cs:142 | Added AIA OCSP responder extraction + HTTP(S) endpoint filtering, and populates `ChainLink.OCSPWebEndpoints` on success |
|
||||
| GetLeafIssuerCert | golang/nats-server/server/certidp/certidp.go:237 | PORTED | src/NATS.Server/Tls/TlsHelper.cs:165 | Added positional issuer extraction helper (`leafPos + 1`) with bounds/self-signed guards |
|
||||
| OCSPResponseCurrent | golang/nats-server/server/certidp/certidp.go:250 | PORTED | src/NATS.Server/Tls/TlsHelper.cs:193 | Added OCSP response currency window checks with configurable clock-skew and fallback TTL when `NextUpdate` is unset |
|
||||
| ValidDelegationCheck | golang/nats-server/server/certidp/certidp.go:288 | PORTED | src/NATS.Server/Tls/TlsHelper.cs:224 | Added delegation validation: direct issuer response accepted, delegated responder requires OCSPSigning EKU |
|
||||
|
||||
### certidp/messages.go — CertIDP message types
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| Error message constants | golang/nats-server/server/certidp/messages.go:17 | MISSING | — | No equivalent error/debug message constants; .NET uses structured logging |
|
||||
| Debug message constants | golang/nats-server/server/certidp/messages.go:47 | MISSING | — | Debug format strings not ported; .NET logs differently |
|
||||
| MsgTLSClientRejectConnection | golang/nats-server/server/certidp/messages.go:81 | PARTIAL | src/NATS.Server/Events/EventTypes.cs:520 | Reject event type exists but literal reject reason string not exposed |
|
||||
| MsgTLSServerRejectConnection | golang/nats-server/server/certidp/messages.go:82 | PARTIAL | src/NATS.Server/Events/EventTypes.cs:520 | Same as above |
|
||||
| MsgCacheOnline / MsgCacheOffline | golang/nats-server/server/certidp/messages.go:96 | MISSING | — | No cache status notification messages |
|
||||
| Error message constants | golang/nats-server/server/certidp/messages.go:17 | PORTED | src/NATS.Server/Tls/OcspPeerMessages.cs:6 | Ported certidp error string constants (returned and directly-logged variants) into `OcspPeerMessages` |
|
||||
| Debug message constants | golang/nats-server/server/certidp/messages.go:47 | PORTED | src/NATS.Server/Tls/OcspPeerMessages.cs:36 | Ported certidp debug-format constants used by OCSP peer/cache workflows |
|
||||
| MsgTLSClientRejectConnection | golang/nats-server/server/certidp/messages.go:81 | PORTED | src/NATS.Server/Tls/OcspPeerMessages.cs:5 | Added literal reject reason constant for client OCSP validation failures |
|
||||
| MsgTLSServerRejectConnection | golang/nats-server/server/certidp/messages.go:82 | PORTED | src/NATS.Server/Tls/OcspPeerMessages.cs:6 | Added literal reject reason constant for server OCSP validation failures |
|
||||
| MsgCacheOnline / MsgCacheOffline | golang/nats-server/server/certidp/messages.go:96 | PORTED | src/NATS.Server/Tls/OcspPeerMessages.cs:7 | Added cache online/offline informational message templates |
|
||||
|
||||
### certidp/ocsp_responder.go — OCSP responder client
|
||||
|
||||
@@ -259,7 +259,7 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| MatchByMap | golang/nats-server/server/certstore/certstore.go:52 | NOT_APPLICABLE | — | .NET equivalent: X509FindType |
|
||||
| ParseCertStore | golang/nats-server/server/certstore/certstore.go:68 | NOT_APPLICABLE | — | .NET has built-in X509Store with StoreLocation |
|
||||
| ParseCertMatchBy | golang/nats-server/server/certstore/certstore.go:80 | NOT_APPLICABLE | — | .NET has X509FindType |
|
||||
| GetLeafIssuer | golang/nats-server/server/certstore/certstore.go:88 | MISSING | — | Could port using X509Chain verification to find issuer |
|
||||
| GetLeafIssuer | golang/nats-server/server/certstore/certstore.go:88 | PORTED | src/NATS.Server/Tls/TlsHelper.cs:176 | Added verified-chain issuer resolver using custom-root trust and returning chain element issuer |
|
||||
| credential (interface) | golang/nats-server/server/certstore/certstore.go:99 | NOT_APPLICABLE | — | .NET uses X509Certificate2 with private key; no separate credential interface needed |
|
||||
|
||||
### certstore/certstore_other.go — Non-Windows cert store stub
|
||||
@@ -336,5 +336,7 @@ After porting work is completed:
|
||||
|
||||
| Date | Change | By |
|
||||
|------|--------|----|
|
||||
| 2026-02-26 | Ported `parseCertPEM` parity by adding strict PEM certificate parser (`CERTIFICATE` blocks only), wiring `LoadCaCertificates` through it, and adding focused TLS helper tests for invalid block rejection and multi-cert bundles. | codex |
|
||||
| 2026-02-25 | File created with LLM analysis instructions | auto |
|
||||
| 2026-02-25 | Completed full gap inventory: 12 Go source files analyzed, 144 symbols classified (20 PORTED, 9 PARTIAL, 70 MISSING, 45 NOT_APPLICABLE, 0 DEFERRED) | claude-opus |
|
||||
| 2026-02-25 | Ported OCSP eligibility/issuer/currentness/delegation helpers, certstore leaf issuer resolution, and certidp error/debug message constants with targeted TLS/Ocsp parity tests | codex |
|
||||
|
||||
+27
-25
@@ -120,13 +120,13 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| `parseSize` (unexported) | golang/nats-server/server/util.go:82 | PORTED | src/NATS.Server/Protocol/NatsParser.cs:434 | `NatsParser.ParseSize(Span<byte>)` — exact behavioral match including -1 on error; tested in InfrastructureGoParityTests |
|
||||
| `parseInt64` (unexported) | golang/nats-server/server/util.go:113 | PORTED | src/NATS.Server/Protocol/NatsParser.cs:434 | Folded into `ParseSize` / inline parser logic; behavior covered by parser tests |
|
||||
| `secondsToDuration` (unexported) | golang/nats-server/server/util.go:127 | NOT_APPLICABLE | — | Go-specific `time.Duration` helper; .NET uses `TimeSpan.FromSeconds(double)` directly |
|
||||
| `parseHostPort` (unexported) | golang/nats-server/server/util.go:134 | PARTIAL | src/NATS.Server/Configuration/ConfigProcessor.cs | ConfigProcessor parses `host:port` strings but does not have a standalone `ParseHostPort` helper matching Go's default-port fallback logic |
|
||||
| `parseHostPort` (unexported) | golang/nats-server/server/util.go:134 | PORTED | src/NATS.Server/Server/ServerUtilities.cs:19 | Added `ParseHostPort(string, int)` with default-port fallback and 0/-1 port normalization |
|
||||
| `urlsAreEqual` (unexported) | golang/nats-server/server/util.go:158 | NOT_APPLICABLE | — | Uses `reflect.DeepEqual` on `*url.URL`; not needed in .NET port (no equivalent URL gossip pattern yet) |
|
||||
| `comma` (unexported) | golang/nats-server/server/util.go:169 | PORTED | tests/NATS.Server.Tests/InfrastructureGoParityTests.cs:960 | Ported as `CommaFormat` helper in InfrastructureGoParityTests (test-side only); monitoring output uses `string.Format("{0:N0}")` in production |
|
||||
| `natsListenConfig` | golang/nats-server/server/util.go:246 | PORTED | src/NATS.Server/NatsServer.cs | .NET `TcpListener` / `Socket` used without OS TCP keepalives; keepalive is disabled by default in .NET socket setup matching Go behavior |
|
||||
| `natsListen` (unexported) | golang/nats-server/server/util.go:252 | PORTED | src/NATS.Server/NatsServer.cs | Equivalent accept loop uses `TcpListener.AcceptTcpClientAsync` without system keepalives |
|
||||
| `natsDialTimeout` (unexported) | golang/nats-server/server/util.go:258 | PARTIAL | src/NATS.Server/Routes/RouteConnection.cs | Route dialing exists but the explicit keepalive=-1 (disabled) setting is not verified in .NET route code |
|
||||
| `redactURLList` (unexported) | golang/nats-server/server/util.go:270 | PARTIAL | tests/NATS.Server.Tests/InfrastructureGoParityTests.cs:979 | `RedactUrl` helper ported in test file; no production-side `redactURLList` for URL slices |
|
||||
| `natsDialTimeout` (unexported) | golang/nats-server/server/util.go:258 | PORTED | src/NATS.Server/Routes/RouteManager.cs:547 | Added `CreateRouteDialSocket()` to explicitly disable TCP keepalive on outbound route dials before connect |
|
||||
| `redactURLList` (unexported) | golang/nats-server/server/util.go:270 | PORTED | src/NATS.Server/Server/ServerUtilities.cs:79 | Added production `RedactUrlList(IEnumerable<string>)` that redacts per-URL user-info passwords |
|
||||
| `redactURLString` (unexported) | golang/nats-server/server/util.go:296 | PORTED | tests/NATS.Server.Tests/InfrastructureGoParityTests.cs:979 | `RedactUrl` in InfrastructureGoParityTests matches behavior; also used in log redaction |
|
||||
| `getURLsAsString` (unexported) | golang/nats-server/server/util.go:308 | NOT_APPLICABLE | — | Internal URL slice utility for clustering; not needed in current .NET scope |
|
||||
| `copyBytes` (unexported) | golang/nats-server/server/util.go:317 | PORTED | (inline) | .NET uses `ReadOnlySpan<byte>.ToArray()`, `Array.Copy`, or `Buffer.BlockCopy` equivalently throughout the codebase |
|
||||
@@ -153,8 +153,8 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `rateCounter` (type) | golang/nats-server/server/rate_counter.go:21 | PORTED | src/NATS.Server/Tls/TlsRateLimiter.cs | `TlsRateLimiter` provides equivalent token-bucket rate limiting for TLS handshakes; also `ApiRateLimiter` for JetStream API |
|
||||
| `newRateCounter` | golang/nats-server/server/rate_counter.go:30 | PORTED | src/NATS.Server/Tls/TlsRateLimiter.cs:9 | `TlsRateLimiter(long tokensPerSecond)` constructor |
|
||||
| `rateCounter.allow` (unexported) | golang/nats-server/server/rate_counter.go:37 | PARTIAL | src/NATS.Server/Tls/TlsRateLimiter.cs:22 | `WaitAsync(CancellationToken)` is async/blocking rather than a synchronous allow-or-deny check; Go's `allow()` is non-blocking |
|
||||
| `rateCounter.countBlocked` (unexported) | golang/nats-server/server/rate_counter.go:58 | MISSING | — | No equivalent "count blocked requests" metric; `TlsRateLimiter` does not expose a blocked-count stat |
|
||||
| `rateCounter.allow` (unexported) | golang/nats-server/server/rate_counter.go:37 | PORTED | src/NATS.Server/Server/RateCounter.cs:21 | Added non-blocking `Allow()` with 1-second window and blocked-counter increment semantics |
|
||||
| `rateCounter.countBlocked` (unexported) | golang/nats-server/server/rate_counter.go:58 | PORTED | src/NATS.Server/Server/RateCounter.cs:42 | Added `CountBlocked()` that returns and resets blocked count |
|
||||
|
||||
### `golang/nats-server/server/sendq.go`
|
||||
|
||||
@@ -206,13 +206,13 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| `ErrReservedPublishSubject` | golang/nats-server/server/errors.go:49 | PORTED | src/NATS.Server/NatsClient.cs | Reserved subject check on publish |
|
||||
| `ErrBadPublishSubject` | golang/nats-server/server/errors.go:52 | PORTED | src/NATS.Server/Protocol/NatsProtocol.cs:28 | `ErrInvalidPublishSubject` constant |
|
||||
| `ErrBadSubject` | golang/nats-server/server/errors.go:55 | PORTED | src/NATS.Server/Protocol/NatsProtocol.cs:29 | `ErrInvalidSubject` constant |
|
||||
| `ErrBadQualifier` | golang/nats-server/server/errors.go:58 | MISSING | — | No dedicated bad-qualifier error; transform validation throws `ArgumentException` |
|
||||
| `ErrBadQualifier` | golang/nats-server/server/errors.go:58 | PORTED | src/NATS.Server/Server/ServerErrorConstants.cs:10 | Added parity literal constant for bad transform qualifier error |
|
||||
| `ErrBadClientProtocol` | golang/nats-server/server/errors.go:61 | PORTED | src/NATS.Server/NatsClient.cs | Protocol version validation on CONNECT |
|
||||
| `ErrTooManyConnections` | golang/nats-server/server/errors.go:64 | PORTED | src/NATS.Server/Protocol/NatsProtocol.cs:25 | `ErrMaxConnectionsExceeded` constant |
|
||||
| `ErrTooManyAccountConnections` | golang/nats-server/server/errors.go:68 | MISSING | — | Account-level connection limits not yet implemented |
|
||||
| `ErrTooManyAccountConnections` | golang/nats-server/server/errors.go:68 | PORTED | src/NATS.Server/Server/ServerErrorConstants.cs:11 | Added parity error literal constant; enforcement work remains tracked separately in account-limit behavior gaps |
|
||||
| `ErrLeafNodeLoop` | golang/nats-server/server/errors.go:72 | PORTED | src/NATS.Server/LeafNode/ | Leaf node loop detection implemented |
|
||||
| `ErrTooManySubs` | golang/nats-server/server/errors.go:76 | MISSING | — | Per-connection subscription limit not yet enforced |
|
||||
| `ErrTooManySubTokens` | golang/nats-server/server/errors.go:79 | MISSING | — | Subject token count limit not yet enforced |
|
||||
| `ErrTooManySubs` | golang/nats-server/server/errors.go:76 | PORTED | src/NATS.Server/Server/ServerErrorConstants.cs:12 | Added parity error literal constant |
|
||||
| `ErrTooManySubTokens` | golang/nats-server/server/errors.go:79 | PORTED | src/NATS.Server/Server/ServerErrorConstants.cs:13 | Added parity error literal constant |
|
||||
| `ErrClientConnectedToRoutePort` | golang/nats-server/server/errors.go:83 | PORTED | src/NATS.Server/Routes/ | Wrong port detection on route listener |
|
||||
| `ErrClientConnectedToLeafNodePort` | golang/nats-server/server/errors.go:87 | PORTED | src/NATS.Server/LeafNode/ | Wrong port detection on leaf node listener |
|
||||
| `ErrLeafNodeHasSameClusterName` | golang/nats-server/server/errors.go:91 | PORTED | src/NATS.Server/LeafNode/ | Same-cluster-name rejection |
|
||||
@@ -220,22 +220,22 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| `ErrConnectedToWrongPort` | golang/nats-server/server/errors.go:98 | PORTED | src/NATS.Server/NatsServer.cs | Port sniffing / wrong-port close |
|
||||
| `ErrAccountExists` | golang/nats-server/server/errors.go:102 | PORTED | src/NATS.Server/Auth/Account.cs | Duplicate account registration check |
|
||||
| `ErrBadAccount` | golang/nats-server/server/errors.go:105 | PORTED | src/NATS.Server/Auth/ | Bad/malformed account |
|
||||
| `ErrReservedAccount` | golang/nats-server/server/errors.go:108 | MISSING | — | Reserved account name check not yet implemented |
|
||||
| `ErrReservedAccount` | golang/nats-server/server/errors.go:108 | PORTED | src/NATS.Server/Server/ServerErrorConstants.cs:14 | Added parity error literal constant |
|
||||
| `ErrMissingAccount` | golang/nats-server/server/errors.go:111 | PORTED | src/NATS.Server/Auth/ | Missing account lookup |
|
||||
| `ErrMissingService` | golang/nats-server/server/errors.go:114 | MISSING | — | Service export/import not yet ported |
|
||||
| `ErrBadServiceType` | golang/nats-server/server/errors.go:117 | MISSING | — | Service latency tracking not yet ported |
|
||||
| `ErrBadSampling` | golang/nats-server/server/errors.go:120 | MISSING | — | Latency sampling validation not yet ported |
|
||||
| `ErrMissingService` | golang/nats-server/server/errors.go:114 | PORTED | src/NATS.Server/Server/ServerErrorConstants.cs:15 | Added parity error literal constant |
|
||||
| `ErrBadServiceType` | golang/nats-server/server/errors.go:117 | PORTED | src/NATS.Server/Server/ServerErrorConstants.cs:16 | Added parity error literal constant |
|
||||
| `ErrBadSampling` | golang/nats-server/server/errors.go:120 | PORTED | src/NATS.Server/Server/ServerErrorConstants.cs:17 | Added parity error literal constant |
|
||||
| `ErrAccountValidation` | golang/nats-server/server/errors.go:123 | PORTED | src/NATS.Server/Auth/ | Account validation logic |
|
||||
| `ErrAccountExpired` | golang/nats-server/server/errors.go:126 | PORTED | src/NATS.Server/Auth/ | Account expiry check |
|
||||
| `ErrNoAccountResolver` | golang/nats-server/server/errors.go:129 | PORTED | src/NATS.Server/Auth/ | No resolver configured check |
|
||||
| `ErrAccountResolverUpdateTooSoon` | golang/nats-server/server/errors.go:132 | MISSING | — | Resolver update rate limiting not yet ported |
|
||||
| `ErrAccountResolverSameClaims` | golang/nats-server/server/errors.go:135 | MISSING | — | Same-claims dedup not yet ported |
|
||||
| `ErrStreamImportAuthorization` | golang/nats-server/server/errors.go:138 | MISSING | — | Stream import auth not yet ported |
|
||||
| `ErrStreamImportBadPrefix` | golang/nats-server/server/errors.go:141 | MISSING | — | Stream import prefix validation not yet ported |
|
||||
| `ErrStreamImportDuplicate` | golang/nats-server/server/errors.go:144 | MISSING | — | Duplicate import detection not yet ported |
|
||||
| `ErrServiceImportAuthorization` | golang/nats-server/server/errors.go:147 | MISSING | — | Service import auth not yet ported |
|
||||
| `ErrImportFormsCycle` | golang/nats-server/server/errors.go:150 | MISSING | — | Import cycle detection not yet ported |
|
||||
| `ErrCycleSearchDepth` | golang/nats-server/server/errors.go:153 | MISSING | — | Cycle search depth limit not yet ported |
|
||||
| `ErrAccountResolverUpdateTooSoon` | golang/nats-server/server/errors.go:132 | PORTED | src/NATS.Server/Server/ServerErrorConstants.cs:18 | Added parity error literal constant |
|
||||
| `ErrAccountResolverSameClaims` | golang/nats-server/server/errors.go:135 | PORTED | src/NATS.Server/Server/ServerErrorConstants.cs:19 | Added parity error literal constant |
|
||||
| `ErrStreamImportAuthorization` | golang/nats-server/server/errors.go:138 | PORTED | src/NATS.Server/Server/ServerErrorConstants.cs:20 | Added parity error literal constant |
|
||||
| `ErrStreamImportBadPrefix` | golang/nats-server/server/errors.go:141 | PORTED | src/NATS.Server/Server/ServerErrorConstants.cs:21 | Added parity error literal constant |
|
||||
| `ErrStreamImportDuplicate` | golang/nats-server/server/errors.go:144 | PORTED | src/NATS.Server/Server/ServerErrorConstants.cs:22 | Added parity error literal constant |
|
||||
| `ErrServiceImportAuthorization` | golang/nats-server/server/errors.go:147 | PORTED | src/NATS.Server/Server/ServerErrorConstants.cs:23 | Added parity error literal constant |
|
||||
| `ErrImportFormsCycle` | golang/nats-server/server/errors.go:150 | PORTED | src/NATS.Server/Server/ServerErrorConstants.cs:24 | Added parity error literal constant |
|
||||
| `ErrCycleSearchDepth` | golang/nats-server/server/errors.go:153 | PORTED | src/NATS.Server/Server/ServerErrorConstants.cs:25 | Added parity error literal constant |
|
||||
| `ErrClientOrRouteConnectedToGatewayPort` | golang/nats-server/server/errors.go:157 | PORTED | src/NATS.Server/Gateways/ | Wrong port detection on gateway listener |
|
||||
| `ErrWrongGateway` | golang/nats-server/server/errors.go:161 | PORTED | src/NATS.Server/Gateways/ | Wrong gateway name on connect |
|
||||
| `ErrGatewayNameHasSpaces` | golang/nats-server/server/errors.go:165 | PORTED | src/NATS.Server/Configuration/ConfigProcessor.cs | Config validation rejects spaces in gateway name |
|
||||
@@ -251,7 +251,7 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| `ErrClusterNameHasSpaces` | golang/nats-server/server/errors.go:199 | PORTED | src/NATS.Server/Configuration/ConfigProcessor.cs | Config validation |
|
||||
| `ErrMalformedSubject` | golang/nats-server/server/errors.go:202 | PORTED | src/NATS.Server/Subscriptions/SubjectMatch.cs | Subject validation rejects malformed subjects |
|
||||
| `ErrSubscribePermissionViolation` | golang/nats-server/server/errors.go:205 | PORTED | src/NATS.Server/NatsClient.cs | Subscribe permission check |
|
||||
| `ErrNoTransforms` | golang/nats-server/server/errors.go:208 | MISSING | — | Transform selection logic not yet fully ported |
|
||||
| `ErrNoTransforms` | golang/nats-server/server/errors.go:208 | PORTED | src/NATS.Server/Server/ServerErrorConstants.cs:26 | Added parity error literal constant |
|
||||
| `ErrCertNotPinned` | golang/nats-server/server/errors.go:211 | PORTED | src/NATS.Server/Tls/TlsHelper.cs | Pinned cert validation |
|
||||
| `ErrDuplicateServerName` | golang/nats-server/server/errors.go:215 | PORTED | src/NATS.Server/Routes/ | Duplicate server name on cluster connect |
|
||||
| `ErrMinimumVersionRequired` | golang/nats-server/server/errors.go:218 | PORTED | src/NATS.Server/Routes/ | Minimum version enforcement on cluster |
|
||||
@@ -270,11 +270,11 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| `configErr` (type) | golang/nats-server/server/errors.go:261 | PORTED | src/NATS.Server/Configuration/ConfigProcessor.cs:1434 | `ConfigProcessorException` with error list |
|
||||
| `configErr.Source` | golang/nats-server/server/errors.go:267 | PARTIAL | src/NATS.Server/Configuration/ConfigProcessor.cs | Source file/line tracking in errors is partial; exception message includes context but not file:line:col |
|
||||
| `configErr.Error` | golang/nats-server/server/errors.go:272 | PORTED | src/NATS.Server/Configuration/ConfigProcessor.cs | Exception `Message` property |
|
||||
| `unknownConfigFieldErr` (type) | golang/nats-server/server/errors.go:280 | PARTIAL | src/NATS.Server/Configuration/ConfigProcessor.cs | Unknown fields trigger `ConfigProcessorException` but without the specific `unknownConfigFieldErr` type distinction |
|
||||
| `configWarningErr` (type) | golang/nats-server/server/errors.go:292 | MISSING | — | No distinction between warnings and errors in .NET config processor; all surfaced as exceptions |
|
||||
| `unknownConfigFieldErr` (type) | golang/nats-server/server/errors.go:280 | PORTED | src/NATS.Server/Configuration/ConfigProcessor.cs:1597 | Added `UnknownConfigFieldWarning` type with field/source metadata and Go-style warning message (`unknown field <name>`). |
|
||||
| `configWarningErr` (type) | golang/nats-server/server/errors.go:292 | PORTED | src/NATS.Server/Configuration/ConfigProcessor.cs:1588 | Added `ConfigWarningException` base warning type to distinguish warning payloads from hard errors. |
|
||||
| `processConfigErr` (type) | golang/nats-server/server/errors.go:303 | PORTED | src/NATS.Server/Configuration/ConfigProcessor.cs:1434 | `ConfigProcessorException` accumulates all errors |
|
||||
| `processConfigErr.Error` | golang/nats-server/server/errors.go:310 | PORTED | src/NATS.Server/Configuration/ConfigProcessor.cs | `Message` + `Errors` list |
|
||||
| `processConfigErr.Warnings` | golang/nats-server/server/errors.go:322 | MISSING | — | No separate warnings list; warnings folded into errors or logged |
|
||||
| `processConfigErr.Warnings` | golang/nats-server/server/errors.go:322 | PORTED | src/NATS.Server/Configuration/ConfigProcessor.cs:1581 | Added `ConfigProcessorException.Warnings` list and wired unknown top-level field warnings into thrown config errors when validation errors are present. |
|
||||
| `processConfigErr.Errors` | golang/nats-server/server/errors.go:327 | PORTED | src/NATS.Server/Configuration/ConfigProcessor.cs:1437 | `ConfigProcessorException.Errors` property |
|
||||
| `errCtx` (type) | golang/nats-server/server/errors.go:332 | PORTED | tests/NATS.Server.Tests/InfrastructureGoParityTests.cs:1069 | `WrappedNatsException` (test-file-scoped) mirrors the error context wrapping; production code uses standard .NET exception chaining |
|
||||
| `NewErrorCtx` | golang/nats-server/server/errors.go:338 | PORTED | tests/NATS.Server.Tests/InfrastructureGoParityTests.cs:1069 | Equivalent via `new WrappedNatsException(inner, ctx)` in tests; production uses `new Exception(msg, inner)` |
|
||||
@@ -444,5 +444,7 @@ After porting work is completed:
|
||||
|
||||
| Date | Change | By |
|
||||
|------|--------|----|
|
||||
| 2026-02-26 | Ported config warning parity slice: added `ConfigWarningException`, `UnknownConfigFieldWarning`, and `ConfigProcessorException.Warnings`; wired unknown top-level fields as warning entries and added focused configuration parity tests. | codex |
|
||||
| 2026-02-25 | File created with LLM analysis instructions | auto |
|
||||
| 2026-02-25 | Full gap inventory populated — all Go source files analyzed, 131 symbols classified | auto |
|
||||
| 2026-02-25 | Ported missing `errors.go` parity literals into `ServerErrorConstants` and added targeted parity tests for the newly added constants | codex |
|
||||
|
||||
+9
-7
@@ -90,7 +90,7 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `wsOpCode` (type) | websocket.go:41 | PORTED | `src/NATS.Server/WebSocket/WsConstants.cs:1` | Int constants replace Go type alias; `WsConstants.TextMessage`, `BinaryMessage`, etc. |
|
||||
| `websocket` (struct) | websocket.go:108 | PORTED | `src/NATS.Server/WebSocket/WsConnection.cs:8` | Fields mapped: `compress`, `maskread`/`maskwrite`, `browser`, `nocompfrag`. Cookie fields moved to `WsUpgradeResult`. `frames`/`fs` buffer management is now in `WsConnection.WriteAsync`. `compressor` (reuse) is NOT pooled — recreated per call. |
|
||||
| `srvWebsocket` (struct) | websocket.go:126 | PARTIAL | `src/NATS.Server/NatsServer.cs:538` | `_wsListener`, `_options.WebSocket` cover port/host/tls; `allowedOrigins` managed via `WsOriginChecker`. `connectURLsMap` ref-count URL set and `authOverride` flag are MISSING — not ported. |
|
||||
| `srvWebsocket` (struct) | websocket.go:126 | PARTIAL | `src/NATS.Server/NatsServer.cs:538` | `_wsListener`, `_options.WebSocket` cover port/host/tls; `allowedOrigins` managed via `WsOriginChecker`; explicit `authOverride` flag is now computed via `WsAuthConfig.Apply(...)`. `connectURLsMap` ref-count URL set is still missing. |
|
||||
| `allowedOrigin` (struct) | websocket.go:145 | PORTED | `src/NATS.Server/WebSocket/WsOriginChecker.cs:80` | Private `AllowedOrigin` record struct with `Scheme` and `Port`. |
|
||||
| `wsUpgradeResult` (struct) | websocket.go:150 | PORTED | `src/NATS.Server/WebSocket/WsUpgrade.cs:346` | `WsUpgradeResult` readonly record struct with equivalent fields. `kind` maps to `WsClientKind` enum. |
|
||||
| `wsReadInfo` (struct) | websocket.go:156 | PORTED | `src/NATS.Server/WebSocket/WsReadInfo.cs:10` | All fields ported: `rem`→`Remaining`, `fs`→`FrameStart`, `ff`→`FirstFrame`, `fc`→`FrameCompressed`, `mask`→`ExpectMask`, `mkpos`→`MaskKeyPos`, `mkey`→`MaskKey`, `cbufs`→`CompressedBuffers`, `coff`→`CompressedOffset`. Extra .NET fields added for control frame output. |
|
||||
@@ -104,7 +104,7 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| `decompressorPool` | websocket.go:99 | PARTIAL | `src/NATS.Server/WebSocket/WsCompression.cs:193` | Go uses `sync.Pool` for `flate.Reader` reuse. .NET creates a new `DeflateStream` per decompression call — no pooling. Functional but slightly less efficient under high load. |
|
||||
| `compressLastBlock` | websocket.go:100 | PORTED | `src/NATS.Server/WebSocket/WsConstants.cs:62` | .NET uses 4-byte `DecompressTrailer` (sync marker only); Go uses 9-byte block. Both work correctly — difference is .NET `DeflateStream` does not need the final stored block. |
|
||||
| `wsGUID` | websocket.go:103 | PORTED | `src/NATS.Server/WebSocket/WsUpgrade.cs:177` | Inline string literal in `ComputeAcceptKey`. |
|
||||
| `wsTestRejectNoMasking` | websocket.go:106 | MISSING | — | Test-only hook to force masking rejection. No equivalent test hook in .NET. |
|
||||
| `wsTestRejectNoMasking` | websocket.go:106 | PORTED | `src/NATS.Server/WebSocket/WsUpgrade.cs:14` | Added test hook `RejectNoMaskingForTest`; when set, no-masking leaf upgrade requests are explicitly rejected |
|
||||
|
||||
#### Methods on `wsReadInfo`
|
||||
|
||||
@@ -148,16 +148,16 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
|
||||
| `srvWebsocket.checkOrigin()` | websocket.go:933 | PORTED | `src/NATS.Server/WebSocket/WsOriginChecker.cs:32` | `WsOriginChecker.CheckOrigin()` — same-origin and allowed-list checks. Go checks `r.TLS != nil` for TLS detection; .NET uses `isTls` parameter passed at call site. |
|
||||
| `wsGetHostAndPort()` | websocket.go:985 | PORTED | `src/NATS.Server/WebSocket/WsOriginChecker.cs:65` | `WsOriginChecker.GetHostAndPort()` and `ParseHostPort()` — missing-port defaults to 80/443 by TLS flag. |
|
||||
| `wsAcceptKey()` | websocket.go:1004 | PORTED | `src/NATS.Server/WebSocket/WsUpgrade.cs:175` | `WsUpgrade.ComputeAcceptKey()` — SHA-1 of key + GUID, base64 encoded. |
|
||||
| `wsMakeChallengeKey()` | websocket.go:1011 | MISSING | — | Generates a random 16-byte base64 client key for outbound WS connections (leaf node acting as WS client). No .NET equivalent. Needed when .NET server connects outbound as a WS leaf. |
|
||||
| `validateWebsocketOptions()` | websocket.go:1020 | PARTIAL | `src/NATS.Server/WebSocket/WebSocketTlsConfig.cs:24` | `WebSocketTlsConfig.Validate()` checks cert+key pair consistency. Full Go validation (TLS required unless NoTLS, AllowedOrigins parseable, NoAuthUser in users list, Token/Username incompatible with users/nkeys, JWTCookie requires TrustedOperators, TLSPinnedCerts, reserved header names) is MISSING from .NET. |
|
||||
| `wsMakeChallengeKey()` | websocket.go:1011 | PORTED | `src/NATS.Server/WebSocket/WsUpgrade.cs:192` | Added `MakeChallengeKey()` generating a random base64-encoded 16-byte challenge nonce |
|
||||
| `validateWebsocketOptions()` | websocket.go:1020 | PORTED | `src/NATS.Server/WebSocket/WebSocketOptionsValidator.cs:11` | Added `WebSocketOptionsValidator.Validate(NatsOptions)` covering TLS cert/key requirement, allowed-origin URI parsing, `NoAuthUser` membership, username/token conflicts with users/nkeys, `JwtCookie` trusted-operator requirement, TLS pinned-cert validation, and reserved response-header protection. Startup now enforces this validation in `NatsServer.StartAsync()` before opening WS listener (`src/NATS.Server/NatsServer.cs:631`). |
|
||||
| `Server.wsSetOriginOptions()` | websocket.go:1083 | PARTIAL | `src/NATS.Server/WebSocket/WsUpgrade.cs:49` | Origin checking is constructed inline in `TryUpgradeAsync` from `options.SameOrigin` and `options.AllowedOrigins`. The Go method persists parsed origins in `srvWebsocket.allowedOrigins` map and supports hot-reload. .NET constructs a `WsOriginChecker` per request — no hot-reload support, but functionally equivalent for initial config. |
|
||||
| `Server.wsSetHeadersOptions()` | websocket.go:1111 | PORTED | `src/NATS.Server/WebSocket/WsUpgrade.cs:141` | Custom headers applied inline in `TryUpgradeAsync` from `options.Headers`. |
|
||||
| `Server.wsConfigAuth()` | websocket.go:1131 | MISSING | — | Sets `srvWebsocket.authOverride` flag based on username/token/noAuthUser presence. No equivalent flag computation in .NET — `WebSocketOptions` properties are read directly. Functionally equivalent but the explicit override flag is absent. |
|
||||
| `Server.wsConfigAuth()` | websocket.go:1131 | PORTED | `src/NATS.Server/WebSocket/WsAuthConfig.cs:5` | Added explicit auth-override computation (`Username`/`Token`/`NoAuthUser`) and startup application via `NatsServer.StartAsync()` before WS listener initialization |
|
||||
| `Server.startWebsocketServer()` | websocket.go:1137 | PORTED | `src/NATS.Server/NatsServer.cs:538` | `NatsServer.StartAsync()` section at line 538 sets up the WS listener, logs, and launches `RunWebSocketAcceptLoopAsync`. Go uses `http.Server` + mux; .NET uses raw `TcpListener`/`Socket.AcceptAsync`. LEAF and MQTT routing at connection time is PARTIAL — LEAF path is wired (`WsClientKind.Leaf`) but MQTT is not handled in `AcceptWebSocketClientAsync`. `lame-duck` / `ldmCh` signaling is MISSING. |
|
||||
| `Server.wsGetTLSConfig()` | websocket.go:1264 | PARTIAL | `src/NATS.Server/NatsServer.cs:807` | TLS is applied once at accept time via `TlsConnectionWrapper.NegotiateAsync`. Go uses `GetConfigForClient` callback for hot-reload TLS config. .NET does not support hot TLS config reload for WS. |
|
||||
| `Server.createWSClient()` | websocket.go:1273 | PORTED | `src/NATS.Server/NatsServer.cs:799` | `AcceptWebSocketClientAsync()` — creates `WsConnection`, constructs `NatsClient`, wires `IsWebSocket`/`WsInfo`, registers client. Go also sends INFO immediately and sets auth timer; .NET's `NatsClient.RunAsync()` handles INFO send and auth timer. |
|
||||
| `isWSURL()` | websocket.go:1544 | MISSING | — | Helper to detect `ws://` scheme in a URL. Used by leaf node / route URL parsing. No .NET equivalent — not yet needed since outbound WS connections are not implemented. |
|
||||
| `isWSSURL()` | websocket.go:1548 | MISSING | — | Helper to detect `wss://` scheme in a URL. Same as above — not yet needed. |
|
||||
| `isWSURL()` | websocket.go:1544 | PORTED | `src/NATS.Server/WebSocket/WsUpgrade.cs:203` | Added helper to detect absolute `ws://` URLs via URI scheme parsing |
|
||||
| `isWSSURL()` | websocket.go:1548 | PORTED | `src/NATS.Server/WebSocket/WsUpgrade.cs:215` | Added helper to detect absolute `wss://` URLs via URI scheme parsing |
|
||||
|
||||
---
|
||||
|
||||
@@ -184,5 +184,7 @@ After porting work is completed:
|
||||
|
||||
| Date | Change | By |
|
||||
|------|--------|----|
|
||||
| 2026-02-26 | Ported `validateWebsocketOptions()` parity by adding `WebSocketOptionsValidator`, wiring startup enforcement, and adding focused validator tests including TLS pinned cert validation. | codex |
|
||||
| 2026-02-25 | Ported `Server.wsConfigAuth()` parity by adding `WsAuthConfig` auth-override computation and applying it during WS startup; added `WebSocketOptions.AuthOverride` plus focused tests. | codex |
|
||||
| 2026-02-25 | File created with LLM analysis instructions | auto |
|
||||
| 2026-02-25 | Full gap inventory populated: 37 Go symbols classified (26 PORTED, 7 PARTIAL, 4 MISSING, 0 NOT_APPLICABLE, 0 DEFERRED) | auto |
|
||||
|
||||
@@ -3,6 +3,27 @@ using NATS.Server.Configuration;
|
||||
using Serilog;
|
||||
using Serilog.Sinks.SystemConsole.Themes;
|
||||
|
||||
static void PrintUsage()
|
||||
{
|
||||
Console.WriteLine("NATS.Server.Host");
|
||||
Console.WriteLine("Usage: nats-server [options]");
|
||||
Console.WriteLine(" -h, --help Show this help");
|
||||
Console.WriteLine(" -c <file> Config file path");
|
||||
Console.WriteLine(" -p, --port <port> Client listen port");
|
||||
Console.WriteLine(" -a, --addr <host> Client listen host");
|
||||
Console.WriteLine(" -m, --http_port <port> Monitoring HTTP port");
|
||||
Console.WriteLine(" --https_port <port> Monitoring HTTPS port");
|
||||
Console.WriteLine(" -l, --log <file> Log file path");
|
||||
Console.WriteLine(" -D, --debug Enable debug logging");
|
||||
Console.WriteLine(" -V, --trace Enable trace logging");
|
||||
}
|
||||
|
||||
if (args.Any(a => a is "-h" or "--help"))
|
||||
{
|
||||
PrintUsage();
|
||||
return;
|
||||
}
|
||||
|
||||
// First pass: scan args for -c flag to get config file path
|
||||
string? configFile = null;
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
|
||||
@@ -8,6 +8,7 @@ public sealed class Account : IDisposable
|
||||
{
|
||||
public const string GlobalAccountName = "$G";
|
||||
public const string SystemAccountName = "$SYS";
|
||||
public const string ClientInfoHdr = "Nats-Request-Info";
|
||||
|
||||
public string Name { get; }
|
||||
public SubList SubList { get; } = new();
|
||||
@@ -789,6 +790,100 @@ public sealed class Account : IDisposable
|
||||
return matchResult.PlainSubs.Length > 0 || matchResult.QueueSubs.Length > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this account has at least one matching subscription for the given subject.
|
||||
/// Go reference: accounts.go SubscriptionInterest.
|
||||
/// </summary>
|
||||
public bool SubscriptionInterest(string subject) => Interest(subject) > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the total number of matching subscriptions (plain + queue) for the given subject.
|
||||
/// Go reference: accounts.go Interest.
|
||||
/// </summary>
|
||||
public int Interest(string subject)
|
||||
{
|
||||
var (plainCount, queueCount) = SubList.NumInterest(subject);
|
||||
return plainCount + queueCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the total number of outstanding response mappings for service exports.
|
||||
/// Go reference: accounts.go NumPendingAllResponses.
|
||||
/// </summary>
|
||||
public int NumPendingAllResponses() => NumPendingResponses(string.Empty);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number of outstanding response mappings for service exports.
|
||||
/// When <paramref name="filter"/> is empty, counts all mappings.
|
||||
/// Go reference: accounts.go NumPendingResponses.
|
||||
/// </summary>
|
||||
public int NumPendingResponses(string filter)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filter))
|
||||
return Exports.Responses.Count;
|
||||
|
||||
var se = GetServiceExportEntry(filter);
|
||||
if (se == null)
|
||||
return 0;
|
||||
|
||||
var count = 0;
|
||||
foreach (var (_, si) in Exports.Responses)
|
||||
{
|
||||
if (ReferenceEquals(si.Export, se))
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number of configured service import subjects.
|
||||
/// Go reference: accounts.go NumServiceImports.
|
||||
/// </summary>
|
||||
public int NumServiceImports() => Imports.Services.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Removes a response service import mapping.
|
||||
/// Go reference: accounts.go removeRespServiceImport.
|
||||
/// </summary>
|
||||
public void RemoveRespServiceImport(ServiceImport? serviceImport, ResponseServiceImportRemovalReason reason = ResponseServiceImportRemovalReason.Ok)
|
||||
{
|
||||
if (serviceImport == null)
|
||||
return;
|
||||
|
||||
string? replyPrefix = null;
|
||||
foreach (var (prefix, si) in Exports.Responses)
|
||||
{
|
||||
if (ReferenceEquals(si, serviceImport))
|
||||
{
|
||||
replyPrefix = prefix;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (replyPrefix == null)
|
||||
return;
|
||||
|
||||
// Current parity scope removes the response mapping. Reason-specific
|
||||
// metrics/latency side effects are tracked separately.
|
||||
ResponseRouter.CleanupResponse(this, replyPrefix, serviceImport);
|
||||
_ = reason;
|
||||
}
|
||||
|
||||
private ServiceExport? GetServiceExportEntry(string subject)
|
||||
{
|
||||
if (Exports.Services.TryGetValue(subject, out var exact))
|
||||
return exact;
|
||||
|
||||
foreach (var (pattern, export) in Exports.Services)
|
||||
{
|
||||
if (SubjectMatch.MatchLiteral(subject, pattern))
|
||||
return export;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all service import subjects registered on this account that are currently
|
||||
/// shadowed by a local subscription in the SubList.
|
||||
@@ -945,6 +1040,17 @@ public sealed record ActivationCheckResult(
|
||||
DateTime? ExpiresAt,
|
||||
TimeSpan? TimeToExpiry);
|
||||
|
||||
/// <summary>
|
||||
/// Reason for removing a response service import.
|
||||
/// Go reference: accounts.go rsiReason enum.
|
||||
/// </summary>
|
||||
public enum ResponseServiceImportRemovalReason
|
||||
{
|
||||
Ok = 0,
|
||||
NoDelivery = 1,
|
||||
Timeout = 2,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Snapshot of account JWT claim fields used for hot-reload diff detection.
|
||||
/// Go reference: server/accounts.go — AccountClaims / jwt.AccountClaims fields applied in updateAccountClaimsWithRefresh (~line 3374).
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Security.Cryptography;
|
||||
using NATS.Server.Protocol;
|
||||
|
||||
namespace NATS.Server.Auth;
|
||||
|
||||
@@ -33,11 +34,13 @@ public sealed class AuthService
|
||||
var authRequired = false;
|
||||
var nonceRequired = false;
|
||||
Dictionary<string, User>? usersMap = null;
|
||||
var users = NormalizeUsers(options.Users);
|
||||
var nkeys = NormalizeNKeys(options.NKeys);
|
||||
|
||||
// TLS certificate mapping (highest priority when enabled)
|
||||
if (options.TlsMap && options.TlsVerify && options.Users is { Count: > 0 })
|
||||
if (options.TlsMap && options.TlsVerify && users is { Count: > 0 })
|
||||
{
|
||||
authenticators.Add(new TlsMapAuthenticator(options.Users));
|
||||
authenticators.Add(new TlsMapAuthenticator(users));
|
||||
authRequired = true;
|
||||
}
|
||||
|
||||
@@ -63,19 +66,19 @@ public sealed class AuthService
|
||||
|
||||
// Priority order (matching Go): NKeys > Users > Token > SimpleUserPassword
|
||||
|
||||
if (options.NKeys is { Count: > 0 })
|
||||
if (nkeys is { Count: > 0 })
|
||||
{
|
||||
authenticators.Add(new NKeyAuthenticator(options.NKeys));
|
||||
authenticators.Add(new NKeyAuthenticator(nkeys));
|
||||
authRequired = true;
|
||||
nonceRequired = true;
|
||||
}
|
||||
|
||||
if (options.Users is { Count: > 0 })
|
||||
if (users is { Count: > 0 })
|
||||
{
|
||||
authenticators.Add(new UserPasswordAuthenticator(options.Users));
|
||||
authenticators.Add(new UserPasswordAuthenticator(users));
|
||||
authRequired = true;
|
||||
usersMap = new Dictionary<string, User>(StringComparer.Ordinal);
|
||||
foreach (var u in options.Users)
|
||||
foreach (var u in users)
|
||||
usersMap[u.Username] = u;
|
||||
}
|
||||
|
||||
@@ -169,4 +172,77 @@ public sealed class AuthService
|
||||
.Replace('+', '-')
|
||||
.Replace('/', '_');
|
||||
}
|
||||
|
||||
private static IReadOnlyList<User>? NormalizeUsers(IReadOnlyList<User>? users)
|
||||
{
|
||||
if (users is null)
|
||||
return null;
|
||||
|
||||
var normalized = new List<User>(users.Count);
|
||||
foreach (var user in users)
|
||||
{
|
||||
normalized.Add(new User
|
||||
{
|
||||
Username = user.Username,
|
||||
Password = user.Password,
|
||||
Account = string.IsNullOrWhiteSpace(user.Account) ? Account.GlobalAccountName : user.Account,
|
||||
Permissions = NormalizePermissions(user.Permissions),
|
||||
ConnectionDeadline = user.ConnectionDeadline,
|
||||
AllowedConnectionTypes = user.AllowedConnectionTypes,
|
||||
ProxyRequired = user.ProxyRequired,
|
||||
});
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private static IReadOnlyList<NKeyUser>? NormalizeNKeys(IReadOnlyList<NKeyUser>? nkeys)
|
||||
{
|
||||
if (nkeys is null)
|
||||
return null;
|
||||
|
||||
var normalized = new List<NKeyUser>(nkeys.Count);
|
||||
foreach (var nkey in nkeys)
|
||||
{
|
||||
normalized.Add(new NKeyUser
|
||||
{
|
||||
Nkey = nkey.Nkey,
|
||||
Account = string.IsNullOrWhiteSpace(nkey.Account) ? Account.GlobalAccountName : nkey.Account,
|
||||
Permissions = NormalizePermissions(nkey.Permissions),
|
||||
SigningKey = nkey.SigningKey,
|
||||
Issued = nkey.Issued,
|
||||
AllowedConnectionTypes = nkey.AllowedConnectionTypes,
|
||||
ProxyRequired = nkey.ProxyRequired,
|
||||
});
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private static Permissions? NormalizePermissions(Permissions? permissions)
|
||||
{
|
||||
if (permissions?.Response is null)
|
||||
return permissions;
|
||||
|
||||
var publish = permissions.Publish;
|
||||
if (publish?.Allow is null)
|
||||
{
|
||||
publish = new SubjectPermission
|
||||
{
|
||||
Allow = [],
|
||||
Deny = publish?.Deny,
|
||||
};
|
||||
}
|
||||
|
||||
var response = permissions.Response;
|
||||
var maxMsgs = response.MaxMsgs == 0 ? NatsProtocol.DefaultAllowResponseMaxMsgs : response.MaxMsgs;
|
||||
var expires = response.Expires == TimeSpan.Zero ? NatsProtocol.DefaultAllowResponseExpiration : response.Expires;
|
||||
|
||||
return new Permissions
|
||||
{
|
||||
Publish = publish,
|
||||
Subscribe = permissions.Subscribe,
|
||||
Response = new ResponsePermission { MaxMsgs = maxMsgs, Expires = expires },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,10 @@ namespace NATS.Server.Auth;
|
||||
|
||||
public sealed class ExternalAuthCalloutAuthenticator : IAuthenticator
|
||||
{
|
||||
public const string AuthCalloutSubject = "$SYS.REQ.USER.AUTH";
|
||||
public const string AuthRequestSubject = "nats-authorization-request";
|
||||
public const string AuthRequestXKeyHeader = "Nats-Server-Xkey";
|
||||
|
||||
private readonly IExternalAuthClient _client;
|
||||
private readonly TimeSpan _timeout;
|
||||
|
||||
|
||||
@@ -6,4 +6,7 @@ public sealed class NKeyUser
|
||||
public Permissions? Permissions { get; init; }
|
||||
public string? Account { get; init; }
|
||||
public string? SigningKey { get; init; }
|
||||
public DateTimeOffset? Issued { get; init; }
|
||||
public IReadOnlySet<string>? AllowedConnectionTypes { get; init; }
|
||||
public bool ProxyRequired { get; init; }
|
||||
}
|
||||
|
||||
@@ -40,9 +40,88 @@ public sealed class TlsMapAuthenticator : IAuthenticator
|
||||
if (cn != null && _usersByCn.TryGetValue(cn, out user))
|
||||
return BuildResult(user);
|
||||
|
||||
// Try SAN-based values
|
||||
var email = cert.GetNameInfo(X509NameType.EmailName, forIssuer: false);
|
||||
if (!string.IsNullOrWhiteSpace(email) && _usersByCn.TryGetValue(email, out user))
|
||||
return BuildResult(user);
|
||||
|
||||
var dns = cert.GetNameInfo(X509NameType.DnsName, forIssuer: false);
|
||||
if (!string.IsNullOrWhiteSpace(dns) && _usersByCn.TryGetValue(dns, out user))
|
||||
return BuildResult(user);
|
||||
|
||||
var uri = cert.GetNameInfo(X509NameType.UrlName, forIssuer: false);
|
||||
if (!string.IsNullOrWhiteSpace(uri) && _usersByCn.TryGetValue(uri, out user))
|
||||
return BuildResult(user);
|
||||
|
||||
// Match using full RDN + DC components if present.
|
||||
var dcs = GetTlsAuthDcs(dn);
|
||||
if (!string.IsNullOrEmpty(dcs))
|
||||
{
|
||||
var rdnWithDcs = string.IsNullOrEmpty(dnString) ? dcs : $"{dnString},{dcs}";
|
||||
if (_usersByDn.TryGetValue(rdnWithDcs, out user))
|
||||
return BuildResult(user);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
internal static string GetTlsAuthDcs(X500DistinguishedName dn)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(dn.Name))
|
||||
return string.Empty;
|
||||
|
||||
var dcs = new List<string>();
|
||||
foreach (var rdn in dn.Name.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
if (!rdn.StartsWith("DC=", StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
|
||||
dcs.Add("DC=" + rdn[3..].Trim());
|
||||
}
|
||||
|
||||
return string.Join(",", dcs);
|
||||
}
|
||||
|
||||
internal static string[] DnsAltNameLabels(string dnsAltName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(dnsAltName))
|
||||
return [];
|
||||
|
||||
return dnsAltName.ToLowerInvariant().Split('.', StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
|
||||
internal static bool DnsAltNameMatches(string[] dnsAltNameLabels, IReadOnlyList<Uri?> urls)
|
||||
{
|
||||
foreach (var url in urls)
|
||||
{
|
||||
if (url == null)
|
||||
continue;
|
||||
|
||||
var hostLabels = url.DnsSafeHost.ToLowerInvariant().Split('.', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (hostLabels.Length != dnsAltNameLabels.Length)
|
||||
continue;
|
||||
|
||||
var i = 0;
|
||||
if (dnsAltNameLabels.Length > 0 && dnsAltNameLabels[0] == "*")
|
||||
i = 1;
|
||||
|
||||
var matched = true;
|
||||
for (; i < dnsAltNameLabels.Length; i++)
|
||||
{
|
||||
if (!string.Equals(dnsAltNameLabels[i], hostLabels[i], StringComparison.Ordinal))
|
||||
{
|
||||
matched = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (matched)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static string? ExtractCn(X500DistinguishedName dn)
|
||||
{
|
||||
var dnString = dn.Name;
|
||||
|
||||
@@ -7,4 +7,6 @@ public sealed class User
|
||||
public Permissions? Permissions { get; init; }
|
||||
public string? Account { get; init; }
|
||||
public DateTimeOffset? ConnectionDeadline { get; init; }
|
||||
public IReadOnlySet<string>? AllowedConnectionTypes { get; init; }
|
||||
public bool ProxyRequired { get; init; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace NATS.Server;
|
||||
|
||||
// Go reference: server/client.go NON_CLIENT/NATS/MQTT/WS constants.
|
||||
public enum ClientConnectionType
|
||||
{
|
||||
NonClient = 0,
|
||||
Nats = 1,
|
||||
Mqtt = 2,
|
||||
WebSocket = 3,
|
||||
}
|
||||
|
||||
// Go reference: server/client.go ClientProtoZero/ClientProtoInfo constants.
|
||||
public static class ClientProtocolVersion
|
||||
{
|
||||
public const int ClientProtoZero = 0;
|
||||
public const int ClientProtoInfo = 1;
|
||||
}
|
||||
@@ -4,6 +4,8 @@
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
using NATS.Server.Auth;
|
||||
using NATS.Server.JetStream;
|
||||
using NATS.Server.Tls;
|
||||
|
||||
namespace NATS.Server.Configuration;
|
||||
|
||||
@@ -43,12 +45,13 @@ public static class ConfigProcessor
|
||||
public static void ApplyConfig(Dictionary<string, object?> config, NatsOptions opts)
|
||||
{
|
||||
var errors = new List<string>();
|
||||
var warnings = new List<string>();
|
||||
|
||||
foreach (var (key, value) in config)
|
||||
{
|
||||
try
|
||||
{
|
||||
ProcessKey(key, value, opts, errors);
|
||||
ProcessKey(key, value, opts, errors, warnings);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -58,11 +61,16 @@ public static class ConfigProcessor
|
||||
|
||||
if (errors.Count > 0)
|
||||
{
|
||||
throw new ConfigProcessorException("Configuration errors", errors);
|
||||
throw new ConfigProcessorException("Configuration errors", errors, warnings);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ProcessKey(string key, object? value, NatsOptions opts, List<string> errors)
|
||||
private static void ProcessKey(
|
||||
string key,
|
||||
object? value,
|
||||
NatsOptions opts,
|
||||
List<string> errors,
|
||||
List<string> warnings)
|
||||
{
|
||||
// Keys are already case-insensitive from the parser (OrdinalIgnoreCase dictionaries),
|
||||
// but we normalize here for the switch statement.
|
||||
@@ -277,8 +285,15 @@ public static class ConfigProcessor
|
||||
ParseWebSocket(wsDict, opts, errors);
|
||||
break;
|
||||
|
||||
// Unknown keys silently ignored (accounts, resolver, operator, etc.)
|
||||
// Accounts block — each key is an account name containing users/limits
|
||||
case "accounts":
|
||||
if (value is Dictionary<string, object?> accountsDict)
|
||||
ParseAccounts(accountsDict, opts, errors);
|
||||
break;
|
||||
|
||||
// Unknown keys silently ignored (resolver, operator, etc.)
|
||||
default:
|
||||
warnings.Add(new UnknownConfigFieldWarning(key).Message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -745,6 +760,9 @@ public static class ConfigProcessor
|
||||
case "store_dir":
|
||||
options.StoreDir = ToString(value);
|
||||
break;
|
||||
case "domain":
|
||||
options.Domain = ToString(value);
|
||||
break;
|
||||
case "max_mem_store":
|
||||
try
|
||||
{
|
||||
@@ -766,6 +784,68 @@ public static class ConfigProcessor
|
||||
errors.Add($"Invalid jetstream.max_file_store: {ex.Message}");
|
||||
}
|
||||
|
||||
break;
|
||||
case "sync_interval":
|
||||
try
|
||||
{
|
||||
options.SyncInterval = ParseDuration(value);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errors.Add($"Invalid jetstream.sync_interval: {ex.Message}");
|
||||
}
|
||||
|
||||
break;
|
||||
case "sync_always":
|
||||
options.SyncAlways = ToBool(value);
|
||||
break;
|
||||
case "compress_ok":
|
||||
options.CompressOk = ToBool(value);
|
||||
break;
|
||||
case "unique_tag":
|
||||
options.UniqueTag = ToString(value);
|
||||
break;
|
||||
case "strict":
|
||||
options.Strict = ToBool(value);
|
||||
break;
|
||||
case "max_ack_pending":
|
||||
options.MaxAckPending = ToInt(value);
|
||||
break;
|
||||
case "memory_max_stream_bytes":
|
||||
try
|
||||
{
|
||||
options.MemoryMaxStreamBytes = ParseByteSize(value);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errors.Add($"Invalid jetstream.memory_max_stream_bytes: {ex.Message}");
|
||||
}
|
||||
|
||||
break;
|
||||
case "store_max_stream_bytes":
|
||||
try
|
||||
{
|
||||
options.StoreMaxStreamBytes = ParseByteSize(value);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errors.Add($"Invalid jetstream.store_max_stream_bytes: {ex.Message}");
|
||||
}
|
||||
|
||||
break;
|
||||
case "max_bytes_required":
|
||||
options.MaxBytesRequired = ToBool(value);
|
||||
break;
|
||||
case "tiers":
|
||||
if (value is Dictionary<string, object?> tiers)
|
||||
{
|
||||
foreach (var (tierName, rawTier) in tiers)
|
||||
{
|
||||
if (rawTier is Dictionary<string, object?> tierDict)
|
||||
options.Tiers[tierName] = ParseJetStreamTier(tierName, tierDict, errors);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -773,6 +853,47 @@ public static class ConfigProcessor
|
||||
return options;
|
||||
}
|
||||
|
||||
private static JetStreamTier ParseJetStreamTier(string tierName, Dictionary<string, object?> dict, List<string> errors)
|
||||
{
|
||||
var tier = new JetStreamTier { Name = tierName };
|
||||
foreach (var (key, value) in dict)
|
||||
{
|
||||
switch (key.ToLowerInvariant())
|
||||
{
|
||||
case "memory" or "max_memory":
|
||||
try
|
||||
{
|
||||
tier.Memory = ParseByteSize(value);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errors.Add($"Invalid jetstream.tiers.{tierName}.memory: {ex.Message}");
|
||||
}
|
||||
|
||||
break;
|
||||
case "store" or "max_store":
|
||||
try
|
||||
{
|
||||
tier.Store = ParseByteSize(value);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errors.Add($"Invalid jetstream.tiers.{tierName}.store: {ex.Message}");
|
||||
}
|
||||
|
||||
break;
|
||||
case "streams" or "max_streams":
|
||||
tier.Streams = ToInt(value);
|
||||
break;
|
||||
case "consumers" or "max_consumers":
|
||||
tier.Consumers = ToInt(value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return tier;
|
||||
}
|
||||
|
||||
// ─── Authorization parsing ─────────────────────────────────────
|
||||
|
||||
private static void ParseAuthorization(Dictionary<string, object?> dict, NatsOptions opts, List<string> errors)
|
||||
@@ -831,12 +952,80 @@ public static class ConfigProcessor
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Accounts parsing ──────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Parses the top-level "accounts" block. Each key is an account name, and each
|
||||
/// value is a dictionary that may contain "users" (array) and account-level limits.
|
||||
/// Users are stamped with the account name and appended to opts.Users / opts.NKeys.
|
||||
/// Go reference: opts.go — configureAccounts / parseAccounts.
|
||||
/// </summary>
|
||||
private static void ParseAccounts(Dictionary<string, object?> accountsDict, NatsOptions opts, List<string> errors)
|
||||
{
|
||||
opts.Accounts ??= new Dictionary<string, AccountConfig>();
|
||||
|
||||
foreach (var (accountName, accountValue) in accountsDict)
|
||||
{
|
||||
if (accountValue is not Dictionary<string, object?> acctDict)
|
||||
{
|
||||
errors.Add($"Expected account '{accountName}' value to be a map");
|
||||
continue;
|
||||
}
|
||||
|
||||
int maxConnections = 0;
|
||||
int maxSubscriptions = 0;
|
||||
List<object?>? userList = null;
|
||||
|
||||
foreach (var (key, value) in acctDict)
|
||||
{
|
||||
switch (key.ToLowerInvariant())
|
||||
{
|
||||
case "users":
|
||||
if (value is List<object?> ul)
|
||||
userList = ul;
|
||||
break;
|
||||
case "max_connections" or "max_conns":
|
||||
maxConnections = ToInt(value);
|
||||
break;
|
||||
case "max_subscriptions" or "max_subs":
|
||||
maxSubscriptions = ToInt(value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
opts.Accounts[accountName] = new AccountConfig
|
||||
{
|
||||
MaxConnections = maxConnections,
|
||||
MaxSubscriptions = maxSubscriptions,
|
||||
};
|
||||
|
||||
if (userList is not null)
|
||||
{
|
||||
var (plainUsers, nkeyUsers) = ParseUsersAndNkeys(userList, errors, defaultAccount: accountName);
|
||||
|
||||
if (plainUsers.Count > 0)
|
||||
{
|
||||
var existing = opts.Users?.ToList() ?? [];
|
||||
existing.AddRange(plainUsers);
|
||||
opts.Users = existing;
|
||||
}
|
||||
|
||||
if (nkeyUsers.Count > 0)
|
||||
{
|
||||
var existing = opts.NKeys?.ToList() ?? [];
|
||||
existing.AddRange(nkeyUsers);
|
||||
opts.NKeys = existing;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Splits a users array into plain users and NKey users.
|
||||
/// An entry with an "nkey" field is an NKey user; entries with "user" are plain users.
|
||||
/// Go reference: opts.go — parseUsers (lines ~2500-2700).
|
||||
/// </summary>
|
||||
private static (List<User> PlainUsers, List<Auth.NKeyUser> NkeyUsers) ParseUsersAndNkeys(List<object?> list, List<string> errors)
|
||||
private static (List<User> PlainUsers, List<Auth.NKeyUser> NkeyUsers) ParseUsersAndNkeys(List<object?> list, List<string> errors, string? defaultAccount = null)
|
||||
{
|
||||
var plainUsers = new List<User>();
|
||||
var nkeyUsers = new List<Auth.NKeyUser>();
|
||||
@@ -888,7 +1077,7 @@ public static class ConfigProcessor
|
||||
{
|
||||
Nkey = nkey,
|
||||
Permissions = permissions,
|
||||
Account = account,
|
||||
Account = account ?? defaultAccount,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
@@ -903,7 +1092,7 @@ public static class ConfigProcessor
|
||||
{
|
||||
Username = username,
|
||||
Password = password ?? string.Empty,
|
||||
Account = account,
|
||||
Account = account ?? defaultAccount,
|
||||
Permissions = permissions,
|
||||
});
|
||||
}
|
||||
@@ -1087,6 +1276,9 @@ public static class ConfigProcessor
|
||||
case "handshake_first_fallback":
|
||||
opts.TlsHandshakeFirstFallback = ParseDuration(value);
|
||||
break;
|
||||
case "ocsp_peer":
|
||||
ParseOcspPeer(value, opts, errors);
|
||||
break;
|
||||
default:
|
||||
// Unknown TLS keys silently ignored
|
||||
break;
|
||||
@@ -1094,6 +1286,31 @@ public static class ConfigProcessor
|
||||
}
|
||||
}
|
||||
|
||||
private static void ParseOcspPeer(object? value, NatsOptions opts, List<string> errors)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case bool verify:
|
||||
opts.OcspPeerVerify = verify;
|
||||
return;
|
||||
case Dictionary<string, object?> dict:
|
||||
try
|
||||
{
|
||||
var cfg = OCSPPeerConfig.Parse(dict);
|
||||
opts.OcspPeerVerify = cfg.Verify;
|
||||
}
|
||||
catch (FormatException ex)
|
||||
{
|
||||
errors.Add(ex.Message);
|
||||
}
|
||||
|
||||
return;
|
||||
default:
|
||||
errors.Add($"expected map to define OCSP peer options, got [{value?.GetType().Name ?? "null"}]");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Tags parsing ──────────────────────────────────────────────
|
||||
|
||||
private static void ParseTags(Dictionary<string, object?> dict, NatsOptions opts)
|
||||
@@ -1431,8 +1648,28 @@ public static class ConfigProcessor
|
||||
/// Thrown when one or more configuration validation errors are detected.
|
||||
/// All errors are collected rather than failing on the first one.
|
||||
/// </summary>
|
||||
public sealed class ConfigProcessorException(string message, List<string> errors)
|
||||
public sealed class ConfigProcessorException(string message, List<string> errors, List<string>? warnings = null)
|
||||
: Exception(message)
|
||||
{
|
||||
public IReadOnlyList<string> Errors => errors;
|
||||
public IReadOnlyList<string> Warnings => warnings ?? [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a non-fatal configuration warning.
|
||||
/// Go reference: configWarningErr.
|
||||
/// </summary>
|
||||
public class ConfigWarningException(string message, string? source = null) : Exception(message)
|
||||
{
|
||||
public string? SourceLocation { get; } = source;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Warning used when an unknown config field is encountered.
|
||||
/// Go reference: unknownConfigFieldErr.
|
||||
/// </summary>
|
||||
public sealed class UnknownConfigFieldWarning(string field, string? source = null)
|
||||
: ConfigWarningException($"unknown field {field}", source)
|
||||
{
|
||||
public string Field { get; } = field;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,115 @@ public sealed class GatewayOptions
|
||||
/// </summary>
|
||||
public sealed class RemoteGatewayOptions
|
||||
{
|
||||
private int _connAttempts;
|
||||
|
||||
public string? Name { get; set; }
|
||||
public List<string> Urls { get; set; } = [];
|
||||
public bool Implicit { get; set; }
|
||||
public byte[]? Hash { get; set; }
|
||||
public byte[]? OldHash { get; set; }
|
||||
public string? TlsName { get; private set; }
|
||||
public bool VarzUpdateUrls { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Deep clone helper for remote gateway options.
|
||||
/// Go reference: RemoteGatewayOpts.clone.
|
||||
/// </summary>
|
||||
public RemoteGatewayOptions Clone()
|
||||
{
|
||||
return new RemoteGatewayOptions
|
||||
{
|
||||
Name = Name,
|
||||
Urls = [.. Urls],
|
||||
Implicit = Implicit,
|
||||
Hash = Hash == null ? null : [.. Hash],
|
||||
OldHash = OldHash == null ? null : [.. OldHash],
|
||||
TlsName = TlsName,
|
||||
VarzUpdateUrls = VarzUpdateUrls,
|
||||
};
|
||||
}
|
||||
|
||||
public int BumpConnAttempts() => Interlocked.Increment(ref _connAttempts);
|
||||
|
||||
public int GetConnAttempts() => Volatile.Read(ref _connAttempts);
|
||||
|
||||
public void ResetConnAttempts() => Interlocked.Exchange(ref _connAttempts, 0);
|
||||
|
||||
public bool IsImplicit() => Implicit;
|
||||
|
||||
public List<Uri> GetUrls(Random? random = null)
|
||||
{
|
||||
var urls = new List<Uri>();
|
||||
foreach (var url in Urls)
|
||||
{
|
||||
if (TryNormalizeRemoteUrl(url, out var uri))
|
||||
urls.Add(uri);
|
||||
}
|
||||
|
||||
random ??= Random.Shared;
|
||||
for (var i = urls.Count - 1; i > 0; i--)
|
||||
{
|
||||
var j = random.Next(i + 1);
|
||||
(urls[i], urls[j]) = (urls[j], urls[i]);
|
||||
}
|
||||
|
||||
return urls;
|
||||
}
|
||||
|
||||
public List<string> GetUrlsAsStrings()
|
||||
{
|
||||
var result = new List<string>();
|
||||
foreach (var uri in GetUrls())
|
||||
result.Add($"{uri.Scheme}://{uri.Authority}");
|
||||
return result;
|
||||
}
|
||||
|
||||
public void UpdateUrls(IEnumerable<string> configuredUrls, IEnumerable<string> discoveredUrls)
|
||||
{
|
||||
var merged = new List<string>();
|
||||
AddUrlsInternal(merged, configuredUrls);
|
||||
AddUrlsInternal(merged, discoveredUrls);
|
||||
Urls = merged;
|
||||
}
|
||||
|
||||
public void SaveTlsHostname(string url)
|
||||
{
|
||||
if (TryNormalizeRemoteUrl(url, out var uri))
|
||||
TlsName = uri.Host;
|
||||
}
|
||||
|
||||
public void AddUrls(IEnumerable<string> discoveredUrls)
|
||||
{
|
||||
AddUrlsInternal(Urls, discoveredUrls);
|
||||
}
|
||||
|
||||
private static void AddUrlsInternal(List<string> target, IEnumerable<string> urls)
|
||||
{
|
||||
var seen = new HashSet<string>(target, StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var url in urls)
|
||||
{
|
||||
if (!TryNormalizeRemoteUrl(url, out var uri))
|
||||
continue;
|
||||
|
||||
var normalized = $"{uri.Scheme}://{uri.Authority}";
|
||||
if (seen.Add(normalized))
|
||||
target.Add(normalized);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryNormalizeRemoteUrl(string? raw, out Uri uri)
|
||||
{
|
||||
uri = default!;
|
||||
if (string.IsNullOrWhiteSpace(raw))
|
||||
return false;
|
||||
|
||||
var normalized = raw.Contains("://", StringComparison.Ordinal) ? raw : $"nats://{raw}";
|
||||
if (Uri.TryCreate(normalized, UriKind.Absolute, out var parsed) && parsed is not null)
|
||||
{
|
||||
uri = parsed;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,11 @@ namespace NATS.Server.Configuration;
|
||||
// Controls the lifecycle parameters for the JetStream subsystem.
|
||||
public sealed class JetStreamOptions
|
||||
{
|
||||
// Go: server/jetstream.go constants (dynJetStreamConfig defaults)
|
||||
public const string JetStreamStoreDir = "jetstream";
|
||||
public const long JetStreamMaxStoreDefault = 1L << 40; // 1 TiB
|
||||
public const long JetStreamMaxMemDefault = 256L * 1024 * 1024; // 256 MiB
|
||||
|
||||
/// <summary>
|
||||
/// Directory where JetStream persists stream data.
|
||||
/// Maps to Go's JetStreamConfig.StoreDir (jetstream.go:enableJetStream:430).
|
||||
@@ -41,4 +46,64 @@ public sealed class JetStreamOptions
|
||||
/// Maps to Go's Options.JetStreamDomain (opts.go).
|
||||
/// </summary>
|
||||
public string? Domain { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// File-store sync interval.
|
||||
/// Go reference: server/jetstream.go JetStreamConfig.SyncInterval.
|
||||
/// </summary>
|
||||
public TimeSpan SyncInterval { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Forces sync on each write when true.
|
||||
/// Go reference: server/jetstream.go JetStreamConfig.SyncAlways.
|
||||
/// </summary>
|
||||
public bool SyncAlways { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether compression is allowed for JetStream replication/storage paths.
|
||||
/// Go reference: server/jetstream.go JetStreamConfig.CompressOK.
|
||||
/// </summary>
|
||||
public bool CompressOk { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Unique placement tag used in clustered deployments.
|
||||
/// Go reference: server/jetstream.go JetStreamConfig.UniqueTag.
|
||||
/// </summary>
|
||||
public string? UniqueTag { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enables strict validation mode for JetStream.
|
||||
/// Go reference: server/jetstream.go JetStreamConfig.Strict.
|
||||
/// </summary>
|
||||
public bool Strict { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Account-level maximum pending acknowledgements.
|
||||
/// Go reference: server/jetstream.go JetStreamAccountLimits.MaxAckPending.
|
||||
/// </summary>
|
||||
public int MaxAckPending { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Maximum bytes allowed per memory-backed stream.
|
||||
/// Go reference: server/jetstream.go JetStreamAccountLimits.MemoryMaxStreamBytes.
|
||||
/// </summary>
|
||||
public long MemoryMaxStreamBytes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Maximum bytes allowed per file-backed stream.
|
||||
/// Go reference: server/jetstream.go JetStreamAccountLimits.StoreMaxStreamBytes.
|
||||
/// </summary>
|
||||
public long StoreMaxStreamBytes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When true, stream configs must specify explicit MaxBytes.
|
||||
/// Go reference: server/jetstream.go JetStreamAccountLimits.MaxBytesRequired.
|
||||
/// </summary>
|
||||
public bool MaxBytesRequired { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional per-tier JetStream limits keyed by tier name.
|
||||
/// Go reference: server/jetstream.go JetStreamAccountLimits tiers.
|
||||
/// </summary>
|
||||
public Dictionary<string, NATS.Server.JetStream.JetStreamTier> Tiers { get; set; } = new(StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,109 @@ public sealed class RemoteLeafOptions
|
||||
|
||||
/// <summary>Whether to not randomize URL order.</summary>
|
||||
public bool DontRandomize { get; init; }
|
||||
|
||||
private int _urlIndex = -1;
|
||||
private TimeSpan _connectDelay;
|
||||
private Timer? _migrateTimer;
|
||||
|
||||
/// <summary>Last URL selected by <see cref="PickNextUrl"/>.</summary>
|
||||
public string? CurrentUrl { get; private set; }
|
||||
|
||||
/// <summary>Saved TLS hostname for SNI usage on solicited connections.</summary>
|
||||
public string? TlsName { get; private set; }
|
||||
|
||||
/// <summary>Username parsed from URL user-info fallback.</summary>
|
||||
public string? Username { get; private set; }
|
||||
|
||||
/// <summary>Password parsed from URL user-info fallback.</summary>
|
||||
public string? Password { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the next URL using round-robin order and updates <see cref="CurrentUrl"/>.
|
||||
/// Go reference: leafnode.go leafNodeCfg.pickNextURL.
|
||||
/// </summary>
|
||||
public string PickNextUrl()
|
||||
{
|
||||
if (Urls.Count == 0)
|
||||
throw new InvalidOperationException("No remote leaf URLs configured.");
|
||||
|
||||
var idx = Interlocked.Increment(ref _urlIndex);
|
||||
var next = Urls[idx % Urls.Count];
|
||||
CurrentUrl = next;
|
||||
return next;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current selected URL, or null if no URL has been selected yet.
|
||||
/// Go reference: leafnode.go leafNodeCfg.getCurrentURL.
|
||||
/// </summary>
|
||||
public string? GetCurrentUrl() => CurrentUrl;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the currently configured reconnect/connect delay for this remote.
|
||||
/// Go reference: leafnode.go leafNodeCfg.getConnectDelay.
|
||||
/// </summary>
|
||||
public TimeSpan GetConnectDelay() => _connectDelay;
|
||||
|
||||
/// <summary>
|
||||
/// Sets reconnect/connect delay for this remote.
|
||||
/// Go reference: leafnode.go leafNodeCfg.setConnectDelay.
|
||||
/// </summary>
|
||||
public void SetConnectDelay(TimeSpan delay) => _connectDelay = delay;
|
||||
|
||||
/// <summary>
|
||||
/// Starts or replaces the JetStream migration timer callback for this remote leaf.
|
||||
/// Go reference: leafnode.go leafNodeCfg.migrateTimer.
|
||||
/// </summary>
|
||||
public void StartMigrateTimer(TimerCallback callback, TimeSpan delay)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(callback);
|
||||
var timer = new Timer(callback, null, delay, Timeout.InfiniteTimeSpan);
|
||||
var previous = Interlocked.Exchange(ref _migrateTimer, timer);
|
||||
previous?.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancels the JetStream migration timer if active.
|
||||
/// Go reference: leafnode.go leafNodeCfg.cancelMigrateTimer.
|
||||
/// </summary>
|
||||
public void CancelMigrateTimer()
|
||||
{
|
||||
var timer = Interlocked.Exchange(ref _migrateTimer, null);
|
||||
timer?.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves TLS hostname from URL for future SNI usage.
|
||||
/// Go reference: leafnode.go leafNodeCfg.saveTLSHostname.
|
||||
/// </summary>
|
||||
public void SaveTlsHostname(string url)
|
||||
{
|
||||
if (TryParseUrl(url, out var uri))
|
||||
TlsName = uri.Host;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves username/password from URL user info for fallback auth.
|
||||
/// Go reference: leafnode.go leafNodeCfg.saveUserPassword.
|
||||
/// </summary>
|
||||
public void SaveUserPassword(string url)
|
||||
{
|
||||
if (!TryParseUrl(url, out var uri) || string.IsNullOrEmpty(uri.UserInfo))
|
||||
return;
|
||||
|
||||
var parts = uri.UserInfo.Split(':', 2, StringSplitOptions.None);
|
||||
Username = Uri.UnescapeDataString(parts[0]);
|
||||
Password = parts.Length > 1 ? Uri.UnescapeDataString(parts[1]) : string.Empty;
|
||||
}
|
||||
|
||||
private static bool TryParseUrl(string url, out Uri uri)
|
||||
{
|
||||
if (Uri.TryCreate(url, UriKind.Absolute, out uri!))
|
||||
return true;
|
||||
|
||||
return Uri.TryCreate($"nats://{url}", UriKind.Absolute, out uri!);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class LeafNodeOptions
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
// Port of Go conf/lex.go — state-machine tokenizer for NATS config files.
|
||||
// Reference: golang/nats-server/conf/lex.go
|
||||
|
||||
using System.Buffers;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
namespace NATS.Server.Configuration;
|
||||
|
||||
public sealed class NatsConfLexer
|
||||
@@ -145,16 +149,23 @@ public sealed class NatsConfLexer
|
||||
return Eof;
|
||||
}
|
||||
|
||||
if (_input[_pos] == '\n')
|
||||
var span = _input.AsSpan(_pos);
|
||||
var status = Rune.DecodeFromUtf16(span, out var rune, out var consumed);
|
||||
if (status != OperationStatus.Done || consumed <= 0)
|
||||
{
|
||||
consumed = 1;
|
||||
rune = new Rune(_input[_pos]);
|
||||
}
|
||||
|
||||
if (rune.Value == '\n')
|
||||
{
|
||||
_line++;
|
||||
_lstart = _pos;
|
||||
}
|
||||
|
||||
var c = _input[_pos];
|
||||
_width = 1;
|
||||
_pos += _width;
|
||||
return c;
|
||||
_width = consumed;
|
||||
_pos += consumed;
|
||||
return rune.IsBmp ? (char)rune.Value : '\uFFFD';
|
||||
}
|
||||
|
||||
private void Ignore()
|
||||
@@ -186,6 +197,20 @@ public sealed class NatsConfLexer
|
||||
return null;
|
||||
}
|
||||
|
||||
private LexState? Errorf(string format, params object?[] args)
|
||||
{
|
||||
if (args.Length == 0)
|
||||
return Errorf(format);
|
||||
|
||||
var escapedArgs = new object?[args.Length];
|
||||
for (var i = 0; i < args.Length; i++)
|
||||
{
|
||||
escapedArgs[i] = args[i] is char c ? EscapeSpecial(c) : args[i];
|
||||
}
|
||||
|
||||
return Errorf(string.Format(CultureInfo.InvariantCulture, format, escapedArgs));
|
||||
}
|
||||
|
||||
// --- Helper methods ---
|
||||
|
||||
private static bool IsWhitespace(char c) => c is '\t' or ' ';
|
||||
@@ -1476,9 +1501,8 @@ public sealed class NatsConfLexer
|
||||
var r = lx.Peek();
|
||||
if (IsNL(r) || r == Eof)
|
||||
{
|
||||
// Consume the comment text but don't emit it as a user-visible token.
|
||||
// Just ignore it and pop back.
|
||||
lx.Ignore();
|
||||
// Match Go behavior: emit comment body as a text token.
|
||||
lx.Emit(TokenType.Text);
|
||||
return lx.Pop();
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using System.Globalization;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace NATS.Server.Configuration;
|
||||
|
||||
@@ -15,6 +16,7 @@ namespace NATS.Server.Configuration;
|
||||
/// </summary>
|
||||
public static class NatsConfParser
|
||||
{
|
||||
private const string BcryptPrefix = "2a$";
|
||||
// Bcrypt hashes start with $2a$ or $2b$. The lexer consumes the leading '$'
|
||||
// and emits a Variable token whose value begins with "2a$" or "2b$".
|
||||
private const string BcryptPrefix2A = "2a$";
|
||||
@@ -34,12 +36,24 @@ public static class NatsConfParser
|
||||
return state.Mapping;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pedantic compatibility API (Go: ParseWithChecks).
|
||||
/// Uses the same parser behavior as <see cref="Parse(string)"/>.
|
||||
/// </summary>
|
||||
public static Dictionary<string, object?> ParseWithChecks(string data) => Parse(data);
|
||||
|
||||
/// <summary>
|
||||
/// Parses a NATS configuration file into a dictionary.
|
||||
/// </summary>
|
||||
public static Dictionary<string, object?> ParseFile(string filePath) =>
|
||||
ParseFile(filePath, includeDepth: 0);
|
||||
|
||||
/// <summary>
|
||||
/// Pedantic compatibility API (Go: ParseFileWithChecks).
|
||||
/// Uses the same parser behavior as <see cref="ParseFile(string)"/>.
|
||||
/// </summary>
|
||||
public static Dictionary<string, object?> ParseFileWithChecks(string filePath) => ParseFile(filePath);
|
||||
|
||||
private static Dictionary<string, object?> ParseFile(string filePath, int includeDepth)
|
||||
{
|
||||
var data = File.ReadAllText(filePath);
|
||||
@@ -68,6 +82,94 @@ public static class NatsConfParser
|
||||
return (state.Mapping, digest);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pedantic compatibility API (Go: ParseFileWithChecksDigest).
|
||||
/// </summary>
|
||||
public static (Dictionary<string, object?> Config, string Digest) ParseFileWithChecksDigest(string filePath)
|
||||
{
|
||||
var data = File.ReadAllText(filePath);
|
||||
var tokens = NatsConfLexer.Tokenize(data);
|
||||
var baseDir = Path.GetDirectoryName(Path.GetFullPath(filePath)) ?? string.Empty;
|
||||
var state = new ParserState(tokens, baseDir, [], includeDepth: 0);
|
||||
state.Run();
|
||||
CleanupUsedEnvVars(state.Mapping);
|
||||
|
||||
var digest = ComputeConfigDigest(state.Mapping);
|
||||
return (state.Mapping, digest);
|
||||
}
|
||||
|
||||
// Go pedantic mode removes env-var wrapper nodes from parsed maps before digesting.
|
||||
// The current parser does not persist env-wrapper nodes, so this remains a no-op hook.
|
||||
private static void CleanupUsedEnvVars(Dictionary<string, object?> _) { }
|
||||
|
||||
private static string ComputeConfigDigest(Dictionary<string, object?> config)
|
||||
{
|
||||
using var ms = new MemoryStream();
|
||||
using (var writer = new Utf8JsonWriter(ms))
|
||||
{
|
||||
WriteCanonicalJsonValue(writer, config);
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
var hashBytes = SHA256.HashData(ms.ToArray());
|
||||
return "sha256:" + Convert.ToHexStringLower(hashBytes);
|
||||
}
|
||||
|
||||
private static void WriteCanonicalJsonValue(Utf8JsonWriter writer, object? value)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case null:
|
||||
writer.WriteNullValue();
|
||||
return;
|
||||
case Dictionary<string, object?> map:
|
||||
writer.WriteStartObject();
|
||||
foreach (var key in map.Keys.OrderBy(static k => k, StringComparer.Ordinal))
|
||||
{
|
||||
writer.WritePropertyName(key);
|
||||
WriteCanonicalJsonValue(writer, map[key]);
|
||||
}
|
||||
writer.WriteEndObject();
|
||||
return;
|
||||
case List<object?> list:
|
||||
writer.WriteStartArray();
|
||||
foreach (var item in list)
|
||||
WriteCanonicalJsonValue(writer, item);
|
||||
writer.WriteEndArray();
|
||||
return;
|
||||
case IReadOnlyList<string> stringList:
|
||||
writer.WriteStartArray();
|
||||
foreach (var item in stringList)
|
||||
writer.WriteStringValue(item);
|
||||
writer.WriteEndArray();
|
||||
return;
|
||||
case bool b:
|
||||
writer.WriteBooleanValue(b);
|
||||
return;
|
||||
case int i:
|
||||
writer.WriteNumberValue(i);
|
||||
return;
|
||||
case long l:
|
||||
writer.WriteNumberValue(l);
|
||||
return;
|
||||
case double d:
|
||||
writer.WriteNumberValue(d);
|
||||
return;
|
||||
case float f:
|
||||
writer.WriteNumberValue(f);
|
||||
return;
|
||||
case decimal dec:
|
||||
writer.WriteNumberValue(dec);
|
||||
return;
|
||||
case string s:
|
||||
writer.WriteStringValue(s);
|
||||
return;
|
||||
default:
|
||||
JsonSerializer.Serialize(writer, value, value.GetType());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal: parse an environment variable value by wrapping it in a synthetic
|
||||
/// key-value assignment and parsing it. Shares the parent's env var cycle tracker.
|
||||
@@ -99,6 +201,8 @@ public static class NatsConfParser
|
||||
|
||||
// Key stack for map assignments.
|
||||
private readonly List<string> _keys = new(4);
|
||||
// Pedantic-mode key token stack (Go parser field: ikeys).
|
||||
private readonly List<Token> _itemKeys = new(4);
|
||||
|
||||
public Dictionary<string, object?> Mapping { get; } = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
@@ -182,6 +286,18 @@ public static class NatsConfParser
|
||||
return last;
|
||||
}
|
||||
|
||||
private void PushItemKey(Token token) => _itemKeys.Add(token);
|
||||
|
||||
private Token PopItemKey()
|
||||
{
|
||||
if (_itemKeys.Count == 0)
|
||||
return default;
|
||||
|
||||
var last = _itemKeys[^1];
|
||||
_itemKeys.RemoveAt(_itemKeys.Count - 1);
|
||||
return last;
|
||||
}
|
||||
|
||||
private void SetValue(object? val)
|
||||
{
|
||||
// Array context: append the value.
|
||||
@@ -195,6 +311,7 @@ public static class NatsConfParser
|
||||
if (_ctx is Dictionary<string, object?> map)
|
||||
{
|
||||
var key = PopKey();
|
||||
_ = PopItemKey();
|
||||
map[key] = val;
|
||||
return;
|
||||
}
|
||||
@@ -211,6 +328,7 @@ public static class NatsConfParser
|
||||
|
||||
case TokenType.Key:
|
||||
PushKey(token.Value);
|
||||
PushItemKey(token);
|
||||
break;
|
||||
|
||||
case TokenType.String:
|
||||
@@ -262,6 +380,7 @@ public static class NatsConfParser
|
||||
break;
|
||||
|
||||
case TokenType.Comment:
|
||||
case TokenType.Text:
|
||||
// Skip comments entirely.
|
||||
break;
|
||||
|
||||
@@ -347,7 +466,8 @@ public static class NatsConfParser
|
||||
|
||||
// Special case: raw bcrypt strings ($2a$... or $2b$...).
|
||||
// The lexer consumed the leading '$', so the variable value starts with "2a$" or "2b$".
|
||||
if (varName.StartsWith(BcryptPrefix2A, StringComparison.Ordinal) ||
|
||||
if (varName.StartsWith(BcryptPrefix, StringComparison.Ordinal) ||
|
||||
varName.StartsWith(BcryptPrefix2A, StringComparison.Ordinal) ||
|
||||
varName.StartsWith(BcryptPrefix2B, StringComparison.Ordinal))
|
||||
{
|
||||
SetValue("$" + varName);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// Port of Go conf/lex.go token types.
|
||||
|
||||
using System.Text.Json;
|
||||
|
||||
namespace NATS.Server.Configuration;
|
||||
|
||||
public enum TokenType
|
||||
@@ -7,6 +9,7 @@ public enum TokenType
|
||||
Error,
|
||||
Eof,
|
||||
Key,
|
||||
Text,
|
||||
String,
|
||||
Bool,
|
||||
Integer,
|
||||
@@ -22,3 +25,34 @@ public enum TokenType
|
||||
}
|
||||
|
||||
public readonly record struct Token(TokenType Type, string Value, int Line, int Position);
|
||||
|
||||
/// <summary>
|
||||
/// Pedantic token wrapper matching Go conf/parse.go token accessors.
|
||||
/// </summary>
|
||||
public sealed class PedanticToken
|
||||
{
|
||||
private readonly Token _item;
|
||||
private readonly object? _value;
|
||||
private readonly bool _usedVariable;
|
||||
private readonly string _sourceFile;
|
||||
|
||||
public PedanticToken(Token item, object? value = null, bool usedVariable = false, string sourceFile = "")
|
||||
{
|
||||
_item = item;
|
||||
_value = value;
|
||||
_usedVariable = usedVariable;
|
||||
_sourceFile = sourceFile ?? string.Empty;
|
||||
}
|
||||
|
||||
public string MarshalJson() => JsonSerializer.Serialize(Value());
|
||||
|
||||
public object? Value() => _value ?? _item.Value;
|
||||
|
||||
public int Line() => _item.Line;
|
||||
|
||||
public bool IsUsedVariable() => _usedVariable;
|
||||
|
||||
public string SourceFile() => _sourceFile;
|
||||
|
||||
public int Position() => _item.Position;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,23 @@
|
||||
// Go reference: server/events.go:2081-2090 — compressionType, snappyCompression,
|
||||
// and events.go:578-598 — internalSendLoop compression via s2.WriterSnappyCompat().
|
||||
|
||||
using System.IO.Compression;
|
||||
using IronSnappy;
|
||||
|
||||
namespace NATS.Server.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Compression encodings supported for event API responses.
|
||||
/// Go reference: events.go compressionType constants.
|
||||
/// </summary>
|
||||
public enum EventCompressionType : sbyte
|
||||
{
|
||||
None = 0,
|
||||
Gzip = 1,
|
||||
Snappy = 2,
|
||||
Unsupported = 3,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides S2 (Snappy-compatible) compression for system event payloads.
|
||||
/// Maps to Go's compressionType / snappyCompression handling in events.go:2082-2098
|
||||
@@ -12,6 +25,9 @@ namespace NATS.Server.Events;
|
||||
/// </summary>
|
||||
public static class EventCompressor
|
||||
{
|
||||
public const string AcceptEncodingHeader = "Accept-Encoding";
|
||||
public const string ContentEncodingHeader = "Content-Encoding";
|
||||
|
||||
// Default threshold: only compress payloads larger than this many bytes.
|
||||
// Compressing tiny payloads wastes CPU and may produce larger output.
|
||||
private const int DefaultThresholdBytes = 256;
|
||||
@@ -56,11 +72,23 @@ public static class EventCompressor
|
||||
/// <param name="payload">Raw bytes to compress.</param>
|
||||
/// <returns>Compressed bytes. Returns an empty array for empty input.</returns>
|
||||
public static byte[] Compress(ReadOnlySpan<byte> payload)
|
||||
=> Compress(payload, EventCompressionType.Snappy);
|
||||
|
||||
/// <summary>
|
||||
/// Compresses <paramref name="payload"/> using the requested <paramref name="compression"/>.
|
||||
/// </summary>
|
||||
public static byte[] Compress(ReadOnlySpan<byte> payload, EventCompressionType compression)
|
||||
{
|
||||
if (payload.IsEmpty)
|
||||
return [];
|
||||
|
||||
return Snappy.Encode(payload);
|
||||
return compression switch
|
||||
{
|
||||
EventCompressionType.None => payload.ToArray(),
|
||||
EventCompressionType.Gzip => CompressGzip(payload),
|
||||
EventCompressionType.Snappy => Snappy.Encode(payload),
|
||||
_ => throw new InvalidOperationException($"Unsupported compression type: {compression}."),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -71,11 +99,23 @@ public static class EventCompressor
|
||||
/// <returns>Decompressed bytes. Returns an empty array for empty input.</returns>
|
||||
/// <exception cref="Exception">Propagated from IronSnappy if data is corrupt.</exception>
|
||||
public static byte[] Decompress(ReadOnlySpan<byte> compressed)
|
||||
=> Decompress(compressed, EventCompressionType.Snappy);
|
||||
|
||||
/// <summary>
|
||||
/// Decompresses <paramref name="compressed"/> using the selected <paramref name="compression"/>.
|
||||
/// </summary>
|
||||
public static byte[] Decompress(ReadOnlySpan<byte> compressed, EventCompressionType compression)
|
||||
{
|
||||
if (compressed.IsEmpty)
|
||||
return [];
|
||||
|
||||
return Snappy.Decode(compressed);
|
||||
return compression switch
|
||||
{
|
||||
EventCompressionType.None => compressed.ToArray(),
|
||||
EventCompressionType.Gzip => DecompressGzip(compressed),
|
||||
EventCompressionType.Snappy => Snappy.Decode(compressed),
|
||||
_ => throw new InvalidOperationException($"Unsupported compression type: {compression}."),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -105,6 +145,15 @@ public static class EventCompressor
|
||||
public static (byte[] Data, bool Compressed) CompressIfBeneficial(
|
||||
ReadOnlySpan<byte> payload,
|
||||
int thresholdBytes = DefaultThresholdBytes)
|
||||
=> CompressIfBeneficial(payload, EventCompressionType.Snappy, thresholdBytes);
|
||||
|
||||
/// <summary>
|
||||
/// Compresses using <paramref name="compression"/> when payload size exceeds threshold.
|
||||
/// </summary>
|
||||
public static (byte[] Data, bool Compressed) CompressIfBeneficial(
|
||||
ReadOnlySpan<byte> payload,
|
||||
EventCompressionType compression,
|
||||
int thresholdBytes = DefaultThresholdBytes)
|
||||
{
|
||||
if (!ShouldCompress(payload.Length, thresholdBytes))
|
||||
{
|
||||
@@ -112,7 +161,7 @@ public static class EventCompressor
|
||||
return (payload.ToArray(), false);
|
||||
}
|
||||
|
||||
var compressed = Compress(payload);
|
||||
var compressed = Compress(payload, compression);
|
||||
Interlocked.Increment(ref _totalCompressed);
|
||||
var saved = payload.Length - compressed.Length;
|
||||
if (saved > 0)
|
||||
@@ -135,4 +184,45 @@ public static class EventCompressor
|
||||
|
||||
return (double)compressedSize / originalSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses an HTTP Accept-Encoding value into a supported compression type.
|
||||
/// Go reference: events.go getAcceptEncoding().
|
||||
/// </summary>
|
||||
public static EventCompressionType GetAcceptEncoding(string? acceptEncoding)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(acceptEncoding))
|
||||
return EventCompressionType.None;
|
||||
|
||||
var value = acceptEncoding.ToLowerInvariant();
|
||||
if (value.Contains("snappy", StringComparison.Ordinal)
|
||||
|| value.Contains("s2", StringComparison.Ordinal))
|
||||
{
|
||||
return EventCompressionType.Snappy;
|
||||
}
|
||||
|
||||
if (value.Contains("gzip", StringComparison.Ordinal))
|
||||
return EventCompressionType.Gzip;
|
||||
|
||||
return EventCompressionType.Unsupported;
|
||||
}
|
||||
|
||||
private static byte[] CompressGzip(ReadOnlySpan<byte> payload)
|
||||
{
|
||||
using var output = new MemoryStream();
|
||||
using (var gzip = new GZipStream(output, CompressionLevel.Fastest, leaveOpen: true))
|
||||
{
|
||||
gzip.Write(payload);
|
||||
}
|
||||
return output.ToArray();
|
||||
}
|
||||
|
||||
private static byte[] DecompressGzip(ReadOnlySpan<byte> compressed)
|
||||
{
|
||||
using var input = new MemoryStream(compressed.ToArray());
|
||||
using var gzip = new GZipStream(input, CompressionMode.Decompress);
|
||||
using var output = new MemoryStream();
|
||||
gzip.CopyTo(output);
|
||||
return output.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +25,9 @@ public static class EventSubjects
|
||||
// Remote server and leaf node events
|
||||
public const string RemoteServerShutdown = "$SYS.SERVER.{0}.REMOTE.SHUTDOWN";
|
||||
public const string RemoteServerUpdate = "$SYS.SERVER.{0}.REMOTE.UPDATE";
|
||||
public const string LeafNodeConnected = "$SYS.SERVER.{0}.LEAFNODE.CONNECT";
|
||||
public const string LeafNodeConnected = "$SYS.ACCOUNT.{0}.LEAFNODE.CONNECT";
|
||||
public const string LeafNodeDisconnected = "$SYS.SERVER.{0}.LEAFNODE.DISCONNECT";
|
||||
public const string RemoteLatency = "$SYS.SERVER.{0}.ACC.{1}.LATENCY.M2";
|
||||
|
||||
// Request-reply subjects (server-specific)
|
||||
public const string ServerReq = "$SYS.REQ.SERVER.{0}.{1}";
|
||||
@@ -36,13 +37,22 @@ public static class EventSubjects
|
||||
|
||||
// Account-scoped request subjects
|
||||
public const string AccountReq = "$SYS.REQ.ACCOUNT.{0}.{1}";
|
||||
public const string UserDirectInfo = "$SYS.REQ.USER.INFO";
|
||||
public const string UserDirectReq = "$SYS.REQ.USER.{0}.INFO";
|
||||
public const string AccountNumSubsReq = "$SYS.REQ.ACCOUNT.NSUBS";
|
||||
public const string AccountSubs = "$SYS._INBOX_.{0}.NSUBS";
|
||||
public const string ClientKickReq = "$SYS.REQ.SERVER.{0}.KICK";
|
||||
public const string ClientLdmReq = "$SYS.REQ.SERVER.{0}.LDM";
|
||||
public const string ServerStatsPingReq = "$SYS.REQ.SERVER.PING.STATSZ";
|
||||
public const string ServerReloadReq = "$SYS.REQ.SERVER.{0}.RELOAD";
|
||||
|
||||
// Inbox for responses
|
||||
public const string InboxResponse = "$SYS._INBOX_.{0}";
|
||||
|
||||
// OCSP advisory events
|
||||
// Go reference: ocsp.go — OCSP peer reject and chain validation subjects.
|
||||
public const string OcspPeerReject = "$SYS.SERVER.{0}.OCSP.PEER.REJECT";
|
||||
public const string OcspPeerReject = "$SYS.SERVER.{0}.OCSP.PEER.CONN.REJECT";
|
||||
public const string OcspPeerChainlinkInvalid = "$SYS.SERVER.{0}.OCSP.PEER.LINK.INVALID";
|
||||
public const string OcspChainValidation = "$SYS.SERVER.{0}.OCSP.CHAIN.VALIDATION";
|
||||
|
||||
// JetStream advisory events
|
||||
|
||||
@@ -2,6 +2,35 @@ using System.Text.Json.Serialization;
|
||||
|
||||
namespace NATS.Server.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Server capability flags.
|
||||
/// Go reference: events.go ServerCapability constants.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum ServerCapability : ulong
|
||||
{
|
||||
None = 0,
|
||||
JetStreamEnabled = 1UL << 0,
|
||||
BinaryStreamSnapshot = 1UL << 1,
|
||||
AccountNRG = 1UL << 2,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Server identity response model used by IDZ requests.
|
||||
/// Go reference: events.go ServerID.
|
||||
/// </summary>
|
||||
public sealed class ServerID
|
||||
{
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("host")]
|
||||
public string Host { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Server identity block embedded in all system events.
|
||||
/// Go reference: events.go:249-265 ServerInfo struct.
|
||||
@@ -53,6 +82,27 @@ public sealed class EventServerInfo
|
||||
[JsonPropertyName("time")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
|
||||
public DateTime Time { get; set; }
|
||||
|
||||
public void SetJetStreamEnabled()
|
||||
{
|
||||
JetStream = true;
|
||||
Flags |= (ulong)ServerCapability.JetStreamEnabled;
|
||||
}
|
||||
|
||||
public bool JetStreamEnabled() =>
|
||||
(Flags & (ulong)ServerCapability.JetStreamEnabled) != 0;
|
||||
|
||||
public void SetBinaryStreamSnapshot() =>
|
||||
Flags |= (ulong)ServerCapability.BinaryStreamSnapshot;
|
||||
|
||||
public bool BinaryStreamSnapshot() =>
|
||||
(Flags & (ulong)ServerCapability.BinaryStreamSnapshot) != 0;
|
||||
|
||||
public void SetAccountNRG() =>
|
||||
Flags |= (ulong)ServerCapability.AccountNRG;
|
||||
|
||||
public bool AccountNRG() =>
|
||||
(Flags & (ulong)ServerCapability.AccountNRG) != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -536,10 +586,66 @@ public sealed class OcspPeerRejectEventMsg
|
||||
[JsonPropertyName("server")]
|
||||
public EventServerInfo Server { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("peer")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public EventCertInfo? Peer { get; set; }
|
||||
|
||||
[JsonPropertyName("reason")]
|
||||
public string Reason { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Certificate identity block used by OCSP peer advisories.
|
||||
/// Go reference: certidp.CertInfo payload embedded in events.go OCSP messages.
|
||||
/// </summary>
|
||||
public sealed class EventCertInfo
|
||||
{
|
||||
[JsonPropertyName("subject")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? Subject { get; set; }
|
||||
|
||||
[JsonPropertyName("issuer")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? Issuer { get; set; }
|
||||
|
||||
[JsonPropertyName("fingerprint")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? Fingerprint { get; set; }
|
||||
|
||||
[JsonPropertyName("raw")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? Raw { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// OCSP chain-link invalid advisory.
|
||||
/// Go reference: events.go OCSPPeerChainlinkInvalidEventMsg.
|
||||
/// </summary>
|
||||
public sealed class OcspPeerChainlinkInvalidEventMsg
|
||||
{
|
||||
public const string EventType = "io.nats.server.advisory.v1.ocsp_peer_link_invalid";
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; set; } = EventType;
|
||||
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("timestamp")]
|
||||
public DateTime Time { get; set; }
|
||||
|
||||
[JsonPropertyName("server")]
|
||||
public EventServerInfo Server { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("link")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public EventCertInfo? Link { get; set; }
|
||||
|
||||
[JsonPropertyName("peer")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public EventCertInfo? Peer { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// OCSP chain validation advisory, published when a certificate's OCSP status
|
||||
/// is checked during TLS handshake.
|
||||
@@ -803,6 +909,149 @@ public sealed class AccNumConnsReq
|
||||
public string Account { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Account subscription-count request.
|
||||
/// Go reference: events.go accNumSubsReq.
|
||||
/// </summary>
|
||||
public sealed class AccNumSubsReq
|
||||
{
|
||||
[JsonPropertyName("server")]
|
||||
public EventServerInfo Server { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("acc")]
|
||||
public string Account { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shared request filter options for system request subjects.
|
||||
/// Go reference: events.go EventFilterOptions.
|
||||
/// </summary>
|
||||
public class EventFilterOptions
|
||||
{
|
||||
[JsonPropertyName("name")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? Name { get; set; }
|
||||
|
||||
[JsonPropertyName("cluster")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? Cluster { get; set; }
|
||||
|
||||
[JsonPropertyName("host")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? Host { get; set; }
|
||||
|
||||
[JsonPropertyName("tags")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string[]? Tags { get; set; }
|
||||
|
||||
[JsonPropertyName("domain")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? Domain { get; set; }
|
||||
}
|
||||
|
||||
public sealed class StatszEventOptions : EventFilterOptions;
|
||||
public sealed class AccInfoEventOptions : EventFilterOptions;
|
||||
public sealed class ConnzEventOptions : EventFilterOptions;
|
||||
public sealed class RoutezEventOptions : EventFilterOptions;
|
||||
public sealed class SubszEventOptions : EventFilterOptions;
|
||||
public sealed class VarzEventOptions : EventFilterOptions;
|
||||
public sealed class GatewayzEventOptions : EventFilterOptions;
|
||||
public sealed class LeafzEventOptions : EventFilterOptions;
|
||||
public sealed class AccountzEventOptions : EventFilterOptions;
|
||||
public sealed class AccountStatzEventOptions : EventFilterOptions;
|
||||
public sealed class JszEventOptions : EventFilterOptions;
|
||||
public sealed class HealthzEventOptions : EventFilterOptions;
|
||||
public sealed class ProfilezEventOptions : EventFilterOptions;
|
||||
public sealed class ExpvarzEventOptions : EventFilterOptions;
|
||||
public sealed class IpqueueszEventOptions : EventFilterOptions;
|
||||
public sealed class RaftzEventOptions : EventFilterOptions;
|
||||
|
||||
/// <summary>
|
||||
/// Generic server API error payload.
|
||||
/// Go reference: events.go server API response errors.
|
||||
/// </summary>
|
||||
public sealed class ServerAPIError
|
||||
{
|
||||
[JsonPropertyName("code")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
|
||||
public int Code { get; set; }
|
||||
|
||||
[JsonPropertyName("description")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? Description { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generic server request/response envelope for $SYS.REQ services.
|
||||
/// Go reference: events.go ServerAPIResponse.
|
||||
/// </summary>
|
||||
public class ServerAPIResponse
|
||||
{
|
||||
[JsonPropertyName("server")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public EventServerInfo? Server { get; set; }
|
||||
|
||||
[JsonPropertyName("data")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public object? Data { get; set; }
|
||||
|
||||
[JsonPropertyName("error")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public ServerAPIError? Error { get; set; }
|
||||
}
|
||||
|
||||
public sealed class ServerAPIConnzResponse : ServerAPIResponse;
|
||||
public sealed class ServerAPIRoutezResponse : ServerAPIResponse;
|
||||
public sealed class ServerAPIGatewayzResponse : ServerAPIResponse;
|
||||
public sealed class ServerAPIJszResponse : ServerAPIResponse;
|
||||
public sealed class ServerAPIHealthzResponse : ServerAPIResponse;
|
||||
public sealed class ServerAPIVarzResponse : ServerAPIResponse;
|
||||
public sealed class ServerAPISubszResponse : ServerAPIResponse;
|
||||
public sealed class ServerAPILeafzResponse : ServerAPIResponse;
|
||||
public sealed class ServerAPIAccountzResponse : ServerAPIResponse;
|
||||
public sealed class ServerAPIExpvarzResponse : ServerAPIResponse;
|
||||
public sealed class ServerAPIpqueueszResponse : ServerAPIResponse;
|
||||
public sealed class ServerAPIRaftzResponse : ServerAPIResponse;
|
||||
|
||||
/// <summary>
|
||||
/// Kick client request payload.
|
||||
/// Go reference: events.go KickClientReq.
|
||||
/// </summary>
|
||||
public sealed class KickClientReq
|
||||
{
|
||||
[JsonPropertyName("cid")]
|
||||
public ulong ClientId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lame duck mode client request payload.
|
||||
/// Go reference: events.go LDMClientReq.
|
||||
/// </summary>
|
||||
public sealed class LDMClientReq
|
||||
{
|
||||
[JsonPropertyName("cid")]
|
||||
public ulong ClientId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// User info payload for direct user info requests.
|
||||
/// Go reference: events.go UserInfo.
|
||||
/// </summary>
|
||||
public sealed class UserInfo
|
||||
{
|
||||
[JsonPropertyName("user")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? User { get; set; }
|
||||
|
||||
[JsonPropertyName("acc")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? Account { get; set; }
|
||||
|
||||
[JsonPropertyName("permissions")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? Permissions { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Factory helpers that construct fully-populated system event messages,
|
||||
/// mirroring Go's inline struct initialization patterns in events.go.
|
||||
|
||||
@@ -61,7 +61,14 @@ public sealed record ConnectEventDetail(
|
||||
string RemoteAddress,
|
||||
string? AccountName,
|
||||
string? UserName,
|
||||
DateTime ConnectedAt);
|
||||
DateTime ConnectedAt,
|
||||
string? Jwt = null,
|
||||
string? IssuerKey = null,
|
||||
string[]? Tags = null,
|
||||
string? NameTag = null,
|
||||
string? Kind = null,
|
||||
string? ClientType = null,
|
||||
string? MqttClientId = null);
|
||||
|
||||
/// <summary>
|
||||
/// Detail payload for a client disconnect advisory.
|
||||
@@ -73,7 +80,15 @@ public sealed record DisconnectEventDetail(
|
||||
string RemoteAddress,
|
||||
string? AccountName,
|
||||
string Reason,
|
||||
DateTime DisconnectedAt);
|
||||
DateTime DisconnectedAt,
|
||||
long RttNanos = 0,
|
||||
string? Jwt = null,
|
||||
string? IssuerKey = null,
|
||||
string[]? Tags = null,
|
||||
string? NameTag = null,
|
||||
string? Kind = null,
|
||||
string? ClientType = null,
|
||||
string? MqttClientId = null);
|
||||
|
||||
/// <summary>
|
||||
/// Manages the server's internal event system with Channel-based send/receive loops.
|
||||
@@ -114,14 +129,29 @@ public sealed class InternalEventSystem : IAsyncDisposable
|
||||
SystemAccount = systemAccount;
|
||||
SystemClient = systemClient;
|
||||
|
||||
// Hash server name for inbox routing (matches Go's shash)
|
||||
ServerHash = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(serverName)))[..8].ToLowerInvariant();
|
||||
// Hash server name for inbox routing (matches Go's shash).
|
||||
ServerHash = GetHash(serverName, GetHashSize());
|
||||
|
||||
_sendQueue = Channel.CreateUnbounded<PublishMessage>(new UnboundedChannelOptions { SingleReader = true });
|
||||
_receiveQueue = Channel.CreateUnbounded<InternalSystemMessage>(new UnboundedChannelOptions { SingleReader = true });
|
||||
_receiveQueuePings = Channel.CreateUnbounded<InternalSystemMessage>(new UnboundedChannelOptions { SingleReader = true });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Equivalent to Go getHashSize(): default short hash width used in eventing subjects.
|
||||
/// </summary>
|
||||
public static int GetHashSize() => 8;
|
||||
|
||||
/// <summary>
|
||||
/// Equivalent to Go getHash() / getHashSize() helpers for server hash identifiers.
|
||||
/// </summary>
|
||||
public static string GetHash(string value, int size)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(size, 1);
|
||||
var full = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(value))).ToLowerInvariant();
|
||||
return size >= full.Length ? full : full[..size];
|
||||
}
|
||||
|
||||
public void Start(NatsServer server)
|
||||
{
|
||||
_server = server;
|
||||
@@ -316,6 +346,13 @@ public sealed class InternalEventSystem : IAsyncDisposable
|
||||
Account = detail.AccountName,
|
||||
User = detail.UserName,
|
||||
Start = detail.ConnectedAt,
|
||||
Jwt = detail.Jwt,
|
||||
IssuerKey = detail.IssuerKey,
|
||||
Tags = detail.Tags,
|
||||
NameTag = detail.NameTag,
|
||||
Kind = detail.Kind,
|
||||
ClientType = detail.ClientType,
|
||||
MqttClient = detail.MqttClientId,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -341,6 +378,14 @@ public sealed class InternalEventSystem : IAsyncDisposable
|
||||
Host = detail.RemoteAddress,
|
||||
Account = detail.AccountName,
|
||||
Stop = detail.DisconnectedAt,
|
||||
RttNanos = detail.RttNanos,
|
||||
Jwt = detail.Jwt,
|
||||
IssuerKey = detail.IssuerKey,
|
||||
Tags = detail.Tags,
|
||||
NameTag = detail.NameTag,
|
||||
Kind = detail.Kind,
|
||||
ClientType = detail.ClientType,
|
||||
MqttClient = detail.MqttClientId,
|
||||
},
|
||||
Reason = detail.Reason,
|
||||
};
|
||||
|
||||
@@ -16,6 +16,7 @@ public sealed class GatewayConnection(Socket socket) : IAsyncDisposable
|
||||
private Task? _loopTask;
|
||||
|
||||
public string? RemoteId { get; private set; }
|
||||
public bool IsOutbound { get; internal set; }
|
||||
public string RemoteEndpoint => socket.RemoteEndPoint?.ToString() ?? Guid.NewGuid().ToString("N");
|
||||
public Func<RemoteSubscription, Task>? RemoteSubscriptionReceived { get; set; }
|
||||
public Func<GatewayMessage, Task>? MessageReceived { get; set; }
|
||||
|
||||
@@ -65,6 +65,9 @@ public sealed class GatewayReconnectPolicy
|
||||
|
||||
public sealed class GatewayManager : IAsyncDisposable
|
||||
{
|
||||
public const string GatewayTlsInsecureWarning =
|
||||
"Gateway TLS insecure configuration is enabled; verify certificates and hostname validation for production.";
|
||||
|
||||
private readonly GatewayOptions _options;
|
||||
private readonly ServerStats _stats;
|
||||
private readonly string _serverId;
|
||||
@@ -103,6 +106,43 @@ public sealed class GatewayManager : IAsyncDisposable
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates gateway options for required fields and basic endpoint correctness.
|
||||
/// Go reference: validateGatewayOptions.
|
||||
/// </summary>
|
||||
public static bool ValidateGatewayOptions(GatewayOptions? options, out string? error)
|
||||
{
|
||||
if (options is null)
|
||||
{
|
||||
error = "Gateway options are required.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(options.Name))
|
||||
{
|
||||
error = "Gateway name is required.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (options.Port is < 0 or > 65535)
|
||||
{
|
||||
error = "Gateway port must be in range 0-65535.";
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var remote in options.Remotes)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(remote))
|
||||
{
|
||||
error = "Gateway remote entries cannot be empty.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
error = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gateway clusters auto-discovered via INFO gossip.
|
||||
/// Go reference: server/gateway.go processImplicitGateway.
|
||||
@@ -284,6 +324,48 @@ public sealed class GatewayManager : IAsyncDisposable
|
||||
public int GetConnectedGatewayCount()
|
||||
=> _registrations.Values.Count(r => r.State == GatewayConnectionState.Connected);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number of active outbound gateway connections.
|
||||
/// Go reference: server/gateway.go NumOutboundGateways / numOutboundGateways.
|
||||
/// </summary>
|
||||
public int NumOutboundGateways()
|
||||
=> _connections.Values.Count(c => c.IsOutbound);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number of active inbound gateway connections.
|
||||
/// Go reference: server/gateway.go numInboundGateways.
|
||||
/// </summary>
|
||||
public int NumInboundGateways()
|
||||
=> _connections.Values.Count(c => !c.IsOutbound);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if an inbound gateway connection exists for the given remote server id.
|
||||
/// Go reference: server/gateway.go srvGateway.hasInbound.
|
||||
/// </summary>
|
||||
public bool HasInbound(string remoteServerId)
|
||||
=> _connections.Values.Any(c => !c.IsOutbound && string.Equals(c.RemoteId, remoteServerId, StringComparison.Ordinal));
|
||||
|
||||
/// <summary>
|
||||
/// Returns the first outbound gateway connection for the given remote server id, or null.
|
||||
/// Go reference: server/gateway.go getOutboundGatewayConnection.
|
||||
/// </summary>
|
||||
public GatewayConnection? GetOutboundGatewayConnection(string remoteServerId)
|
||||
=> _connections.Values.FirstOrDefault(c => c.IsOutbound && string.Equals(c.RemoteId, remoteServerId, StringComparison.Ordinal));
|
||||
|
||||
/// <summary>
|
||||
/// Returns all outbound gateway connections.
|
||||
/// Go reference: server/gateway.go getOutboundGatewayConnections.
|
||||
/// </summary>
|
||||
public IReadOnlyList<GatewayConnection> GetOutboundGatewayConnections()
|
||||
=> _connections.Values.Where(c => c.IsOutbound).ToList();
|
||||
|
||||
/// <summary>
|
||||
/// Returns all inbound gateway connections.
|
||||
/// Go reference: server/gateway.go getInboundGatewayConnections.
|
||||
/// </summary>
|
||||
public IReadOnlyList<GatewayConnection> GetInboundGatewayConnections()
|
||||
=> _connections.Values.Where(c => !c.IsOutbound).ToList();
|
||||
|
||||
/// <summary>
|
||||
/// Atomically increments the messages-sent counter for the named gateway.
|
||||
/// Go reference: server/gateway.go outboundGateway.msgs.
|
||||
@@ -364,7 +446,7 @@ public sealed class GatewayManager : IAsyncDisposable
|
||||
var endPoint = ParseEndpoint(remote);
|
||||
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
||||
await socket.ConnectAsync(endPoint.Address, endPoint.Port, ct);
|
||||
var connection = new GatewayConnection(socket);
|
||||
var connection = new GatewayConnection(socket) { IsOutbound = true };
|
||||
await connection.PerformOutboundHandshakeAsync(_serverId, ct);
|
||||
Register(connection);
|
||||
return;
|
||||
|
||||
@@ -9,14 +9,42 @@ namespace NATS.Server.Gateways;
|
||||
/// </summary>
|
||||
public static class ReplyMapper
|
||||
{
|
||||
private const string GatewayReplyPrefix = "_GR_.";
|
||||
public const string GatewayReplyPrefix = "_GR_.";
|
||||
public const string OldGatewayReplyPrefix = "$GR.";
|
||||
public const int GatewayReplyPrefixLen = 5;
|
||||
public const int OldGatewayReplyPrefixLen = 4;
|
||||
public const int GatewayHashLen = 6;
|
||||
public const int OldGatewayHashLen = 4;
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the subject starts with the gateway reply prefix <c>_GR_.</c>.
|
||||
/// Checks whether the subject starts with either gateway reply prefix:
|
||||
/// <c>_GR_.</c> (current) or <c>$GR.</c> (legacy).
|
||||
/// </summary>
|
||||
public static bool HasGatewayReplyPrefix(string? subject)
|
||||
=> !string.IsNullOrWhiteSpace(subject)
|
||||
&& subject.StartsWith(GatewayReplyPrefix, StringComparison.Ordinal);
|
||||
=> IsGatewayRoutedSubject(subject, out _);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true when the subject is gateway-routed and indicates if the legacy
|
||||
/// old prefix (<c>$GR.</c>) was used.
|
||||
/// Go reference: isGWRoutedSubjectAndIsOldPrefix.
|
||||
/// </summary>
|
||||
public static bool IsGatewayRoutedSubject(string? subject, out bool isOldPrefix)
|
||||
{
|
||||
isOldPrefix = false;
|
||||
if (string.IsNullOrWhiteSpace(subject))
|
||||
return false;
|
||||
|
||||
if (subject.StartsWith(GatewayReplyPrefix, StringComparison.Ordinal))
|
||||
return true;
|
||||
|
||||
if (subject.StartsWith(OldGatewayReplyPrefix, StringComparison.Ordinal))
|
||||
{
|
||||
isOldPrefix = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes a deterministic FNV-1a hash of the reply subject.
|
||||
@@ -40,6 +68,26 @@ public static class ReplyMapper
|
||||
return (long)(hash & 0x7FFFFFFFFFFFFFFF);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the short (6-char) gateway hash used in modern gateway reply routing.
|
||||
/// Go reference: getGWHash.
|
||||
/// </summary>
|
||||
public static string ComputeGatewayHash(string gatewayName)
|
||||
{
|
||||
var digest = System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(gatewayName));
|
||||
return Convert.ToHexString(digest.AsSpan(0, 3)).ToLowerInvariant();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the short (4-char) legacy gateway hash used with old prefixes.
|
||||
/// Go reference: getOldHash.
|
||||
/// </summary>
|
||||
public static string ComputeOldGatewayHash(string gatewayName)
|
||||
{
|
||||
var digest = System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(gatewayName));
|
||||
return Convert.ToHexString(digest.AsSpan(0, 2)).ToLowerInvariant();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a reply subject to gateway form with an explicit hash segment.
|
||||
/// Format: <c>_GR_.{clusterId}.{hash}.{originalReply}</c>.
|
||||
@@ -75,14 +123,14 @@ public static class ReplyMapper
|
||||
{
|
||||
restoredReply = string.Empty;
|
||||
|
||||
if (!HasGatewayReplyPrefix(gatewayReply))
|
||||
if (!IsGatewayRoutedSubject(gatewayReply, out _))
|
||||
return false;
|
||||
|
||||
var current = gatewayReply!;
|
||||
while (HasGatewayReplyPrefix(current))
|
||||
while (IsGatewayRoutedSubject(current, out var isOldPrefix))
|
||||
{
|
||||
// Skip the "_GR_." prefix
|
||||
var afterPrefix = current[GatewayReplyPrefix.Length..];
|
||||
var prefixLen = isOldPrefix ? OldGatewayReplyPrefixLen : GatewayReplyPrefixLen;
|
||||
var afterPrefix = current[prefixLen..];
|
||||
|
||||
// Find the first dot (end of clusterId)
|
||||
var firstDot = afterPrefix.IndexOf('.');
|
||||
@@ -117,10 +165,10 @@ public static class ReplyMapper
|
||||
{
|
||||
clusterId = string.Empty;
|
||||
|
||||
if (!HasGatewayReplyPrefix(gatewayReply))
|
||||
if (!IsGatewayRoutedSubject(gatewayReply, out var isOldPrefix))
|
||||
return false;
|
||||
|
||||
var afterPrefix = gatewayReply![GatewayReplyPrefix.Length..];
|
||||
var afterPrefix = gatewayReply![(isOldPrefix ? OldGatewayReplyPrefixLen : GatewayReplyPrefixLen)..];
|
||||
var dot = afterPrefix.IndexOf('.');
|
||||
if (dot <= 0)
|
||||
return false;
|
||||
@@ -137,10 +185,10 @@ public static class ReplyMapper
|
||||
{
|
||||
hash = 0;
|
||||
|
||||
if (!HasGatewayReplyPrefix(gatewayReply))
|
||||
if (!IsGatewayRoutedSubject(gatewayReply, out var isOldPrefix))
|
||||
return false;
|
||||
|
||||
var afterPrefix = gatewayReply![GatewayReplyPrefix.Length..];
|
||||
var afterPrefix = gatewayReply![(isOldPrefix ? OldGatewayReplyPrefixLen : GatewayReplyPrefixLen)..];
|
||||
|
||||
// Skip clusterId
|
||||
var firstDot = afterPrefix.IndexOf('.');
|
||||
|
||||
@@ -254,31 +254,43 @@ public class SequenceSet
|
||||
/// <summary>Encodes the set to a compact binary format.</summary>
|
||||
public byte[] Encode()
|
||||
{
|
||||
var encLen = EncodeLength();
|
||||
var buf = new byte[encLen];
|
||||
var buf = new byte[EncodeLength()];
|
||||
var written = Encode(buf);
|
||||
return buf.AsSpan(0, written).ToArray();
|
||||
}
|
||||
|
||||
buf[0] = Magic;
|
||||
buf[1] = Version;
|
||||
/// <summary>
|
||||
/// Encodes the set into a caller-provided buffer.
|
||||
/// Returns the number of bytes written.
|
||||
/// </summary>
|
||||
public int Encode(byte[] destination)
|
||||
{
|
||||
var encLen = EncodeLength();
|
||||
if (destination.Length < encLen)
|
||||
throw new ArgumentException("Destination buffer too small for encoded SequenceSet.", nameof(destination));
|
||||
|
||||
destination[0] = Magic;
|
||||
destination[1] = Version;
|
||||
var i = HdrLen;
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(buf.AsSpan(i), (uint)_nodes);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(buf.AsSpan(i + 4), (uint)_size);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(destination.AsSpan(i), (uint)_nodes);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(destination.AsSpan(i + 4), (uint)_size);
|
||||
i += 8;
|
||||
|
||||
Node.NodeIter(Root, n =>
|
||||
{
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(buf.AsSpan(i), n.Base);
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(destination.AsSpan(i), n.Base);
|
||||
i += 8;
|
||||
for (var bi = 0; bi < NumBuckets; bi++)
|
||||
{
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(buf.AsSpan(i), n.Bits[bi]);
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(destination.AsSpan(i), n.Bits[bi]);
|
||||
i += 8;
|
||||
}
|
||||
|
||||
BinaryPrimitives.WriteUInt16LittleEndian(buf.AsSpan(i), (ushort)n.Height);
|
||||
BinaryPrimitives.WriteUInt16LittleEndian(destination.AsSpan(i), (ushort)n.Height);
|
||||
i += 2;
|
||||
});
|
||||
|
||||
return buf.AsSpan(0, i).ToArray();
|
||||
return i;
|
||||
}
|
||||
|
||||
/// <summary>Decodes a SequenceSet from a binary buffer. Returns the set and number of bytes read.</summary>
|
||||
|
||||
@@ -643,8 +643,14 @@ public class GenericSubjectList<T> where T : IEquatable<T>
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SimpleSubjectList is an alias for GenericSubjectList that uses int values,
|
||||
/// Empty marker used by <see cref="SimpleSubjectList"/> to mirror Go's `struct{}` payload.
|
||||
/// Go reference: server/gsl/gsl.go SimpleSublist
|
||||
/// </summary>
|
||||
public readonly record struct SimpleSublistValue;
|
||||
|
||||
/// <summary>
|
||||
/// SimpleSubjectList is an alias for GenericSubjectList that uses an empty marker payload,
|
||||
/// useful for tracking interest only.
|
||||
/// Go reference: server/gsl/gsl.go SimpleSublist
|
||||
/// </summary>
|
||||
public class SimpleSubjectList : GenericSubjectList<int>;
|
||||
public class SimpleSubjectList : GenericSubjectList<SimpleSublistValue>;
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using NATS.Server.Internal.Gsl;
|
||||
|
||||
// Go reference: server/stree/stree.go
|
||||
namespace NATS.Server.Internal.SubjectTree;
|
||||
|
||||
@@ -161,8 +165,57 @@ public class SubjectTree<T>
|
||||
IterInternal(Root, [], ordered: false, cb);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dumps a human-readable representation of the tree.
|
||||
/// Go reference: server/stree/dump.go
|
||||
/// </summary>
|
||||
public void Dump(TextWriter writer)
|
||||
{
|
||||
Dump(writer, Root, 0);
|
||||
writer.WriteLine();
|
||||
}
|
||||
|
||||
#region Internal Methods
|
||||
|
||||
private void Dump(TextWriter writer, INode? node, int depth)
|
||||
{
|
||||
if (node == null)
|
||||
{
|
||||
writer.WriteLine("EMPTY");
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.IsLeaf)
|
||||
{
|
||||
var leaf = (Leaf<T>)node;
|
||||
writer.WriteLine($"{DumpPre(depth)} LEAF: Suffix: {QuoteBytes(leaf.Suffix)} Value: {leaf.Value}");
|
||||
return;
|
||||
}
|
||||
|
||||
var bn = node.Base!;
|
||||
writer.WriteLine($"{DumpPre(depth)} {node.Kind} Prefix: {QuoteBytes(bn.Prefix)}");
|
||||
depth++;
|
||||
node.Iter(n =>
|
||||
{
|
||||
Dump(writer, n, depth);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private static string DumpPre(int depth)
|
||||
{
|
||||
if (depth == 0)
|
||||
return "-- ";
|
||||
|
||||
var sb = new StringBuilder(depth * 2 + 4);
|
||||
for (var i = 0; i < depth; i++)
|
||||
sb.Append(" ");
|
||||
sb.Append("|__ ");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static string QuoteBytes(byte[] data) => $"\"{Encoding.UTF8.GetString(data)}\"";
|
||||
|
||||
/// <summary>
|
||||
/// Internal recursive insert.
|
||||
/// Go reference: server/stree/stree.go:insert
|
||||
@@ -613,4 +666,85 @@ public static class SubjectTreeHelper
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Matches all items in the subject tree that have interest in the given sublist.
|
||||
/// The callback is invoked at most once per matching subject.
|
||||
/// Go reference: server/stree/stree.go IntersectGSL
|
||||
/// </summary>
|
||||
public static void IntersectGSL<T, SL>(
|
||||
SubjectTree<T>? tree,
|
||||
GenericSubjectList<SL>? sublist,
|
||||
Action<byte[], T> cb) where SL : IEquatable<SL>
|
||||
{
|
||||
if (tree?.Root == null || sublist == null)
|
||||
return;
|
||||
|
||||
IntersectGslInternal(tree.Root, [], sublist, cb);
|
||||
}
|
||||
|
||||
private static void IntersectGslInternal<T, SL>(
|
||||
INode node,
|
||||
byte[] pre,
|
||||
GenericSubjectList<SL> sublist,
|
||||
Action<byte[], T> cb) where SL : IEquatable<SL>
|
||||
{
|
||||
if (node.IsLeaf)
|
||||
{
|
||||
var leaf = (Leaf<T>)node;
|
||||
var subject = Concat(pre, leaf.Suffix);
|
||||
if (sublist.HasInterest(BytesToString(subject)))
|
||||
cb(subject, leaf.Value);
|
||||
return;
|
||||
}
|
||||
|
||||
var bn = node.Base!;
|
||||
pre = Concat(pre, bn.Prefix);
|
||||
|
||||
foreach (var child in node.Children())
|
||||
{
|
||||
if (child == null)
|
||||
continue;
|
||||
|
||||
var subject = Concat(pre, child.Path());
|
||||
if (!HasInterestForTokens(sublist, subject, pre.Length))
|
||||
continue;
|
||||
|
||||
IntersectGslInternal(child, pre, sublist, cb);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool HasInterestForTokens<SL>(
|
||||
GenericSubjectList<SL> sublist,
|
||||
byte[] subject,
|
||||
int since) where SL : IEquatable<SL>
|
||||
{
|
||||
for (var i = since; i < subject.Length; i++)
|
||||
{
|
||||
if (subject[i] != Parts.Tsep)
|
||||
continue;
|
||||
|
||||
if (!sublist.HasInterestStartingIn(BytesToString(subject.AsSpan(0, i))))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string BytesToString(ReadOnlySpan<byte> data)
|
||||
{
|
||||
if (data.Length == 0)
|
||||
return string.Empty;
|
||||
return Encoding.UTF8.GetString(data);
|
||||
}
|
||||
|
||||
private static byte[] Concat(byte[] a, byte[] b)
|
||||
{
|
||||
if (a.Length == 0) return b;
|
||||
if (b.Length == 0) return a;
|
||||
var result = new byte[a.Length + b.Length];
|
||||
a.CopyTo(result, 0);
|
||||
b.CopyTo(result, a.Length);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace NATS.Server.Internal.SysMem;
|
||||
|
||||
/// <summary>
|
||||
/// Cross-platform system memory query helpers.
|
||||
/// Go reference: server/sysmem/mem_*.go Memory
|
||||
/// </summary>
|
||||
public static class SystemMemory
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns total memory available to the runtime for the current OS.
|
||||
/// </summary>
|
||||
public static long Memory()
|
||||
{
|
||||
var total = GC.GetGCMemoryInfo().TotalAvailableMemoryBytes;
|
||||
if (total > 0)
|
||||
return total;
|
||||
|
||||
// Conservative fallback when runtime does not report a limit.
|
||||
return Environment.Is64BitProcess ? long.MaxValue : int.MaxValue;
|
||||
}
|
||||
}
|
||||
@@ -400,6 +400,12 @@ public class HashWheel
|
||||
/// </summary>
|
||||
internal Slot?[] Wheel => _wheel;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a hash wheel entry (sequence + expiration timestamp).
|
||||
/// Go reference: server/thw/thw.go HashWheelEntry
|
||||
/// </summary>
|
||||
internal readonly record struct HashWheelEntry(ulong Sequence, long Expires);
|
||||
|
||||
/// <summary>
|
||||
/// Represents a single slot in the wheel containing entries that hash to the same position.
|
||||
/// </summary>
|
||||
|
||||
@@ -53,30 +53,57 @@ public static class ConsumerApiHandlers
|
||||
: JetStreamApiResponse.NotFound(subject);
|
||||
}
|
||||
|
||||
public static JetStreamApiResponse HandleNames(string subject, ConsumerManager consumerManager)
|
||||
public static JetStreamApiResponse HandleNames(string subject, ReadOnlySpan<byte> payload, ConsumerManager consumerManager)
|
||||
{
|
||||
var stream = ParseStreamSubject(subject, NamesPrefix);
|
||||
if (stream == null)
|
||||
return JetStreamApiResponse.NotFound(subject);
|
||||
|
||||
var offset = ParseOffset(payload);
|
||||
var all = consumerManager.ListNames(stream);
|
||||
var page = offset >= all.Count ? [] : all.Skip(offset).ToList();
|
||||
return new JetStreamApiResponse
|
||||
{
|
||||
ConsumerNames = consumerManager.ListNames(stream),
|
||||
ConsumerNames = page,
|
||||
PaginationTotal = all.Count,
|
||||
PaginationOffset = offset,
|
||||
};
|
||||
}
|
||||
|
||||
public static JetStreamApiResponse HandleList(string subject, ConsumerManager consumerManager)
|
||||
public static JetStreamApiResponse HandleList(string subject, ReadOnlySpan<byte> payload, ConsumerManager consumerManager)
|
||||
{
|
||||
var stream = ParseStreamSubject(subject, ListPrefix);
|
||||
if (stream == null)
|
||||
return JetStreamApiResponse.NotFound(subject);
|
||||
|
||||
var offset = ParseOffset(payload);
|
||||
var all = consumerManager.ListConsumerInfos(stream);
|
||||
var page = offset >= all.Count ? [] : all.Skip(offset).ToList();
|
||||
return new JetStreamApiResponse
|
||||
{
|
||||
ConsumerNames = consumerManager.ListNames(stream),
|
||||
ConsumerInfoList = page,
|
||||
PaginationTotal = all.Count,
|
||||
PaginationOffset = offset,
|
||||
};
|
||||
}
|
||||
|
||||
private static int ParseOffset(ReadOnlySpan<byte> payload)
|
||||
{
|
||||
if (payload.IsEmpty) return 0;
|
||||
try
|
||||
{
|
||||
using var doc = JsonDocument.Parse(payload.ToArray());
|
||||
if (doc.RootElement.TryGetProperty("offset", out var el) && el.TryGetInt32(out var v))
|
||||
return Math.Max(v, 0);
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Malformed offset payload: {ex.Message}");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static JetStreamApiResponse HandlePause(string subject, ReadOnlySpan<byte> payload, ConsumerManager consumerManager)
|
||||
{
|
||||
var parsed = ParseSubject(subject, PausePrefix);
|
||||
@@ -254,15 +281,21 @@ public static class ConsumerApiHandlers
|
||||
{
|
||||
using var doc = JsonDocument.Parse(payload.ToArray());
|
||||
var root = doc.RootElement;
|
||||
|
||||
// The client wraps config in a "config" property (CreateConsumerRequest).
|
||||
// Go reference: consumer.go — CreateConsumerRequest { Config ConsumerConfig `json:"config"` }
|
||||
var configEl = root.TryGetProperty("config", out var nested) ? nested : root;
|
||||
var config = new ConsumerConfig();
|
||||
|
||||
if (root.TryGetProperty("durable_name", out var durableEl))
|
||||
if (configEl.TryGetProperty("durable_name", out var durableEl))
|
||||
config.DurableName = durableEl.GetString() ?? string.Empty;
|
||||
else if (configEl.TryGetProperty("name", out var nameEl))
|
||||
config.DurableName = nameEl.GetString() ?? string.Empty;
|
||||
|
||||
if (root.TryGetProperty("filter_subject", out var filterEl))
|
||||
if (configEl.TryGetProperty("filter_subject", out var filterEl))
|
||||
config.FilterSubject = filterEl.GetString();
|
||||
|
||||
if (root.TryGetProperty("filter_subjects", out var filterSubjectsEl) && filterSubjectsEl.ValueKind == JsonValueKind.Array)
|
||||
if (configEl.TryGetProperty("filter_subjects", out var filterSubjectsEl) && filterSubjectsEl.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
foreach (var item in filterSubjectsEl.EnumerateArray())
|
||||
{
|
||||
@@ -272,41 +305,41 @@ public static class ConsumerApiHandlers
|
||||
}
|
||||
}
|
||||
|
||||
if (root.TryGetProperty("ephemeral", out var ephemeralEl) && ephemeralEl.ValueKind == JsonValueKind.True)
|
||||
if (configEl.TryGetProperty("ephemeral", out var ephemeralEl) && ephemeralEl.ValueKind == JsonValueKind.True)
|
||||
config.Ephemeral = true;
|
||||
|
||||
if (root.TryGetProperty("push", out var pushEl) && pushEl.ValueKind == JsonValueKind.True)
|
||||
if (configEl.TryGetProperty("push", out var pushEl) && pushEl.ValueKind == JsonValueKind.True)
|
||||
config.Push = true;
|
||||
|
||||
if (root.TryGetProperty("heartbeat_ms", out var hbEl) && hbEl.TryGetInt32(out var hbMs))
|
||||
if (configEl.TryGetProperty("heartbeat_ms", out var hbEl) && hbEl.TryGetInt32(out var hbMs))
|
||||
config.HeartbeatMs = hbMs;
|
||||
|
||||
if (root.TryGetProperty("ack_wait_ms", out var ackWaitEl) && ackWaitEl.TryGetInt32(out var ackWait))
|
||||
if (configEl.TryGetProperty("ack_wait_ms", out var ackWaitEl) && ackWaitEl.TryGetInt32(out var ackWait))
|
||||
config.AckWaitMs = ackWait;
|
||||
|
||||
if (root.TryGetProperty("max_deliver", out var maxDeliverEl) && maxDeliverEl.TryGetInt32(out var maxDeliver))
|
||||
if (configEl.TryGetProperty("max_deliver", out var maxDeliverEl) && maxDeliverEl.TryGetInt32(out var maxDeliver))
|
||||
config.MaxDeliver = Math.Max(maxDeliver, 0);
|
||||
|
||||
if (root.TryGetProperty("max_ack_pending", out var maxAckPendingEl) && maxAckPendingEl.TryGetInt32(out var maxAckPending))
|
||||
if (configEl.TryGetProperty("max_ack_pending", out var maxAckPendingEl) && maxAckPendingEl.TryGetInt32(out var maxAckPending))
|
||||
config.MaxAckPending = Math.Max(maxAckPending, 0);
|
||||
|
||||
if (root.TryGetProperty("flow_control", out var flowControlEl) && flowControlEl.ValueKind is JsonValueKind.True or JsonValueKind.False)
|
||||
if (configEl.TryGetProperty("flow_control", out var flowControlEl) && flowControlEl.ValueKind is JsonValueKind.True or JsonValueKind.False)
|
||||
config.FlowControl = flowControlEl.GetBoolean();
|
||||
|
||||
if (root.TryGetProperty("rate_limit_bps", out var rateLimitEl) && rateLimitEl.TryGetInt64(out var rateLimit))
|
||||
if (configEl.TryGetProperty("rate_limit_bps", out var rateLimitEl) && rateLimitEl.TryGetInt64(out var rateLimit))
|
||||
config.RateLimitBps = Math.Max(rateLimit, 0);
|
||||
|
||||
if (root.TryGetProperty("opt_start_seq", out var optStartSeqEl) && optStartSeqEl.TryGetUInt64(out var optStartSeq))
|
||||
if (configEl.TryGetProperty("opt_start_seq", out var optStartSeqEl) && optStartSeqEl.TryGetUInt64(out var optStartSeq))
|
||||
config.OptStartSeq = optStartSeq;
|
||||
|
||||
if (root.TryGetProperty("opt_start_time_utc", out var optStartTimeEl)
|
||||
if (configEl.TryGetProperty("opt_start_time_utc", out var optStartTimeEl)
|
||||
&& optStartTimeEl.ValueKind == JsonValueKind.String
|
||||
&& DateTime.TryParse(optStartTimeEl.GetString(), out var optStartTime))
|
||||
{
|
||||
config.OptStartTimeUtc = optStartTime.ToUniversalTime();
|
||||
}
|
||||
|
||||
if (root.TryGetProperty("backoff_ms", out var backoffEl) && backoffEl.ValueKind == JsonValueKind.Array)
|
||||
if (configEl.TryGetProperty("backoff_ms", out var backoffEl) && backoffEl.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
foreach (var item in backoffEl.EnumerateArray())
|
||||
{
|
||||
@@ -315,7 +348,7 @@ public static class ConsumerApiHandlers
|
||||
}
|
||||
}
|
||||
|
||||
if (root.TryGetProperty("ack_policy", out var ackPolicyEl))
|
||||
if (configEl.TryGetProperty("ack_policy", out var ackPolicyEl))
|
||||
{
|
||||
var ackPolicy = ackPolicyEl.GetString();
|
||||
if (string.Equals(ackPolicy, "explicit", StringComparison.OrdinalIgnoreCase))
|
||||
@@ -324,7 +357,7 @@ public static class ConsumerApiHandlers
|
||||
config.AckPolicy = AckPolicy.All;
|
||||
}
|
||||
|
||||
if (root.TryGetProperty("deliver_policy", out var deliverPolicyEl))
|
||||
if (configEl.TryGetProperty("deliver_policy", out var deliverPolicyEl))
|
||||
{
|
||||
var deliver = deliverPolicyEl.GetString();
|
||||
if (string.Equals(deliver, "last", StringComparison.OrdinalIgnoreCase))
|
||||
@@ -339,7 +372,7 @@ public static class ConsumerApiHandlers
|
||||
config.DeliverPolicy = DeliverPolicy.LastPerSubject;
|
||||
}
|
||||
|
||||
if (root.TryGetProperty("replay_policy", out var replayPolicyEl))
|
||||
if (configEl.TryGetProperty("replay_policy", out var replayPolicyEl))
|
||||
{
|
||||
var replay = replayPolicyEl.GetString();
|
||||
if (string.Equals(replay, "original", StringComparison.OrdinalIgnoreCase))
|
||||
|
||||
@@ -102,17 +102,47 @@ public static class StreamApiHandlers
|
||||
return JetStreamApiResponse.PurgeResponse((ulong)purged);
|
||||
}
|
||||
|
||||
public static JetStreamApiResponse HandleNames(StreamManager streamManager)
|
||||
public static JetStreamApiResponse HandleNames(ReadOnlySpan<byte> payload, StreamManager streamManager)
|
||||
{
|
||||
var offset = ParseOffset(payload);
|
||||
var all = streamManager.ListNames();
|
||||
var page = offset >= all.Count ? [] : all.Skip(offset).ToList();
|
||||
return new JetStreamApiResponse
|
||||
{
|
||||
StreamNames = streamManager.ListNames(),
|
||||
StreamNames = page,
|
||||
PaginationTotal = all.Count,
|
||||
PaginationOffset = offset,
|
||||
};
|
||||
}
|
||||
|
||||
public static JetStreamApiResponse HandleList(StreamManager streamManager)
|
||||
public static JetStreamApiResponse HandleList(ReadOnlySpan<byte> payload, StreamManager streamManager)
|
||||
{
|
||||
return HandleNames(streamManager);
|
||||
var offset = ParseOffset(payload);
|
||||
var all = streamManager.ListStreamInfos();
|
||||
var page = offset >= all.Count ? [] : all.Skip(offset).ToList();
|
||||
return new JetStreamApiResponse
|
||||
{
|
||||
StreamInfoList = page,
|
||||
PaginationTotal = all.Count,
|
||||
PaginationOffset = offset,
|
||||
};
|
||||
}
|
||||
|
||||
private static int ParseOffset(ReadOnlySpan<byte> payload)
|
||||
{
|
||||
if (payload.IsEmpty) return 0;
|
||||
try
|
||||
{
|
||||
using var doc = System.Text.Json.JsonDocument.Parse(payload.ToArray());
|
||||
if (doc.RootElement.TryGetProperty("offset", out var el) && el.TryGetInt32(out var v))
|
||||
return Math.Max(v, 0);
|
||||
}
|
||||
catch (System.Text.Json.JsonException ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Malformed offset payload: {ex.Message}");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static JetStreamApiResponse HandleMessageGet(string subject, ReadOnlySpan<byte> payload, StreamManager streamManager)
|
||||
@@ -445,7 +475,9 @@ public static class StreamApiHandlers
|
||||
if (root.TryGetProperty("max_msgs_per", out var maxMsgsPerEl) && maxMsgsPerEl.TryGetInt32(out var maxMsgsPer))
|
||||
config.MaxMsgsPer = maxMsgsPer;
|
||||
|
||||
if (root.TryGetProperty("max_age_ms", out var maxAgeMsEl) && maxAgeMsEl.TryGetInt32(out var maxAgeMs))
|
||||
if (root.TryGetProperty("max_age", out var maxAgeNsEl) && maxAgeNsEl.TryGetInt64(out var maxAgeNs))
|
||||
config.MaxAge = maxAgeNs;
|
||||
else if (root.TryGetProperty("max_age_ms", out var maxAgeMsEl) && maxAgeMsEl.TryGetInt32(out var maxAgeMs))
|
||||
config.MaxAgeMs = maxAgeMs;
|
||||
|
||||
if (root.TryGetProperty("max_msg_size", out var maxMsgSizeEl) && maxMsgSizeEl.TryGetInt32(out var maxMsgSize))
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace NATS.Server.JetStream.Api;
|
||||
|
||||
/// <summary>
|
||||
/// JetStream API size and queue limits aligned with Go server constants.
|
||||
/// Go reference: server/jetstream_api.go (JSMaxDescriptionLen, JSMaxMetadataLen,
|
||||
/// JSMaxNameLen, JSDefaultRequestQueueLimit).
|
||||
/// </summary>
|
||||
public static class JetStreamApiLimits
|
||||
{
|
||||
public const int JSMaxDescriptionLen = 4_096;
|
||||
public const int JSMaxMetadataLen = 128 * 1024;
|
||||
public const int JSMaxNameLen = 255;
|
||||
public const int JSDefaultRequestQueueLimit = 10_000;
|
||||
}
|
||||
@@ -9,7 +9,9 @@ public sealed class JetStreamApiResponse
|
||||
public JetStreamConsumerInfo? ConsumerInfo { get; init; }
|
||||
public JetStreamAccountInfo? AccountInfo { get; init; }
|
||||
public IReadOnlyList<string>? StreamNames { get; init; }
|
||||
public IReadOnlyList<JetStreamStreamInfo>? StreamInfoList { get; init; }
|
||||
public IReadOnlyList<string>? ConsumerNames { get; init; }
|
||||
public IReadOnlyList<JetStreamConsumerInfo>? ConsumerInfoList { get; init; }
|
||||
public JetStreamStreamMessage? StreamMessage { get; init; }
|
||||
public JetStreamDirectMessage? DirectMessage { get; init; }
|
||||
public JetStreamSnapshot? Snapshot { get; init; }
|
||||
@@ -17,6 +19,17 @@ public sealed class JetStreamApiResponse
|
||||
public bool Success { get; init; }
|
||||
public ulong Purged { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Total count of all items (before pagination). Used by list responses for offset-based pagination.
|
||||
/// Go reference: jetstream_api.go — ApiPaged struct includes Total, Offset, Limit fields.
|
||||
/// </summary>
|
||||
public int PaginationTotal { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Requested offset for pagination. Echoed back to client so it can calculate the next page.
|
||||
/// </summary>
|
||||
public int PaginationOffset { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the consumer is currently paused. Populated by pause/resume API responses.
|
||||
/// Go reference: server/consumer.go jsConsumerPauseResponse.paused field.
|
||||
@@ -29,6 +42,123 @@ public sealed class JetStreamApiResponse
|
||||
/// </summary>
|
||||
public DateTime? PauseUntil { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns a wire-format object for JSON serialization matching the Go server's
|
||||
/// flat response structure (e.g., config/state at root level for stream responses,
|
||||
/// not nested under a wrapper property).
|
||||
/// </summary>
|
||||
public object ToWireFormat()
|
||||
{
|
||||
if (StreamInfo != null)
|
||||
{
|
||||
if (Error != null)
|
||||
return new { type = "io.nats.jetstream.api.v1.stream_create_response", error = Error };
|
||||
return new
|
||||
{
|
||||
type = "io.nats.jetstream.api.v1.stream_create_response",
|
||||
config = ToWireConfig(StreamInfo.Config),
|
||||
state = ToWireState(StreamInfo.State),
|
||||
};
|
||||
}
|
||||
|
||||
if (ConsumerInfo != null)
|
||||
{
|
||||
if (Error != null)
|
||||
return new { type = "io.nats.jetstream.api.v1.consumer_create_response", error = Error };
|
||||
return new
|
||||
{
|
||||
type = "io.nats.jetstream.api.v1.consumer_create_response",
|
||||
stream_name = ConsumerInfo.StreamName,
|
||||
name = ConsumerInfo.Name,
|
||||
config = ToWireConsumerConfig(ConsumerInfo.Config),
|
||||
};
|
||||
}
|
||||
|
||||
if (Error != null)
|
||||
return new { error = Error };
|
||||
|
||||
if (StreamInfoList != null)
|
||||
{
|
||||
var wireStreams = StreamInfoList.Select(s => new
|
||||
{
|
||||
config = ToWireConfig(s.Config),
|
||||
state = ToWireState(s.State),
|
||||
}).ToList();
|
||||
return new { total = PaginationTotal, offset = PaginationOffset, limit = wireStreams.Count, streams = wireStreams };
|
||||
}
|
||||
|
||||
if (StreamNames != null)
|
||||
return new { total = PaginationTotal, offset = PaginationOffset, limit = StreamNames.Count, streams = StreamNames };
|
||||
|
||||
if (ConsumerInfoList != null)
|
||||
{
|
||||
var wireConsumers = ConsumerInfoList.Select(c => new
|
||||
{
|
||||
stream_name = c.StreamName,
|
||||
name = c.Name,
|
||||
config = ToWireConsumerConfig(c.Config),
|
||||
}).ToList();
|
||||
return new { total = PaginationTotal, offset = PaginationOffset, limit = wireConsumers.Count, consumers = wireConsumers };
|
||||
}
|
||||
|
||||
if (ConsumerNames != null)
|
||||
return new { total = PaginationTotal, offset = PaginationOffset, limit = ConsumerNames.Count, consumers = ConsumerNames };
|
||||
|
||||
if (Purged > 0 || Success)
|
||||
return new { success = Success, purged = Purged };
|
||||
|
||||
return new { success = Success };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Go-compatible wire format for StreamConfig.
|
||||
/// Only includes fields the Go server sends, with enums as lowercase strings.
|
||||
/// Go reference: server/stream.go StreamConfig JSON marshaling.
|
||||
/// </summary>
|
||||
private static object ToWireConfig(StreamConfig c) => new
|
||||
{
|
||||
name = c.Name,
|
||||
subjects = c.Subjects,
|
||||
retention = c.Retention.ToString().ToLowerInvariant(),
|
||||
max_consumers = c.MaxConsumers,
|
||||
max_msgs = c.MaxMsgs,
|
||||
max_bytes = c.MaxBytes,
|
||||
max_age = c.MaxAge,
|
||||
max_msgs_per_subject = c.MaxMsgsPer,
|
||||
max_msg_size = c.MaxMsgSize,
|
||||
storage = c.Storage.ToString().ToLowerInvariant(),
|
||||
discard = c.Discard.ToString().ToLowerInvariant(),
|
||||
num_replicas = c.Replicas,
|
||||
duplicate_window = (long)c.DuplicateWindowMs * 1_000_000L,
|
||||
sealed_field = c.Sealed,
|
||||
deny_delete = c.DenyDelete,
|
||||
deny_purge = c.DenyPurge,
|
||||
allow_direct = c.AllowDirect,
|
||||
first_seq = c.FirstSeq,
|
||||
};
|
||||
|
||||
private static object ToWireState(ApiStreamState s) => new
|
||||
{
|
||||
messages = s.Messages,
|
||||
bytes = s.Bytes,
|
||||
first_seq = s.FirstSeq,
|
||||
last_seq = s.LastSeq,
|
||||
consumer_count = 0,
|
||||
};
|
||||
|
||||
private static object ToWireConsumerConfig(ConsumerConfig c) => new
|
||||
{
|
||||
durable_name = string.IsNullOrEmpty(c.DurableName) ? null : c.DurableName,
|
||||
name = string.IsNullOrEmpty(c.DurableName) ? null : c.DurableName,
|
||||
deliver_policy = c.DeliverPolicy.ToString().ToLowerInvariant(),
|
||||
ack_policy = c.AckPolicy.ToString().ToLowerInvariant(),
|
||||
replay_policy = c.ReplayPolicy.ToString().ToLowerInvariant(),
|
||||
ack_wait = (long)c.AckWaitMs * 1_000_000L,
|
||||
max_deliver = c.MaxDeliver,
|
||||
max_ack_pending = c.MaxAckPending,
|
||||
filter_subject = c.FilterSubject,
|
||||
};
|
||||
|
||||
public static JetStreamApiResponse NotFound(string subject) => new()
|
||||
{
|
||||
Error = new JetStreamApiError
|
||||
@@ -99,6 +229,8 @@ public sealed class JetStreamStreamInfo
|
||||
|
||||
public sealed class JetStreamConsumerInfo
|
||||
{
|
||||
public string? Name { get; init; }
|
||||
public string? StreamName { get; init; }
|
||||
public required ConsumerConfig Config { get; init; }
|
||||
}
|
||||
|
||||
|
||||
@@ -249,10 +249,10 @@ public sealed class JetStreamApiRouter
|
||||
return StreamApiHandlers.HandleInfo(subject, _streamManager);
|
||||
|
||||
if (subject.Equals(JetStreamApiSubjects.StreamNames, StringComparison.Ordinal))
|
||||
return StreamApiHandlers.HandleNames(_streamManager);
|
||||
return StreamApiHandlers.HandleNames(payload, _streamManager);
|
||||
|
||||
if (subject.Equals(JetStreamApiSubjects.StreamList, StringComparison.Ordinal))
|
||||
return StreamApiHandlers.HandleList(_streamManager);
|
||||
return StreamApiHandlers.HandleList(payload, _streamManager);
|
||||
|
||||
if (subject.StartsWith(JetStreamApiSubjects.StreamUpdate, StringComparison.Ordinal))
|
||||
return StreamApiHandlers.HandleUpdate(subject, payload, _streamManager);
|
||||
@@ -288,10 +288,10 @@ public sealed class JetStreamApiRouter
|
||||
return ConsumerApiHandlers.HandleInfo(subject, _consumerManager);
|
||||
|
||||
if (subject.StartsWith(JetStreamApiSubjects.ConsumerNames, StringComparison.Ordinal))
|
||||
return ConsumerApiHandlers.HandleNames(subject, _consumerManager);
|
||||
return ConsumerApiHandlers.HandleNames(subject, payload, _consumerManager);
|
||||
|
||||
if (subject.StartsWith(JetStreamApiSubjects.ConsumerList, StringComparison.Ordinal))
|
||||
return ConsumerApiHandlers.HandleList(subject, _consumerManager);
|
||||
return ConsumerApiHandlers.HandleList(subject, payload, _consumerManager);
|
||||
|
||||
if (subject.StartsWith(JetStreamApiSubjects.ConsumerDelete, StringComparison.Ordinal))
|
||||
return ConsumerApiHandlers.HandleDelete(subject, _consumerManager);
|
||||
|
||||
@@ -4,6 +4,7 @@ using NATS.Server.JetStream.Cluster;
|
||||
using NATS.Server.JetStream.Consumers;
|
||||
using NATS.Server.JetStream.Models;
|
||||
using NATS.Server.JetStream.Storage;
|
||||
using NATS.Server.JetStream.Validation;
|
||||
using NATS.Server.Subscriptions;
|
||||
|
||||
namespace NATS.Server.JetStream;
|
||||
@@ -40,6 +41,12 @@ public sealed class ConsumerManager : IDisposable
|
||||
return JetStreamApiResponse.ErrorResponse(400, "durable name required");
|
||||
}
|
||||
|
||||
if (!JetStreamConfigValidator.IsValidName(config.DurableName))
|
||||
return JetStreamApiResponse.ErrorResponse(400, "invalid durable name");
|
||||
|
||||
if (!JetStreamConfigValidator.IsMetadataWithinLimit(config.Metadata))
|
||||
return JetStreamApiResponse.ErrorResponse(400, "consumer metadata exceeds maximum size");
|
||||
|
||||
if (config.FilterSubjects.Count == 0 && !string.IsNullOrWhiteSpace(config.FilterSubject))
|
||||
config.FilterSubjects.Add(config.FilterSubject);
|
||||
|
||||
@@ -58,6 +65,8 @@ public sealed class ConsumerManager : IDisposable
|
||||
{
|
||||
ConsumerInfo = new JetStreamConsumerInfo
|
||||
{
|
||||
Name = handle.Config.DurableName,
|
||||
StreamName = stream,
|
||||
Config = handle.Config,
|
||||
},
|
||||
};
|
||||
@@ -71,6 +80,8 @@ public sealed class ConsumerManager : IDisposable
|
||||
{
|
||||
ConsumerInfo = new JetStreamConsumerInfo
|
||||
{
|
||||
Name = handle.Config.DurableName,
|
||||
StreamName = stream,
|
||||
Config = handle.Config,
|
||||
},
|
||||
};
|
||||
@@ -95,6 +106,13 @@ public sealed class ConsumerManager : IDisposable
|
||||
.OrderBy(x => x, StringComparer.Ordinal)
|
||||
.ToArray();
|
||||
|
||||
public IReadOnlyList<JetStreamConsumerInfo> ListConsumerInfos(string stream)
|
||||
=> _consumers
|
||||
.Where(kv => string.Equals(kv.Key.Stream, stream, StringComparison.Ordinal))
|
||||
.OrderBy(kv => kv.Key.Name, StringComparer.Ordinal)
|
||||
.Select(kv => new JetStreamConsumerInfo { Name = kv.Value.Config.DurableName, StreamName = stream, Config = kv.Value.Config })
|
||||
.ToList();
|
||||
|
||||
public bool Pause(string stream, string durableName, bool paused)
|
||||
{
|
||||
if (!_consumers.TryGetValue((stream, durableName), out var handle))
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
namespace NATS.Server.JetStream;
|
||||
|
||||
/// <summary>
|
||||
/// API usage counters for JetStream.
|
||||
/// Go reference: server/jetstream.go JetStreamAPIStats.
|
||||
/// </summary>
|
||||
public sealed class JetStreamApiStats
|
||||
{
|
||||
public int Level { get; set; }
|
||||
public ulong Total { get; set; }
|
||||
public ulong Errors { get; set; }
|
||||
public int Inflight { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Per-tier JetStream resource view.
|
||||
/// Go reference: server/jetstream.go JetStreamTier.
|
||||
/// </summary>
|
||||
public sealed class JetStreamTier
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public long Memory { get; set; }
|
||||
public long Store { get; set; }
|
||||
public int Streams { get; set; }
|
||||
public int Consumers { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Per-account JetStream limits.
|
||||
/// Go reference: server/jetstream.go JetStreamAccountLimits.
|
||||
/// </summary>
|
||||
public sealed class JetStreamAccountLimits
|
||||
{
|
||||
public long MaxMemory { get; set; }
|
||||
public long MaxStore { get; set; }
|
||||
public int MaxStreams { get; set; }
|
||||
public int MaxConsumers { get; set; }
|
||||
public int MaxAckPending { get; set; }
|
||||
public long MemoryMaxStreamBytes { get; set; }
|
||||
public long StoreMaxStreamBytes { get; set; }
|
||||
public bool MaxBytesRequired { get; set; }
|
||||
public Dictionary<string, JetStreamTier> Tiers { get; set; } = new(StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Server-level JetStream usage stats.
|
||||
/// Go reference: server/jetstream.go JetStreamStats.
|
||||
/// </summary>
|
||||
public sealed class JetStreamStats
|
||||
{
|
||||
public long Memory { get; set; }
|
||||
public long Store { get; set; }
|
||||
public long ReservedMemory { get; set; }
|
||||
public long ReservedStore { get; set; }
|
||||
public int Accounts { get; set; }
|
||||
public int HaAssets { get; set; }
|
||||
public JetStreamApiStats Api { get; set; } = new();
|
||||
}
|
||||
@@ -8,7 +8,18 @@ public sealed class StreamConfig
|
||||
public int MaxMsgs { get; set; }
|
||||
public long MaxBytes { get; set; }
|
||||
public int MaxMsgsPer { get; set; }
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
public int MaxAgeMs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// MaxAge in nanoseconds for JSON wire compatibility with Go server.
|
||||
/// Go reference: StreamConfig.MaxAge is a time.Duration (nanoseconds in JSON).
|
||||
/// </summary>
|
||||
public long MaxAge
|
||||
{
|
||||
get => (long)MaxAgeMs * 1_000_000L;
|
||||
set => MaxAgeMs = (int)(value / 1_000_000);
|
||||
}
|
||||
public int MaxMsgSize { get; set; }
|
||||
public int MaxConsumers { get; set; }
|
||||
public int DuplicateWindowMs { get; set; }
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user