- 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
34 KiB
34 KiB
RAFT — Gap Analysis
This file tracks what has and hasn't been ported from Go to .NET for the RAFT module. See stillmissing.md for the full LOC comparison across all modules.
LLM Instructions: How to Analyze This Category
Step 1: Read the Go Reference Files
Read each Go source file listed below. For every file:
- Extract all exported types (structs, interfaces, type aliases)
- Extract all exported methods on those types (receiver functions)
- Extract all exported standalone functions
- Note key constants, enums, and protocol states
- Note important unexported helpers that implement core logic (functions >20 lines)
- Pay attention to concurrency patterns (goroutines, mutexes, channels) — these map to different .NET patterns
Step 2: Read the .NET Implementation Files
Read all .cs files in the .NET directories listed below. For each Go symbol found in Step 1:
- Search for a matching type, method, or function in .NET
- If found, compare the behavior: does it handle the same edge cases? Same error paths?
- If partially implemented, note what's missing
- If not found, note it as MISSING
Step 3: Cross-Reference Tests
Compare Go test functions against .NET test methods:
- For each Go
Test*function, check if a corresponding .NET[Fact]or[Theory]exists - Note which test scenarios are covered and which are missing
- Check the parity DB (
docs/test_parity.db) for existing mappings:sqlite3 docs/test_parity.db "SELECT go_test, dotnet_test, confidence FROM test_mappings tm JOIN go_tests gt ON tm.go_test_id=gt.rowid JOIN dotnet_tests dt ON tm.dotnet_test_id=dt.rowid WHERE gt.go_file LIKE '%PATTERN%'"
Step 4: Classify Each Item
Use these status values:
| Status | Meaning |
|---|---|
| PORTED | Equivalent exists in .NET with matching behavior |
| PARTIAL | .NET implementation exists but is incomplete (missing edge cases, error handling, or features) |
| MISSING | No .NET equivalent found — needs to be ported |
| NOT_APPLICABLE | Go-specific pattern that doesn't apply to .NET (build tags, platform-specific goroutine tricks, etc.) |
| DEFERRED | Intentionally skipped for now (document why) |
Step 5: Fill In the Gap Inventory
Add rows to the Gap Inventory table below. Group by Go source file. Include the Go file and line number so a porting LLM can jump directly to the reference implementation.
Key Porting Notes for RAFT
- RAFT is used for JetStream cluster consensus — both meta-cluster (stream/consumer placement) and per-stream/consumer groups.
- Key operations: leader election, log append/commit, snapshotting, peer management.
- The .NET implementation (20 files, 3,045 LOC) is more granular than Go's single file. Check if the decomposition covers all RAFT states.
Go Reference Files (Source)
golang/nats-server/server/raft.go— RAFT consensus for clustered JetStream (~5,037 lines). Meta-cluster for metadata, per-stream/consumer RAFT groups. Leader election, log replication, snapshotting.
Go Reference Files (Tests)
golang/nats-server/server/raft_test.gogolang/nats-server/server/raft_helpers_test.gogolang/nats-server/server/raft_chain_of_blocks_helpers_test.go
.NET Implementation Files (Source)
src/NATS.Server/Raft/(all 20 files)
.NET Implementation Files (Tests)
tests/NATS.Server.Tests/Raft/
Gap Inventory
golang/nats-server/server/raft.go — Exported Interfaces & Types
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|---|---|---|---|---|
| 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 | 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 | 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
| 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 |
golang/nats-server/server/raft.go — Core State Machine (unexported but critical)
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|---|---|---|---|---|
| raft (struct) | raft.go:151-251 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:3 | RaftNode has basic fields (term, vote, log, peers, commit) but missing: WAL, catchup state, progress map, pae cache, ipQueues, removed peers, many flags |
| run() | raft.go:2275-2362 | MISSING | — | No main run loop / state machine goroutine |
| runAsFollower() | raft.go:2441-2496 | MISSING | — | No follower state machine with select on channels |
| runAsCandidate() | raft.go:3587-3670 | MISSING | — | No candidate state machine with vote collection |
| runAsLeader() | raft.go:2951-3067 | MISSING | — | No leader state machine with heartbeat ticker and proposal batching |
| processAppendEntry() | raft.go:3857-4248 | MISSING | — | Complex append entry processing with catchup, WAL alignment, truncation — not ported |
| processAppendEntryResponse() | raft.go:4287-4339 | MISSING | — | No response processing with catchup triggering |
| processVoteRequest() | raft.go:4780-4845 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:142 | GrantVote handles basic vote logic but missing: log up-to-dateness check (lastTerm/lastIndex), catchup cancellation, campaign timeout reset |
| requestVote() | raft.go:4856-4872 | MISSING | — | No vote request broadcast via RPC |
| handleAppendEntry() | raft.go:3672-3684 | MISSING | — | No wire callback for append entries |
| handleAppendEntryResponse() | raft.go:4342-4346 | MISSING | — | No wire callback for AE responses |
| handleVoteRequest() | raft.go:4847-4854 | MISSING | — | No wire callback for vote requests |
| handleVoteResponse() | raft.go:4764-4778 | MISSING | — | No wire callback for vote responses |
| handleForwardedProposal() | raft.go:2854-2874 | MISSING | — | No forwarded proposal handler |
| handleForwardedRemovePeerProposal() | raft.go:2820-2851 | MISSING | — | No forwarded remove-peer handler |
golang/nats-server/server/raft.go — WAL & Storage
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|---|---|---|---|---|
| storeToWAL() | raft.go:4360-4396 | PARTIAL | src/NATS.Server/Raft/RaftWal.cs:74 | RaftWal.AppendAsync exists but different storage model (custom binary vs Go's filestore/memstore) |
| loadEntry() | raft.go:3339-3346 | MISSING | — | No entry loading from WAL by index |
| loadFirstEntry() | raft.go:3126-3130 | MISSING | — | No first-entry loading |
| truncateWAL() | raft.go:3758-3815 | MISSING | — | No WAL truncation with snapshot invalidation |
| resetWAL() | raft.go:3819-3821 | MISSING | — | No WAL reset |
| cachePendingEntry() | raft.go:4449-4460 | MISSING | — | No pending append entry cache |
golang/nats-server/server/raft.go — Snapshots
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|---|---|---|---|---|
| snapshot (struct) | raft.go:1243-1248 | PARTIAL | src/NATS.Server/Raft/RaftSnapshot.cs:3 | RaftSnapshot has LastIncludedIndex/Term/Data but missing peerstate encoding |
| encodeSnapshot() | raft.go:1254-1279 | MISSING | — | No binary snapshot encoding with highwayhash |
| loadLastSnapshot() | raft.go:1660-1707 | MISSING | — | No binary snapshot loading with hash verification |
| setupLastSnapshot() | raft.go:1578-1656 | MISSING | — | No startup snapshot recovery from disk |
| installSnapshot() | raft.go:1315-1350 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:699 | InstallSnapshotAsync simplified — no WAL compact, no snapshot file management |
| termAndIndexFromSnapFile() | raft.go:1564-1573 | MISSING | — | No snapshot filename parsing |
golang/nats-server/server/raft.go — Peer & Membership State
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|---|---|---|---|---|
| peerState (struct) | raft.go:4470-4474 | MISSING | — | No peer state struct with knownPeers/clusterSize/domainExt |
| encodePeerState() | raft.go:4480-4492 | MISSING | — | No binary peer state encoding |
| decodePeerState() | raft.go:4494-4514 | MISSING | — | No binary peer state decoding |
| writePeerState() | raft.go:4603-4609 | MISSING | — | No peer state file persistence |
| readPeerState() | raft.go:4611-4620 | MISSING | — | No peer state file reading |
| processPeerState() | raft.go:4264-4282 | MISSING | — | No peer state processing from leader |
| addPeer() | raft.go:2879-2898 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:129 | AddMember exists but no cluster size/quorum adjustment, no removed-list reversal |
| removePeer() | raft.go:2903-2913 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:131 | RemoveMember exists but no removed tracking, no cluster size/quorum adjustment |
| adjustClusterSizeAndQuorum() | raft.go:3534-3556 | MISSING | — | No dynamic cluster size/quorum recalculation |
| trackPeer() | raft.go:3559-3585 | MISSING | — | No automatic peer tracking with add-on-discovery |
golang/nats-server/server/raft.go — Elections & Voting
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|---|---|---|---|---|
| switchToFollower() | raft.go:4958-4983 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:706 | RequestStepDown sets Follower but missing: aflr reset, leaderState/leaderSince clear, acks reset, leader update |
| switchToCandidate() | raft.go:4985-5014 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:133 | StartElection increments term and votes but missing: observer/paused/processed checks, quorum loss signaling |
| switchToLeader() | raft.go:5016-5037 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:986 | TryBecomeLeader sets Role but missing: aflr setup, peer state broadcast, leader update |
| campaign() | raft.go:2002-2009 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:971 | CampaignWithPreVote starts election but no random timeout |
| wonElection() | raft.go:4886-4888 | PORTED | src/NATS.Server/Raft/RaftNode.cs:986 | Quorum check in TryBecomeLeader |
| 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 | 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
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|---|---|---|---|---|
| voteRequest (struct) | raft.go:4549-4556 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:84 | RaftVoteRequestWire with Encode/Decode |
| voteResponse (struct) | raft.go:4730-4735 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:132 | RaftVoteResponseWire with Encode/Decode |
| appendEntry (struct) | raft.go:2557-2569 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:189 | RaftAppendEntryWire with Encode/Decode |
| appendEntryResponse (struct) | raft.go:2760-2766 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:318 | RaftAppendEntryResponseWire with Encode/Decode |
| ae.encode() | raft.go:2662-2711 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:202 | RaftAppendEntryWire.Encode() |
| decodeAppendEntry() | raft.go:2714-2746 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:255 | RaftAppendEntryWire.Decode() |
| vr.encode() (voteRequest) | raft.go:4560-4568 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:94 | RaftVoteRequestWire.Encode() |
| decodeVoteRequest() | raft.go:4571-4583 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:109 | RaftVoteRequestWire.Decode() |
| vr.encode() (voteResponse) | raft.go:4739-4751 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:142 | RaftVoteResponseWire.Encode() |
| decodeVoteResponse() | raft.go:4753-4762 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:159 | RaftVoteResponseWire.Decode() |
| ar.encode() | raft.go:2777-2794 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:328 | RaftAppendEntryResponseWire.Encode() |
| decodeAppendEntryResponse() | raft.go:2799-2817 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:343 | RaftAppendEntryResponseWire.Decode() |
| idLen constant | raft.go:2756 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:23 | RaftWireConstants.IdLen = 8 |
| RaftWireHelpers (WriteId/ReadId) | raft.go:2693,4581 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:561 | RaftWireHelpers class |
| RaftWireHelpers (uvarint) | raft.go:2682,2740 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:597 | WriteUvarint/ReadUvarint |
golang/nats-server/server/raft.go — NATS Subjects & Transport
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|---|---|---|---|---|
| raftAllSubj constant | raft.go:2162 | PORTED | src/NATS.Server/Raft/RaftSubjects.cs:16 | RaftSubjects.All |
| raftVoteSubj constant | raft.go:2163 | PORTED | src/NATS.Server/Raft/RaftSubjects.cs:22 | RaftSubjects.Vote() |
| raftAppendSubj constant | raft.go:2164 | PORTED | src/NATS.Server/Raft/RaftSubjects.cs:28 | RaftSubjects.AppendEntry() |
| raftPropSubj constant | raft.go:2165 | PORTED | src/NATS.Server/Raft/RaftSubjects.cs:34 | RaftSubjects.Proposal() |
| raftRemovePeerSubj constant | raft.go:2166 | PORTED | src/NATS.Server/Raft/RaftSubjects.cs:40 | RaftSubjects.RemovePeer() |
| raftReply constant | raft.go:2167 | PORTED | src/NATS.Server/Raft/RaftSubjects.cs:46 | RaftSubjects.Reply() |
| raftCatchupReply constant | raft.go:2168 | PORTED | src/NATS.Server/Raft/RaftSubjects.cs:52 | RaftSubjects.CatchupReply() |
| sendRPC() | raft.go:4874-4878 | PARTIAL | src/NATS.Server/Raft/NatsRaftTransport.cs:29 | NatsRaftTransport._publish delegate, but no sendq integration |
| sendReply() | raft.go:4880-4884 | MISSING | — | No reply sending via sendq |
| sendHeartbeat() | raft.go:4545-4547 | PARTIAL | src/NATS.Server/Raft/NatsRaftTransport.cs:229 | SendHeartbeatAsync exists but optimistic (no real ACK tracking) |
| IRaftTransport | — | PORTED | src/NATS.Server/Raft/RaftTransport.cs:3 | Interface with AppendEntries, RequestVote, InstallSnapshot, SendTimeoutNow, SendHeartbeat |
| InMemoryRaftTransport | — | PORTED | src/NATS.Server/Raft/RaftTransport.cs:26 | Full in-memory transport for testing |
| NatsRaftTransport | — | PORTED | src/NATS.Server/Raft/NatsRaftTransport.cs:18 | Wire-level NATS transport with binary encoding |
| createInternalSubs() | raft.go:2209-2233 | MISSING | — | No internal NATS subscription creation |
| subscribe() / unsubscribe() | raft.go:2194-2206 | MISSING | — | No internal subscription management |
golang/nats-server/server/raft.go — Catchup & Leader Coordination
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|---|---|---|---|---|
| catchupState (struct) | raft.go:260-268 | MISSING | — | No catchup state tracking |
| runCatchup() | raft.go:3132-3236 | MISSING | — | No catchup goroutine for lagging followers |
| catchupFollower() | raft.go:3266-3337 | MISSING | — | No follower catchup initiation |
| createCatchup() | raft.go:3718-3735 | MISSING | — | No catchup subscription/inbox creation |
| cancelCatchup() | raft.go:3689-3697 | MISSING | — | No catchup cancellation |
| catchupStalled() | raft.go:3702-3712 | MISSING | — | No stall detection |
| sendSnapshotToFollower() | raft.go:3239-3264 | MISSING | — | No snapshot streaming to follower |
| sendCatchupSignal() | raft.go:3738-3745 | MISSING | — | No catchup signal to upper layer |
| cancelCatchupSignal() | raft.go:3748-3754 | MISSING | — | No catchup signal cancellation |
golang/nats-server/server/raft.go — Commit & Apply
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|---|---|---|---|---|
| applyCommit() | raft.go:3350-3462 | MISSING | — | Complex commit application with entry type dispatch, peer state processing, membership changes |
| tryCommit() | raft.go:3468-3487 | MISSING | — | No quorum-based commit with ack counting |
| trackResponse() | raft.go:3493-3529 | MISSING | — | No response tracking with ack map |
| sendMembershipChange() | raft.go:2918-2949 | MISSING | — | No membership change send with WAL store |
golang/nats-server/server/raft.go — Persistence & Term/Vote
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|---|---|---|---|---|
| writeTermVote() | raft.go:4708-4727 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:993 | PersistAsync writes meta.json (JSON) but Go uses binary tav.idx with dedup |
| readTermVote() | raft.go:4637-4656 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:1013 | LoadPersistedStateAsync reads meta.json but Go uses binary tav.idx |
| writePeerState() (on raft) | raft.go:4589-4600 | MISSING | — | No binary peer state file writing |
| setWriteErr() / setWriteErrLocked() | raft.go:4659-4704 | MISSING | — | No write error tracking with permission/space error escalation |
golang/nats-server/server/raft.go — Server Integration
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|---|---|---|---|---|
| bootstrapRaftNode() | raft.go:346-407 | MISSING | — | No raft bootstrap with peer validation and directory setup |
| initRaftNode() | raft.go:410-614 | MISSING | — | No full raft initialization with WAL replay, snapshot recovery, peer state restore |
| startRaftNode() | raft.go:617-628 | MISSING | — | No start-and-run goroutine |
| registerRaftNode() | raft.go:776-783 | MISSING | — | No server-level raft registration |
| unregisterRaftNode() | raft.go:786-792 | MISSING | — | No server-level raft unregistration |
| lookupRaftNode() | raft.go:803-811 | MISSING | — | No raft node lookup by group |
| numRaftNodes() | raft.go:795-799 | MISSING | — | No raft node count |
| stepdownRaftNodes() | raft.go:831-851 | MISSING | — | No server-wide raft stepdown |
| shutdownRaftNodes() | raft.go:856-875 | MISSING | — | No server-wide raft shutdown |
| transferRaftLeaders() | raft.go:880-903 | MISSING | — | No server-wide leader transfer |
| reloadDebugRaftNodes() | raft.go:815-827 | NOT_APPLICABLE | — | Debug flag reload is Go-specific |
| serverNameForNode() | raft.go:759-763 | NOT_APPLICABLE | — | Server-level node mapping not yet needed |
| clusterNameForNode() | raft.go:767-772 | NOT_APPLICABLE | — | Cluster-level node mapping not yet needed |
golang/nats-server/server/raft.go — .NET Extensions (not in Go)
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|---|---|---|---|---|
| (none — .NET addition) | — | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:366 | RaftPreVoteRequestWire — pre-vote wire format (Go does not have pre-vote) |
| (none — .NET addition) | — | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:410 | RaftPreVoteResponseWire — pre-vote wire format |
| (none — .NET addition) | — | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:455 | RaftTimeoutNowWire — leadership transfer wire format |
| (none — .NET addition) | — | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:508 | RaftInstallSnapshotChunkWire — chunked snapshot wire format |
| (none — .NET addition) | — | PORTED | src/NATS.Server/Raft/CompactionPolicy.cs:9 | CompactionPolicy / CompactionOptions — configurable log compaction |
| (none — .NET addition) | — | PORTED | src/NATS.Server/Raft/SnapshotChunkEnumerator.cs:16 | SnapshotChunkEnumerator — snapshot chunking |
| (none — .NET addition) | — | PORTED | src/NATS.Server/Raft/RaftNode.cs:251 | ReadIndexAsync — linearizable reads (Go does not expose this) |
| (none — .NET addition) | — | PORTED | src/NATS.Server/Raft/RaftNode.cs:464 | Joint consensus (BeginJointConsensus/CommitJointConsensus) |
| (none — .NET addition) | — | PORTED | src/NATS.Server/Raft/RaftNode.cs:790 | TransferLeadershipAsync with TimeoutNow RPC |
| (none — .NET addition) | — | PORTED | src/NATS.Server/Raft/RaftNode.cs:906 | Pre-vote protocol (StartPreVote/RequestPreVote) |
golang/nats-server/server/raft.go — Object Pools & Helpers
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|---|---|---|---|---|
| cePool / newCommittedEntry / ReturnToPool | raft.go:2498-2532 | NOT_APPLICABLE | — | Go sync.Pool for CommittedEntry; .NET uses GC |
| entryPool / newEntry | raft.go:2534-2547 | NOT_APPLICABLE | — | Go sync.Pool for Entry; .NET uses GC |
| aePool / newAppendEntry / returnToPool | raft.go:2549-2583 | NOT_APPLICABLE | — | Go sync.Pool for appendEntry; .NET uses GC |
| pePool / newProposedEntry | raft.go:2586-2603 | NOT_APPLICABLE | — | Go sync.Pool for proposedEntry; .NET uses GC |
| arPool / newAppendEntryResponse | raft.go:2748-2775 | NOT_APPLICABLE | — | Go sync.Pool for appendEntryResponse; .NET uses GC |
| peers sync.Map (string interning) | raft.go:2797 | NOT_APPLICABLE | — | Go-specific string interning optimization |
| debug() / warn() / error() | raft.go:2364-2379 | NOT_APPLICABLE | — | Logging helpers — .NET uses ILogger |
| dios semaphore | raft.go:1665-1667 | NOT_APPLICABLE | — | Go disk I/O semaphore — .NET uses async I/O |
| ipQueue usage | raft.go:233-238 | MISSING | — | Go uses typed ipQueue channels for proposals, entries, votes, responses — .NET has no equivalent internal queue infrastructure |
golang/nats-server/server/raft.go — Constants & Configuration
| 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 | 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 | 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 |
Keeping This File Updated
After porting work is completed:
- Update status: Change
MISSING → PORTEDorPARTIAL → PORTEDfor each item completed - Add .NET path: Fill in the ".NET Equivalent" column with the actual file:line
- Re-count LOC: Update the LOC numbers in
stillmissing.md:# Re-count .NET source LOC for this module find src/NATS.Server/Raft/ -name '*.cs' -type f -exec cat {} + | wc -l # Re-count .NET test LOC for this module find tests/NATS.Server.Tests/Raft/ -name '*.cs' -type f -exec cat {} + | wc -l - Add a changelog entry below with date and summary of what was ported
- Update the parity DB if new test mappings were created:
sqlite3 docs/test_parity.db "INSERT INTO test_mappings (go_test_id, dotnet_test_id, confidence, notes) VALUES (?, ?, 'manual', 'ported in YYYY-MM-DD session')"
Change Log
| Date | Change | By |
|---|---|---|
| 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 |