Files
natsdotnet/gaps/leaf-nodes.md
2026-02-25 15:12:52 -05:00

28 KiB

Leaf Nodes — Gap Analysis

This file tracks what has and hasn't been ported from Go to .NET for the Leaf Nodes 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:

  1. Extract all exported types (structs, interfaces, type aliases)
  2. Extract all exported methods on those types (receiver functions)
  3. Extract all exported standalone functions
  4. Note key constants, enums, and protocol states
  5. Note important unexported helpers that implement core logic (functions >20 lines)
  6. 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:

  1. Search for a matching type, method, or function in .NET
  2. If found, compare the behavior: does it handle the same edge cases? Same error paths?
  3. If partially implemented, note what's missing
  4. If not found, note it as MISSING

Step 3: Cross-Reference Tests

Compare Go test functions against .NET test methods:

  1. For each Go Test* function, check if a corresponding .NET [Fact] or [Theory] exists
  2. Note which test scenarios are covered and which are missing
  3. 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 Leaf Nodes

  • Leaf nodes only share subscription interest with the hub — no full mesh.
  • Loop detection uses the $LDS. subject prefix.
  • Leaf connections use ClientKind = LEAF.
  • Leaf nodes can connect through WebSocket and support TLS.

Go Reference Files (Source)

  • golang/nats-server/server/leafnode.go — Hub-and-spoke topology for edge deployments (~3,470 lines). Only subscribed subjects shared with hub. Loop detection via $LDS. prefix.

Go Reference Files (Tests)

  • golang/nats-server/server/leafnode_test.go
  • golang/nats-server/server/leafnode_proxy_test.go
  • golang/nats-server/test/leafnode_test.go (integration)

.NET Implementation Files (Source)

  • src/NATS.Server/LeafNodes/ (all files)

.NET Implementation Files (Tests)

  • tests/NATS.Server.Tests/LeafNodes/

Gap Inventory

golang/nats-server/server/leafnode.go

