# Auth & Accounts — Gap Analysis

> This file tracks what has and hasn't been ported from Go to .NET for the **Auth & Accounts** module.
> See [stillmissing.md](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:
   ```bash
   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 Auth & Accounts

- `accounts.go` is the **second-largest functional file** (~4,100 lines). It manages multi-tenant isolation with per-account Sublists, service latency tracking, and subject import/export mappings.
- The Go implementation supports 6+ auth mechanisms. Check which are implemented in .NET's `Auth/` directory.
- JWT handling in Go includes account claims, user claims, activation tokens, and revocation lists.
- Service imports/exports allow cross-account message delivery — check `src/NATS.Server/Imports/`.

---

## Go Reference Files (Source)

- `golang/nats-server/server/auth.go` — Auth mechanisms: username/password, token, NKeys (Ed25519), JWT, external auth callout, LDAP
- `golang/nats-server/server/auth_callout.go` — External auth callout service
- `golang/nats-server/server/nkey.go` — NKey (Ed25519) validation
- `golang/nats-server/server/jwt.go` — JWT claims parsing, account/user claims
- `golang/nats-server/server/accounts.go` — Multi-tenant account isolation (~4,100 lines). Each account has its own Sublist, client set, subject namespace. Supports exports/imports.

## Go Reference Files (Tests)

- `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 Implementation Files (Source)

- `src/NATS.Server/Auth/` (all files including `Jwt/` subdirectory)
- `src/NATS.Server/Imports/` (account import/export)

## .NET Implementation Files (Tests)

- `tests/NATS.Server.Tests/Auth/`
- `tests/NATS.Server.Tests/Accounts/`

---

## Gap Inventory

<!-- After analysis, fill in this table. Group rows by Go source file. -->

### auth.go (1,697 lines)

| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|-----------|:-------------|--------|:----------------|-------|
| 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 | 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 | |
| ResponsePermission struct | golang/nats-server/server/auth.go:134 | PORTED | src/NATS.Server/Auth/Permissions.cs:16 | |
| Permissions struct | golang/nats-server/server/auth.go:141 | PORTED | src/NATS.Server/Auth/Permissions.cs:3 | |
| RoutePermissions struct | golang/nats-server/server/auth.go:150 | MISSING | — | Route-level permissions for cluster; needed for clustering |
| 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 | 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: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 | 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 | 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: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 |
| isLeafNodeAuthorized() | golang/nats-server/server/auth.go:1443 | MISSING | — | Leaf node authentication |
| isBcrypt() | golang/nats-server/server/auth.go:1569 | PORTED | src/NATS.Server/Auth/UserPasswordAuthenticator.cs:65 | Simplified to StartsWith("$2") check |
| comparePasswords() | golang/nats-server/server/auth.go:1577 | PORTED | src/NATS.Server/Auth/UserPasswordAuthenticator.cs:46 | bcrypt + constant-time comparison |
| validateAuth() | golang/nats-server/server/auth.go:1595 | MISSING | — | Top-level auth config validation |
| validateAllowedConnectionTypes() | golang/nats-server/server/auth.go:1612 | PARTIAL | src/NATS.Server/Auth/Jwt/JwtConnectionTypes.cs:18 | Validation logic exists in JWT context; not used for config-level validation |
| validateNoAuthUser() | golang/nats-server/server/auth.go:1631 | MISSING | — | Validates no_auth_user exists in Users/Nkeys |
| validateProxies() | golang/nats-server/server/auth.go:1657 | MISSING | — | Validates proxy trusted keys are valid NKeys |
| processProxiesTrustedKeys() | golang/nats-server/server/auth.go:1672 | MISSING | — | Builds NKey keypairs from proxy trusted keys |
| getAuthErrClosedState() | golang/nats-server/server/auth.go:1688 | MISSING | — | Maps auth errors to connection close states |

### auth_callout.go (497 lines)

| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|-----------|:-------------|--------|:----------------|-------|
| 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 |

### nkey.go (47 lines)

| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|-----------|:-------------|--------|:----------------|-------|
| nonceRawLen const | golang/nats-server/server/nkey.go:23 | PORTED | src/NATS.Server/Auth/AuthService.cs:147 | Uses 11-byte raw nonce |
| nonceLen const | golang/nats-server/server/nkey.go:24 | NOT_APPLICABLE | — | Derived constant (base64 length) |
| NonceRequired() | golang/nats-server/server/nkey.go:28 | PORTED | src/NATS.Server/Auth/AuthService.cs:18 | Property on AuthService |
| nonceRequired() | golang/nats-server/server/nkey.go:36 | PORTED | src/NATS.Server/Auth/AuthService.cs:34 | Checked during Build based on NKeys/TrustedKeys |
| generateNonce() | golang/nats-server/server/nkey.go:42 | PORTED | src/NATS.Server/Auth/AuthService.cs:146 | GenerateNonce + EncodeNonce use RandomNumberGenerator.Fill |

### jwt.go (245 lines)

| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|-----------|:-------------|--------|:----------------|-------|
| jwtPrefix const | golang/nats-server/server/jwt.go:29 | PORTED | src/NATS.Server/Auth/Jwt/NatsJwt.cs:17 | "eyJ" constant |
| ReadOperatorJWT() | golang/nats-server/server/jwt.go:32 | MISSING | — | Reads operator JWT from file/inline string |
| readOperatorJWT() | golang/nats-server/server/jwt.go:37 | MISSING | — | Internal helper for operator JWT parsing |
| wipeSlice() | golang/nats-server/server/jwt.go:61 | NOT_APPLICABLE | — | Go-specific memory wiping; .NET has CryptographicOperations.ZeroMemory |
| validateTrustedOperators() | golang/nats-server/server/jwt.go:70 | MISSING | — | Validates operator JWT config (keys, resolver, system account, version) |
| validateSrc() | golang/nats-server/server/jwt.go:182 | MISSING | — | Validates user JWT source CIDR restrictions |
| validateTimes() | golang/nats-server/server/jwt.go:204 | MISSING | — | Validates user JWT time-of-day restrictions |

### accounts.go (4,774 lines)

| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|-----------|:-------------|--------|:----------------|-------|
| globalAccountName const | golang/nats-server/server/accounts.go:44 | PORTED | src/NATS.Server/Auth/Account.cs:9 | Account.GlobalAccountName = "$G" |
| Account struct | golang/nats-server/server/accounts.go:52 | PARTIAL | src/NATS.Server/Auth/Account.cs:7 | Core fields ported (Name, SubList, limits, clients, revocations, exports, imports). Missing: stats (gw/rt/ln), gwReplyMapping, claimJWT, mappings, hasMapped, lleafs, leafClusters, js/jsLimits, nrgAccount, extAuth, defaultPerms, nameTag, tags, traceDest |
| 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 | 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 |
| ServiceRespType.String() | golang/nats-server/server/accounts.go:203 | NOT_APPLICABLE | — | Go Stringer interface; .NET enum has ToString() |
| exportAuth struct | golang/nats-server/server/accounts.go:217 | PORTED | src/NATS.Server/Imports/ExportAuth.cs:5 | TokenRequired, AccountPosition, ApprovedAccounts, RevokedAccounts |
| streamExport struct | golang/nats-server/server/accounts.go:225 | PORTED | src/NATS.Server/Imports/StreamExport.cs:3 | |
| serviceExport struct | golang/nats-server/server/accounts.go:230 | PORTED | src/NATS.Server/Imports/ServiceExport.cs:5 | |
| serviceLatency struct | golang/nats-server/server/accounts.go:245 | PORTED | src/NATS.Server/Imports/ServiceLatency.cs:3 | |
| exportMap struct | golang/nats-server/server/accounts.go:251 | PORTED | src/NATS.Server/Imports/ExportMap.cs:3 | |
| importMap struct | golang/nats-server/server/accounts.go:259 | PORTED | src/NATS.Server/Imports/ImportMap.cs:5 | |
| NewAccount() | golang/nats-server/server/accounts.go:266 | PORTED | src/NATS.Server/Auth/Account.cs:91 | Account constructor |
| Account.String() | golang/nats-server/server/accounts.go:275 | NOT_APPLICABLE | — | Trivial Go Stringer |
| Account.setTraceDest() | golang/nats-server/server/accounts.go:279 | MISSING | — | Message tracing destination |
| Account.getTraceDestAndSampling() | golang/nats-server/server/accounts.go:285 | MISSING | — | Message tracing config |
| Account.shallowCopy() | golang/nats-server/server/accounts.go:298 | MISSING | — | Shallow copy for account reload |
| Account.nextEventID() | golang/nats-server/server/accounts.go:354 | MISSING | — | NUID-based event ID generation |
| Account.getClientsLocked() | golang/nats-server/server/accounts.go:363 | MISSING | — | Returns client slice under lock |
| Account.getClients() | golang/nats-server/server/accounts.go:375 | MISSING | — | Returns client slice with lock acquisition |
| Account.getExternalClientsLocked() | golang/nats-server/server/accounts.go:384 | MISSING | — | Returns only external clients |
| Account.updateRemoteServer() | golang/nats-server/server/accounts.go:399 | MISSING | — | Cluster remote server tracking |
| Account.removeRemoteServer() | golang/nats-server/server/accounts.go:446 | MISSING | — | Cluster remote server removal |
| Account.expectedRemoteResponses() | golang/nats-server/server/accounts.go:460 | MISSING | — | Expected responses from remote servers |
| Account.clearEventing() | golang/nats-server/server/accounts.go:472 | MISSING | — | Clears event timers |
| Account.GetName() | golang/nats-server/server/accounts.go:484 | PORTED | src/NATS.Server/Auth/Account.cs:12 | Name property |
| Account.NumConnections() | golang/nats-server/server/accounts.go:519 | PORTED | src/NATS.Server/Auth/Account.cs:96 | ClientCount property |
| Account.NumRemoteConnections() | golang/nats-server/server/accounts.go:528 | MISSING | — | Remote connection count (clustering) |
| Account.NumLocalConnections() | golang/nats-server/server/accounts.go:537 | PORTED | src/NATS.Server/Auth/Account.cs:96 | ClientCount is local only |
| Account.MaxTotalConnectionsReached() | golang/nats-server/server/accounts.go:563 | PORTED | src/NATS.Server/Auth/Account.cs:103 | AddClient checks MaxConnections |
| Account.MaxActiveConnections() | golang/nats-server/server/accounts.go:575 | PORTED | src/NATS.Server/Auth/Account.cs:15 | MaxConnections property |
| Account.MaxTotalLeafNodesReached() | golang/nats-server/server/accounts.go:583 | MISSING | — | Leaf node connection limits |
| Account.NumLeafNodes() | golang/nats-server/server/accounts.go:599 | MISSING | — | Leaf node count |
| Account.NumRemoteLeafNodes() | golang/nats-server/server/accounts.go:608 | MISSING | — | Remote leaf count |
| Account.MaxActiveLeafNodes() | golang/nats-server/server/accounts.go:618 | MISSING | — | Leaf node limit |
| Account.RoutedSubs() | golang/nats-server/server/accounts.go:627 | MISSING | — | Routed subscription count |
| Account.TotalSubs() | golang/nats-server/server/accounts.go:634 | PORTED | src/NATS.Server/Auth/Account.cs:97 | SubscriptionCount |
| Account.shouldLogMaxSubErr() | golang/nats-server/server/accounts.go:643 | MISSING | — | Rate-limited logging for max sub errors |
| MapDest struct | golang/nats-server/server/accounts.go:660 | MISSING | — | Subject mapping destination with weight |
| NewMapDest() | golang/nats-server/server/accounts.go:666 | MISSING | — | MapDest constructor |
| destination struct | golang/nats-server/server/accounts.go:671 | MISSING | — | Internal mapping destination |
| mapping struct | golang/nats-server/server/accounts.go:677 | MISSING | — | Subject mapping with weighted destinations |
| Account.AddMapping() | golang/nats-server/server/accounts.go:686 | MISSING | — | Subject remapping (1:1) |
| Account.AddWeightedMappings() | golang/nats-server/server/accounts.go:691 | MISSING | — | Weighted subject mappings |
| 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 | 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 |
| Account.isLeafNodeClusterIsolated() | golang/nats-server/server/accounts.go:1004 | MISSING | — | Leaf cluster isolation check |
| Account.removeLeafNode() | golang/nats-server/server/accounts.go:1018 | MISSING | — | Remove leaf node from account |
| Account.removeClient() | golang/nats-server/server/accounts.go:1038 | PORTED | src/NATS.Server/Auth/Account.cs:111 | RemoveClient |
| setExportAuth() | golang/nats-server/server/accounts.go:1075 | PARTIAL | src/NATS.Server/Imports/ExportAuth.cs:5 | ExportAuth struct exists; setExportAuth helper logic partially in Account.AddServiceExport |
| Account.AddServiceExport() | golang/nats-server/server/accounts.go:1101 | PORTED | src/NATS.Server/Auth/Account.cs:309 | AddServiceExport |
| Account.AddServiceExportWithResponse() | golang/nats-server/server/accounts.go:1111 | PORTED | src/NATS.Server/Auth/Account.cs:309 | Included in AddServiceExport with responseType param |
| Account.TrackServiceExport() | golang/nats-server/server/accounts.go:1163 | PARTIAL | src/NATS.Server/Auth/ServiceLatencyTracker.cs:7 | Tracker exists but wiring to export subjects via NATS internal pub missing |
| Account.TrackServiceExportWithSampling() | golang/nats-server/server/accounts.go:1169 | PARTIAL | src/NATS.Server/Imports/ServiceLatency.cs:5 | SamplingPercentage field exists; full integration missing |
| Account.UnTrackServiceExport() | golang/nats-server/server/accounts.go:1238 | MISSING | — | Remove latency tracking from export |
| Account.IsExportService() | golang/nats-server/server/accounts.go:1281 | PORTED | src/NATS.Server/Auth/Account.cs:298 | HasServiceExport |
| Account.IsExportServiceTracking() | golang/nats-server/server/accounts.go:1298 | MISSING | — | Check if latency tracking is enabled for export |
| ServiceLatency struct | golang/nats-server/server/accounts.go:1326 | PORTED | src/NATS.Server/Imports/LatencyTracker.cs:5 | ServiceLatencyMsg |
| ServiceLatencyType const | golang/nats-server/server/accounts.go:1340 | PORTED | src/NATS.Server/Imports/LatencyTracker.cs:8 | Type field default |
| ServiceLatency.NATSTotalTime() | golang/nats-server/server/accounts.go:1343 | MISSING | — | Total time computation |
| ServiceLatency.merge() | golang/nats-server/server/accounts.go:1354 | MISSING | — | Merge latency metrics |
| sanitizeLatencyMetric() | golang/nats-server/server/accounts.go:1370 | MISSING | — | Sanitize negative latencies |
| remoteLatency struct | golang/nats-server/server/accounts.go:1380 | MISSING | — | Remote latency for cluster |
| Account.sendLatencyResult() | golang/nats-server/server/accounts.go:1388 | MISSING | — | Publish latency result to tracking subject |
| Account.sendBadRequestTrackingLatency() | golang/nats-server/server/accounts.go:1401 | MISSING | — | Track bad request latency |
| Account.sendReplyInterestLostTrackLatency() | golang/nats-server/server/accounts.go:1414 | MISSING | — | Track lost reply interest |
| Account.sendBackendErrorTrackingLatency() | golang/nats-server/server/accounts.go:1430 | MISSING | — | Track backend error latency |
| Account.sendTrackingLatency() | golang/nats-server/server/accounts.go:1458 | MISSING | — | Core latency tracking send |
| updateAllClientsServiceExportResponseTime() | golang/nats-server/server/accounts.go:1527 | MISSING | — | Update client response times |
| Account.lowestServiceExportResponseTime() | golang/nats-server/server/accounts.go:1542 | MISSING | — | Find lowest response time |
| Account.AddServiceImportWithClaim() | golang/nats-server/server/accounts.go:1554 | PARTIAL | src/NATS.Server/Auth/Account.cs:338 | AddServiceImport exists; JWT claim-based import missing |
| addServiceImportWithClaim() | golang/nats-server/server/accounts.go:1560 | PARTIAL | src/NATS.Server/Auth/Account.cs:338 | Core logic ported; missing claim validation, activation token handling |
| MaxAccountCycleSearchDepth const | golang/nats-server/server/accounts.go:1593 | MISSING | — | Depth limit for cycle detection |
| Account.serviceImportFormsCycle() | golang/nats-server/server/accounts.go:1595 | PORTED | src/NATS.Server/Auth/AccountImportExport.cs:18 | DetectCycle |
| Account.checkServiceImportsForCycles() | golang/nats-server/server/accounts.go:1599 | PORTED | src/NATS.Server/Auth/AccountImportExport.cs:18 | DFS cycle detection |
| Account.streamImportFormsCycle() | golang/nats-server/server/accounts.go:1627 | PORTED | src/NATS.Server/Auth/Account.cs:405 | StreamImportFormsCycle |
| Account.hasServiceExportMatching() | golang/nats-server/server/accounts.go:1632 | PORTED | src/NATS.Server/Auth/Account.cs:298 | HasServiceExport |
| Account.hasStreamExportMatching() | golang/nats-server/server/accounts.go:1642 | MISSING | — | Check if stream export matches subject |
| Account.checkStreamImportsForCycles() | golang/nats-server/server/accounts.go:1651 | PORTED | src/NATS.Server/Auth/Account.cs:412 | DetectStreamImportCycle (private) |
| 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 | 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 |
| Account.checkForReverseEntries() | golang/nats-server/server/accounts.go:1872 | PARTIAL | src/NATS.Server/Auth/Account.cs:761 | Lookup exists; missing recursive check + interest validation |
| Account.serviceImportShadowed() | golang/nats-server/server/accounts.go:2015 | PORTED | src/NATS.Server/Auth/Account.cs:786 | ServiceImportShadowed |
| Account.serviceImportExists() | golang/nats-server/server/accounts.go:2031 | MISSING | — | Check if service import exists by dest account + from |
| Account.addServiceImport() | golang/nats-server/server/accounts.go:2041 | PARTIAL | src/NATS.Server/Auth/Account.cs:338 | Core add logic; missing claim/activation token handling |
| Account.unsubscribeInternal() | golang/nats-server/server/accounts.go:2130 | MISSING | — | Internal subscription cleanup |
| Account.subscribeServiceImportResponse() | golang/nats-server/server/accounts.go:2137 | MISSING | — | Response subscription for service import |
| Account.subscribeInternalEx() | golang/nats-server/server/accounts.go:2141 | MISSING | — | Internal subscription with routing flag |
| Account.addServiceImportSub() | golang/nats-server/server/accounts.go:2156 | MISSING | — | Wire up service import subscription |
| Account.removeAllServiceImportSubs() | golang/nats-server/server/accounts.go:2190 | MISSING | — | Remove all service import subscriptions |
| Account.addAllServiceImportSubs() | golang/nats-server/server/accounts.go:2215 | MISSING | — | Add all service import subscriptions |
| shouldSample() | golang/nats-server/server/accounts.go:2279 | PORTED | src/NATS.Server/Imports/LatencyTracker.cs:28 | LatencyTracker.ShouldSample |
| Account.processServiceImportResponse() | golang/nats-server/server/accounts.go:2358 | MISSING | — | Handle incoming response for service import |
| Account.createRespWildcard() | golang/nats-server/server/accounts.go:2380 | MISSING | — | Create wildcard reply prefix |
| isTrackedReply() | golang/nats-server/server/accounts.go:2391 | MISSING | — | Check if reply is for a tracked service |
| Account.newServiceReply() | golang/nats-server/server/accounts.go:2398 | PARTIAL | src/NATS.Server/Imports/ResponseRouter.cs:19 | GenerateReplyPrefix exists |
| serviceExport.setResponseThresholdTimer() | golang/nats-server/server/accounts.go:2446 | MISSING | — | Timer for response threshold |
| serviceExport.clearResponseThresholdTimer() | golang/nats-server/server/accounts.go:2454 | MISSING | — | Clear response threshold timer |
| serviceExport.checkExpiredResponses() | golang/nats-server/server/accounts.go:2465 | MISSING | — | Clean up expired responses |
| Account.ServiceExportResponseThreshold() | golang/nats-server/server/accounts.go:2510 | PORTED | src/NATS.Server/Auth/Account.cs:468 | GetServiceResponseThreshold |
| Account.SetServiceExportResponseThreshold() | golang/nats-server/server/accounts.go:2522 | PORTED | src/NATS.Server/Auth/Account.cs:461 | SetServiceResponseThreshold |
| Account.SetServiceExportAllowTrace() | golang/nats-server/server/accounts.go:2549 | MISSING | — | Enable tracing on service export |
| Account.addRespServiceImport() | golang/nats-server/server/accounts.go:2562 | PORTED | src/NATS.Server/Imports/ResponseRouter.cs:33 | CreateResponseImport |
| Account.AddStreamImportWithClaim() | golang/nats-server/server/accounts.go:2598 | PARTIAL | src/NATS.Server/Auth/Account.cs:371 | AddStreamImport exists; claim validation missing |
| Account.AddMappedStreamImport() | golang/nats-server/server/accounts.go:2629 | PORTED | src/NATS.Server/Auth/Account.cs:371 | AddStreamImport with 'to' parameter |
| Account.isStreamImportDuplicate() | golang/nats-server/server/accounts.go:2694 | MISSING | — | Duplicate stream import detection |
| Account.AddStreamImport() | golang/nats-server/server/accounts.go:2704 | PORTED | src/NATS.Server/Auth/Account.cs:371 | |
| IsPublicExport var | golang/nats-server/server/accounts.go:2709 | MISSING | — | Sentinel for public export |
| Account.AddStreamExport() | golang/nats-server/server/accounts.go:2713 | PORTED | src/NATS.Server/Auth/Account.cs:323 | AddStreamExport |
| Account.checkStreamImportAuthorized() | golang/nats-server/server/accounts.go:2746 | PARTIAL | src/NATS.Server/Imports/ExportAuth.cs:12 | IsAuthorized checks approved accounts; missing token/claim validation |
| Account.checkAuth() | golang/nats-server/server/accounts.go:2761 | PARTIAL | src/NATS.Server/Imports/ExportAuth.cs:12 | Basic approval; missing token validation and wildcard matching |
| Account.checkStreamExportApproved() | golang/nats-server/server/accounts.go:2782 | PARTIAL | src/NATS.Server/Imports/ExportAuth.cs:12 | Via IsAuthorized; missing wildcard subject matching |
| Account.checkServiceExportApproved() | golang/nats-server/server/accounts.go:2809 | PARTIAL | src/NATS.Server/Imports/ExportAuth.cs:12 | Via IsAuthorized; missing wildcard subject matching |
| Account.getServiceExport() | golang/nats-server/server/accounts.go:2837 | PORTED | src/NATS.Server/Auth/Account.cs:267 | GetExactServiceExport |
| Account.getWildcardServiceExport() | golang/nats-server/server/accounts.go:2849 | PORTED | src/NATS.Server/Auth/Account.cs:279 | GetWildcardServiceExport |
| Account.streamActivationExpired() | golang/nats-server/server/accounts.go:2860 | PARTIAL | src/NATS.Server/Auth/Account.cs:613 | IsActivationExpired exists; missing auto-removal and re-check logic |
| Account.serviceActivationExpired() | golang/nats-server/server/accounts.go:2895 | PARTIAL | src/NATS.Server/Auth/Account.cs:613 | IsActivationExpired exists; missing auto-removal |
| Account.activationExpired() | golang/nats-server/server/accounts.go:2920 | PARTIAL | src/NATS.Server/Auth/Account.cs:620 | GetExpiredActivations + RemoveExpiredActivations |
| isRevoked() | golang/nats-server/server/accounts.go:2929 | PORTED | src/NATS.Server/Auth/Account.cs:52 | IsUserRevoked |
| Account.checkActivation() | golang/nats-server/server/accounts.go:2943 | PARTIAL | src/NATS.Server/Auth/Account.cs:598 | CheckActivationExpiry exists; missing issuer trust chain validation |
| Account.isIssuerClaimTrusted() | golang/nats-server/server/accounts.go:2988 | MISSING | — | Validates issuer against account signing keys |
| Account.checkStreamImportsEqual() | golang/nats-server/server/accounts.go:3011 | MISSING | — | Equality check for reload |
| Account.checkStreamExportsEqual() | golang/nats-server/server/accounts.go:3036 | MISSING | — | Equality check for reload |
| isStreamExportEqual() | golang/nats-server/server/accounts.go:3054 | MISSING | — | Stream export comparison |
| Account.checkServiceExportsEqual() | golang/nats-server/server/accounts.go:3067 | MISSING | — | Equality check for reload |
| isServiceExportEqual() | golang/nats-server/server/accounts.go:3085 | MISSING | — | Service export comparison |
| isExportAuthEqual() | golang/nats-server/server/accounts.go:3121 | MISSING | — | Export auth comparison |
| Account.checkServiceImportAuthorized() | golang/nats-server/server/accounts.go:3148 | PARTIAL | src/NATS.Server/Imports/ExportAuth.cs:12 | Via IsAuthorized; missing token validation |
| Account.IsExpired() | golang/nats-server/server/accounts.go:3165 | PORTED | src/NATS.Server/Auth/Account.cs:523 | IsExpired property |
| Account.expiredTimeout() | golang/nats-server/server/accounts.go:3170 | MISSING | — | Timer callback for expired accounts |
| Account.setExpirationTimer() | golang/nats-server/server/accounts.go:3184 | PARTIAL | src/NATS.Server/Auth/Account.cs:553 | SetExpiration exists; actual Timer not created |
| Account.clearExpirationTimer() | golang/nats-server/server/accounts.go:3189 | PARTIAL | src/NATS.Server/Auth/Account.cs:557 | ClearExpiration exists; no Timer to clear |
| Account.checkUserRevoked() | golang/nats-server/server/accounts.go:3199 | PORTED | src/NATS.Server/Auth/Account.cs:52 | IsUserRevoked |
| Account.failBearer() | golang/nats-server/server/accounts.go:3206 | MISSING | — | Check if account disallows bearer tokens |
| Account.checkExpiration() | golang/nats-server/server/accounts.go:3213 | PARTIAL | src/NATS.Server/Auth/Account.cs:523 | IsExpired check exists; missing timer setup |
| Account.hasIssuer() | golang/nats-server/server/accounts.go:3235 | MISSING | — | Check signing keys with scope |
| Account.getLDSubject() | golang/nats-server/server/accounts.go:3249 | MISSING | — | Leaf node deny subject |
| SetAccountResolver() | golang/nats-server/server/accounts.go:3267 | MISSING | — | Server-level resolver assignment |
| AccountResolver() | golang/nats-server/server/accounts.go:3274 | MISSING | — | Server-level resolver getter |
| Account.isClaimAccount() | golang/nats-server/server/accounts.go:3283 | MISSING | — | Check if account backed by JWT |
| UpdateAccountClaims() | golang/nats-server/server/accounts.go:3290 | PARTIAL | src/NATS.Server/Auth/Account.cs:684 | UpdateAccountClaims exists; missing full claim application (exports/imports rebuild, signing keys, external auth, mappings) |
| updateAccountClaimsWithRefresh() | golang/nats-server/server/accounts.go:3374 | PARTIAL | src/NATS.Server/Auth/Account.cs:684 | Diff-based update exists; missing export/import refresh, dependent account refresh |
| buildInternalAccount() | golang/nats-server/server/accounts.go:3957 | MISSING | — | Build account from JWT claims |
| buildPermissionsFromJwt() | golang/nats-server/server/accounts.go:3979 | PORTED | src/NATS.Server/Auth/JwtAuthenticator.cs:117 | Inline in JwtAuthenticator.Authenticate |
| buildInternalNkeyUser() | golang/nats-server/server/accounts.go:4012 | MISSING | — | Build NkeyUser from JWT user claims |
| fetchAccount() | golang/nats-server/server/accounts.go:4027 | MISSING | — | Fetch account JWT with NKey validation |
| AccountResolver interface | golang/nats-server/server/accounts.go:4035 | PARTIAL | src/NATS.Server/Auth/Jwt/AccountResolver.cs:14 | IAccountResolver has Fetch/Store/IsReadOnly; missing Start, IsTrackingUpdate, Reload, Close |
| MemAccResolver struct | golang/nats-server/server/accounts.go:4073 | PORTED | src/NATS.Server/Auth/Jwt/AccountResolver.cs:44 | MemAccountResolver |
| MemAccResolver.Fetch() | golang/nats-server/server/accounts.go:4079 | PORTED | src/NATS.Server/Auth/Jwt/AccountResolver.cs:53 | FetchAsync |
| MemAccResolver.Store() | golang/nats-server/server/accounts.go:4087 | PORTED | src/NATS.Server/Auth/Jwt/AccountResolver.cs:60 | StoreAsync |
| URLAccResolver struct | golang/nats-server/server/accounts.go:4097 | MISSING | — | HTTP-based account resolver |
| NewURLAccResolver() | golang/nats-server/server/accounts.go:4104 | MISSING | — | URL resolver constructor |
| URLAccResolver.Fetch() | golang/nats-server/server/accounts.go:4123 | MISSING | — | HTTP GET for account JWT |
| DirAccResolver struct | golang/nats-server/server/accounts.go:4143 | MISSING | — | Directory-based resolver with NATS sync |
| DirAccResolver.Start() | golang/nats-server/server/accounts.go:4352 | MISSING | — | Start directory resolver with NATS subscriptions |
| DirAccResolver.Fetch() | golang/nats-server/server/accounts.go:4533 | MISSING | — | Fetch from directory store |
| DirAccResolver.Store() | golang/nats-server/server/accounts.go:4548 | MISSING | — | Store to directory |
| NewDirAccResolver() | golang/nats-server/server/accounts.go:4574 | MISSING | — | Dir resolver constructor |
| CacheDirAccResolver struct | golang/nats-server/server/accounts.go:4594 | MISSING | — | Caching directory resolver |
| Server.fetch() | golang/nats-server/server/accounts.go:4599 | MISSING | — | Server-level account fetch via NATS |
| NewCacheDirAccResolver() | golang/nats-server/server/accounts.go:4664 | MISSING | — | Cache dir resolver constructor |
| CacheDirAccResolver.Start() | golang/nats-server/server/accounts.go:4679 | MISSING | — | Start cache resolver with NATS sync |

---

## 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`:
   ```bash
   # Re-count .NET source LOC for this module
   find src/NATS.Server/Auth/ src/NATS.Server/Imports/ -name '*.cs' -type f -exec cat {} + | wc -l
   # Re-count .NET test LOC for this module
   find tests/NATS.Server.Tests/Auth/ tests/NATS.Server.Tests/Accounts/ -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:
   ```bash
   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-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 |