Constants and Types

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
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

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) 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) 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) 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 (+/`
(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) 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

Methods on Server

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) 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
(s *Server) connectToRemoteLeafNode(remote, firstConnect) golang/nats-server/server/leafnode.go:625 PARTIAL src/NATS.Server/LeafNodes/LeafNodeManager.cs:583 ConnectSolicitedWithRetryAsync covers basic TCP connect + retry. Missing: proxy tunnel support, system account delay for first connect, JetStream migrate timer, isLeafConnectDisabled check
(s *Server) clearObserverState(remote) golang/nats-server/server/leafnode.go:768 MISSING Clears JetStream RAFT observer state after reconnect. RAFT not ported
(s *Server) checkJetStreamMigrate(remote) golang/nats-server/server/leafnode.go:802 PARTIAL src/NATS.Server/LeafNodes/LeafNodeManager.cs:367 CheckJetStreamMigrate validates domain conflicts. Missing: actual RAFT StepDown/SetObserver calls
(s *Server) isLeafConnectDisabled() golang/nats-server/server/leafnode.go:844 PORTED src/NATS.Server/LeafNodes/LeafNodeManager.cs:90 IsLeafConnectDisabled(remoteUrl) / IsGloballyDisabled
(s *Server) startLeafNodeAcceptLoop() golang/nats-server/server/leafnode.go:875 PARTIAL src/NATS.Server/LeafNodes/LeafNodeManager.cs:200 StartAsync starts an accept loop. Missing: TLS setup, INFO JSON generation, nonce generation, async INFO propagation, setLeafNodeInfoHostPortAndIP
(s *Server) copyLeafNodeInfo() golang/nats-server/server/leafnode.go:1083 MISSING Deep-copies the leaf node INFO struct. No Info type in .NET leaf module
(s *Server) addLeafNodeURL(urlStr) golang/nats-server/server/leafnode.go:1096 MISSING Adds a leaf URL from route and regenerates INFO JSON. Not ported
(s *Server) removeLeafNodeURL(urlStr) golang/nats-server/server/leafnode.go:1108 MISSING Removes a leaf URL and regenerates INFO JSON. Not ported
(s *Server) generateLeafNodeInfoJSON() golang/nats-server/server/leafnode.go:1122 MISSING Regenerates the serialized INFO JSON bytes. Not ported
(s *Server) sendAsyncLeafNodeInfo() golang/nats-server/server/leafnode.go:1131 MISSING Sends async INFO to all connected leaf nodes (URL list updates). Not ported
(s *Server) createLeafNode(conn, rURL, remote, ws) golang/nats-server/server/leafnode.go:1140 PARTIAL src/NATS.Server/LeafNodes/LeafNodeManager.cs:566 HandleInboundAsync / ConnectSolicitedAsync create connections. Missing: client struct setup, maxPay/maxSubs, TLS negotiation, websocket detection, auth timer, temp client registration, read/write loop spawning
(s *Server) negotiateLeafCompression(c, didSolicit, infoCompression, co) golang/nats-server/server/leafnode.go:1648 MISSING Negotiates S2 compression mode between hub and leaf. Not ported
(s *Server) setLeafNodeInfoHostPortAndIP() golang/nats-server/server/leafnode.go:1763 MISSING Sets advertise host/port for leaf INFO. Not ported
(s *Server) addLeafNodeConnection(c, srvName, clusterName, checkForDup) golang/nats-server/server/leafnode.go:1811 PARTIAL src/NATS.Server/LeafNodes/LeafNodeManager.cs:693 ValidateRemoteLeafNode checks for duplicate + domain conflict. Missing: JetStream domain API deny-list merging, system account detection, RAFT observer mode toggling, JS mapping table setup, actual s.leafs map insertion
(s *Server) removeLeafNodeConnection(c) golang/nats-server/server/leafnode.go:1975 PARTIAL src/NATS.Server/LeafNodes/LeafNodeManager.cs:671 WatchConnectionAsync removes connection on close. Missing: gwSub removal from gwLeafSubs, tsubt timer stop, proxyKey removal
(s *Server) checkInternalSyncConsumers(acc) golang/nats-server/server/leafnode.go:2193 MISSING Kicks JetStream source/mirror consumers after leaf connect. Requires full JetStream integration
(s *Server) sendPermsAndAccountInfo(c) golang/nats-server/server/leafnode.go:2244 PARTIAL src/NATS.Server/LeafNodes/LeafNodeManager.cs:292 SendPermsAndAccountInfo syncs allow-lists to connection. Missing: actual INFO JSON protocol send to remote, IsSystemAccount flag, ConnectInfo flag
(s *Server) initLeafNodeSmapAndSendSubs(c) golang/nats-server/server/leafnode.go:2264 PARTIAL src/NATS.Server/LeafNodes/LeafNodeManager.cs:325 InitLeafNodeSmapAndSendSubs sends LS+ for a list of subjects. Missing: gathering subs from SubList, loop detection subject, gateway interest, siReply prefix, tsub transient map, spoke-only local sub filtering
(s *Server) updateInterestForAccountOnGateway(accName, sub, delta) golang/nats-server/server/leafnode.go:2428 MISSING Called from gateway code to update leaf node smap. Requires gateway integration
(s *Server) leafNodeResumeConnectProcess(c) golang/nats-server/server/leafnode.go:3369 MISSING Sends CONNECT protocol and starts write loop on solicited leaf. Not ported (no CONNECT handshake)
(s *Server) leafNodeFinishConnectProcess(c) golang/nats-server/server/leafnode.go:3409 MISSING Registers leaf with account, initialises smap, sends sys connect event. Not ported

Methods on Account

Go Symbol Go File:Line Status .NET Equivalent Notes
(acc *Account) updateLeafNodesEx(sub, delta, hubOnly) golang/nats-server/server/leafnode.go:2442 MISSING Propagates sub interest delta to all leaf connections for the account, with hub-only option
(acc *Account) updateLeafNodes(sub, delta) golang/nats-server/server/leafnode.go:2515 PARTIAL src/NATS.Server/LeafNodes/LeafNodeManager.cs:265 PropagateLocalSubscription / PropagateLocalUnsubscription broadcast to all connections. Missing: per-connection smap refcounting, isolated/hub-only filtering, origin-cluster filtering

Methods on leafNodeCfg

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

Standalone Functions

Go Symbol Go File:Line Status .NET Equivalent Notes
validateLeafNode(o *Options) golang/nats-server/server/leafnode.go:214 MISSING Validates all leaf node config options (accounts, operator mode, TLS, proxy, compression). Not ported
checkLeafMinVersionConfig(mv) golang/nats-server/server/leafnode.go:343 MISSING Validates minimum version string. Not ported
validateLeafNodeAuthOptions(o) golang/nats-server/server/leafnode.go:357 MISSING Validates single-user vs. multi-user leaf auth config. Not ported
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

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

.NET-only additions (no Go equivalent — extensions)

.NET Symbol .NET File:Line Notes
LeafConnection.SetPermissions() src/NATS.Server/LeafNodes/LeafConnection.cs:66 Allows-list sync API — exposes what Go does internally
LeafNodeManager.DisableLeafConnect() / EnableLeafConnect() src/NATS.Server/LeafNodes/LeafNodeManager.cs:98 Per-remote disable/enable API (Go uses a simple bool flag)
LeafNodeManager.DisableAllLeafConnections() / EnableAllLeafConnections() src/NATS.Server/LeafNodes/LeafNodeManager.cs:121 Global disable API
LeafNodeManager.ComputeBackoff(attempt) src/NATS.Server/LeafNodes/LeafNodeManager.cs:541 Exponential backoff utility (Go uses fixed reconnectDelay + jitter)
LeafNodeManager.RegisterLeafNodeCluster() / UnregisterLeafNodeCluster() src/NATS.Server/LeafNodes/LeafNodeManager.cs:446 Cluster topology registry (partially mirrors Go's registerLeafNodeCluster)
LeafHubSpokeMapper.Map() src/NATS.Server/LeafNodes/LeafHubSpokeMapper.cs:77 Account mapping hub↔spoke (Go does this inline in client publish path)
LeafHubSpokeMapper.IsSubjectAllowed() src/NATS.Server/LeafNodes/LeafHubSpokeMapper.cs:92 Allow/deny list filtering (mirrors Go's permission check in sendLeafNodeSubUpdate)
LeafLoopDetector.Mark() / IsLooped() / TryUnmark() src/NATS.Server/LeafNodes/LeafLoopDetector.cs Loop detection helpers (Go does this inline in processLeafSub)
WebSocketStreamAdapter src/NATS.Server/LeafNodes/WebSocketStreamAdapter.cs WebSocket→Stream adapter; Go uses its own ws read/write path in client.go
LeafPermSyncResult, LeafTlsReloadResult, LeafValidationResult src/NATS.Server/LeafNodes/LeafNodeManager.cs:758 Result types for .NET API surface (Go returns error tuples)
JetStreamMigrationResult, JetStreamMigrationStatus src/NATS.Server/LeafNodes/LeafNodeManager.cs:804 JetStream migration result type
LeafClusterInfo src/NATS.Server/LeafNodes/LeafNodeManager.cs:829 Cluster topology entry (partial Go analog)

Summary

Status Count
PORTED 5
PARTIAL 18
MISSING 38
NOT_APPLICABLE 1
DEFERRED 0
Total 62

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.
  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.
  5. No HTTP proxy tunnel: establishHTTPProxyTunnel for WebSocket-via-proxy leaf connections is not ported.
  6. No SubList integration: Inbound leaf subscriptions (LS+) are not inserted into the server's SubList trie, so received leaf subscriptions do not create actual server-side subscription state for message routing.
  7. No route/gateway propagation: When a leaf sub arrives, Go also updates routes and gateways; .NET does not.
  8. No reconnect delay enforcement: After loop detection, permission violations, or cluster name collision, Go enforces a 30-second reconnect delay; .NET detects but does not enforce.
  9. No JetStream RAFT integration: clearObserverState, checkJetStreamMigrate (actual RAFT step-down), and checkInternalSyncConsumers are all absent.
  10. WebSocket path incomplete: The WebSocketStreamAdapter adapts the byte stream, but the HTTP upgrade handshake (GET /leafnode with Sec-WebSocket-Key) is not implemented for solicited WS leaf connections.

Keeping This File Updated

After porting work is completed:

  1. Update status: Change MISSING → PORTED or PARTIAL → PORTED for each item completed
  2. Add .NET path: Fill in the ".NET Equivalent" column with the actual file:line
  3. Re-count LOC: Update the LOC numbers in stillmissing.md:
    # Re-count .NET source LOC for this module
    find src/NATS.Server/LeafNodes/ -name '*.cs' -type f -exec cat {} + | wc -l
    # Re-count .NET test LOC for this module
    find tests/NATS.Server.Tests/LeafNodes/ -name '*.cs' -type f -exec cat {} + | wc -l
    
  4. Add a changelog entry below with date and summary of what was ported
  5. 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 inventory populated: 62 symbols classified (5 PORTED, 18 PARTIAL, 38 MISSING, 1 NOT_APPLICABLE) claude-sonnet-4-6