# JetStream — Gap Analysis

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

- **This is the largest module** (55,228 Go source LOC, 30% ported). Break analysis into sub-areas:
  1. Core orchestration (`jetstream.go`, `jetstream_api.go`)
  2. Stream lifecycle (`stream.go`) — retention policies: Limits, Interest, WorkQueue
  3. Consumer state machine (`consumer.go`) — push vs pull, ack policies (None, All, Explicit)
  4. Storage engine (`filestore.go`, `memstore.go`) — S2 compression (IronSnappy NuGet), AEAD encryption (ChaCha20Poly1305/AesGcm)
  5. Cluster coordination (`jetstream_cluster.go`) — depends on RAFT module
- Go's `filestore.go` alone is 12,600 lines — the single largest implementation file.
- The storage interface (`store.go`) defines the contract; analyze this first to understand the abstraction.

---

## Go Reference Files (Source)

- `golang/nats-server/server/jetstream.go` — Orchestration, API subject handlers (`$JS.API.*`)
- `golang/nats-server/server/jetstream_api.go` — JetStream API handlers (~5,300 lines)
- `golang/nats-server/server/jetstream_events.go` — JetStream event publishing
- `golang/nats-server/server/jetstream_errors.go` — Error definitions
- `golang/nats-server/server/jetstream_errors_generated.go` — Generated error codes
- `golang/nats-server/server/jetstream_batching.go` — Batch processing
- `golang/nats-server/server/jetstream_versioning.go` — Version compatibility
- `golang/nats-server/server/stream.go` — Stream lifecycle, retention policies (~8,000 lines)
- `golang/nats-server/server/consumer.go` — Consumer state machine (~6,000 lines). Push vs pull, ack policies.
- `golang/nats-server/server/store.go` — Storage abstraction interface
- `golang/nats-server/server/filestore.go` — Block-based persistent storage (~12,600 lines). S2 compression, encryption.
- `golang/nats-server/server/memstore.go` — In-memory storage with TTL
- `golang/nats-server/server/dirstore.go` — Directory-based storage
- `golang/nats-server/server/disk_avail.go` — Disk space checking
- `golang/nats-server/server/jetstream_cluster.go` — Clustered JetStream (~10,900 lines)

## Go Reference Files (Tests)

- `golang/nats-server/server/jetstream_test.go`
- `golang/nats-server/server/jetstream_consumer_test.go`
- `golang/nats-server/server/jetstream_errors_test.go`
- `golang/nats-server/server/jetstream_batching_test.go`
- `golang/nats-server/server/jetstream_versioning_test.go`
- `golang/nats-server/server/jetstream_helpers_test.go`
- `golang/nats-server/server/jetstream_jwt_test.go`
- `golang/nats-server/server/jetstream_tpm_test.go`
- `golang/nats-server/server/jetstream_sourcing_scaling_test.go`
- `golang/nats-server/server/jetstream_benchmark_test.go`
- `golang/nats-server/server/filestore_test.go`
- `golang/nats-server/server/memstore_test.go`
- `golang/nats-server/server/dirstore_test.go`
- `golang/nats-server/server/store_test.go`
- `golang/nats-server/server/jetstream_cluster_1_test.go` through `_4_test.go`
- `golang/nats-server/server/jetstream_super_cluster_test.go`
- `golang/nats-server/server/jetstream_leafnode_test.go`
- `golang/nats-server/server/jetstream_cluster_long_test.go`

## .NET Implementation Files (Source)

- `src/NATS.Server/JetStream/` — Root files (orchestration)
- `src/NATS.Server/JetStream/Api/` — API handlers
- `src/NATS.Server/JetStream/Consumers/` — Consumer state machine
- `src/NATS.Server/JetStream/Models/` — StreamConfig, ConsumerConfig, etc.
- `src/NATS.Server/JetStream/MirrorSource/` — Stream mirroring & sourcing
- `src/NATS.Server/JetStream/Publish/` — Publish handling
- `src/NATS.Server/JetStream/Snapshots/` — State snapshots
- `src/NATS.Server/JetStream/Validation/` — Config validation
- `src/NATS.Server/JetStream/Storage/` — FileStore, MemStore, encryption
- `src/NATS.Server/JetStream/Cluster/` — Clustered JetStream

## .NET Implementation Files (Tests)

- `tests/NATS.Server.Tests/JetStream/` — Root tests
- `tests/NATS.Server.Tests/JetStream/Api/`
- `tests/NATS.Server.Tests/JetStream/Cluster/`
- `tests/NATS.Server.Tests/JetStream/Consumers/`
- `tests/NATS.Server.Tests/JetStream/MirrorSource/`
- `tests/NATS.Server.Tests/JetStream/Snapshots/`
- `tests/NATS.Server.Tests/JetStream/Storage/`
- `tests/NATS.Server.Tests/JetStream/Streams/`

---

## Gap Inventory

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

<!-- === JS-1: Core === -->

### jetstream.go — Core Orchestration (~2866 lines)

| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|-----------|:-------------|--------|:----------------|-------|
| JetStreamConfig (struct) | golang/nats-server/server/jetstream.go:42 | PORTED | src/NATS.Server/Configuration/JetStreamOptions.cs | Added missing config fields: `SyncInterval`, `SyncAlways`, `CompressOk`, `UniqueTag`, `Strict` (plus parser wiring) |
| JetStreamStats (struct) | golang/nats-server/server/jetstream.go:55 | PORTED | src/NATS.Server/JetStream/JetStreamParityModels.cs (`JetStreamStats`) | Added server-usage model with `Memory`, `Store`, `ReservedMemory`, `ReservedStore`, `Accounts`, `HaAssets`, and `Api` |
| JetStreamAccountLimits (struct) | golang/nats-server/server/jetstream.go:65 | PORTED | src/NATS.Server/JetStream/JetStreamParityModels.cs (`JetStreamAccountLimits`), src/NATS.Server/Configuration/JetStreamOptions.cs | Added missing limits fields: `MaxAckPending`, `MemoryMaxStreamBytes`, `StoreMaxStreamBytes`, `MaxBytesRequired`, and tier map support |
| JetStreamTier (struct) | golang/nats-server/server/jetstream.go:76 | PORTED | src/NATS.Server/JetStream/JetStreamParityModels.cs (`JetStreamTier`) | Added per-tier model (`Name`, `Memory`, `Store`, `Streams`, `Consumers`) |
| JetStreamAccountStats (struct) | golang/nats-server/server/jetstream.go:87 | PARTIAL | src/NATS.Server/JetStream/Api/JetStreamApiResponse.cs:105 | JetStreamAccountInfo has Streams/Consumers counts only. Missing: memory/store usage, tiers, domain, API stats |
| JetStreamAPIStats (struct) | golang/nats-server/server/jetstream.go:95 | PORTED | src/NATS.Server/JetStream/JetStreamParityModels.cs (`JetStreamApiStats`) | Added API stats model with `Level`, `Total`, `Errors`, `Inflight` |
| jetStream (internal struct) | golang/nats-server/server/jetstream.go:103 | PARTIAL | src/NATS.Server/JetStream/JetStreamService.cs:11 | JetStreamService covers lifecycle. Missing: apiInflight/apiTotal/apiErrors atomics, memUsed/storeUsed tracking, accounts map, apiSubs, cluster, oos/shuttingDown state |
| jsAccount (internal struct) | golang/nats-server/server/jetstream.go:151 | MISSING | — | Per-account JetStream state (streams map, usage tracking, cluster usage updates) not modeled |
| jsaUsage (internal struct) | golang/nats-server/server/jetstream.go:181 | MISSING | — | Per-account mem/store usage tracking |
| EnableJetStream (Server method) | golang/nats-server/server/jetstream.go:188 | PARTIAL | src/NATS.Server/JetStream/JetStreamService.cs:95 | StartAsync handles StoreDir creation and API subject registration. Missing: dynamic config, system memory detection, encryption init, cluster init |
| jsKeyGen (Server method) | golang/nats-server/server/jetstream.go:240 | MISSING | — | HMAC-SHA256 key generation for JetStream encryption |
| decryptMeta (Server method) | golang/nats-server/server/jetstream.go:257 | MISSING | — | Encrypted metafile decryption (AEAD with cipher fallback) |
| checkStoreDir (Server method) | golang/nats-server/server/jetstream.go:320 | MISSING | — | Legacy store directory migration logic |
| initJetStreamEncryption (Server method) | golang/nats-server/server/jetstream.go:382 | MISSING | — | TPM-backed encryption key initialization |
| enableJetStream (Server method) | golang/nats-server/server/jetstream.go:414 | PARTIAL | src/NATS.Server/JetStream/JetStreamService.cs:95 | Core startup covered. Missing: gcbOutMax, system account setup, JS banner, encryption notice, internal subscriptions setup, cluster mode init |
| canExtendOtherDomain (Server method) | golang/nats-server/server/jetstream.go:533 | MISSING | — | Leaf node domain extension check |
| restartJetStream (Server method) | golang/nats-server/server/jetstream.go:556 | MISSING | — | Re-enable JetStream during config reload |
| setupJetStreamExports (Server method) | golang/nats-server/server/jetstream.go:590 | MISSING | — | System account service export setup |
| handleOutOfSpace (Server method) | golang/nats-server/server/jetstream.go:613 | MISSING | — | OOS handling: disable JS + advisory |
| DisableJetStream (Server method) | golang/nats-server/server/jetstream.go:643 | PARTIAL | src/NATS.Server/JetStream/JetStreamService.cs:141 | DisposeAsync clears subjects. Missing: cluster meta leader transfer, RAFT node cleanup |
| enableJetStreamAccounts (Server method) | golang/nats-server/server/jetstream.go:690 | MISSING | — | Multi-account JS enablement with parallel task workers |
| enableAllJetStreamServiceImportsAndMappings (Account method) | golang/nats-server/server/jetstream.go:714 | MISSING | — | Per-account service imports and domain mappings |
| configJetStream (Server method) | golang/nats-server/server/jetstream.go:771 | MISSING | — | Per-account JS config (enable/update/disable) |
| configAllJetStreamAccounts (Server method) | golang/nats-server/server/jetstream.go:809 | MISSING | — | Walk all accounts and restore JetStream state |
| JetStreamEnabled (Server method) | golang/nats-server/server/jetstream.go:904 | PORTED | src/NATS.Server/NatsServer.cs:159 (`JetStreamEnabled`) | Server-level JetStream enabled check now exposed and backed by service running state |
| JetStreamEnabledForDomain (Server method) | golang/nats-server/server/jetstream.go:909 | MISSING | — | Domain-wide JS availability check |
| signalPullConsumers (Server method) | golang/nats-server/server/jetstream.go:930 | MISSING | — | Shutdown signal to R1 pull consumers |
| shutdownJetStream (Server method) | golang/nats-server/server/jetstream.go:977 | PARTIAL | src/NATS.Server/JetStream/JetStreamService.cs:141 | Basic cleanup in DisposeAsync. Missing: account removal, cluster qch signaling |
| JetStreamConfig (Server method) | golang/nats-server/server/jetstream.go:1055 | PORTED | src/NATS.Server/NatsServer.cs:161 (`JetStreamConfig`) | Returns a copy of configured JetStream options (store dir, limits, domain) |
| StoreDir (Server method) | golang/nats-server/server/jetstream.go:1065 | PORTED | src/NATS.Server/NatsServer.cs:177 (`StoreDir`) | Server now exposes current configured JetStream store directory |
| JetStreamNumAccounts (Server method) | golang/nats-server/server/jetstream.go:1074 | MISSING | — | Enabled account count |
| JetStreamReservedResources (Server method) | golang/nats-server/server/jetstream.go:1085 | MISSING | — | Reserved mem/store bytes |
| Account.EnableJetStream | golang/nats-server/server/jetstream.go:1107 | MISSING | — | Per-account JS enablement with limits, store dir, cluster usage |
| Account.UpdateJetStreamLimits | golang/nats-server/server/jetstream.go:1736 | MISSING | — | Update account limits with delta checking |
| Account.JetStreamUsage | golang/nats-server/server/jetstream.go:1834 | MISSING | — | Full account usage stats with tier breakdown |
| Account.DisableJetStream | golang/nats-server/server/jetstream.go:1961 | MISSING | — | Per-account JS disable |
| Account.lookupStream | golang/nats-server/server/jetstream.go:1717 | MISSING | — | Stream lookup by name (via jsa) |
| Account.streams / filteredStreams | golang/nats-server/server/jetstream.go:1681 | MISSING | — | All/filtered stream enumeration |
| jsAccount.updateUsage | golang/nats-server/server/jetstream.go:2187 | MISSING | — | Storage usage delta tracking |
| jsAccount.checkAndSyncUsage | golang/nats-server/server/jetstream.go:2108 | MISSING | — | Usage drift detection and correction |
| jsAccount.sendClusterUsageUpdate | golang/nats-server/server/jetstream.go:2250 | MISSING | — | Binary-encoded cluster usage publication |
| jsAccount.remoteUpdateUsage | golang/nats-server/server/jetstream.go:2021 | MISSING | — | Process incoming remote usage updates |
| jetStream.wouldExceedLimits | golang/nats-server/server/jetstream.go:2299 | MISSING | — | Server-level resource limit check |
| jetStream.checkLimits | golang/nats-server/server/jetstream.go:2434 | MISSING | — | Account+server limit checking for stream config |
| jetStream.checkBytesLimits | golang/nats-server/server/jetstream.go:2446 | MISSING | — | Byte-level limits check for mem/file storage |
| jetStream.sufficientResources | golang/nats-server/server/jetstream.go:2549 | MISSING | — | System resource sufficiency check for new accounts |
| jetStream.reserveStreamResources | golang/nats-server/server/jetstream.go:2606 | MISSING | — | Reserve MaxBytes for a stream |
| jetStream.releaseStreamResources | golang/nats-server/server/jetstream.go:2627 | MISSING | — | Release reserved MaxBytes |
| jetStream.usageStats | golang/nats-server/server/jetstream.go:2520 | MISSING | — | Server-wide JS usage stats |
| jsAccount.selectLimits | golang/nats-server/server/jetstream.go:2337 | MISSING | — | Tier-based limit selection |
| jsAccount.storageTotals | golang/nats-server/server/jetstream.go:2360 | MISSING | — | Aggregate mem/store totals |
| jsAccount.reservedStorage | golang/nats-server/server/jetstream.go:1801 | MISSING | — | Reserved bytes by tier |
| jsAccount.delete | golang/nats-server/server/jetstream.go:2481 | MISSING | — | Delete all JS resources for account |
| dynJetStreamConfig (Server method) | golang/nats-server/server/jetstream.go:2659 | MISSING | — | Dynamic config: 75% sysmem, disk available |
| isValidName | golang/nats-server/server/jetstream.go:2735 | PORTED | src/NATS.Server/JetStream/Validation/JetStreamConfigValidator.cs:10 | `IsValidName` enforces non-empty names, UTF-8 max 255 bytes, and rejects whitespace / `*` / `>`; applied in stream + consumer create paths |
| friendlyBytes | golang/nats-server/server/jetstream.go:2723 | NOT_APPLICABLE | — | Logging helper; .NET has built-in formatting |
| tierName | golang/nats-server/server/jetstream.go:2316 | MISSING | — | Compute tier name from replica count |
| validateJetStreamOptions | golang/nats-server/server/jetstream.go:2767 | MISSING | — | Validates JS options (domain, cluster, etc.) |
| fixCfgMirrorWithDedupWindow | golang/nats-server/server/jetstream.go:2848 | NOT_APPLICABLE | — | Bug fix for legacy config; not needed in new port |
| JetStreamStoreDir (const) | golang/nats-server/server/jetstream.go:2649 | PORTED | src/NATS.Server/Configuration/JetStreamOptions.cs:8 | Added Go-parity constant `"jetstream"` |
| JetStreamMaxStoreDefault (const) | golang/nats-server/server/jetstream.go:2651 | PORTED | src/NATS.Server/Configuration/JetStreamOptions.cs:9 | Added Go-parity default max store constant (`1 TiB`) |
| JetStreamMaxMemDefault (const) | golang/nats-server/server/jetstream.go:2653 | PORTED | src/NATS.Server/Configuration/JetStreamOptions.cs:10 | Added Go-parity default max memory constant (`256 MiB`) |
| Stream recovery logic (doStream/doConsumers) | golang/nats-server/server/jetstream.go:1223-1636 | MISSING | — | Full stream/consumer recovery from disk: metafile reading, checksum, encryption, versioning, subject repair |
| keyGen (type) | golang/nats-server/server/jetstream.go:237 | MISSING | — | Key generation function signature for encryption |
| resourcesExceededError (Server method) | golang/nats-server/server/jetstream.go:2743 | MISSING | — | Throttled error logging + meta leader stepdown |
| handleWritePermissionError (Server method) | golang/nats-server/server/jetstream.go:2857 | MISSING | — | FS permission error handling |

### jetstream_api.go — API Handlers (~5165 lines)

| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|-----------|:-------------|--------|:----------------|-------|
| JSApi* subject constants (50+) | golang/nats-server/server/jetstream_api.go:36-312 | PORTED | src/NATS.Server/JetStream/Api/JetStreamApiSubjects.cs:1 | All major API subjects defined. Minor: some template variants (T-suffixed) not needed in .NET |
| JSAdvisory* prefix constants (25+) | golang/nats-server/server/jetstream_api.go:229-311 | PARTIAL | src/NATS.Server/JetStream/Api/AdvisoryPublisher.cs:1 | Stream create/delete/update, consumer create/delete covered. Missing: snapshot, restore, leader elected, quorum lost, batch abandoned, out-of-storage, server removed, API limit, pause, pinned, unpinned advisory prefixes |
| JSMaxDescriptionLen (const) | golang/nats-server/server/jetstream_api.go:352 | PORTED | src/NATS.Server/JetStream/Api/JetStreamApiLimits.cs:10 | Added Go-parity constant and enforced in stream create/update validation (`StreamManager`) |
| JSMaxMetadataLen (const) | golang/nats-server/server/jetstream_api.go:356 | PORTED | src/NATS.Server/JetStream/Api/JetStreamApiLimits.cs:11 | Added Go-parity constant; metadata byte-size helper validates stream/consumer metadata against this limit |
| JSMaxNameLen (const) | golang/nats-server/server/jetstream_api.go:360 | PORTED | src/NATS.Server/JetStream/Api/JetStreamApiLimits.cs:12 | Added Go-parity constant; name validation uses UTF-8 byte length limit through `JetStreamConfigValidator.IsValidName` |
| JSDefaultRequestQueueLimit (const) | golang/nats-server/server/jetstream_api.go:364 | PORTED | src/NATS.Server/JetStream/Api/JetStreamApiLimits.cs:13 | Added Go-parity default request queue limit constant for JetStream API request orchestration |
| ApiResponse (struct) | golang/nats-server/server/jetstream_api.go:369 | PARTIAL | src/NATS.Server/JetStream/Api/JetStreamApiResponse.cs:5 | Type field missing; error structure simplified |
| ApiPaged / ApiPagedRequest (structs) | golang/nats-server/server/jetstream_api.go:395-404 | MISSING | — | Paged API request/response not implemented |
| JSApiAccountInfoResponse | golang/nats-server/server/jetstream_api.go:407 | PARTIAL | src/NATS.Server/JetStream/Api/JetStreamApiResponse.cs:105 | Basic streams/consumers count. Missing: full JetStreamAccountStats embedding |
| JSApiStreamCreateResponse | golang/nats-server/server/jetstream_api.go:415 | PARTIAL | src/NATS.Server/JetStream/Api/JetStreamApiResponse.cs:94 | StreamInfo returned. Missing: DidCreate field |
| JSApiStreamDeleteResponse | golang/nats-server/server/jetstream_api.go:423 | PORTED | src/NATS.Server/JetStream/Api/JetStreamApiResponse.cs:17 | Success field present |
| JSApiStreamInfoRequest / Response | golang/nats-server/server/jetstream_api.go:434-446 | PARTIAL | src/NATS.Server/JetStream/Api/JetStreamApiResponse.cs:94 | Basic info works. Missing: DeletedDetails, SubjectsFilter, paged response |
| JSApiStreamNamesRequest / Response | golang/nats-server/server/jetstream_api.go:453-467 | PARTIAL | src/NATS.Server/JetStream/Api/JetStreamApiResponse.cs:11 | StreamNames returned. Missing: paging, subject filter |
| JSApiStreamListRequest / Response | golang/nats-server/server/jetstream_api.go:469-485 | MISSING | — | Detailed stream list with paging, missing/offline |
| JSApiStreamPurgeRequest / Response | golang/nats-server/server/jetstream_api.go:492-507 | PARTIAL | src/NATS.Server/JetStream/Api/JetStreamApiResponse.cs:76 | Purge response with count. Missing: filter/sequence/keep options in request |
| JSApiStreamUpdateResponse | golang/nats-server/server/jetstream_api.go:519 | PARTIAL | src/NATS.Server/JetStream/Api/JetStreamApiResponse.cs:94 | Returns StreamInfo. Response type constant missing |
| JSApiMsgDeleteRequest / Response | golang/nats-server/server/jetstream_api.go:528-538 | PORTED | src/NATS.Server/JetStream/Api/Handlers/StreamApiHandlers.cs | HandleMessageDelete implemented |
| JSApiStreamSnapshotRequest / Response | golang/nats-server/server/jetstream_api.go:540-569 | PARTIAL | src/NATS.Server/JetStream/Api/JetStreamApiResponse.cs:125 | Basic snapshot modeled. Missing: DeliverSubject, NoConsumers, ChunkSize, WindowSize, CheckMsgs options |
| JSApiStreamRestoreRequest / Response | golang/nats-server/server/jetstream_api.go:572-586 | PARTIAL | src/NATS.Server/JetStream/Api/Handlers/StreamApiHandlers.cs | HandleRestore exists. Missing: chunked delivery protocol |
| JSApiStreamRemovePeerRequest / Response | golang/nats-server/server/jetstream_api.go:589-600 | PARTIAL | src/NATS.Server/JetStream/Api/Handlers/ClusterControlApiHandlers.cs | HandleStreamPeerRemove exists as stub |
| JSApiStreamLeaderStepDownResponse | golang/nats-server/server/jetstream_api.go:603-608 | PARTIAL | src/NATS.Server/JetStream/Api/Handlers/ClusterControlApiHandlers.cs | HandleStreamLeaderStepdown exists |
| JSApiConsumerLeaderStepDownResponse | golang/nats-server/server/jetstream_api.go:611-616 | PARTIAL | src/NATS.Server/JetStream/Api/Handlers/ClusterControlApiHandlers.cs | HandleConsumerLeaderStepdown exists |
| JSApiLeaderStepdownRequest / Response | golang/nats-server/server/jetstream_api.go:619-629 | PARTIAL | src/NATS.Server/JetStream/Api/Handlers/ClusterControlApiHandlers.cs | HandleMetaLeaderStepdown exists |
| JSApiMetaServerRemoveRequest / Response | golang/nats-server/server/jetstream_api.go:632-646 | PARTIAL | src/NATS.Server/JetStream/Api/Handlers/AccountControlApiHandlers.cs | HandleServerRemove exists as stub |
| JSApiMetaServerStreamMoveRequest | golang/nats-server/server/jetstream_api.go:650-659 | PARTIAL | src/NATS.Server/JetStream/Api/Handlers/AccountControlApiHandlers.cs | HandleAccountStreamMove exists as stub |
| JSApiAccountPurgeResponse | golang/nats-server/server/jetstream_api.go:663-667 | PARTIAL | src/NATS.Server/JetStream/Api/Handlers/AccountControlApiHandlers.cs | HandleAccountPurge exists as stub |
| JSApiMsgGetRequest / Response | golang/nats-server/server/jetstream_api.go:670-699 | PARTIAL | src/NATS.Server/JetStream/Api/Handlers/StreamApiHandlers.cs | HandleMessageGet exists. Missing: Batch, MaxBytes, StartTime, MultiLastFor, UpToSeq/Time, NoHeaders |
| JSApiConsumerCreateResponse | golang/nats-server/server/jetstream_api.go:704-709 | PARTIAL | src/NATS.Server/JetStream/Api/JetStreamApiResponse.cs:100 | ConsumerInfo returned. Response type constant missing |
| JSApiConsumerDeleteResponse | golang/nats-server/server/jetstream_api.go:711-716 | PORTED | src/NATS.Server/JetStream/Api/Handlers/ConsumerApiHandlers.cs | HandleDelete implemented |
| JSApiConsumerPauseRequest / Response | golang/nats-server/server/jetstream_api.go:718-729 | PORTED | src/NATS.Server/JetStream/Api/JetStreamApiResponse.cs:86 | PauseResponse with Paused/PauseUntil |
| JSApiConsumerInfoResponse | golang/nats-server/server/jetstream_api.go:731-736 | PARTIAL | src/NATS.Server/JetStream/Api/JetStreamApiResponse.cs:100 | ConsumerInfo returned. Missing: type field |
| JSApiConsumerNamesResponse | golang/nats-server/server/jetstream_api.go:742-748 | PARTIAL | src/NATS.Server/JetStream/Api/JetStreamApiResponse.cs:12 | ConsumerNames returned. Missing: paging |
| JSApiConsumerListResponse | golang/nats-server/server/jetstream_api.go:750-758 | MISSING | — | Detailed consumer list with paging, missing/offline |
| JSApiConsumerGetNextRequest | golang/nats-server/server/jetstream_api.go:761-768 | PARTIAL | src/NATS.Server/JetStream/Api/Handlers/ConsumerApiHandlers.cs | HandleNext exists. Missing: Expires, MaxBytes, Heartbeat, PriorityGroup |
| JSApiConsumerResetRequest / Response | golang/nats-server/server/jetstream_api.go:771-781 | PARTIAL | src/NATS.Server/JetStream/Api/Handlers/ConsumerApiHandlers.cs | HandleReset exists as stub |
| JSApiConsumerUnpinRequest / Response | golang/nats-server/server/jetstream_api.go:509-517 | PARTIAL | src/NATS.Server/JetStream/Api/Handlers/ConsumerApiHandlers.cs | HandleUnpin exists as stub |
| generateJSMappingTable | golang/nats-server/server/jetstream_api.go:323-349 | MISSING | — | Domain prefix mapping table generation |
| apiDispatch (jetStream method) | golang/nats-server/server/jetstream_api.go:795 | PARTIAL | src/NATS.Server/JetStream/Api/JetStreamApiRouter.cs:222 | Router.Route matches subjects to handlers. Missing: ClientInfoHdr check, inflight tracking, queue-based dispatch pool |
| processJSAPIRoutedRequests | golang/nats-server/server/jetstream_api.go:882 | MISSING | — | Worker pool goroutine for processing routed API requests |
| setJetStreamExportSubs | golang/nats-server/server/jetstream_api.go:913 | PARTIAL | src/NATS.Server/JetStream/JetStreamService.cs:16 | AllApiSubjects list covers subject registration. Missing: worker pool, Sublist-based routing |
| sendAPIResponse / sendAPIErrResponse | golang/nats-server/server/jetstream_api.go:986-999 | MISSING | — | API response sending with audit advisory |
| sendJetStreamAPIAuditAdvisory | golang/nats-server/server/jetstream_api.go:999+ | MISSING | — | JS API audit advisory publication |
| jsAccountInfoRequest | golang/nats-server/server/jetstream_api.go:1238 | PARTIAL | src/NATS.Server/JetStream/Api/Handlers/AccountApiHandlers.cs | HandleInfo returns basic counts. Missing: full JetStreamAccountStats |
| jsStreamCreateRequest | golang/nats-server/server/jetstream_api.go:1335 | PARTIAL | src/NATS.Server/JetStream/Api/Handlers/StreamApiHandlers.cs | HandleCreate parses config and creates. Missing: clustered forwarding, permission checks, JSRequiredApiLevel header check |
| jsStreamUpdateRequest | golang/nats-server/server/jetstream_api.go:1452 | PARTIAL | src/NATS.Server/JetStream/Api/Handlers/StreamApiHandlers.cs | HandleUpdate exists. Missing: config diff validation, clustered proposal |
| jsStreamNamesRequest | golang/nats-server/server/jetstream_api.go:1557 | PARTIAL | src/NATS.Server/JetStream/Api/Handlers/StreamApiHandlers.cs | HandleNames returns list. Missing: paging, subject filter |
| jsStreamListRequest | golang/nats-server/server/jetstream_api.go:1690 | MISSING | — | Detailed stream list with full StreamInfo per entry |
| jsStreamInfoRequest | golang/nats-server/server/jetstream_api.go:1809 | PARTIAL | src/NATS.Server/JetStream/Api/Handlers/StreamApiHandlers.cs | HandleInfo returns config+state. Missing: subject detail paging, deleted details |
| jsStreamDeleteRequest | golang/nats-server/server/jetstream_api.go:3073 | PARTIAL | src/NATS.Server/JetStream/Api/Handlers/StreamApiHandlers.cs | HandleDelete works. Missing: clustered forwarding |
| jsStreamPurgeRequest | golang/nats-server/server/jetstream_api.go:3572 | PARTIAL | src/NATS.Server/JetStream/Api/Handlers/StreamApiHandlers.cs | HandlePurge works. Missing: filter/sequence/keep options |
| jsMsgDeleteRequest | golang/nats-server/server/jetstream_api.go:3147 | PARTIAL | src/NATS.Server/JetStream/Api/Handlers/StreamApiHandlers.cs | HandleMessageDelete works. Missing: NoErase option, clustered forwarding |
| jsMsgGetRequest | golang/nats-server/server/jetstream_api.go:3272 | PARTIAL | src/NATS.Server/JetStream/Api/Handlers/StreamApiHandlers.cs | HandleMessageGet works for basic seq/subject. Missing: batch, multi-last, time-based queries |
| jsStreamSnapshotRequest | golang/nats-server/server/jetstream_api.go:4007 | PARTIAL | src/NATS.Server/JetStream/Api/Handlers/StreamApiHandlers.cs | HandleSnapshot exists. Missing: chunk delivery, ack flow, window sizing |
| jsStreamRestoreRequest | golang/nats-server/server/jetstream_api.go:3722 | PARTIAL | src/NATS.Server/JetStream/Api/Handlers/StreamApiHandlers.cs | HandleRestore exists. Missing: chunk receive protocol |
| jsStreamLeaderStepDownRequest | golang/nats-server/server/jetstream_api.go:2034 | PARTIAL | src/NATS.Server/JetStream/Api/Handlers/ClusterControlApiHandlers.cs | Stub exists |
| jsStreamRemovePeerRequest | golang/nats-server/server/jetstream_api.go:2275 | PARTIAL | src/NATS.Server/JetStream/Api/Handlers/ClusterControlApiHandlers.cs | Stub exists |
| jsConsumerCreateRequest | golang/nats-server/server/jetstream_api.go:4244 | PARTIAL | src/NATS.Server/JetStream/Api/Handlers/ConsumerApiHandlers.cs | HandleCreate works for basic cases. Missing: JSApiConsumerCreateEx subject parsing, action (Create/Update), config validation depth |
| jsConsumerNamesRequest | golang/nats-server/server/jetstream_api.go:4470 | PARTIAL | src/NATS.Server/JetStream/Api/Handlers/ConsumerApiHandlers.cs | HandleNames works. Missing: paging |
| jsConsumerListRequest | golang/nats-server/server/jetstream_api.go:4597 | MISSING | — | Detailed consumer list with paging |
| jsConsumerInfoRequest | golang/nats-server/server/jetstream_api.go:4707 | PARTIAL | src/NATS.Server/JetStream/Api/Handlers/ConsumerApiHandlers.cs | HandleInfo returns config. Missing: full ConsumerInfo (delivered, ack_floor, num_ack_pending, etc.) |
| jsConsumerDeleteRequest | golang/nats-server/server/jetstream_api.go:4913 | PARTIAL | src/NATS.Server/JetStream/Api/Handlers/ConsumerApiHandlers.cs | HandleDelete works |
| jsConsumerPauseRequest | golang/nats-server/server/jetstream_api.go:4991 | PARTIAL | src/NATS.Server/JetStream/Api/Handlers/ConsumerApiHandlers.cs | HandlePause works. Missing: PauseRemaining duration in response |
| jsConsumerUnpinRequest | golang/nats-server/server/jetstream_api.go:3429 | PARTIAL | src/NATS.Server/JetStream/Api/Handlers/ConsumerApiHandlers.cs | HandleUnpin exists as stub |
| jsLeaderServerRemoveRequest | golang/nats-server/server/jetstream_api.go:2383 | PARTIAL | src/NATS.Server/JetStream/Api/Handlers/AccountControlApiHandlers.cs | Stub exists |
| jsLeaderServerStreamMoveRequest | golang/nats-server/server/jetstream_api.go:2514 | PARTIAL | src/NATS.Server/JetStream/Api/Handlers/AccountControlApiHandlers.cs | Stub exists |
| jsLeaderServerStreamCancelMoveRequest | golang/nats-server/server/jetstream_api.go:2678 | PARTIAL | src/NATS.Server/JetStream/Api/Handlers/AccountControlApiHandlers.cs | Stub exists |
| jsLeaderAccountPurgeRequest | golang/nats-server/server/jetstream_api.go:2793 | PARTIAL | src/NATS.Server/JetStream/Api/Handlers/AccountControlApiHandlers.cs | Stub exists |
| jsLeaderStepDownRequest | golang/nats-server/server/jetstream_api.go:2893 | PARTIAL | src/NATS.Server/JetStream/Api/Handlers/ClusterControlApiHandlers.cs | Stub exists |
| delayedAPIResponder | golang/nats-server/server/jetstream_api.go:1047 | MISSING | — | Delayed API response delivery with ordered linked list |
| JSDirectMsgGet handler | golang/nats-server/server/jetstream_api.go:105-111 | PARTIAL | src/NATS.Server/JetStream/Api/Handlers/DirectApiHandlers.cs | HandleGet exists. Missing: batch support, multi-last, header stripping |
| ILeaderForwarder interface | — | PORTED | src/NATS.Server/JetStream/Api/JetStreamApiRouter.cs:10 | .NET-native abstraction for leader forwarding |
| JetStreamApiRouter | — | PORTED | src/NATS.Server/JetStream/Api/JetStreamApiRouter.cs:68 | Subject-to-handler routing with leader check |
| ApiRateLimiter | — | PORTED | src/NATS.Server/JetStream/Api/ApiRateLimiter.cs:11 | Concurrency limiter + dedup cache |
| ClusteredRequestProcessor | — | PORTED | src/NATS.Server/JetStream/Api/ClusteredRequestProcessor.cs:13 | Pending request correlation with TCS pattern |

### jetstream_events.go — Advisory Event Types (~366 lines)

| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|-----------|:-------------|--------|:----------------|-------|
| publishAdvisory (Server method) | golang/nats-server/server/jetstream_events.go:23 | PARTIAL | src/NATS.Server/JetStream/Api/AdvisoryPublisher.cs:9 | AdvisoryPublisher publishes via delegate. Missing: interest check (SubList.HasInterest), gateway interest check, system account fallback |
| JSAPIAudit (struct) | golang/nats-server/server/jetstream_events.go:50 | MISSING | — | API audit advisory type with Server, Client, Subject, Request, Response, Domain |
| JSAPIAuditType (const) | golang/nats-server/server/jetstream_events.go:60 | MISSING | — | "io.nats.jetstream.advisory.v1.api_audit" |
| ActionAdvisoryType (type + consts) | golang/nats-server/server/jetstream_events.go:63-69 | MISSING | — | CreateEvent/DeleteEvent/ModifyEvent enum |
| JSStreamActionAdvisory (struct) | golang/nats-server/server/jetstream_events.go:72 | PARTIAL | src/NATS.Server/JetStream/Api/AdvisoryPublisher.cs:28 | StreamCreated/Deleted/Updated covered. Missing: Action field, Domain field, TypedEvent base |
| JSConsumerActionAdvisory (struct) | golang/nats-server/server/jetstream_events.go:82 | PARTIAL | src/NATS.Server/JetStream/Api/AdvisoryPublisher.cs:75 | ConsumerCreated/Deleted covered. Missing: Action field, Domain field |
| JSConsumerPauseAdvisory (struct) | golang/nats-server/server/jetstream_events.go:93 | MISSING | — | Consumer pause/unpause advisory |
| JSConsumerAckMetric (struct) | golang/nats-server/server/jetstream_events.go:106 | MISSING | — | Ack latency metric |
| JSConsumerDeliveryExceededAdvisory (struct) | golang/nats-server/server/jetstream_events.go:122 | MISSING | — | Max delivery exceeded advisory |
| JSConsumerDeliveryNakAdvisory (struct) | golang/nats-server/server/jetstream_events.go:135 | MISSING | — | NAK advisory |
| JSConsumerDeliveryTerminatedAdvisory (struct) | golang/nats-server/server/jetstream_events.go:149 | MISSING | — | Terminated message advisory |
| JSSnapshotCreateAdvisory (struct) | golang/nats-server/server/jetstream_events.go:166 | MISSING | — | Snapshot start advisory |
| JSSnapshotCompleteAdvisory (struct) | golang/nats-server/server/jetstream_events.go:177 | MISSING | — | Snapshot complete advisory |
| JSRestoreCreateAdvisory (struct) | golang/nats-server/server/jetstream_events.go:191 | MISSING | — | Restore start advisory |
| JSRestoreCompleteAdvisory (struct) | golang/nats-server/server/jetstream_events.go:201 | MISSING | — | Restore complete advisory |
| JSDomainLeaderElectedAdvisory (struct) | golang/nats-server/server/jetstream_events.go:221 | MISSING | — | Domain leader election advisory |
| JSStreamLeaderElectedAdvisory (struct) | golang/nats-server/server/jetstream_events.go:233 | MISSING | — | Stream leader election advisory |
| JSStreamQuorumLostAdvisory (struct) | golang/nats-server/server/jetstream_events.go:247 | MISSING | — | Stream quorum lost advisory |
| JSStreamBatchAbandonedAdvisory (struct) | golang/nats-server/server/jetstream_events.go:259 | MISSING | — | Batch abandoned advisory |
| BatchAbandonReason (type + consts) | golang/nats-server/server/jetstream_events.go:268-274 | MISSING | — | timeout/large/incomplete reasons |
| JSConsumerLeaderElectedAdvisory (struct) | golang/nats-server/server/jetstream_events.go:280 | MISSING | — | Consumer leader election advisory |
| JSConsumerQuorumLostAdvisory (struct) | golang/nats-server/server/jetstream_events.go:295 | MISSING | — | Consumer quorum lost advisory |
| JSConsumerGroupPinnedAdvisory (struct) | golang/nats-server/server/jetstream_events.go:307 | MISSING | — | Priority group pin advisory |
| JSConsumerGroupUnpinnedAdvisory (struct) | golang/nats-server/server/jetstream_events.go:320 | MISSING | — | Priority group unpin advisory |
| JSServerOutOfSpaceAdvisory (struct) | golang/nats-server/server/jetstream_events.go:335 | MISSING | — | Server out of space advisory |
| JSServerRemovedAdvisory (struct) | golang/nats-server/server/jetstream_events.go:348 | MISSING | — | Server removed advisory |
| JSAPILimitReachedAdvisory (struct) | golang/nats-server/server/jetstream_events.go:360 | MISSING | — | API queue limit reached advisory |

### jetstream_errors.go — Error Framework (~103 lines)

| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|-----------|:-------------|--------|:----------------|-------|
| ErrorIdentifier (type) | golang/nats-server/server/jetstream_errors.go:29 | MISSING | — | uint16 error identifier type |
| ErrorOption / Unless | golang/nats-server/server/jetstream_errors.go:12-19 | MISSING | — | Functional options for error creation |
| IsNatsErr | golang/nats-server/server/jetstream_errors.go:32 | MISSING | — | Error identity checker by ErrorIdentifier |
| ApiError (struct) | golang/nats-server/server/jetstream_errors.go:57 | PARTIAL | src/NATS.Server/JetStream/Api/JetStreamApiError.cs:3 | Code + Description present. Missing: ErrCode (uint16 error code), Error() method |
| ErrorsData (struct) | golang/nats-server/server/jetstream_errors.go:64 | MISSING | — | Source data for generated errors |
| ApiError.toReplacerArgs | golang/nats-server/server/jetstream_errors.go:79 | MISSING | — | Template replacement helper |

### jetstream_errors_generated.go — Error Codes (~3176 lines)

| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|-----------|:-------------|--------|:----------------|-------|
| ErrorIdentifier constants (200+) | golang/nats-server/server/jetstream_errors_generated.go:7-400+ | PARTIAL | src/NATS.Server/JetStream/Publish/AtomicBatchPublishEngine.cs:14 | Only 9 atomic-batch error codes ported. Missing: ~190 other error identifiers (stream, consumer, cluster, etc.) |
| ApiErrors map | golang/nats-server/server/jetstream_errors_generated.go:400+ | MISSING | — | Global map[ErrorIdentifier]*ApiError with HTTP codes and descriptions |
| NewJS*Error factory functions (200+) | golang/nats-server/server/jetstream_errors_generated.go:varies | MISSING | — | Factory functions for each error type with template substitution |

### jetstream_versioning.go — Version Negotiation (~237 lines)

| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|-----------|:-------------|--------|:----------------|-------|
| JSApiLevel (const) | golang/nats-server/server/jetstream_versioning.go:20 | PORTED | src/NATS.Server/JetStream/JsVersioning.cs:19 | Value 3 matches |
| JSRequiredLevelMetadataKey (const) | golang/nats-server/server/jetstream_versioning.go:22 | PORTED | src/NATS.Server/JetStream/JsVersioning.cs:25 | "_nats.req.level" |
| JSServerVersionMetadataKey (const) | golang/nats-server/server/jetstream_versioning.go:23 | PORTED | src/NATS.Server/JetStream/JsVersioning.cs:28 | "_nats.ver" |
| JSServerLevelMetadataKey (const) | golang/nats-server/server/jetstream_versioning.go:24 | PORTED | src/NATS.Server/JetStream/JsVersioning.cs:31 | "_nats.level" |
| getRequiredApiLevel | golang/nats-server/server/jetstream_versioning.go:28 | PORTED | src/NATS.Server/JetStream/JsVersioning.cs:37 | Exact behavior match |
| supportsRequiredApiLevel | golang/nats-server/server/jetstream_versioning.go:36 | PORTED | src/NATS.Server/JetStream/JsVersioning.cs:48 | Exact behavior match |
| setStaticStreamMetadata | golang/nats-server/server/jetstream_versioning.go:44 | PORTED | src/NATS.Server/JetStream/JsVersioning.cs:63 | All feature level checks match (TTL, counter, batch, schedule, persist) |
| setDynamicStreamMetadata | golang/nats-server/server/jetstream_versioning.go:88 | PORTED | src/NATS.Server/JetStream/JsVersioning.cs:101 | Copy + add version/level |
| copyStreamMetadata | golang/nats-server/server/jetstream_versioning.go:110 | PORTED | src/NATS.Server/JetStream/JsVersioning.cs:121 | Copy versioning fields |
| setOrDeleteInStreamMetadata | golang/nats-server/server/jetstream_versioning.go:118 | PORTED | src/NATS.Server/JetStream/JsVersioning.cs:197 | Set or delete helper |
| setStaticConsumerMetadata | golang/nats-server/server/jetstream_versioning.go:136 | PORTED | src/NATS.Server/JetStream/JsVersioning.cs:132 | PauseUntil + priority checks match |
| setDynamicConsumerMetadata | golang/nats-server/server/jetstream_versioning.go:164 | PORTED | src/NATS.Server/JetStream/JsVersioning.cs:157 | Copy + add version/level |
| setDynamicConsumerInfoMetadata | golang/nats-server/server/jetstream_versioning.go:181 | MISSING | — | Wraps setDynamicConsumerMetadata for ConsumerInfo |
| copyConsumerMetadata | golang/nats-server/server/jetstream_versioning.go:198 | PORTED | src/NATS.Server/JetStream/JsVersioning.cs:176 | Copy versioning fields |
| setOrDeleteInConsumerMetadata | golang/nats-server/server/jetstream_versioning.go:206 | PORTED | src/NATS.Server/JetStream/JsVersioning.cs:213 | Set or delete helper |
| deleteDynamicMetadata | golang/nats-server/server/jetstream_versioning.go:222 | PORTED | src/NATS.Server/JetStream/JsVersioning.cs:187 | Remove version/level keys |
| errorOnRequiredApiLevel | golang/nats-server/server/jetstream_versioning.go:229 | MISSING | — | Header-based API level rejection check |

### jetstream_batching.go — Batch Operations (~663 lines)

| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|-----------|:-------------|--------|:----------------|-------|
| globalInflightBatches (var) | golang/nats-server/server/jetstream_batching.go:31 | MISSING | — | Global atomic counter across all streams |
| batching (struct) | golang/nats-server/server/jetstream_batching.go:35 | PARTIAL | src/NATS.Server/JetStream/Publish/AtomicBatchPublishEngine.cs:91 | AtomicBatchPublishEngine uses ConcurrentDictionary. Missing: per-group store, timer-based cleanup |
| batchGroup (struct) | golang/nats-server/server/jetstream_batching.go:40 | PARTIAL | src/NATS.Server/JetStream/Publish/AtomicBatchPublishEngine.cs:61 | InFlightBatch tracks messages. Missing: lseq, store (StreamStore), timer |
| batchGroup.newBatchGroup | golang/nats-server/server/jetstream_batching.go:47 | PARTIAL | src/NATS.Server/JetStream/Publish/AtomicBatchPublishEngine.cs:150 | Batch creation in Process(). Missing: per-batch file store, server-configured timeout |
| getBatchStoreDir | golang/nats-server/server/jetstream_batching.go:66 | MISSING | — | Compute batch store directory path |
| newBatchStore | golang/nats-server/server/jetstream_batching.go:79 | MISSING | — | Create file or memory store for batch staging |
| batchGroup.readyForCommit | golang/nats-server/server/jetstream_batching.go:104 | PARTIAL | src/NATS.Server/JetStream/Publish/AtomicBatchPublishEngine.cs:258 | Commit path exists. Missing: timer.Stop() check, FlushAllPending |
| batchGroup.cleanup / cleanupLocked | golang/nats-server/server/jetstream_batching.go:113-125 | PARTIAL | src/NATS.Server/JetStream/Publish/AtomicBatchPublishEngine.cs:317 | EvictExpiredBatches. Missing: store.Delete, globalInflightBatches decrement |
| batchGroup.stopLocked | golang/nats-server/server/jetstream_batching.go:128 | MISSING | — | Stop batch without cleanup |
| batchStagedDiff (struct) | golang/nats-server/server/jetstream_batching.go:135 | MISSING | — | Staged diff for consistency checks: msgIds, counter, inflight, expectedPerSubject |
| batchStagedDiff.commit | golang/nats-server/server/jetstream_batching.go:147 | MISSING | — | Commit staged diff to stream state |
| batchApply (struct) | golang/nats-server/server/jetstream_batching.go:198 | MISSING | — | Cluster-level batch apply state |
| batchApply.clearBatchStateLocked | golang/nats-server/server/jetstream_batching.go:209 | MISSING | — | Clear in-memory batch state |
| batchApply.rejectBatchStateLocked | golang/nats-server/server/jetstream_batching.go:220 | MISSING | — | Reject batch and adjust CLFS |
| checkMsgHeadersPreClusteredProposal | golang/nats-server/server/jetstream_batching.go:240 | MISSING | — | Pre-proposal header validation (~420 lines): expected-seq, per-subject-seq, msg ID dedup, counter CRDT, TTL, scheduling, rollup, discard-new checks |
| AtomicBatchPublishEngine (.NET-only) | — | PORTED | src/NATS.Server/JetStream/Publish/AtomicBatchPublishEngine.cs:91 | .NET-native batch engine with stage/commit/error flow |
| AtomicBatchPublishErrorCodes (.NET-only) | — | PORTED | src/NATS.Server/JetStream/Publish/AtomicBatchPublishEngine.cs:14 | 9 error codes matching Go generated codes |
| BatchPublishRequest (.NET-only) | — | PORTED | src/NATS.Server/JetStream/Publish/AtomicBatchPublishEngine.cs:331 | Request model for batch messages |
| AtomicBatchResult (.NET-only) | — | PORTED | src/NATS.Server/JetStream/Publish/AtomicBatchPublishEngine.cs:358 | Staged/Committed/Error result type |

---

## 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/JetStream/ -name '*.cs' -type f -exec cat {} + | wc -l
   # Re-count .NET test LOC for this module
   find tests/NATS.Server.Tests/JetStream/ -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')"
   ```


<!-- === JS-2: Stream (store.go + stream.go) === -->
| `StorageType` (enum: FileStorage, MemoryStorage) | store.go:30-38 | PORTED | `Models/StreamConfig.cs` `StorageType` enum + `Models/JetStreamPolicies.cs` | Values mapped as `Memory`/`File` |
| `ErrStoreClosed` | store.go:42 | MISSING | — | No sentinel error constants defined in .NET storage layer |
| `ErrStoreMsgNotFound` | store.go:44 | MISSING | — | Same |
| `ErrStoreEOF` | store.go:46 | MISSING | — | Same |
| `ErrMaxMsgs` | store.go:48 | MISSING | — | Same |
| `ErrMaxBytes` | store.go:50 | MISSING | — | Same |
| `ErrMaxMsgsPerSubject` | store.go:52 | MISSING | — | Same |
| `ErrStoreSnapshotInProgress` | store.go:55 | MISSING | — | Same |
| `ErrMsgTooLarge` | store.go:57 | MISSING | — | Same |
| `ErrStoreWrongType` | store.go:59 | MISSING | — | Same |
| `ErrNoAckPolicy` | store.go:61 | MISSING | — | Same |
| `ErrSequenceMismatch` | store.go:63 | MISSING | — | Same |
| `ErrCorruptStreamState` | store.go:65 | MISSING | — | Same |
| `ErrTooManyResults` | store.go:67 | MISSING | — | Same |
| `StoreMsg` (struct) | store.go:71-78 | PORTED | `Storage/StoreMsg.cs` | Class with Subject, Header, Data, Sequence, Timestamp; Clear() matches Go's clear() |
| `StoreMsg.copy()` | store.go:740-748 | MISSING | — | No buffer-reuse copy semantics; .NET class doesn't pool backing buffer |
| `StoreMsg.clear()` | store.go:751-759 | PORTED | `StoreMsg.Clear()` | Simplified — no backing buffer pool |
| `StorageUpdateHandler` (callback) | store.go:82-83 | MISSING | — | No callback registration model in .NET IStreamStore |
| `StorageRemoveMsgHandler` (callback) | store.go:85 | MISSING | — | Same |
| `ProcessJetStreamMsgHandler` (callback) | store.go:89 | MISSING | — | Same |
| `StreamStore` (interface) | store.go:91-135 | PORTED | `Storage/IStreamStore.cs` | All 34 Go methods have signatures; default implementations throw NotSupportedException |
| `StreamStore.StoreMsg` | store.go:92 | PORTED | `IStreamStore.StoreMsg` | Signature matches |
| `StreamStore.StoreRawMsg` | store.go:93 | PORTED | `IStreamStore.StoreRawMsg` | Signature matches |
| `StreamStore.SkipMsg` | store.go:94 | PORTED | `IStreamStore.SkipMsg` | Returns ulong only (Go returns (uint64, error)) |
| `StreamStore.SkipMsgs` | store.go:95 | PORTED | `IStreamStore.SkipMsgs` | Signature matches |
| `StreamStore.FlushAllPending` | store.go:96 | PORTED | `IStreamStore.FlushAllPending` | Returns Task (async adaptation) |
| `StreamStore.LoadMsg` | store.go:97 | PORTED | `IStreamStore.LoadMsg` | Signature matches |
| `StreamStore.LoadNextMsg` | store.go:98 | PORTED | `IStreamStore.LoadNextMsg` | Returns tuple `(StoreMsg, ulong Skip)` |
| `StreamStore.LoadNextMsgMulti` | store.go:99 | MISSING | — | gsl.SimpleSublist not ported; no multi-filter load |
| `StreamStore.LoadLastMsg` | store.go:100 | PORTED | `IStreamStore.LoadLastMsg` | Signature matches |
| `StreamStore.LoadPrevMsg` | store.go:101 | PORTED | `IStreamStore.LoadPrevMsg` | Signature matches |
| `StreamStore.LoadPrevMsgMulti` | store.go:102 | MISSING | — | gsl.SimpleSublist not ported |
| `StreamStore.RemoveMsg` | store.go:103 | PORTED | `IStreamStore.RemoveMsg` | Returns bool only (Go returns (bool, error)) |
| `StreamStore.EraseMsg` | store.go:104 | PORTED | `IStreamStore.EraseMsg` | Same note |
| `StreamStore.Purge` | store.go:105 | PORTED | `IStreamStore.Purge` | Returns ulong only |
| `StreamStore.PurgeEx` | store.go:106 | PORTED | `IStreamStore.PurgeEx` | Signature matches |
| `StreamStore.Compact` | store.go:107 | PORTED | `IStreamStore.Compact` | Signature matches |
| `StreamStore.Truncate` | store.go:108 | PORTED | `IStreamStore.Truncate` | Void (Go returns error) |
| `StreamStore.GetSeqFromTime` | store.go:109 | PORTED | `IStreamStore.GetSeqFromTime` | Uses DateTime instead of time.Time |
| `StreamStore.FilteredState` | store.go:110 | PORTED | `IStreamStore.FilteredState` | Signature matches |
| `StreamStore.SubjectsState` | store.go:111 | PORTED | `IStreamStore.SubjectsState` | Returns Dictionary<string, SimpleState> |
| `StreamStore.SubjectsTotals` | store.go:112 | PORTED | `IStreamStore.SubjectsTotals` | Returns Dictionary<string, ulong> |
| `StreamStore.AllLastSeqs` | store.go:113 | PORTED | `IStreamStore.AllLastSeqs` | Returns ulong[] |
| `StreamStore.MultiLastSeqs` | store.go:114 | PORTED | `IStreamStore.MultiLastSeqs` | Signature matches |
| `StreamStore.SubjectForSeq` | store.go:115 | PORTED | `IStreamStore.SubjectForSeq` | Signature matches |
| `StreamStore.NumPending` | store.go:116 | PORTED | `IStreamStore.NumPending` | Returns tuple `(ulong Total, ulong ValidThrough)` |
| `StreamStore.NumPendingMulti` | store.go:117 | MISSING | — | gsl.SimpleSublist not ported |
| `StreamStore.State` | store.go:118 | PORTED | `IStreamStore.State` | Returns `Storage.StreamState` |
| `StreamStore.FastState` | store.go:119 | PORTED | `IStreamStore.FastState(ref)` | Uses `ref` parameter |
| `StreamStore.EncodedStreamState` | store.go:120 | PORTED | `IStreamStore.EncodedStreamState` | Signature matches |
| `StreamStore.SyncDeleted` | store.go:121 | MISSING | — | DeleteBlocks interface not ported |
| `StreamStore.Type` | store.go:122 | PORTED | `IStreamStore.Type` | Signature matches |
| `StreamStore.RegisterStorageUpdates` | store.go:123 | MISSING | — | No callback registration |
| `StreamStore.RegisterStorageRemoveMsg` | store.go:124 | MISSING | — | No callback registration |
| `StreamStore.RegisterProcessJetStreamMsg` | store.go:125 | MISSING | — | No callback registration |
| `StreamStore.UpdateConfig` | store.go:126 | PORTED | `IStreamStore.UpdateConfig` | Signature matches |
| `StreamStore.Delete` | store.go:127 | PORTED | `IStreamStore.Delete` | Signature matches |
| `StreamStore.Stop` | store.go:128 | PORTED | `IStreamStore.Stop` | Signature matches |
| `StreamStore.ConsumerStore` | store.go:129 | PORTED | `IStreamStore.ConsumerStore` | Returns IConsumerStore |
| `StreamStore.AddConsumer` | store.go:130 | MISSING | — | Not in IStreamStore |
| `StreamStore.RemoveConsumer` | store.go:131 | MISSING | — | Not in IStreamStore |
| `StreamStore.Snapshot` | store.go:132 | PARTIAL | `Snapshots/StreamSnapshotService.cs` | TAR+S2 snapshot exists but not on IStreamStore interface; different signature |
| `StreamStore.Utilization` | store.go:133 | MISSING | — | Not in IStreamStore |
| `StreamStore.ResetState` | store.go:134 | PORTED | `IStreamStore.ResetState` | Signature matches |
| `RetentionPolicy` (enum) | store.go:137-148 | PORTED | `Models/JetStreamPolicies.cs` `RetentionPolicy` | Limits, Interest, WorkQueue |
| `RetentionPolicy.String()` | store.go:478-489 | NOT_APPLICABLE | — | .NET enum has ToString() by default |
| `RetentionPolicy.MarshalJSON` | store.go:491-502 | NOT_APPLICABLE | — | System.Text.Json handles enum serialization |
| `RetentionPolicy.UnmarshalJSON` | store.go:504-516 | NOT_APPLICABLE | — | Same |
| `DiscardPolicy` (enum) | store.go:152-159 | PORTED | `Models/JetStreamPolicies.cs` `DiscardPolicy` | Old, New |
| `DiscardPolicy.String/MarshalJSON/UnmarshalJSON` | store.go:518-550 | NOT_APPLICABLE | — | Handled by System.Text.Json |
| `StorageType.String/MarshalJSON/UnmarshalJSON` | store.go:562-594 | NOT_APPLICABLE | — | Handled by System.Text.Json |
| `AckPolicy` (enum: AckNone, AckAll, AckExplicit) | store.go:596-633 | PORTED | `Models/ConsumerConfig.cs` `AckPolicy` | None, Explicit, All |
| `AckPolicy.MarshalJSON/UnmarshalJSON` | store.go:608-633 | NOT_APPLICABLE | — | Handled by System.Text.Json |
| `ReplayPolicy` (enum) | store.go:635-666 | PORTED | `Models/JetStreamPolicies.cs` `ReplayPolicy` | Instant, Original |
| `ReplayPolicy.MarshalJSON/UnmarshalJSON` | store.go:645-666 | NOT_APPLICABLE | — | Same |
| `DeliverPolicy` (enum) | store.go:668-726 | PORTED | `Models/JetStreamPolicies.cs` `DeliverPolicy` | All, Last, New, ByStartSequence, ByStartTime, LastPerSubject |
| `DeliverPolicy.MarshalJSON/UnmarshalJSON` | store.go:688-726 | NOT_APPLICABLE | — | Same |
| `StreamState` (struct) | store.go:162-175 | PORTED | `Storage/StreamState.cs` | record struct with all fields |
| `SimpleState` (struct) | store.go:178-187 | PORTED | `Storage/StreamState.cs` | record struct; internal firstNeedsUpdate/lastNeedsUpdate fields not ported |
| `LostStreamData` (struct) | store.go:190-193 | PORTED | `Storage/StreamState.cs` `LostStreamData` class | Msgs and Bytes fields |
| `SnapshotResult` (struct) | store.go:196-200 | PARTIAL | `Snapshots/StreamSnapshotService.cs` `SnapshotRestoreResult` | Different shape; no Reader/errCh; restore-oriented |
| Stream state encoding constants | store.go:202-211 | PARTIAL | `Storage/ConsumerStateCodec.cs` | Magic/version constants exist for consumer state; stream state encoding constants not in dedicated file |
| `DeleteBlock` (interface) | store.go:218-221 | MISSING | — | Not ported |
| `DeleteBlocks` (slice type) | store.go:223 | MISSING | — | Not ported |
| `StreamReplicatedState` (struct) | store.go:227-234 | MISSING | — | Not ported as standalone type |
| `IsEncodedStreamState()` | store.go:237-239 | MISSING | — | Not ported |
| `DecodeStreamState()` | store.go:243-305 | MISSING | — | Not ported |
| `DeleteRange` (struct + methods) | store.go:308-328 | MISSING | — | Not ported |
| `DeleteSlice` (type + methods) | store.go:331-347 | MISSING | — | Not ported |
| `DeleteBlocks.NumDeleted()` | store.go:349-355 | MISSING | — | Not ported |
| `ConsumerStore` (interface) | store.go:358-374 | PORTED | `Storage/IConsumerStore.cs` | All 13 methods ported |
| `ConsumerStore.SetStarting` | store.go:359 | PORTED | `IConsumerStore.SetStarting` | Void (Go returns error) |
| `ConsumerStore.UpdateStarting` | store.go:360 | PORTED | `IConsumerStore.UpdateStarting` | Matches |
| `ConsumerStore.Reset` | store.go:361 | PORTED | `IConsumerStore.Reset` | Void (Go returns error) |
| `ConsumerStore.HasState` | store.go:362 | PORTED | `IConsumerStore.HasState` | Matches |
| `ConsumerStore.UpdateDelivered` | store.go:363 | PORTED | `IConsumerStore.UpdateDelivered` | Void (Go returns error) |
| `ConsumerStore.UpdateAcks` | store.go:364 | PORTED | `IConsumerStore.UpdateAcks` | Void (Go returns error) |
| `ConsumerStore.UpdateConfig` | store.go:365 | MISSING | — | Not on IConsumerStore |
| `ConsumerStore.Update` | store.go:366 | PORTED | `IConsumerStore.Update` | Matches |
| `ConsumerStore.State` | store.go:367 | PORTED | `IConsumerStore.State` | Returns `ConsumerState` (Go returns `(*ConsumerState, error)`) |
| `ConsumerStore.BorrowState` | store.go:368 | PORTED | `IConsumerStore.BorrowState` | Matches |
| `ConsumerStore.EncodedState` | store.go:369 | PORTED | `IConsumerStore.EncodedState` | Returns byte[] |
| `ConsumerStore.Type` | store.go:370 | PORTED | `IConsumerStore.Type` | Matches |
| `ConsumerStore.Stop` | store.go:371 | PORTED | `IConsumerStore.Stop` | Matches |
| `ConsumerStore.Delete` | store.go:372 | PORTED | `IConsumerStore.Delete` | Matches |
| `ConsumerStore.StreamDelete` | store.go:373 | PORTED | `IConsumerStore.StreamDelete` | Matches |
| `SequencePair` (struct) | store.go:377-380 | PORTED | `Storage/ConsumerState.cs` `SequencePair` | record struct |
| `ConsumerState` (struct) | store.go:383-394 | PORTED | `Storage/ConsumerState.cs` | Class with Delivered, AckFloor, Pending, Redelivered |
| `Pending` (struct) | store.go:461-464 | PORTED | `Storage/ConsumerState.cs` `Pending` | record struct |
| `encodeConsumerState()` | store.go:397-457 | PORTED | `Storage/ConsumerStateCodec.Encode()` | Wire-compatible binary encoding |
| `isOutOfSpaceErr()` | store.go:728-730 | MISSING | — | No dedicated helper |
| `errFirstSequenceMismatch` | store.go:733 | MISSING | — | Not ported |
| `isClusterResetErr()` | store.go:735-737 | MISSING | — | Not ported |
| `bytesToString()` | store.go:763-769 | NOT_APPLICABLE | — | .NET strings are native; no unsafe cast needed |
| `stringToBytes()` | store.go:772-778 | NOT_APPLICABLE | — | Same |
| `copyString()` | store.go:783-787 | NOT_APPLICABLE | — | .NET strings are immutable; always safe |
| `isPermissionError()` | store.go:789-791 | MISSING | — | No dedicated helper |
<!-- ## stream.go (lines 1-4000) — Types, Config, and Stream Lifecycle -->
| `StreamConfigRequest` | stream.go:41-46 | MISSING | — | No `Pedantic` field in .NET; validation is in `JetStreamConfigValidator` |
| `StreamConfig` (struct, ~80 fields) | stream.go:50-125 | PARTIAL | `Models/StreamConfig.cs` | Core fields ported; missing: `NoAck`, `Placement`, `Compression`, `MirrorDirect`, `DiscardNewPer`, `AllowRollup`, `ConsumerLimits`, `Mirror` as `StreamSource` (simplified to string), `Sources` as `[]*StreamSource` (simplified) |
| `StreamConfig.clone()` | stream.go:129-161 | PARTIAL | `StreamManager.NormalizeConfig()` | Deep copy via manual property assignment; similar intent |
| `StreamConsumerLimits` | stream.go:163-166 | MISSING | — | Not ported |
| `SubjectTransformConfig` | stream.go:169-172 | PARTIAL | `StreamConfig.SubjectTransformSource/Dest` | Flattened into StreamConfig instead of nested object |
| `RePublish` | stream.go:175-179 | PARTIAL | `StreamConfig.RePublishSource/Dest/HeadersOnly` | Flattened into StreamConfig instead of nested object |
| `PersistModeType` (enum) | stream.go:182-202 | PORTED | `Models/StreamConfig.cs` `PersistMode` | Sync=0, Async=1 |
| `PersistModeType.MarshalJSON/UnmarshalJSON` | stream.go:215-236 | NOT_APPLICABLE | — | System.Text.Json handles it |
| `JSPubAckResponse` | stream.go:239-242 | PARTIAL | `Publish/PubAck.cs` | Different structure; no wrapping ApiError |
| `PubAck` | stream.go:255-263 | PARTIAL | `Publish/PubAck.cs` | Has Stream/Seq; missing Domain, Duplicate, Value, BatchId, BatchSize |
| `CounterValue` | stream.go:267-269 | MISSING | — | Not ported |
| `CounterSources` | stream.go:273 | MISSING | — | Not ported |
| `StreamInfo` | stream.go:276-287 | PARTIAL | `Api/JetStreamApiResponse.cs` `JetStreamStreamInfo` | Has Config+State; missing Created, Domain, Cluster, Mirror, Sources, Alternates, TimeStamp |
| `ClusterInfo` | stream.go:304-312 | MISSING | — | Not ported as standalone API type |
| `PeerInfo` | stream.go:316-325 | MISSING | — | Not ported as standalone API type |
| `StreamSourceInfo` | stream.go:328-336 | PARTIAL | `MirrorSource/MirrorCoordinator.cs` `MirrorInfoResponse` | Simplified; missing External, SubjectTransforms |
| `StreamSource` (struct) | stream.go:339-349 | PARTIAL | `Models/StreamConfig.cs` `StreamSourceConfig` | Has Name, FilterSubject, SourceAccount; missing OptStartSeq, OptStartTime, SubjectTransforms, External, iname |
| `ExternalStream` | stream.go:352-355 | MISSING | — | Not ported |
| `ExternalStream.Domain()` | stream.go:358-363 | MISSING | — | Not ported |
| stream ingest constants | stream.go:366-384 | MISSING | — | streamDefaultMaxQueueMsgs/Bytes, batch inflight constants not ported |
| `stream` (struct, ~110 fields) | stream.go:388-497 | PARTIAL | `StreamManager` + `StreamHandle` | StreamHandle is simplified record; stream struct's ~110 fields reduced to Config+Store; no mu/client/sysc/msgs/gets/ackq/lseq/ddmap/mirror/sources/node/etc |
| `inflightSubjectRunningTotal` | stream.go:500-503 | MISSING | — | Not ported |
| `msgCounterRunningTotal` | stream.go:508-511 | MISSING | — | Not ported |
| `sourceInfo` (struct) | stream.go:514-533 | PARTIAL | `MirrorSource/SourceCoordinator.cs` | Simplified source tracking |
| JS header constants (JSMsgId, JSExpectedStream, etc.) | stream.go:543-598 | PARTIAL | `Publish/PublishOptions.cs` | Some headers used in publish path; no centralized header constant file |
| KV operation headers | stream.go:571-573 | MISSING | — | Not ported |
| Scheduler headers | stream.go:577-580 | MISSING | — | Not ported |
| Republish response headers | stream.go:584-591 | MISSING | — | Not ported |
| Rollup constants | stream.go:595-597 | MISSING | — | Not ported |
| Delete marker reason constants | stream.go:601-604 | MISSING | — | Not ported |
| `ddentry` (struct) | stream.go:612-616 | PARTIAL | `Publish/PublishPreconditions.cs` `DedupeEntry` | Has Sequence+Timestamp; id handled as dictionary key |
| `StreamMaxReplicas` | stream.go:619 | MISSING | — | Not ported as constant |
| `addStream()` / `addStreamWithAssignment()` | stream.go:621-980 | PARTIAL | `StreamManager.CreateOrUpdate()` | High-level create/update logic present; missing: account limits checking, inflight WaitGroup, file store config auto-tuning, cluster mode handling, internal client setup, pubAck template, dedupe rebuild, advisory sending |
| `StreamSource.composeIName()` | stream.go:986-1022 | MISSING | — | Source indexing not ported |
| `StreamSource.setIndexName()` | stream.go:1026-1027 | MISSING | — | Same |
| `stream.streamAssignment()` | stream.go:1030-1033 | MISSING | — | Not ported (cluster-specific) |
| `stream.setStreamAssignment()` | stream.go:1036-1078 | MISSING | — | Not ported (cluster-specific) |
| `stream.monitorQuitC()` | stream.go:1081-1091 | MISSING | — | Not ported |
| `stream.signalMonitorQuit()` | stream.go:1095-1102 | MISSING | — | Not ported |
| `stream.updateC()` | stream.go:1105-1111 | MISSING | — | Not ported |
| `stream.IsLeader()` / `isLeader()` | stream.go:1115-1126 | PARTIAL | `JetStreamMetaGroup` has leader concept | Not on stream level |
| `stream.setLeader()` | stream.go:1142-1185 | MISSING | — | No stream-level leader management |
| `stream.startClusterSubs()` / `stopClusterSubs()` | stream.go:1189-1200 | MISSING | — | Not ported |
| `stream.account()` | stream.go:1203-1219 | MISSING | — | No per-stream account binding |
| `stream.maxMsgSize()` | stream.go:1223-1253 | PARTIAL | `StreamApiHandlers` has MaxMsgSize validation | Not a stream method |
| `stream.autoTuneFileStorageBlockSize()` | stream.go:1259-1288 | MISSING | — | No block size auto-tuning |
| `stream.rebuildDedupe()` | stream.go:1296-1326 | PARTIAL | `PublishPreconditions` | Basic dedupe exists; no rebuild-from-store on recovery |
| `stream.lastSeqAndCLFS()` | stream.go:1330-1331 | MISSING | — | Cluster failure sequence tracking not ported |
| `stream.getCLFS()` / `setCLFS()` | stream.go:1334-1346 | MISSING | — | Same |
| `stream.lastSeq()` / `setLastSeq()` | stream.go:1349-1358 | MISSING | — | StreamHandle doesn't track lseq |
| `stream.sendCreateAdvisory()` | stream.go:1361-1390 | MISSING | — | Advisory publishing not ported at stream level |
| `stream.sendDeleteAdvisoryLocked()` | stream.go:1393-1413 | MISSING | — | Same |
| `stream.sendUpdateAdvisoryLocked()` | stream.go:1416-1436 | MISSING | — | Same |
| `stream.sendStreamBatchAbandonedAdvisory()` | stream.go:1439-1464 | MISSING | — | Same |
| `stream.createdTime()` / `setCreatedTime()` | stream.go:1468-1479 | MISSING | — | Not tracked on StreamHandle |
| `jsa.subjectsOverlap()` | stream.go:1485-1498 | PORTED | `StreamManager.ValidateConfigUpdate()` | Subject overlap check exists in ValidateConfigUpdate |
| `checkStreamCfg()` | stream.go:1500-2084 | PARTIAL | `JetStreamConfigValidator.Validate()` + `StreamManager.CreateOrUpdate()` | Basic name/subject/retention validation; many checks missing: MaxBytes limits, per-account limits, subject transform validation, republish cycle detection done separately, pedantic mode, placement validation |
| `jsa.configUpdateCheck()` | stream.go:2104-2236 | PARTIAL | `StreamManager.ValidateConfigUpdate()` | Immutable fields checked: storage, retention, mirror, sources, maxConsumers, replicas; missing: sealed/denyDelete/denyPurge unset checks, TTL status, counter setting, schedules, persist mode, limit adjustment, compression |
| `stream.update()` / `updateWithAdvisory()` | stream.go:2240-2554 | PARTIAL | `StreamManager.CreateOrUpdate()` | Config update path exists but much simplified; missing: subscription management, dedupe timer reset, source consumer management, direct/mirror-direct subscription toggling, republish transform update, subject transform update, tier usage tracking, advisory sending |
| `stream.purge()` / `purgeLocked()` | stream.go:2567-2642 | PARTIAL | `StreamManager.Purge()` + `PurgeEx()` | Basic purge and extended purge present; missing: sealed check, consumer purge propagation, preAck clearing |
| `stream.deleteMsg()` / `removeMsg()` | stream.go:2645-2663 | PARTIAL | `StreamManager.DeleteMessage()` | Basic removal present; missing: preAck clearing |
| `stream.eraseMsg()` | stream.go:2666-2678 | PARTIAL | `IStreamStore.EraseMsg` on interface | Interface defined but no erase implementation that overwrites with random data |
| `stream.isMirror()` | stream.go:2681-2685 | PORTED | `StreamManager.GetMirrorInfo()` checks mirror | Inline check |
| `stream.sourcesInfo()` | stream.go:2688-2695 | PARTIAL | `StreamManager.GetSourceInfos()` | Simplified |
| `stream.sourceInfo()` | stream.go:2699-2735 | PARTIAL | `SourceCoordinator.GetSourceInfo()` | Simplified |
| `stream.mirrorInfo()` | stream.go:2739-2743 | PARTIAL | `StreamManager.GetMirrorInfo()` | Simplified |
| `stream.retryDisconnectedSyncConsumers()` | stream.go:2747-2778 | MISSING | — | Not ported |
| source health constants | stream.go:2782-2785 | MISSING | — | Not ported |
| `stream.processMirrorMsgs()` | stream.go:2788-2854 | PARTIAL | `MirrorCoordinator` | Simplified mirror message processing; no health check, stall detection, flow control |
| `sourceInfo.isCurrentSub()` | stream.go:2859-2860 | MISSING | — | Not ported |
| `stream.processInboundMirrorMsg()` | stream.go:2863-3014 | PARTIAL | `MirrorCoordinator.OnOriginAppendAsync()` | Greatly simplified; no sequence tracking, flow control, heartbeat handling, skip logic |
| `stream.setMirrorErr()` | stream.go:3017-3022 | MISSING | — | Not ported |
| `stream.cancelMirrorConsumer()` | stream.go:3027-3032 | MISSING | — | Not ported |
| `stream.retryMirrorConsumer()` | stream.go:3038-3044 | MISSING | — | Not ported |
| `stream.skipMsgs()` | stream.go:3048-3073 | MISSING | — | No skip with NRG proposal |
| `calculateRetryBackoff()` | stream.go:3084-3089 | MISSING | — | Not ported |
| `stream.scheduleSetupMirrorConsumerRetry()` | stream.go:3098-3118 | MISSING | — | Not ported |
| `stream.setupMirrorConsumer()` | stream.go:3125-3416 | MISSING | — | Full mirror consumer setup with API-based consumer creation, subject transforms, delivery tracking not ported |
| `stream.streamSource()` | stream.go:3419-3424 | MISSING | — | Not ported |
| `stream.retrySourceConsumerAtSeq()` | stream.go:3429-3435 | MISSING | — | Not ported |
| `stream.cancelSourceConsumer()` | stream.go:3439-3443 | MISSING | — | Not ported |
| `stream.cancelSourceInfo()` | stream.go:3451-3470 | MISSING | — | Not ported |
| `stream.setupSourceConsumer()` | stream.go:3478-3500+ | MISSING | — | Full source consumer setup not ported (continues past line 4000) |
<!-- ## Summary Counts -->
| Status | Count |
| PORTED | 55 |
| PARTIAL | 30 |
| MISSING | 75 |
| NOT_APPLICABLE | 12 |
| DEFERRED | 0 |
| **Total** | **172** |
<!-- ## Key Gaps Analysis -->

<!-- JS-2b: stream.go lines 4001-end -->
| `(si *sourceInfo) genSourceHeader` | stream.go:4010 | MISSING | — | Generates 2.10-style source header (stream name, seq, filter, transform). No .NET equivalent; SourceCoordinator tracks seq differently. |
| `streamAndSeqFromAckReply` | stream.go:4041 | MISSING | — | Parses stream/seq from `$JS.ACK` reply tokens. No wire-level reply parsing in .NET. |
| `streamAndSeq` | stream.go:4058 | MISSING | — | Extracts stream name, index name, sequence from source header string. No .NET equivalent. |
| `(mset *stream) setStartingSequenceForSources` | stream.go:4079 | PARTIAL | `SourceCoordinator.LastOriginSequence` | Go scans stored messages backwards to find starting seq per source using gsl.SimpleSublist. .NET SourceCoordinator has simpler resume-from-last logic. |
| `(mset *stream) resetSourceInfo` | stream.go:4172 | PARTIAL | `StreamManager.RebuildReplicationCoordinators` | Go resets all sourceInfo structs with transforms. .NET rebuilds coordinators but lacks per-source subject transforms array. |
| `(mset *stream) startingSequenceForSources` | stream.go:4207 | PARTIAL | `SourceCoordinator.LastOriginSequence` | Go does reverse scan with SimpleSublist filtering. .NET relies on simpler incremental tracking. |
<!-- ## Source Consumer Setup & Lifecycle -->
| `(mset *stream) setupSourceConsumers` | stream.go:4318 | PARTIAL | `SourceCoordinator.StartSyncLoop / StartPullSyncLoop` | Go creates internal consumers per source with proper starting sequences. .NET has background sync loops but no internal consumer creation. |
| `(mset *stream) subscribeToStream` | stream.go:4348 | MISSING | — | Creates internal subscriptions for all stream subjects + mirror/source setup. .NET uses StreamManager.Capture instead of subscription-based ingest. |
| `(mset *stream) subscribeToDirect` | stream.go:4406 | PARTIAL | `DirectApiHandlers.HandleGet` | Go creates queue subscriptions for `$JS.API.DIRECT.GET.{stream}`. .NET has API handler but no queue-subscription wiring. |
| `(mset *stream) unsubscribeToDirect` | stream.go:4431 | MISSING | — | Unsubscribes direct get subscriptions. No .NET equivalent. |
| `(mset *stream) subscribeToMirrorDirect` | stream.go:4443 | MISSING | — | Sets up direct-get queue subs for mirror stream name. Not implemented in .NET. |
| `(mset *stream) unsubscribeToMirrorDirect` | stream.go:4474 | MISSING | — | Clears mirror direct-get subscriptions. Not implemented. |
| `(mset *stream) stopSourceConsumers` | stream.go:4487 | PORTED | `SourceCoordinator.StopAsync` | .NET coordinator has async stop with cancellation. |
| `(mset *stream) removeInternalConsumer` | stream.go:4494 | MISSING | — | Clears consumer name on sourceInfo. .NET doesn't track internal consumer names. |
| `(mset *stream) unsubscribeToStream` | stream.go:4503 | MISSING | — | Unsubscribes from all stream subjects, mirrors, sources. .NET uses StreamManager.Delete instead. |
| `(mset *stream) deleteInflightBatches` | stream.go:4535 | PARTIAL | `JetStreamPublisher.ClearBatches` | Go cleans up batch state with lock ordering. .NET has simplified ClearBatches on AtomicBatchPublishEngine. |
| `(mset *stream) deleteBatchApplyState` | stream.go:4552 | MISSING | — | Cleans up raft batch-apply entries with pool return. No .NET equivalent (no RAFT batch apply). |
<!-- ## Internal Subscription Helpers -->
| `(mset *stream) subscribeInternal` | stream.go:4563 | MISSING | — | Creates internal subscription with auto-incremented SID. .NET server doesn't use internal subscriptions for JetStream. |
| `(mset *stream) queueSubscribeInternal` | stream.go:4577 | MISSING | — | Creates internal queue subscription. Not implemented in .NET. |
| `(mset *stream) unsubscribeInternal` | stream.go:4594 | MISSING | — | Removes internal subscription by subject lookup. Not implemented. |
| `(mset *stream) unsubscribe` | stream.go:4616 | MISSING | — | Unsubscribes by subscription pointer. Not implemented. |
<!-- ## Store Setup & Callbacks -->
| `(mset *stream) setupStore` | stream.go:4623 | PARTIAL | `StreamManager.CreateStore` | Go wires up MemStore/FileStore with encryption, callbacks for storage updates, remove msg, and process inbound. .NET creates store but lacks callback wiring. |
| `(mset *stream) storeUpdates` | stream.go:4682 | MISSING | — | Callback for store changes: updates consumer pending counts, JetStream account usage. No .NET equivalent. |
<!-- ## Duplicate Detection -->
| `(mset *stream) numMsgIds` | stream.go:4714 | MISSING | — | Returns count of tracked dedup entries. Not exposed in .NET. |
| `(mset *stream) checkMsgId` | stream.go:4721 | PORTED | `PublishPreconditions.IsDuplicate` | .NET uses ConcurrentDictionary-based dedup check. |
| `(mset *stream) purgeMsgIds` | stream.go:4731 | PORTED | `PublishPreconditions.TrimOlderThan` | Go uses timer-driven purge with GC compaction. .NET does cutoff-based trim. Lacks timer scheduling. |
| `(mset *stream) storeMsgId` | stream.go:4778 | PORTED | `PublishPreconditions.Record` | .NET records msgId with timestamp. |
| `(mset *stream) storeMsgIdLocked` | stream.go:4786 | PORTED | `PublishPreconditions.Record` | Internal locked variant; .NET uses ConcurrentDictionary. |
<!-- ## Header Extraction Helpers -->
| `getMsgId` | stream.go:4803 | MISSING | — | Fast lookup of `Nats-Msg-Id` header from raw bytes. .NET uses PublishOptions.MsgId instead. |
| `getExpectedLastMsgId` | stream.go:4808 | MISSING | — | Fast lookup of `Nats-Expected-Last-Msg-Id`. .NET checks via PublishOptions.ExpectedLastMsgId. |
| `getExpectedStream` | stream.go:4813 | MISSING | — | Fast lookup of `Nats-Expected-Stream`. Not implemented as wire parser. |
| `getExpectedLastSeq` | stream.go:4818 | MISSING | — | Fast lookup of `Nats-Expected-Last-Sequence`. .NET uses PublishOptions.ExpectedLastSeq. |
| `getRollup` | stream.go:4827 | MISSING | — | Fast lookup of `Nats-Rollup` header. No rollup support in .NET. |
| `getExpectedLastSeqPerSubject` | stream.go:4836 | MISSING | — | Fast lookup of `Nats-Expected-Last-Subject-Sequence`. .NET uses PublishOptions but no wire parser. |
| `getExpectedLastSeqPerSubjectForSubject` | stream.go:4845 | MISSING | — | Subject override for per-subject seq check. Not in .NET. |
| `getMessageTTL` | stream.go:4852 | MISSING | — | Parses `Nats-Msg-TTL` header from raw bytes. Not implemented in .NET. |
| `parseMessageTTL` | stream.go:4863 | MISSING | — | Parses TTL string ("never", duration, seconds). Not implemented. |
| `getMessageIncr` | stream.go:4888 | MISSING | — | Parses `Nats-Msg-Incr` header for counter streams. Not implemented. |
| `getMessageSchedule` | stream.go:4898 | MISSING | — | Parses `Nats-Msg-Schedule` header. Not implemented. |
| `nextMessageSchedule` | stream.go:4907 | MISSING | — | Calculates next schedule from header + timestamp. Not implemented. |
| `getMessageScheduleTTL` | stream.go:4917 | MISSING | — | Parses schedule TTL header. Not implemented. |
| `getMessageScheduleTarget` | stream.go:4930 | MISSING | — | Parses schedule target header. Not implemented. |
| `getMessageScheduleSource` | stream.go:4938 | MISSING | — | Parses schedule source header. Not implemented. |
| `getMessageScheduler` | stream.go:4946 | MISSING | — | Parses scheduler header. Not implemented. |
| `getBatchId` | stream.go:4954 | MISSING | — | Parses `Nats-Batch-Id` header from raw bytes. .NET uses PublishOptions.BatchId instead. |
| `getBatchSequence` | stream.go:4962 | MISSING | — | Parses `Nats-Batch-Seq` header. .NET uses PublishOptions.BatchSeq. |
<!-- ## Cluster State Helpers -->
| `(mset *stream) IsClustered` | stream.go:4971 | NOT_APPLICABLE | — | Checks if RAFT node is present. .NET uses JetStreamMetaGroup for cluster simulation but not per-stream RAFT. |
| `(mset *stream) isClustered` | stream.go:4978 | NOT_APPLICABLE | — | Lock-held variant. |
<!-- ## Inbound Message Queue & Pool -->
| `inMsg` struct | stream.go:4983 | MISSING | — | Internal message queue struct (subj, reply, hdr, msg, sourceInfo, msgTrace). .NET doesn't have internal msg queue. |
| `inMsgPool` | stream.go:4994 | MISSING | — | sync.Pool for inMsg objects. Not needed without internal queue. |
| `(im *inMsg) returnToPool` | stream.go:4999 | MISSING | — | Returns inMsg to pool. |
| `(mset *stream) queueInbound` | stream.go:5004 | MISSING | — | Enqueues inbound message with rate limit error on overflow. .NET uses direct method calls instead. |
<!-- ## Direct Get Request Handling -->
| `dgPool` / `directGetReq` | stream.go:5019 | MISSING | — | Pool and struct for deferred direct get requests. |
| `(mset *stream) processDirectGetRequest` | stream.go:5033 | PARTIAL | `DirectApiHandlers.HandleGet` | Go handles full JSApiMsgGetRequest with validation. .NET only supports seq-based lookup. Missing: LastFor, NextFor, MultiLastFor, StartTime, Batch, MaxBytes. |
| `(mset *stream) processDirectGetLastBySubjectRequest` | stream.go:5087 | MISSING | — | Direct get by subject appended to request subject. Not implemented in .NET. |
| `(mset *stream) getDirectMulti` | stream.go:5158 | MISSING | — | Multi-subject direct get with batch response + EOB. Not implemented. |
| `(mset *stream) getDirectRequest` | stream.go:5270 | PARTIAL | `DirectApiHandlers.HandleGet` | Go handles seq, NextFor, LastFor, batch, MaxBytes, NoHeaders. .NET only handles seq lookup. |
| Direct get header constants (`dg`, `dgb`, `eob`, `eobm`) | stream.go:5150 | MISSING | — | Wire format header templates for direct get responses. |
<!-- ## Inbound JetStream Message Processing -->
| `(mset *stream) processInboundJetStreamMsg` | stream.go:5409 | MISSING | — | Entry point: copies msg, adds trace, queues inbound. .NET uses StreamManager.Capture. |
| Error sentinel variables | stream.go:5428 | PARTIAL | — | `errLastSeqMismatch`, `errMsgIdDuplicate`, etc. .NET uses error codes in PubAck instead. |
| `(mset *stream) processJetStreamMsg` | stream.go:5437 | PARTIAL | `JetStreamPublisher.TryCaptureWithOptions + StreamManager.Capture` | Core message processing. Go: full consistency checks, subject transform, sealed/sealed check, header extraction, dedup, TTL, counter increment, schedule validation, rollup, interest retention skip, store. .NET: basic dedup + expected-last-seq + capture. Missing: rollup, TTL, counters, schedules, per-subject seq, interest retention skip, sealed check at msg level, clustered RAFT propose. |
<!-- ## Output Queue & Internal Loop -->
| `jsPubMsg` struct | stream.go:6770 | MISSING | — | Internal pub message with dest subject, reply, store msg, consumer ref. |
| `newJSPubMsg` | stream.go:6786 | MISSING | — | Constructor with pool allocation. |
| `jsPubMsgPool` | stream.go:6784 | MISSING | — | sync.Pool for jsPubMsg. |
| `(pm *jsPubMsg) returnToPool` | stream.go:6815 | MISSING | — | Pool return. |
| `(pm *jsPubMsg) size` | stream.go:6826 | MISSING | — | Size calculation for flow control. |
| `jsOutQ` struct | stream.go:6834 | MISSING | — | Typed output queue wrapper around ipQueue. |
| `(q *jsOutQ) sendMsg` | stream.go:6838 | MISSING | — | Sends message on output queue. |
| `(q *jsOutQ) send` | stream.go:6844 | MISSING | — | Pushes jsPubMsg to queue. |
| `StoredMsg` struct | stream.go:6858 | PORTED | `StoredMessage` | .NET StoredMessage record has Subject, Sequence, Payload, TimestampUtc. |
| `(mset *stream) setupSendCapabilities` | stream.go:6869 | MISSING | — | Creates output queue and launches internalLoop goroutine. |
| `(mset *stream) accName` | stream.go:6881 | NOT_APPLICABLE | — | Returns account name. .NET uses Account directly. |
| `(mset *stream) name / nameLocked` | stream.go:6892 | NOT_APPLICABLE | — | Returns stream name. .NET uses StreamHandle.Config.Name. |
| `(mset *stream) internalLoop` | stream.go:6907 | MISSING | — | Main event loop: outq dispatch, inbound msg processing, direct gets, ack queue for interest retention. Complex select{} loop. Not implemented in .NET. |
<!-- ## Stream Stop / Delete -->
| `(mset *stream) resetAndWaitOnConsumers` | stream.go:7037 | MISSING | — | Steps down and waits for consumer monitor goroutines. |
| `(mset *stream) delete` | stream.go:7059 | PARTIAL | `StreamManager.Delete` | Go calls stop(true, true) with advisory. .NET removes from ConcurrentDictionary. |
| `(mset *stream) stop` | stream.go:7067 | PARTIAL | `StreamManager.Delete` | Go: full shutdown sequence (mark closed, signal monitor, unsubscribe, cleanup consumers, advisories, RAFT node stop/delete, dedup timer cleanup, queue unregister, store delete/stop). .NET: simple dictionary removal. |
<!-- ## Stream State & Accessors -->
| `(mset *stream) getMsg` | stream.go:7249 | PORTED | `StreamManager.GetMessage` | .NET loads from store by sequence. |
| `(mset *stream) getConsumers` | stream.go:7266 | PARTIAL | `ConsumerManager` | Go returns copy of consumer list. .NET has ConsumerManager but not per-stream consumer list. |
| `(mset *stream) numPublicConsumers` | stream.go:7273 | MISSING | — | Count of non-DIRECT consumers. Not in .NET. |
| `(mset *stream) getPublicConsumers` | stream.go:7278 | MISSING | — | Filters out DIRECT consumers. Not in .NET. |
| `(mset *stream) getDirectConsumers` | stream.go:7292 | MISSING | — | Returns only DIRECT consumers. Not in .NET. |
| `(mset *stream) numDirectConsumers` | stream.go:7471 | MISSING | — | Counts DIRECT consumers. Not in .NET. |
| `(mset *stream) state` | stream.go:7487 | PORTED | `StreamManager.GetStateAsync` | .NET delegates to store.GetStateAsync. |
| `(mset *stream) stateWithDetail` | stream.go:7490 | PARTIAL | `StreamManager.GetStateAsync` | Go has fast path (FastState) vs detailed (State). .NET always returns full state. |
| `(mset *stream) Store` | stream.go:7509 | PORTED | `StreamHandle.Store` | Direct property access in .NET record. |
<!-- ## Consumer Management on Stream -->
| `(mset *stream) setConsumer` | stream.go:7367 | MISSING | — | Registers consumer on stream with filter tracking and consumer sublist (gsl). .NET uses separate ConsumerManager. |
| `(mset *stream) removeConsumer` | stream.go:7388 | MISSING | — | Removes consumer from stream's maps and sublists. .NET uses ConsumerManager. |
| `(mset *stream) swapSigSubs` | stream.go:7416 | MISSING | — | Updates signal subscriptions when consumer filter changes. Complex lock ordering. Not in .NET. |
| `(mset *stream) lookupConsumer` | stream.go:7464 | PARTIAL | `ConsumerManager` | Go looks up by name on stream. .NET uses separate manager. |
| `(mset *stream) partitionUnique` | stream.go:7516 | MISSING | — | Checks consumer filter partitions don't overlap. Not implemented. |
| `(mset *stream) potentialFilteredConsumers` | stream.go:7541 | MISSING | — | Checks if filtered consumer matching is needed. Not in .NET. |
<!-- ## Interest Retention & Pre-Ack -->
| `checkInterestStateT/J` constants | stream.go:7307 | MISSING | — | Timer interval/jitter for interest state checks. |
| `(mset *stream) checkInterestState` | stream.go:7320 | PARTIAL | `InterestRetentionPolicy.ShouldRetain` | Go checks ack floors across all consumers and compacts. .NET has simpler per-seq interest check. |
| `(mset *stream) isInterestRetention` | stream.go:7353 | PORTED | Config check | .NET checks `stream.Config.Retention == RetentionPolicy.Interest`. |
| `(mset *stream) numConsumers` | stream.go:7360 | PARTIAL | `ConsumerManager` | Go returns from stream's consumer map. .NET has ConsumerManager. |
| `(mset *stream) noInterest` | stream.go:7556 | PARTIAL | `InterestRetentionPolicy.ShouldRetain` | .NET has simplified interest check without pre-ack support. |
| `(mset *stream) noInterestWithSubject` | stream.go:7562 | PARTIAL | `InterestRetentionPolicy.ShouldRetain` | .NET uses SubjectMatch filtering. |
| `(mset *stream) checkForInterest` | stream.go:7567 | PARTIAL | `InterestRetentionPolicy.ShouldRetain` | Go loads message for subject, checks filtered consumers. .NET simpler. |
| `(mset *stream) checkForInterestWithSubject` | stream.go:7588 | PARTIAL | `InterestRetentionPolicy.ShouldRetain` | Go iterates consumers with needAck check. .NET iterates registered interests. |
| `(mset *stream) hasPreAck` | stream.go:7605 | MISSING | — | Pre-ack tracking for messages acked before stored. Not in .NET. |
| `(mset *stream) hasAllPreAcks` | stream.go:7619 | MISSING | — | Checks if all consumers pre-acked a sequence. Not in .NET. |
| `(mset *stream) clearAllPreAcks` | stream.go:7631 | MISSING | — | Clears pre-ack map for a sequence. Not in .NET. |
| `(mset *stream) clearAllPreAcksBelowFloor` | stream.go:7636 | MISSING | — | Clears pre-acks below a floor sequence. Not in .NET. |
| `(mset *stream) registerPreAckLock` | stream.go:7645 | MISSING | — | Lock-acquiring pre-ack registration. Not in .NET. |
| `(mset *stream) registerPreAck` | stream.go:7654 | MISSING | — | Registers pre-ack for consumer/sequence. Not in .NET. |
| `(mset *stream) clearPreAck` | stream.go:7669 | MISSING | — | Clears single consumer pre-ack. Not in .NET. |
| `(mset *stream) ackMsg` | stream.go:7685 | PARTIAL | `InterestRetentionPolicy + AckProcessor` | Go: full ack processing with pre-ack, clustered proposal, interest-raise-first. .NET has basic interest check + removal. |
<!-- ## Snapshot & Restore -->
| `(mset *stream) snapshot` | stream.go:7764 | PORTED | `StreamSnapshotService.CreateTarSnapshotAsync` | .NET uses TAR+S2 snapshot service. |
| `(a *Account) RestoreStream` | stream.go:7776 | PARTIAL | `StreamSnapshotService.RestoreTarSnapshotAsync` | Go: full restore with config validation, limit checks, directory management, consumer restore. .NET: simplified restore (purge + replay). Missing: config validation, consumer restore, limit checks. |
<!-- ## Orphan Message Check -->
| `(mset *stream) checkForOrphanMsgs` | stream.go:7979 | MISSING | — | Checks for dangling messages on interest retention streams at startup. Not implemented. |
<!-- ## Cluster Monitor Helpers -->
| `(mset *stream) checkConsumerReplication` | stream.go:8007 | MISSING | — | Verifies consumer replication matches stream for interest retention. Not in .NET. |
| `(mset *stream) checkInMonitor` | stream.go:8029 | NOT_APPLICABLE | — | Atomic flag for monitor goroutine. .NET has no stream monitor loop. |
| `(mset *stream) clearMonitorRunning` | stream.go:8041 | NOT_APPLICABLE | — | Clears monitor flag + cleanup. |
| `(mset *stream) isMonitorRunning` | stream.go:8051 | NOT_APPLICABLE | — | Checks monitor flag. |
| `(mset *stream) trackReplicationTraffic` | stream.go:8058 | NOT_APPLICABLE | — | Adjusts account stats for replication. .NET has no account-level replication stats. |
<!-- ## Summary Counts -->
| Status | Count |
| PORTED | 11 |
| PARTIAL | 24 |
| MISSING | 73 |
| NOT_APPLICABLE | 8 |
| DEFERRED | 0 |
| **Total** | **116** |


<!-- === JS-3: Consumer (consumer.go) === -->
<!-- JS-3a: lines 1-3500 -->
| `ConsumerInfo` | consumer.go:55 | PARTIAL | `JetStreamConsumerInfo` (Api/JetStreamApiResponse.cs) | .NET has a simplified version inside API response; missing most fields (Delivered, AckFloor, NumAckPending, NumRedelivered, NumWaiting, NumPending, Cluster, PushBound, Paused, PauseRemaining, TimeStamp, PriorityGroups) |
| `consumerInfoClusterResponse` | consumer.go:77 | MISSING | — | Cluster-internal response type for consumer info propagation |
| `PriorityGroupState` | consumer.go:82 | MISSING | — | Reported in ConsumerInfo; no equivalent struct |
| `ConsumerConfig` | consumer.go:88 | PORTED | `ConsumerConfig` (Models/ConsumerConfig.cs) | All major fields present. Minor: missing `Description`, `SampleFrequency`, `Replicas`, `MemoryStorage`, `Direct`, `InactiveThreshold`, `HeadersOnly`, `Name` (vs Durable), `DeliverGroup` |
| `SequenceInfo` | consumer.go:143 | MISSING | — | Used in ConsumerInfo; no standalone equivalent |
| `CreateConsumerRequest` | consumer.go:149 | PARTIAL | Parsed inline in `ConsumerApiHandlers.ParseConfig` | Missing `Action` and `Pedantic` fields |
| `ConsumerAction` enum | consumer.go:156 | MISSING | — | ActionCreateOrUpdate/ActionUpdate/ActionCreate not modeled |
| `ConsumerAction.MarshalJSON/UnmarshalJSON` | consumer.go:188-213 | MISSING | — | JSON serialization for consumer actions |
| `ConsumerNakOptions` | consumer.go:216 | PARTIAL | Parsed inline in `AckProcessor.ProcessAck` | Delay extraction exists but not as a typed struct |
| `PriorityPolicy` enum | consumer.go:221 | PORTED | `PriorityPolicy` (Models/ConsumerConfig.cs:80) | Has None, Overflow, PinnedClient. Missing `Prioritized` variant |
| `PriorityPolicy.MarshalJSON/UnmarshalJSON` | consumer.go:261-290 | NOT_APPLICABLE | — | .NET uses enum serialization |
| `DeliverPolicy` enum | consumer.go:293 | PORTED | `DeliverPolicy` (Models/JetStreamPolicies.cs:16) | All 6 variants present |
| `AckPolicy` enum | consumer.go:330 | PORTED | `AckPolicy` (Models/ConsumerConfig.cs:69) | All 3 variants present |
| `ReplayPolicy` enum | consumer.go:354 | PORTED | `ReplayPolicy` (Models/JetStreamPolicies.cs:27) | Both variants present |
<!-- ## Ack Constants & Protocol -->
| `AckAck/AckOK/AckNak/AckProgress/AckNext/AckTerm` | consumer.go:376-388 | PORTED | `AckType` enum + `AckProcessor.ParseAckType` (Consumers/AckProcessor.cs:127) | All ack types recognized; AckNext (+NXT) handled implicitly |
| `ackTermLimitsReason/ackTermUnackedLimitsReason` | consumer.go:391-394 | MISSING | — | Reason strings for limit-based terminations |
| `JSPullRequestPendingMsgs/Bytes/NatsPinId` | consumer.go:43-46 | PARTIAL | Used in heartbeat headers in PushConsumerEngine | PinId header not used |
| `JsPullRequestRemainingBytesT` | consumer.go:53 | MISSING | — | Template for 409 Batch Completed response |
| `validGroupName` regex | consumer.go:49 | MISSING | — | Group name validation regex `^[a-zA-Z0-9/_=-]{1,16}$` |
<!-- ## Consumer Struct & Fields -->
| `consumer` struct | consumer.go:409 | PARTIAL | `ConsumerHandle` (ConsumerManager.cs:331) | ConsumerHandle is a simplified record with NextSequence, Paused, PauseUntilUtc, Pending, PushFrames, AckProcessor. Missing: most of the ~80 fields (leader, client, sysc, sid, sseq, dseq, adflr, asflr, subjf, filters, dsubj, qgroup, lss, rlimit, ackSub, reqSub, etc.) |
| `subjectFilter` struct | consumer.go:529 | PORTED | `CompiledFilter` (Consumers/PullConsumerEngine.cs:15) | Different approach but equivalent functionality |
| `subjectFilters` type | consumer.go:535 | PORTED | `CompiledFilter.FromConfig` | Encapsulated in CompiledFilter |
| `proposal` struct | consumer.go:547 | MISSING | — | RAFT proposal linked list for clustered consumers |
<!-- ## Constants & Defaults -->
| `JsAckWaitDefault` (30s) | consumer.go:554 | PORTED | `ConsumerConfig.AckWaitMs = 30_000` | Default set in ConsumerConfig |
| `JsDeleteWaitTimeDefault` (5s) | consumer.go:557 | PARTIAL | `DeliveryInterestTracker(TimeSpan.FromSeconds(30))` | Different default (30s vs 5s) |
| `JsFlowControlMaxPending` (32MB) | consumer.go:559 | PARTIAL | `MaxFlowControlPending = 2` (frames, not bytes) | Different unit/approach |
| `JsDefaultMaxAckPending` (1000) | consumer.go:561 | MISSING | — | Not applied as default in ConsumerConfig |
| `JsDefaultPinnedTTL` (2min) | consumer.go:564 | MISSING | — | Not applied as default |
<!-- ## Config Defaults & Validation -->
| `setConsumerConfigDefaults` | consumer.go:568 | MISSING | — | 110-line function setting MaxDeliver=-1, MaxWaiting defaults, AckWait defaults, BackOff override, MaxAckPending defaults, MaxRequestBatch limits, PinnedTTL default. No .NET equivalent |
| `checkConsumerCfg` | consumer.go:680 | MISSING | — | 270-line comprehensive validation function. No .NET equivalent (JetStreamConfigValidator only validates streams) |
| `ConsumerConfig.replicas` | consumer.go:397 | MISSING | — | Replicas calculation against parent stream |
| `deliveryFormsCycle` | referenced at consumer.go:743 | MISSING | — | Cycle detection for push delivery subjects |
<!-- ## Consumer Creation & Lifecycle -->
| `stream.addConsumer` | consumer.go:956 | PARTIAL | `ConsumerManager.CreateOrUpdate` | Simplified: no WorkQueue checks, no replica validation, no RAFT, no event subjects |
| `stream.addConsumerWithAction` | consumer.go:952 | PARTIAL | `ConsumerManager.CreateOrUpdate` | Action parameter not used |
| `stream.addConsumerWithAssignment` | consumer.go:960 | PARTIAL | `ConsumerManager.CreateOrUpdate` | 370-line function. .NET has ~30 lines. Missing: durable update detection, WorkQueue partition uniqueness, consumer limit checks, store creation, ack subscription setup, subject filter compilation, stored state restoration, cluster assignment, rate limit setup, advisory sending |
| `consumer.updateInactiveThreshold` | consumer.go:1335 | MISSING | — | Jitter calculation for ephemeral delete timers |
| `consumer.updatePauseState` | consumer.go:1358 | PARTIAL | `ConsumerManager.Pause(stream, durable, DateTime)` | Timer-based resume exists but simpler implementation |
<!-- ## Cluster / Leader Management -->
| `consumer.consumerAssignment` | consumer.go:1383 | MISSING | — | No RAFT consumer assignment tracking |
| `consumer.setConsumerAssignment` | consumer.go:1389 | MISSING | — | |
| `consumer.monitorQuitC` | consumer.go:1407 | MISSING | — | Monitor goroutine quit channel |
| `consumer.signalMonitorQuit` | consumer.go:1422 | MISSING | — | |
| `consumer.updateC` | consumer.go:1431 | MISSING | — | |
| `consumer.checkQueueInterest` | consumer.go:1439 | MISSING | — | Queue group interest checking |
| `consumer.clearNode` | consumer.go:1459 | MISSING | — | RAFT node cleanup |
| `consumer.IsLeader/isLeader` | consumer.go:1469 | MISSING | — | No leader election concept |
| `consumer.setLeader` | consumer.go:1478 | MISSING | — | 240-line leader transition handling (subscribe internal ack/req/reset subs, flow control, push mode interest, delete timer, pause state, replay mode, start goroutines). Entirely missing |
<!-- ## Internal Subscriptions -->
| `consumer.subscribeInternal` | consumer.go:1723 | MISSING | — | Internal subscription for ack/request subjects |
| `consumer.unsubscribe` | consumer.go:1743 | MISSING | — | |
| `consumer.handleClusterConsumerInfoRequest` | consumer.go:1718 | MISSING | — | Cluster info request handler |
<!-- ## Advisories -->
| `consumer.sendAdvisory` | consumer.go:1752 | MISSING | — | Generic advisory sender with interest check |
| `consumer.sendDeleteAdvisoryLocked` | consumer.go:1771 | MISSING | — | Delete advisory |
| `consumer.sendPinnedAdvisoryLocked` | consumer.go:1788 | MISSING | — | Pinned consumer advisory |
| `consumer.sendUnpinnedAdvisoryLocked` | consumer.go:1807 | MISSING | — | Unpinned consumer advisory |
| `consumer.sendCreateAdvisory` | consumer.go:1827 | MISSING | — | Create advisory |
| `consumer.sendPauseAdvisoryLocked` | consumer.go:1847 | MISSING | — | Pause/unpause advisory |
<!-- ## Delivery Interest & Cleanup -->
| `consumer.hasDeliveryInterest` | consumer.go:1886 | PARTIAL | `DeliveryInterestTracker` (Consumers/DeliveryInterestTracker.cs) | .NET tracks subscriber count; Go checks sublist + gateway interest |
| `Server.hasGatewayInterest` | consumer.go:1908 | MISSING | — | Gateway interest checking |
| `consumer.updateDeliveryInterest` | consumer.go:1925 | PARTIAL | `DeliveryInterestTracker.OnSubscribe/OnUnsubscribe` | Simplified; no delete timer or queue group handling |
| `consumer.deleteNotActive` | consumer.go:1977 | PARTIAL | `DeliveryInterestTracker.ShouldDelete` | Go has 160-line function with pull-mode elapsed checking, pending ack consideration, clustered delete proposal with retry. .NET is a simple boolean property |
| `consumer.watchGWinterest` | consumer.go:2141 | MISSING | — | Gateway interest polling timer |
<!-- ## Config Update -->
| `Account.checkNewConsumerConfig` | consumer.go:2274 | MISSING | — | 55-line validation of which config fields can be updated |
| `consumer.updateConfig` | consumer.go:2333 | MISSING | — | 145-line config update handler (DeliverSubject, MaxAckPending, AckWait, RateLimit, SampleFrequency, MaxDeliver, InactiveThreshold, PauseUntil, FilterSubjects). Not ported |
| `consumer.updateDeliverSubject` | consumer.go:2480 | MISSING | — | Push consumer delivery subject change |
| `consumer.updateDeliverSubjectLocked` | consumer.go:2489 | MISSING | — | |
| `configsEqualSansDelivery` | consumer.go:2506 | MISSING | — | Config comparison ignoring delivery subject |
<!-- ## Ack Processing -->
| `jsAckMsg` struct | consumer.go:2519 | MISSING | — | Ack message struct with pooling |
| `jsAckMsgPool` | consumer.go:2526 | MISSING | — | sync.Pool for ack messages |
| `consumer.pushAck` | consumer.go:2552 | MISSING | — | Wire handler that pushes to ackMsgs queue |
| `consumer.processAck` | consumer.go:2558 | PORTED | `AckProcessor.ProcessAck(ulong, ReadOnlySpan<byte>)` | All ack types dispatched (+ACK, -NAK, +TERM, +WPI, +NXT). Missing: ackReplyInfo parsing from subject, ack reply handling |
| `consumer.progressUpdate` | consumer.go:2604 | PORTED | `AckProcessor.ProcessProgress` | Resets deadline |
| `consumer.updateSkipped` | consumer.go:2616 | MISSING | — | RAFT proposal for skip |
| `consumer.resetStartingSeq` | consumer.go:2628 | PARTIAL | `ConsumerManager.ResetToSequence` | Simplified; missing deliver policy validation, RAFT proposal, interest stream cleanup |
| `consumer.resetLocalStartingSeq` | consumer.go:2691 | PARTIAL | `ConsumerManager.ResetToSequence` | Clears pending and resets sequence |
<!-- ## RAFT Proposals -->
| `consumer.loopAndForwardProposals` | consumer.go:2700 | MISSING | — | RAFT proposal forwarding goroutine |
| `consumer.propose` | consumer.go:2761 | MISSING | — | Add to proposal linked list |
| `consumer.updateDelivered` | consumer.go:2778 | MISSING | — | RAFT-aware delivery tracking (proposes or stores locally) |
| `consumer.addAckReply` | consumer.go:2799 | MISSING | — | Pending ack reply tracking for replicated consumers |
| `consumer.addReplicatedQueuedMsg` | consumer.go:2808 | MISSING | — | Pending delivery tracking for replicated consumers |
| `consumer.updateAcks` | consumer.go:2829 | MISSING | — | RAFT-aware ack update (proposes or stores locally) |
| `consumer.addClusterPendingRequest` | consumer.go:2854 | PARTIAL | `PullConsumerEngine.ProposeWaitingRequest` | Simplified cluster pending tracking |
| `consumer.removeClusterPendingRequest` | consumer.go:2866 | PARTIAL | `PullConsumerEngine.RemoveClusterPending` | |
| `consumer.setPendingRequestsOk` | consumer.go:2877 | MISSING | — | Version-gated pending request support |
| `consumer.checkAndSetPendingRequestsOk` | consumer.go:2890 | MISSING | — | Peer version checking |
| `consumer.checkPendingRequests` | consumer.go:2915 | MISSING | — | Leadership change pending request notification |
| `consumer.releaseAnyPendingRequests` | consumer.go:2931 | MISSING | — | Release waiting requests on delete/stop |
<!-- ## NAK / TERM Processing -->
| `consumer.processNak` | consumer.go:2950 | PORTED | `AckProcessor.ProcessNak` | Delay extraction, backoff, redelivery queue. Missing: NAK advisory sending, JSON delay parsing (`ConsumerNakOptions`), duration string parsing |
| `consumer.processTerm` | consumer.go:3030 | PORTED | `AckProcessor.ProcessTerm` | Removes from pending. Missing: TERM advisory, reason extraction |
<!-- ## Pending & Redelivery State -->
| `ackWaitDelay` constant | consumer.go:3060 | MISSING | — | 1ms delay added to ack wait timer |
| `consumer.ackWait` | consumer.go:3063 | MISSING | — | AckWait + delay calculation |
| `consumer.checkRedelivered` | consumer.go:3072 | MISSING | — | Sanity check for rdc below ack floor |
| `consumer.readStoredState` | consumer.go:3091 | MISSING | — | Restore state from store |
| `consumer.applyState` | consumer.go:3107 | MISSING | — | Apply ConsumerState to consumer fields |
| `consumer.setStoreState` | consumer.go:3135 | MISSING | — | Set state from snapshot restore |
| `consumer.writeStoreState/Unlocked` | consumer.go:3147-3172 | MISSING | — | Persist state to store |
<!-- ## Consumer Info -->
| `consumer.initialInfo` | consumer.go:3176 | MISSING | — | One-shot initial info |
| `consumer.clearInitialInfo` | consumer.go:3189 | MISSING | — | |
| `consumer.info` | consumer.go:3196 | PARTIAL | `ConsumerManager.GetInfo` | .NET returns only config; Go returns full ConsumerInfo with delivery/ack state, pending counts, waiting count, cluster info, priority groups |
| `consumer.infoWithSnap` | consumer.go:3200 | MISSING | — | |
| `consumer.infoWithSnapAndReply` | consumer.go:3204 | MISSING | — | 120-line info builder with store state, cluster info |
<!-- ## Signaling & Sampling -->
| `consumer.signalNewMessages` | consumer.go:3330 | PORTED | `PushConsumerEngine.Signal(ConsumerSignal.NewMessage)` | Channel-based signaling |
| `consumer.shouldSample` | consumer.go:3339 | PORTED | `SampleTracker.ShouldSample` (Consumers/SampleTracker.cs) | Stochastic sampling |
| `consumer.sampleAck` | consumer.go:3352 | PARTIAL | `SampleTracker.RecordLatency` | Records sample but does not send advisory |
| `consumer.processAckMsg` | consumer.go:3382 | PARTIAL | `AckProcessor.AckSequence` / `AckProcessor.AckAll` | Core ack processing for Explicit/All/None policies. Missing: sample sending, cluster replication, retention policy notification, floor advancement algorithm differs |
<!-- ## Max Delivery & Redelivery -->
| `consumer.hasMaxDeliveries` | consumer.go:2172 | PORTED | `AckProcessor.ScheduleRedelivery` (max deliver check) | Checks and handles exceeded sequences |
| `consumer.forceExpirePending` | consumer.go:2203 | MISSING | — | Force all pending to redeliver queue |
| `consumer.deliveryCount` | referenced | MISSING | — | Per-sequence delivery count lookup |
| `consumer.notifyDeliveryExceeded` | referenced at 2180 | MISSING | — | Max delivery exceeded advisory |
<!-- ## Rate Limiting -->
| `consumer.setRateLimit` | consumer.go:2246 | PORTED | `TokenBucketRateLimiter` (Consumers/TokenBucketRateLimiter.cs) | Token bucket rate limiter. Go uses `golang.org/x/time/rate`; .NET has custom impl |
| `consumer.setRateLimitNeedsLocks` | consumer.go:2228 | MISSING | — | Lock-safe rate limit update |
<!-- ## Consumer Store (persistence) -->
| `ConsumerState` struct (store.go) | referenced | PORTED | `ConsumerState` (Storage/ConsumerState.cs) | All fields: Delivered, AckFloor, Pending, Redelivered |
| `SequencePair` struct (store.go) | referenced | PORTED | `SequencePair` (Storage/ConsumerState.cs:8) | |
| `Pending` struct (store.go) | referenced | PORTED | `Pending` (Storage/ConsumerState.cs:17) | |
| `ConsumerStore` interface | referenced | PORTED | `IConsumerStore` (Storage/IConsumerStore.cs) | All methods: SetStarting, UpdateStarting, Reset, HasState, UpdateDelivered, UpdateAcks, Update, State, BorrowState, EncodedState, Type, Stop, Delete, StreamDelete |
| `consumerFileStore` | referenced | PORTED | `ConsumerFileStore` (Storage/ConsumerFileStore.cs) | File-backed store with background flusher |
| `encodeConsumerState/decodeConsumerState` | referenced | PORTED | `ConsumerStateCodec` (Storage/ConsumerStateCodec.cs) | Go-compatible binary codec with varint encoding |
<!-- ## Status Counts -->
| Status | Count |
| PORTED | 23 |
| PARTIAL | 21 |
| MISSING | 70 |
| NOT_APPLICABLE | 1 |
| **Total** | **115** |
<!-- ## Key Gaps Summary -->
<!-- JS-3b: lines 3501-end -->
| `consumer.isFiltered()` | consumer.go:3524 | PARTIAL | `CompiledFilter` in PullConsumerEngine.cs | .NET has CompiledFilter but lacks stream-subject comparison logic (checks subjf vs mset.cfg.Subjects) |
| `consumer.needAck()` | consumer.go:3571 | PARTIAL | `InterestRetentionPolicy.ShouldRetain()` | .NET covers interest retention case but missing full ack-policy switch (AckNone/AckAll/AckExplicit), filtered match, and leader/follower state borrow |
| `consumer.isFilteredMatch()` | consumer.go:4490 | PORTED | `FilterSkipTracker.ShouldDeliver()`, `CompiledFilter.Matches()` | Both use SubjectMatch for token-based matching; Go has tokenized optimization |
| `consumer.isEqualOrSubsetMatch()` | consumer.go:4515 | MISSING | — | Checks if filter subject is equal to or subset of consumer's filter subjects; used in config validation |
<!-- ## Priority Groups & Pin ID -->
| `PriorityGroup` (struct) | consumer.go:3635 | PORTED | `PriorityGroupManager` + `PullWaitingRequest.Priority` | Go struct fields (Group, MinPending, MinAckPending, Id, Priority) mapped across .NET types |
| `consumer.setPinnedTimer()` | consumer.go:4000 | PARTIAL | `PriorityGroupManager.AssignPinId()` | .NET assigns pin ID but lacks TTL timer + unpinned advisory on timeout |
| `consumer.assignNewPinId()` | consumer.go:4020 | PARTIAL | `PriorityGroupManager.AssignPinId()` | .NET assigns but missing pinnedTS tracking and pinned advisory send |
| `consumer.unassignPinId()` | consumer.go:4032 | PORTED | `PriorityGroupManager.UnassignPinId()` | Timer stop + pin clear |
<!-- ## Wait Queue (Pull Requests) -->
| `waitingRequest` (struct) | consumer.go:3690 | PORTED | `PullWaitingRequest` + `PullRequest` | Represented as records; Go has linked list pointers, .NET uses separate queue |
| `waitingDelivery` (struct) | consumer.go:3731 | MISSING | — | Tracks pending replicated deliveries (seq, pending msgs/bytes); needed for clustered pull consumers |
| `waitQueue` (struct) | consumer.go:3753 | PORTED | `PullRequestWaitQueue` + `WaitingRequestQueue` | Two .NET implementations: priority-based and FIFO |
| `newWaitQueue()` | consumer.go:3761 | PORTED | `PullRequestWaitQueue` constructor | |
| `waitQueue.insertSorted()` | consumer.go:3771 | PORTED | `PullRequestWaitQueue.Enqueue()` | Stable priority insertion sort |
| `waitQueue.addPrioritized()` | consumer.go:3783 | PORTED | `PullRequestWaitQueue.Enqueue()` | |
| `waitQueue.add()` | consumer.go:3798 | PORTED | `WaitingRequestQueue.Enqueue()` | FIFO append |
| `waitQueue.isFull()` | consumer.go:3821 | PORTED | `PullRequestWaitQueue` max size check | |
| `waitQueue.isEmpty()` | consumer.go:3828 | PORTED | `WaitingRequestQueue.IsEmpty` | |
| `waitQueue.len()` | consumer.go:3835 | PORTED | `WaitingRequestQueue.Count` | |
| `waitQueue.peek()` | consumer.go:3843 | PORTED | `PullRequestWaitQueue.Peek()` | |
| `waitQueue.cycle()` | consumer.go:3850 | MISSING | — | Removes head and re-adds to tail; used for round-robin cycling |
| `waitQueue.popOrPopAndRequeue()` | consumer.go:3860 | PORTED | `PullRequestWaitQueue.PopAndRequeue()` | Dispatches based on priority policy |
| `waitQueue.pop()` | consumer.go:3873 | PORTED | `PullRequestWaitQueue.Dequeue()` | |
| `waitQueue.popAndRequeue()` | consumer.go:3892 | PORTED | `PullRequestWaitQueue.PopAndRequeue()` | |
| `insertAtPosition()` | consumer.go:3925 | PORTED | (inline in PullRequestWaitQueue.Enqueue) | Priority insertion logic |
| `waitQueue.removeCurrent()` | consumer.go:3959 | PORTED | (implicit in Dequeue) | |
| `waitQueue.remove()` | consumer.go:3964 | PARTIAL | — | Go supports arbitrary removal from linked list; .NET queue doesn't have arbitrary mid-list removal |
| `consumer.pendingRequests()` | consumer.go:3988 | PARTIAL | `PullConsumerEngine.GetClusterPendingRequests()` | Returns pending as reply-keyed map vs collection |
| `consumer.nextWaiting()` | consumer.go:4044 | PARTIAL | `PullRequestWaitQueue.PopAndRequeue()` | .NET has basic pop; Go has full priority pin-check logic, max-bytes rejection, expiry, interest checking |
<!-- ## Next Message Request Processing -->
| `nextReqFromMsg()` | consumer.go:3652 | PARTIAL | `PullFetchRequest` parsing in ConsumerApiHandlers | .NET parses batch from JSON; Go parses batch, maxBytes, noWait, expires, heartbeat, priorityGroup |
| `nextMsgReq` (struct) | consumer.go:4186 | NOT_APPLICABLE | — | Pool-based message wrapper; .NET uses different request pipeline |
| `consumer.processNextMsgReq()` | consumer.go:4219 | PARTIAL | `ConsumerApiHandlers.HandleNext()` | .NET has basic batch fetch; Go validates push vs pull, API level, and queues to ipQueue |
| `consumer.processNextMsgRequest()` | consumer.go:4276 | PARTIAL | `PullConsumerEngine.FetchAsync()` | .NET handles basic fetch; Go handles maxRequestBatch/Expires/MaxBytes limits, priority group validation, noWait, interest tracking, queue add |
| `consumer.processResetReq()` | consumer.go:4241 | PORTED | `ConsumerManager.ResetToSequence()` + `ConsumerApiHandlers.HandleReset()` | API handler + manager reset |
| `trackDownAccountAndInterest()` | consumer.go:4419 | NOT_APPLICABLE | — | Account export response tracking for cross-account pull requests; not yet needed in .NET |
<!-- ## Delivery Count & Redelivery -->
| `consumer.deliveryCount()` | consumer.go:4439 | PORTED | `RedeliveryTracker._deliveryCounts` access | Via dictionary lookup |
| `consumer.incDeliveryCount()` | consumer.go:4452 | PORTED | `RedeliveryTracker.IncrementDeliveryCount()` | |
| `consumer.decDeliveryCount()` | consumer.go:4463 | MISSING | — | Adjusts delivery count down on failed delivery; not present in .NET tracker |
| `consumer.notifyDeliveryExceeded()` | consumer.go:4471 | PARTIAL | `AckProcessor.ExceededSequences` + `DeliveryExceededPolicy` | .NET tracks exceeded sequences; Go sends advisory event via sendAdvisory |
<!-- ## Message Retrieval & Delivery Loop -->
| `consumer.getNextMsg()` | consumer.go:4540 | PARTIAL | `PullConsumerEngine.FetchAsync()` | .NET handles basic store load + filter match; Go has full redelivery queue processing, max pending check, skip list, multi-filter LoadNextMsg/LoadNextMsgMulti |
| `consumer.processWaiting()` | consumer.go:4645 | PARTIAL | `WaitingRequestQueue.RemoveExpired()` | .NET only removes expired; Go also checks interest, sends heartbeats, handles noWait EOS, replicated deliveries |
| `consumer.checkWaitingForInterest()` | consumer.go:4745 | MISSING | — | Checks if any waiting requests still have interest |
| `consumer.hbTimer()` | consumer.go:4751 | PORTED | `PushConsumerEngine.StartIdleHeartbeatTimer()` | |
| `consumer.checkAckFloor()` | consumer.go:4762 | MISSING | — | Corrects ack floor drift when stream FirstSeq advances past consumer asflr; processes stale pending entries |
| `consumer.processInboundAcks()` | consumer.go:4854 | PARTIAL | `AckProcessor.ProcessAck()` is synchronous | Go runs as dedicated goroutine with ack floor drift ticker; .NET processes inline |
| `consumer.processInboundNextMsgReqs()` | consumer.go:4902 | PARTIAL | `ConsumerApiHandlers.HandleNext()` | Go runs as dedicated goroutine draining ipQueue; .NET handles synchronously per API call |
| `consumer.suppressDeletion()` | consumer.go:4926 | MISSING | — | Resets inactivity timer on ack activity to prevent premature ephemeral deletion |
| `consumer.loopAndGatherMsgs()` | consumer.go:4948 | PARTIAL | `PushConsumerEngine.LoopAndGatherMsgsAsync()` | .NET has basic gather loop; Go has full pause check, push active check, pull wait check, replay delay, rate limit, flow control, heartbeat timer |
<!-- ## Idle Heartbeat & Flow Control -->
| `consumer.sendIdleHeartbeat()` | consumer.go:5222 | PORTED | `PushConsumerEngine.SendIdleHeartbeatCallback()` | Includes stall header and pending counts |
| `consumer.ackReply()` | consumer.go:5234 | MISSING | — | Formats `$JS.ACK.{stream}.{consumer}.{dc}.{sseq}.{dseq}.{ts}.{pending}` reply subject |
| `consumer.setMaxPendingBytes()` | consumer.go:5239 | MISSING | — | Sets flow control byte limits; used in testing |
| `consumer.checkNumPending()` | consumer.go:5252 | MISSING | — | Sanity-checks cached num pending against store state |
| `consumer.numPending()` | consumer.go:5271 | MISSING | — | Returns cached pending count (npc clamped to 0) |
| `consumer.checkNumPendingOnEOF()` | consumer.go:5281 | MISSING | — | Resets npc/npf when consumer has caught up to stream end |
| `consumer.streamNumPendingLocked()` | consumer.go:5294 | MISSING | — | Acquires lock and delegates to streamNumPending |
| `consumer.streamNumPending()` | consumer.go:5303 | MISSING | — | Forces recalculation of num pending from store |
| `consumer.calculateNumPending()` | consumer.go:5319 | MISSING | — | Calculates num pending using NumPending/NumPendingMulti with filter awareness |
| `convertToHeadersOnly()` | consumer.go:5336 | MISSING | — | Strips msg payload and replaces with Nats-Msg-Size header for HeadersOnly consumers |
| `consumer.deliverMsg()` | consumer.go:5364 | PARTIAL | `PushConsumerEngine.Enqueue()` + `RunDeliveryLoopAsync()` | .NET enqueues frames and sends via loop; Go handles dseq tracking, pending tracking, flow control, replicated delivery, rate limiting, interest ack for AckNone |
| `consumer.replicateDeliveries()` | consumer.go:5428 | NOT_APPLICABLE | — | Cluster-specific: checks if deliveries must be replicated before sending |
| `consumer.needFlowControl()` | consumer.go:5432 | PARTIAL | `PushConsumerEngine.IsFlowControlStalled` | .NET tracks pending FC count; Go tracks pbytes vs maxpb threshold |
| `consumer.processFlowControl()` | consumer.go:5448 | PARTIAL | `PushConsumerEngine.AcknowledgeFlowControl()` | .NET decrements count; Go also doubles maxpb (slow start ramp), updates pbytes accounting |
| `consumer.fcReply()` | consumer.go:5476 | MISSING | — | Generates unique flow control reply subject: `_FCR_.{stream}.{consumer}.{rand4}` |
| `consumer.sendFlowControl()` | consumer.go:5495 | PARTIAL | FC frame in `PushConsumerEngine.RunDeliveryLoopAsync()` | .NET sends FC frame; Go also tracks fcsz/fcid state |
<!-- ## Pending Tracking & Redelivery Queue -->
| `consumer.trackPending()` | consumer.go:5507 | PARTIAL | `AckProcessor.Register()` | .NET registers with ackWaitMs; Go also handles backoff array indexing and ptmr reset |
| `consumer.creditWaitingRequest()` | consumer.go:5541 | MISSING | — | Credits back a failed delivery in the wait queue (n++, d--) |
| `consumer.didNotDeliver()` | consumer.go:5554 | MISSING | — | Handles failed delivery: decrements delivery count, checks push/pull mode, queues redelivery, checks delivery interest |
| `consumer.addToRedeliverQueue()` | consumer.go:5595 | PORTED | `RedeliveryTracker.Schedule()` | .NET uses PriorityQueue; Go uses rdq slice + rdqi set |
| `consumer.hasRedeliveries()` | consumer.go:5603 | PORTED | `RedeliveryTracker.GetDue()` returns non-empty | |
| `consumer.getNextToRedeliver()` | consumer.go:5607 | PORTED | `RedeliveryTracker.GetDue()` enumeration | |
| `consumer.onRedeliverQueue()` | consumer.go:5625 | PORTED | `RedeliveryTracker.IsTracking()` (via _deliveryCounts) | |
| `consumer.removeFromRedeliverQueue()` | consumer.go:5631 | PORTED | `RedeliveryTracker.Acknowledge()` | Removes from entries and delivery counts |
| `consumer.checkPending()` | consumer.go:5651 | PARTIAL | `AckProcessor.TryGetExpired()` | .NET checks one expired at a time; Go scans all pending with backoff, checks awl interlock, removes stale below fseq/asflr, sorts expired, adjusts timestamps |
<!-- ## Reply Subject Parsing -->
| `consumer.seqFromReply()` | consumer.go:5770 | MISSING | — | Extracts delivery sequence from ack reply subject |
| `consumer.streamSeqFromReply()` | consumer.go:5776 | MISSING | — | Extracts stream sequence from ack reply subject |
| `parseAckReplyNum()` | consumer.go:5782 | MISSING | — | Fast ASCII digit parser for ack reply tokens |
| `replyInfo()` | consumer.go:5798 | MISSING | — | Parses all 5 fields from `$JS.ACK.{stream}.{consumer}.{dc}.{sseq}.{dseq}.{ts}.{pending}` |
| `ackReplyInfo()` | consumer.go:5821 | MISSING | — | Parses sseq, dseq, dc from ack reply subject |
<!-- ## Starting Sequence Selection -->
| `lastSeqSkipList` (struct) | consumer.go:5849 | MISSING | — | Holds skip list for DeliverLastPerSubject startup |
| `consumer.hasSkipListPending()` | consumer.go:5856 | MISSING | — | Checks if skip list has pending entries |
| `consumer.selectStartingSeqNo()` | consumer.go:5861 | PARTIAL | `PullConsumerEngine.ResolveInitialSequenceAsync()` | .NET handles DeliverAll/Last/New/ByStartSeq/ByStartTime/LastPerSubject; Go also handles filtered subjects for DeliverLast, MaxMsgsPer optimization for LastPerSubject, MultiLastSeqs, time-based filtered skip-ahead, empty/future/past clamping |
| `consumer.nextSeq()` | consumer.go:5841 | PORTED | `ConsumerHandle.NextSequence` property | |
<!-- ## Consumer Identity & Mode Checks -->
| `isDurableConsumer()` | consumer.go:5969 | PORTED | `ConsumerConfig.Ephemeral` flag (inverse) | |
| `consumer.isDurable()` | consumer.go:5973 | PORTED | `!config.Ephemeral` | |
| `consumer.isPushMode()` | consumer.go:5978 | PORTED | `ConsumerConfig.Push` property | |
| `consumer.isPullMode()` | consumer.go:5982 | PORTED | `!config.Push` | |
| `consumer.String()` | consumer.go:5987 | NOT_APPLICABLE | — | Trivial name getter |
| `createConsumerName()` | consumer.go:5994 | PORTED | `Guid.NewGuid()` in ConsumerManager | Different generation approach but same purpose |
| `stream.deleteConsumer()` | consumer.go:5999 | PORTED | `ConsumerManager.Delete()` | |
| `consumer.getStream()` | consumer.go:6003 | PORTED | `ConsumerHandle.Stream` property | |
| `consumer.streamName()` | consumer.go:6010 | PORTED | `ConsumerHandle.Stream` property | |
| `consumer.isActive()` | consumer.go:6021 | PARTIAL | `ConsumerHandle.Paused` (inverse) | .NET uses Paused; Go checks active flag + mset != nil |
| `consumer.hasNoLocalInterest()` | consumer.go:6029 | PARTIAL | `DeliveryInterestTracker.HasInterest` (inverse) | .NET tracks subscriber count; Go checks sublist for delivery subject |
<!-- ## Consumer Purge & Lifecycle -->
| `consumer.purge()` | consumer.go:6040 | MISSING | — | Called when parent stream is purged; adjusts sseq/asflr, clears stale pending, filters wider purge, updates store state |
| `stopAndClearTimer()` | consumer.go:6124 | NOT_APPLICABLE | — | Go timer helper; .NET uses Timer.Dispose() |
| `consumer.stop()` | consumer.go:6135 | PARTIAL | `PushConsumerEngine.StopDeliveryLoop()` + `StopGatherLoop()` | .NET stops loops; Go has full stopWithFlags with advisory, cleanup, subscription unsubscribe |
| `consumer.deleteWithoutAdvisory()` | consumer.go:6139 | MISSING | — | Deletes without sending advisory |
| `consumer.delete()` | consumer.go:6144 | PORTED | `ConsumerManager.Delete()` | |
| `consumer.isClosed()` | consumer.go:6149 | MISSING | — | Checks closed flag under lock |
| `consumer.stopWithFlags()` | consumer.go:6155 | MISSING | — | Full lifecycle shutdown: cluster assignment check, advisory, subscription cleanup, client close, interest clear, store delete/stop, RAFT node stop, directory cleanup |
| `consumer.cleanupNoInterestMessages()` | consumer.go:6334 | MISSING | — | Removes messages with no remaining consumer interest; used on interest-policy stream consumer deletion |
| `deliveryFormsCycle()` | consumer.go:6400 | MISSING | — | Validates that delivery subject doesn't create a cycle with stream subjects |
| `consumer.switchToEphemeral()` | consumer.go:6410 | MISSING | — | Converts durable to ephemeral on startup recovery; clears Durable, updates inactive threshold |
| `consumer.requestNextMsgSubject()` | consumer.go:6430 | MISSING | — | Returns the subject for next-message pull requests |
<!-- ## Stream Pending & Signal -->
| `consumer.decStreamPending()` | consumer.go:6434 | MISSING | — | Decrements cached num pending on message deletion; processes pending term if message was pending |
| `consumer.account()` | consumer.go:6459 | NOT_APPLICABLE | — | Account getter |
| `consumer.signalSubs()` | consumer.go:6468 | MISSING | — | Creates sublist filter subjects for stream signal registration |
| `consumer.processStreamSignal()` | consumer.go:6494 | PARTIAL | `PushConsumerEngine.Signal(ConsumerSignal.NewMessage)` | .NET signals via channel; Go checks leader, updates npc, checks push active / pull waiting |
| `subjectSliceEqual()` | consumer.go:6517 | NOT_APPLICABLE | — | Utility set equality; trivial |
| `gatherSubjectFilters()` | consumer.go:6536 | NOT_APPLICABLE | — | Utility to collect filter strings; trivial |
<!-- ## Monitor & State Checking -->
| `consumer.shouldStartMonitor()` | consumer.go:6546 | MISSING | — | Atomically checks/sets monitor running flag with WaitGroup |
| `consumer.clearMonitorRunning()` | consumer.go:6560 | MISSING | — | Clears monitor running flag and decrements WaitGroup |
| `consumer.isMonitorRunning()` | consumer.go:6571 | MISSING | — | Checks monitor running flag |
| `consumer.checkStateForInterestStream()` | consumer.go:6582 | MISSING | — | For interest/WQ streams: walks sequences from FirstSeq to ack floor, acks already-consumed messages; handles pending above floor; adjusts chkflr |
<!-- ## Timer & Pool Helpers -->
| `consumer.resetPtmr()` | consumer.go:6692 | PARTIAL | Timer in `AckProcessor.TryGetExpired()` loop | Go uses AfterFunc timer; .NET checks expiry on poll |
| `consumer.stopAndClearPtmr()` | consumer.go:6701 | PARTIAL | — | Go stops timer and clears; .NET has no dedicated pending timer |
| `consumer.resetPendingDeliveries()` | consumer.go:6706 | MISSING | — | Recycles pending and waiting delivery pool entries; clustered delivery cleanup |
<!-- ## Summary Counts -->
| Status | Count |
| PORTED | 31 |
| PARTIAL | 28 |
| MISSING | 37 |
| NOT_APPLICABLE | 9 |
| DEFERRED | 0 |
| **Total** | **105** |
<!-- ## Key Gaps by Priority -->
<!-- ### Critical (core message path) -->
<!-- ### High (operational correctness) -->
<!-- ### Medium (clustering & ephemeral) -->


<!-- === JS-4: Storage (memstore + filestore + dirstore) === -->
<!-- JS-4a: memstore.go + filestore.go lines 1-4000 -->
| `memStore` struct | memstore.go:33 | PARTIAL | `MemStore` class in MemStore.cs:16 | .NET uses `Dictionary<string,SubjectState>` instead of `stree.SubjectTree[SimpleState]`; no `scb`/`rmcb`/`pmsgcb` callbacks; no `ageChk` timer; no `scheduling`; no `sdm` |
| `newMemStore` | memstore.go:54 | PORTED | `MemStore(StreamConfig)` constructor, MemStore.cs:111 | TTL init present; FirstSeq handled; no `ats.Register()` |
| `UpdateConfig` | memstore.go:86 | PARTIAL | `IStreamStore.UpdateConfig`, MemStore.cs:799 | TTL create/destroy ported; per-subject limit enforcement ported; missing: age timer start/stop, `enforceMsgLimit`/`enforceBytesLimit` on config change, scheduling support |
| `recoverTTLState` | memstore.go:147 | MISSING | -- | TTL state recovery from existing messages not implemented |
| `recoverMsgSchedulingState` | memstore.go:171 | MISSING | -- | Message scheduling not implemented |
<!-- ### Store operations -->
| `storeRawMsg` (internal) | memstore.go:195 | PARTIAL | `StoreInternal`, MemStore.cs:872 | Core store logic ported (seq check, per-subject tracking, msg/bytes limits); missing: `DiscardNew` policy checks, `DiscardNewPer` check, TTL tracking in THW, age timer management, scheduling support |
| `StoreRawMsg` | memstore.go:329 | PARTIAL | `IStreamStore.StoreRawMsg`, MemStore.cs:299 | Delegates to StoreInternal; missing: `scb` callback, `receivedAny` first-message age check |
| `StoreMsg` | memstore.go:350 | PORTED | `IStreamStore.StoreMsg`, MemStore.cs:287 | Core logic matches Go |
| `SkipMsg` | memstore.go:368 | PORTED | `IStreamStore.SkipMsg`, MemStore.cs:308 | Sequence check and dmap handling present |
| `SkipMsgs` | memstore.go:395 | PORTED | `IStreamStore.SkipMsgs`, MemStore.cs:332 | Multi-skip ported |
| `FlushAllPending` | memstore.go:424 | PORTED | `IStreamStore.FlushAllPending`, MemStore.cs:358 | No-op in both |
<!-- ### Callback registration -->
| `RegisterStorageUpdates` | memstore.go:431 | MISSING | -- | Storage update callback not implemented |
| `RegisterStorageRemoveMsg` | memstore.go:439 | MISSING | -- | Removal callback not implemented |
| `RegisterProcessJetStreamMsg` | memstore.go:446 | MISSING | -- | JetStream msg processing callback not implemented |
<!-- ### Query operations -->
| `GetSeqFromTime` | memstore.go:454 | PORTED | `IStreamStore.GetSeqFromTime`, MemStore.cs:571 | Binary search with gap awareness ported |
| `FilteredState` | memstore.go:531 | PORTED | `IStreamStore.FilteredState`, MemStore.cs:639 | Delegates to internal method |
| `filteredStateLocked` | memstore.go:540 | PARTIAL | `FilteredStateLocked`, MemStore.cs (internal) | Core matching ported; lacks `lastPerSubject` partial-scan optimization, `firstNeedsUpdate`/`lastNeedsUpdate` lazy recalculation |
| `SubjectsState` | memstore.go:748 | PORTED | `IStreamStore.SubjectsState`, MemStore.cs:643 | Implemented with Dictionary instead of SubjectTree |
| `AllLastSeqs` | memstore.go:780 | PORTED | `IStreamStore.AllLastSeqs`, MemStore.cs:679 | Sorted last sequences |
| `filterIsAll` | memstore.go:811 | MISSING | -- | Helper to check if filters == stream subjects |
| `MultiLastSeqs` | memstore.go:828 | PORTED | `IStreamStore.MultiLastSeqs`, MemStore.cs:691 | Matching logic ported |
| `SubjectsTotals` | memstore.go:881 | PORTED | `IStreamStore.SubjectsTotals`, MemStore.cs:664 | Per-subject counts ported |
| `NumPending` | memstore.go:913 | PORTED | `IStreamStore.NumPending`, MemStore.cs:747 | Delegates to filteredStateLocked |
| `NumPendingMulti` | memstore.go:923 | MISSING | -- | Multi-subject sublist-based pending count not implemented |
<!-- ### Limit enforcement -->
| `enforcePerSubjectLimit` | memstore.go:1072 | PORTED | `EnforcePerSubjectLimit`, MemStore.cs (internal) | Removes oldest per-subject messages |
| `enforceMsgLimit` | memstore.go:1088 | PARTIAL | Inline in `StoreInternal` | Logic present but not separated into a named method; `DiscardOld` policy not checked |
| `enforceBytesLimit` | memstore.go:1102 | PARTIAL | Inline in `StoreInternal` | Similar to enforceMsgLimit |
<!-- ### Age/TTL expiration -->
| `startAgeChk` | memstore.go:1116 | MISSING | -- | Age check timer not implemented |
| `resetAgeChk` | memstore.go:1126 | MISSING | -- | Age check timer reset not implemented |
| `cancelAgeChk` | memstore.go:1194 | MISSING | -- | Age check cancellation not implemented |
| `expireMsgs` | memstore.go:1203 | MISSING | -- | Full MaxAge + TTL expiration loop not implemented |
| `shouldProcessSdm` | memstore.go:1322 | MISSING | -- | Subject delete marker tracking not implemented |
| `shouldProcessSdmLocked` | memstore.go:1329 | MISSING | -- | SDM tracking internals |
| `handleRemovalOrSdm` | memstore.go:1362 | MISSING | -- | SDM/removal handler |
| `runMsgScheduling` | memstore.go:1383 | MISSING | -- | Message scheduling not implemented |
<!-- ### Purge/Compact/Truncate -->
| `PurgeEx` | memstore.go:1422 | PORTED | `IStreamStore.PurgeEx`, MemStore.cs:481 | Subject-specific + keep + seq purge ported |
| `Purge` | memstore.go:1471 | PORTED | `IStreamStore.Purge`, MemStore.cs:472 | Full purge ported |
| `purge` (internal) | memstore.go:1475 | PORTED | `PurgeInternal`, MemStore.cs | Resets msgs/fss/dmap |
| `Compact` | memstore.go:1509 | PORTED | `IStreamStore.Compact`, MemStore.cs:533 | Compaction ported |
| `compact` (internal) | memstore.go:1513 | PORTED | `CompactInternal`, MemStore.cs | Removes messages before seq |
| `reset` | memstore.go:1583 | PORTED | `IStreamStore.Truncate(0)`, MemStore.cs:540 | Full reset handled via Truncate(0) |
| `Truncate` | memstore.go:1618 | PORTED | `IStreamStore.Truncate`, MemStore.cs:536 | Removes messages after seq |
<!-- ### Message load operations -->
| `SubjectForSeq` | memstore.go:1678 | PORTED | `IStreamStore.SubjectForSeq`, MemStore.cs:736 | |
| `LoadMsg` | memstore.go:1692 | PORTED | `IStreamStore.LoadMsg`, MemStore.cs:361 | |
| `loadMsgLocked` | memstore.go:1697 | PORTED | Inline in LoadMsg | Lock parameter not needed (.NET uses `lock(_gate)`) |
| `LoadLastMsg` | memstore.go:1724 | PORTED | `IStreamStore.LoadLastMsg`, MemStore.cs:393 | Wildcard + literal + fwcs paths |
| `loadLastLocked` | memstore.go:1733 | PORTED | Inline in LoadLastMsg | |
| `LoadNextMsgMulti` | memstore.go:1763 | MISSING | -- | Multi-subject sublist-based next message not implemented |
| `LoadNextMsg` | memstore.go:1798 | PARTIAL | `IStreamStore.LoadNextMsg`, MemStore.cs:372 | Linear scan only; missing `shouldLinearScan` optimization, `nextWildcardMatchLocked`, `nextLiteralMatchLocked` |
| `nextWildcardMatchLocked` | memstore.go:1810 | MISSING | -- | FSS-based wildcard bounds optimization |
| `nextLiteralMatchLocked` | memstore.go:1852 | MISSING | -- | FSS-based literal bounds optimization |
| `shouldLinearScan` | memstore.go:1868 | MISSING | -- | Linear scan decision helper |
| `loadNextMsgLocked` | memstore.go:1877 | PARTIAL | Inline in LoadNextMsg | Core linear scan ported; FSS-based optimizations missing |
| `LoadPrevMsg` | memstore.go:1925 | PORTED | `IStreamStore.LoadPrevMsg`, MemStore.cs:439 | Backward walk ported |
| `LoadPrevMsgMulti` | memstore.go:1952 | MISSING | -- | Multi-subject sublist-based prev message not implemented |
<!-- ### Remove/Erase -->
| `RemoveMsg` | memstore.go:1987 | PORTED | `IStreamStore.RemoveMsg`, MemStore.cs:454 | |
| `EraseMsg` | memstore.go:1995 | PARTIAL | `IStreamStore.EraseMsg`, MemStore.cs:463 | Does not overwrite with random bytes (secure erase) |
| `updateFirstSeq` | memstore.go:2004 | PORTED | Inline in `RemoveInternal` | Updates first seq on removal |
| `removeSeqPerSubject` | memstore.go:2037 | PORTED | `RemoveSeqPerSubject`, MemStore.cs | Per-subject tracking update on remove |
| `recalculateForSubj` | memstore.go:2070 | MISSING | -- | Lazy first/last recalculation for per-subject state (uses `firstNeedsUpdate`/`lastNeedsUpdate` flags) |
| `removeMsg` (internal) | memstore.go:2110 | PARTIAL | `RemoveInternal`, MemStore.cs | Core removal ported; missing: TTL removal from THW, secure erase with random bytes, `scb` callback |
| `deleteFirstMsgOrPanic` | memstore.go:1667 | PORTED | Inline in limit enforcement | |
| `deleteFirstMsg` | memstore.go:1673 | PORTED | Inline in limit enforcement | |
<!-- ### State/Utility -->
| `Type` | memstore.go:2167 | PORTED | `IStreamStore.Type`, MemStore.cs:797 | Returns `StorageType.Memory` |
| `FastState` | memstore.go:2173 | PORTED | `IStreamStore.FastState`, MemStore.cs:781 | Populates ref struct |
| `State` | memstore.go:2192 | PORTED | `IStreamStore.State`, MemStore.cs:757 | Full state with deleted list |
| `Utilization` | memstore.go:2221 | MISSING | -- | Returns (total, reported) byte counts |
| `memStoreMsgSize` | memstore.go:2231 | PORTED | `MsgSize`, MemStore.cs (internal) | Size calculation for accounting |
| `memStoreMsgSizeRaw` | memstore.go:2227 | PORTED | Inline in MsgSize | |
| `ResetState` | memstore.go:2236 | PORTED | `IStreamStore.ResetState`, MemStore.cs:846 | No-op (scheduling not implemented) |
| `Delete` | memstore.go:2245 | PORTED | `IStreamStore.Delete`, MemStore.cs:831 | |
| `Stop` | memstore.go:2249 | PORTED | `IStreamStore.Stop`, MemStore.cs:828 | |
| `isClosed` | memstore.go:2272 | MISSING | -- | Closed check (msgs == nil) |
| `EncodedStreamState` | memstore.go:2322 | PARTIAL | `IStreamStore.EncodedStreamState`, MemStore.cs:849 | Returns empty array stub — binary encoding not implemented |
| `SyncDeleted` | memstore.go:2357 | MISSING | -- | Delete block synchronization for NRG not implemented |
<!-- ### consumerMemStore -->
| `consumerMemStore` struct | memstore.go:2278 | MISSING | -- | In-memory consumer state store not implemented as a distinct type |
| `ConsumerStore` (factory) | memstore.go:2286 | MISSING | -- | Consumer store creation stub; returns `NotSupportedException` |
| `AddConsumer` | memstore.go:2301 | MISSING | -- | Consumer count tracking |
| `RemoveConsumer` | memstore.go:2308 | MISSING | -- | Consumer count tracking |
| `Snapshot` | memstore.go:2317 | NOT_APPLICABLE | -- | Returns "no impl" in Go too |
| `consumerMemStore.Update` | memstore.go:2382 | MISSING | -- | Consumer state update |
| `consumerMemStore.SetStarting` | memstore.go:2428 | MISSING | -- | Set starting sequence |
| `consumerMemStore.UpdateStarting` | memstore.go:2437 | MISSING | -- | Update starting sequence |
| `consumerMemStore.Reset` | memstore.go:2451 | MISSING | -- | Reset consumer state |
| `consumerMemStore.HasState` | memstore.go:2459 | MISSING | -- | Check if state exists |
| `consumerMemStore.UpdateDelivered` | memstore.go:2466 | MISSING | -- | Update delivery tracking |
| `consumerMemStore.UpdateAcks` | memstore.go:2532 | MISSING | -- | Ack processing (AckAll / AckExplicit) |
| `consumerMemStore.UpdateConfig` | memstore.go:2606 | MISSING | -- | Update consumer config |
| `consumerMemStore.Stop` | memstore.go:2615 | MISSING | -- | Stop consumer store |
| `consumerMemStore.Delete` | memstore.go:2624 | MISSING | -- | Delete consumer store |
| `consumerMemStore.StreamDelete` | memstore.go:2628 | MISSING | -- | Stream-level delete |
| `consumerMemStore.State` | memstore.go:2632 | MISSING | -- | Get consumer state |
| `consumerMemStore.BorrowState` | memstore.go:2638 | MISSING | -- | Borrow state (no copy) |
| `consumerMemStore.EncodedState` | memstore.go:2672 | MISSING | -- | Binary-encode consumer state |
| `consumerMemStore.Type` | memstore.go:2700 | MISSING | -- | Returns MemoryStorage |
<!-- ## FileStore (filestore.go lines 1-4000) -->
<!-- ### Types & Constants -->
| `FileStoreConfig` struct | filestore.go:55 | PORTED | `FileStoreConfig` class, FileStoreConfig.cs:12 | All fields ported (StoreDir, BlockSize, CacheExpire, SubjectStateExpire, SyncInterval, SyncAlways, AsyncFlush, Cipher, Compression); missing: `srv` internal reference |
| `FileStreamInfo` | filestore.go:80 | MISSING | -- | Created + StreamConfig wrapper not defined |
| `StoreCipher` enum | filestore.go:85 | PORTED | `StoreCipher` enum, AeadEncryptor.cs:22 | ChaCha, AES, NoCipher |
| `StoreCompression` enum | filestore.go:106 | PORTED | `StoreCompression` enum, AeadEncryptor.cs:39 | NoCompression, S2Compression |
| `StoreCompression.String/MarshalJSON/UnmarshalJSON` | filestore.go:113-151 | NOT_APPLICABLE | -- | .NET enums have built-in string conversion; JSON handled by System.Text.Json |
| `FileConsumerInfo` | filestore.go:154 | MISSING | -- | Consumer info with Created + Name + Config |
| `psi` struct | filestore.go:166 | MISSING | -- | Per-subject index (total, fblk, lblk) for PSIM tree |
| `fileStore` struct | filestore.go:172 | PARTIAL | `FileStore` class, FileStore.cs:23 | .NET uses in-memory Dictionary instead of block-based PSIM; missing: `psim` SubjectTree, `bim` block index map, `tombs`, `ld`, callbacks (`scb`/`rmcb`/`pmsgcb`), age timer, sync timer, `prf`/`aek` encryption keys, `hh` highway hash, `qch`/`fsld` channels, async flush state |
| `msgBlock` struct | filestore.go:217 | PARTIAL | `MsgBlock` class, MsgBlock.cs:24 | Basic block file + index + delete tracking ported; missing: encryption (`aek`/`bek`/`seed`/`nonce`), compression (`cmp`), FSS subject tree, cache expiry timer (`ctmr`), write-error tracking (`werr`), `fch`/`qch` channels, loading/flusher flags, compact/sync flags |
| `cache` struct | filestore.go:270 | PARTIAL | `_cache` Dictionary in MsgBlock.cs:49 | .NET uses Dictionary<ulong,MessageRecord>; Go uses byte-buffer + index array |
| `msgId` struct | filestore.go:278 | MISSING | -- | Sequence+timestamp pair |
| Constants (magic, version, dirs, sizes) | filestore.go:283-377 | PARTIAL | -- | Some constants like block sizes defined differently in `FileStoreOptions`; most Go constants (magic bytes, dir names, scan patterns, thresholds) not ported |
<!-- ### FileStore constructor & initialization -->
| `newFileStore` | filestore.go:379 | PARTIAL | `FileStore` constructor, FileStore.cs | .NET creates directory + first block; missing: `dynBlkSize`, encryption key setup, highway hash init, full recovery pipeline |
| `newFileStoreWithCreated` | filestore.go:383 | PARTIAL | (merged into constructor) | Core directory setup present; missing: `prf`/`oldprf` key gen, AEK recovery, full state recovery, TTL/scheduling recovery, tombstone processing, meta file write, sync timer, flush state loop |
| `lockAllMsgBlocks`/`unlockAllMsgBlocks` | filestore.go:641-653 | NOT_APPLICABLE | -- | .NET uses different concurrency model |
| `UpdateConfig` | filestore.go:655 | PARTIAL | `IStreamStore.UpdateConfig` in FileStore.cs | Basic config update present; missing: meta file write, TTL/scheduling, limits enforcement, async flush toggle |
| `dynBlkSize` | filestore.go:763 | MISSING | -- | Dynamic block size calculation based on retention/maxBytes/encryption |
<!-- ### Encryption -->
| `genEncryptionKey` | filestore.go:800 | PORTED | `AeadEncryptor.Encrypt/Decrypt`, AeadEncryptor.cs:73 | ChaCha20-Poly1305 and AES-GCM both supported |
| `genEncryptionKeys` | filestore.go:816 | MISSING | -- | Key encryption key generation with PRF not ported |
| `genBlockEncryptionKey` | filestore.go:864 | MISSING | -- | Block-level stream cipher (ChaCha20/AES-CTR) not ported |
| `recoverAEK` | filestore.go:878 | MISSING | -- | AEK recovery from key file |
| `setupAEK` | filestore.go:907 | MISSING | -- | AEK initial setup |
| `writeStreamMeta` | filestore.go:929 | MISSING | -- | Stream metadata + checksum write |
| `loadEncryptionForMsgBlock` | filestore.go:1078 | MISSING | -- | Per-block encryption key loading |
| `convertCipher` | filestore.go:1318 | MISSING | -- | Cipher conversion between ChaCha/AES |
| `convertToEncrypted` | filestore.go:1407 | MISSING | -- | Plaintext to encrypted block conversion |
<!-- ### Block pool & record format -->
| `getMsgBlockBuf`/`recycleMsgBlockBuf` | filestore.go:996-1032 | MISSING | -- | Pooled block buffers (Go sync.Pool); .NET uses GC |
| `msgHdrSize`/`checksumSize`/`emptyRecordLen` | filestore.go:1034-1038 | PARTIAL | `MessageRecord.Encode/Decode` in MessageRecord.cs | .NET uses its own binary format (not identical to Go's 22-byte header) |
| `noTrackSubjects` | filestore.go:1041 | MISSING | -- | Helper to check if subject tracking is needed |
| `initMsgBlock` | filestore.go:1046 | PARTIAL | `MsgBlock.Create`, MsgBlock.cs:174 | Basic block init; missing highway hash, cache/sync config |
<!-- ### Block recovery -->
| `checkAndLoadEncryption` | filestore.go:1068 | MISSING | -- | Encryption check on block load |
| `ensureLastChecksumLoaded` | filestore.go:1139 | MISSING | -- | Lazy last checksum load |
| `recoverMsgBlock` | filestore.go:1148 | PARTIAL | `MsgBlock.Recover`, MsgBlock.cs:188 | Basic file scan + index rebuild present; missing: encryption handling, index file check, FSS population, lost data tracking |
| `lostData`/`addLostData`/`removeFromLostData` | filestore.go:1224-1275 | MISSING | -- | Lost data tracking not implemented |
| `rebuildState`/`rebuildStateLocked` | filestore.go:1277-1315 | PARTIAL | -- | FileStore does a simplified rebuild; Go's full block-by-block state aggregation not ported |
| `rebuildStateLocked` (msgBlock) | filestore.go:1454 | MISSING | -- | Per-block state rebuild from raw buffer |
| `rebuildStateFromBufLocked` | filestore.go:1493 | MISSING | -- | Detailed record-by-record scan with tombstone/ebit/gap detection |
<!-- ### State recovery -->
| `warn`/`debug` (logging) | filestore.go:1708-1724 | NOT_APPLICABLE | -- | .NET uses ILogger<T> |
| `updateTrackingState` | filestore.go:1727 | MISSING | -- | Helper to aggregate per-block state |
| `trackingStatesEqual` | filestore.go:1743 | MISSING | -- | State consistency check |
| `recoverFullState` | filestore.go:1754 | MISSING | -- | Full state recovery from index.db |
| `recoverTTLState` | filestore.go:2042 | MISSING | -- | TTL hash wheel recovery from thw.db |
| `recoverMsgSchedulingState` | filestore.go:2123 | MISSING | -- | Message scheduling recovery from sched.db |
| `lastChecksum` (msgBlock) | filestore.go:2204 | PARTIAL | `MsgBlock.LastChecksum`, MsgBlock.cs:155 | Checksum tracked on write; missing: load from encrypted block |
| `cleanupOldMeta` | filestore.go:2236 | MISSING | -- | Remove legacy .idx/.fss files |
| `recoverMsgs` | filestore.go:2263 | PARTIAL | Recovery in FileStore constructor | Basic block scan + re-open present; missing: per-block first/last propagation, tombstone handling, orphan key cleanup |
| `expireMsgsOnRecover` | filestore.go:2401 | MISSING | -- | Startup age expiration |
| `copyMsgBlocks` | filestore.go:2589 | NOT_APPLICABLE | -- | Go slice copy helper |
<!-- ### Query & Filter operations (filestore) -->
| `GetSeqFromTime` (fs) | filestore.go:2600 | PARTIAL | In FileStore.cs | Basic implementation present; missing: block selection by timestamp, binary search across blocks |
| `firstMatchingMulti` (mb) | filestore.go:2666 | MISSING | -- | Block-level multi-subject first match |
| `firstMatching` (mb) | filestore.go:2806 | MISSING | -- | Block-level first matching with FSS optimization |
| `prevMatchingMulti` (mb) | filestore.go:2948 | MISSING | -- | Block-level reverse multi-subject match |
| `filteredPending` / `filteredPendingLocked` (mb) | filestore.go:3062-3188 | MISSING | -- | Per-block filtered pending count |
| `FilteredState` (fs) | filestore.go:3191 | PARTIAL | In FileStore.cs | Simplified version; missing: per-block aggregation with FSS |
| `checkSkipFirstBlock` | filestore.go:3241 | MISSING | -- | Block skip optimization using PSIM |
| `checkSkipFirstBlockMulti` | filestore.go:3269 | MISSING | -- | Multi-subject block skip |
| `selectSkipFirstBlock` | filestore.go:3287 | MISSING | -- | Block index selection helper |
| `numFilteredPending` / `numFilteredPendingNoLast` / `numFilteredPendingWithLast` | filestore.go:3308-3410 | MISSING | -- | Optimized filtered pending using PSIM |
| `SubjectsState` (fs) | filestore.go:3413 | PARTIAL | In FileStore.cs | In-memory implementation; missing: per-block FSS aggregation |
| `AllLastSeqs` / `allLastSeqsLocked` (fs) | filestore.go:3495-3553 | PARTIAL | In FileStore.cs | In-memory implementation; missing: reverse block walk with FSS |
| `filterIsAll` (fs) | filestore.go:3558 | MISSING | -- | Filter optimization helper |
| `MultiLastSeqs` (fs) | filestore.go:3575 | PARTIAL | In FileStore.cs | In-memory implementation; missing: PSIM-based optimization, per-block walk |
| `NumPending` (fs) | filestore.go:3710 | PARTIAL | In FileStore.cs | Simplified version; missing: PSIM optimization, block-level aggregation |
<!-- ## Summary Counts -->
| Status | MemStore | FileStore (1-4000) | Total |
| PORTED | 33 | 4 | 37 |
| PARTIAL | 11 | 16 | 27 |
| MISSING | 36 | 35 | 71 |
| NOT_APPLICABLE | 1 | 4 | 5 |
| DEFERRED | 0 | 0 | 0 |
| **Total** | **81** | **59** | **140** |
<!-- ### Key Gaps by Priority -->
<!-- JS-4b: filestore.go lines 4001-7000 -->
| `fs.NumPendingMulti` | filestore.go:4051 | MISSING | -- | Multi-filter NumPending using SimpleSublist; consumer optimization path |
| `fs.SubjectsTotals` | filestore.go:4387 | PORTED | `FileStore.SubjectsTotals` | .NET iterates _messages dict; Go uses psim trie Match |
| `fs.subjectsTotalsLocked` | filestore.go:4394 | PORTED | (inlined in SubjectsTotals) | Internal locked helper |
| `fs.RegisterStorageUpdates` | filestore.go:4412 | MISSING | -- | Callback registration for storage change notifications |
| `fs.RegisterStorageRemoveMsg` | filestore.go:4424 | MISSING | -- | Callback registration for message removals (used by replicated streams) |
| `fs.RegisterProcessJetStreamMsg` | filestore.go:4431 | MISSING | -- | Callback registration for new JetStream message processing |
| `fs.hashKeyForBlock` | filestore.go:4439 | MISSING | -- | HighwayHash key derivation per block index |
| `mb.setupWriteCache` | filestore.go:4443 | PARTIAL | `MsgBlock._cache` / `WriteCacheManager` | .NET has a cache dict + WriteCacheManager; Go has elastic pointer, cache expiry timer, and loadMsgs fallback |
| `mb.finishedWithCache` | filestore.go:4477 | PARTIAL | `WriteCacheManager.EvictBlock` | .NET evicts on block rotation; Go weakens elastic reference when no pending writes |
| `fs.newMsgBlockForWrite` | filestore.go:4485 | PARTIAL | `FileStore.RotateBlock` | .NET creates new MsgBlock; Go also handles flush of pending, cache recycling, HighwayHash init, encryption key gen, flush loop spin-up |
| `fs.genEncryptionKeysForBlock` | filestore.go:4575 | MISSING | -- | Per-block AEAD key generation and keyfile writing; .NET uses per-payload AEAD instead |
| `fs.storeRawMsg` (internal) | filestore.go:4599 | PARTIAL | `FileStore.StoreRawMsg` | .NET stores but lacks: per-subject max enforcement (MaxMsgsPer), DiscardNew checks, psim updates, enforceMsgLimit/enforceBytesLimit calls, per-message TTL wheel, message scheduling |
| `fs.StoreRawMsg` (exported) | filestore.go:4759 | PORTED | `FileStore.StoreRawMsg` | Public wrapper with callback |
| `fs.StoreMsg` | filestore.go:4779 | PORTED | `FileStore.StoreMsg` | Returns (seq, ts); .NET simplified |
| `mb.skipMsg` | filestore.go:4801 | PARTIAL | `MsgBlock.WriteSkip` | .NET writes skip record; Go also handles empty-block meta update, dmap insert, ebit flag |
| `fs.SkipMsg` | filestore.go:4829 | PORTED | `FileStore.SkipMsg` | Sequence mismatch check, first/last update |
| `fs.SkipMsgs` | filestore.go:4868 | PORTED | `FileStore.SkipMsgs` | .NET iterates SkipMsg; Go batch-inserts into dmap with single write placeholder |
| `fs.FlushAllPending` | filestore.go:4933 | PORTED | `FileStore.FlushAllPending` | .NET flushes active block + writes stream.state |
| `fs.rebuildFirst` | filestore.go:4940 | MISSING | -- | Rebuilds first-block state after removal errors |
| `fs.firstSeqForSubj` | filestore.go:4966 | MISSING | -- | Optimized first-seq lookup per subject using psim fblk/lblk |
| `fs.enforceMsgLimit` | filestore.go:5033 | MISSING | -- | Drop oldest messages when MaxMsgs exceeded; bulk purge of full blocks |
| `fs.enforceBytesLimit` | filestore.go:5061 | MISSING | -- | Drop oldest messages when MaxBytes exceeded; bulk purge of full blocks |
| `fs.enforceMsgPerSubjectLimit` | filestore.go:5091 | MISSING | -- | Complex per-subject enforcement with psim skew detection and rebuild |
| `fs.deleteFirstMsg` | filestore.go:5222 | MISSING | -- | Trivial wrapper: removeMsgViaLimits(firstSeq) |
| `fs.removeMsgViaLimits` | filestore.go:5229 | MISSING | -- | Remove via limits (no forced index update) |
| `fs.RemoveMsg` | filestore.go:5235 | PORTED | `FileStore.RemoveMsg` | .NET simplified: removes from dict + soft-deletes in block |
| `fs.EraseMsg` | filestore.go:5239 | PORTED | `FileStore.EraseMsg` | .NET does secure erase via MsgBlock.Delete(secureErase: true) |
| `fs.removePerSubject` | filestore.go:5245 | MISSING | -- | Decrements psim per-subject counter, removes from trie at zero |
| `fs.removeMsg` (internal) | filestore.go:5267 | PARTIAL | `FileStore.RemoveMsg` / `FileStore.EraseMsg` | .NET simplified; Go handles: tombstone writing, cache loading, per-subject tracking, FIFO optimization, out-of-order dmap insert, inline compaction, secure erase, callback |
| `fs.removeMsgFromBlock` | filestore.go:5307 | PARTIAL | `FileStore.DeleteInBlock` | .NET delegates to MsgBlock.Delete; Go handles full per-block accounting, tombstone write, secure erase, FIFO selectNextFirst, dmap, inline compact, empty-block removal |
| `fs.removeMsgsInRange` | filestore.go:5509 | MISSING | -- | Bulk removal in [first,last] with block-level purge optimization |
| `mb.shouldCompactInline` | filestore.go:5553 | MISSING | -- | Heuristic: compact when rbytes > minimum and 2x savings possible |
| `mb.shouldCompactSync` | filestore.go:5561 | MISSING | -- | Sync-time compaction heuristic |
| `mb.compact` | filestore.go:5567 | MISSING | -- | Full block rewrite compaction |
| `mb.compactWithFloor` | filestore.go:5576 | MISSING | -- | Compaction with tombstone cleanup; handles compression, encryption, dmap pruning |
| `mb.slotInfo` | filestore.go:5735 | MISSING | -- | Cache index slot lookup for record offset/length |
| `fs.isClosed` | filestore.go:5778 | PORTED | `FileStore._stopped` | Atomic bool in Go; simple bool in .NET |
| `mb.spinUpFlushLoop` | filestore.go:5783 | MISSING | -- | Goroutine-based flush loop per block |
| `mb.spinUpFlushLoopLocked` | filestore.go:5791 | MISSING | -- | Locked version of flush loop startup |
| `kickFlusher` | filestore.go:5805 | MISSING | -- | Non-blocking channel send to wake flush goroutine |
| `mb.kickFlusher` | filestore.go:5815 | MISSING | -- | Per-block flusher kick |
| `mb.setInFlusher` | filestore.go:5821 | MISSING | -- | Flusher state tracking |
| `mb.clearInFlusher` | filestore.go:5827 | MISSING | -- | Flusher cleanup, close channels |
| `mb.flushLoop` | filestore.go:5842 | MISSING | -- | Background goroutine: coalesce pending writes, flush, close FDs when no longer last block |
| `mb.eraseMsg` | filestore.go:5890 | PARTIAL | `MsgBlock.Delete(secureErase)` | .NET overwrites payload with random bytes; Go overwrites full record including header, recalculates hash, updates both cache and disk |
| `mb.truncate` | filestore.go:5940 | MISSING | -- | Truncate block to target sequence; handles decompression/re-encryption, eof calc, per-subject reset |
| `mb.isEmpty` | filestore.go:6046 | PARTIAL | `MsgBlock.MessageCount == 0` | Go uses atomic first/last comparison |
| `mb.selectNextFirst` | filestore.go:6051 | MISSING | -- | Walk dmap to find next non-deleted first sequence |
| `fs.selectNextFirst` | filestore.go:6091 | MISSING | -- | Clean up empty leading blocks, advance first |
| `mb.resetCacheExpireTimer` | filestore.go:6123 | MISSING | -- | Per-block timer reset for cache expiry |
| `mb.startCacheExpireTimer` | filestore.go:6135 | MISSING | -- | Start per-block cache expiry timer |
| `mb.clearCacheAndOffset` | filestore.go:6141 | PARTIAL | `MsgBlock.ClearCache` | .NET clears cache dict; Go also resets linear scan tracker |
| `mb.clearCache` | filestore.go:6148 | PARTIAL | `MsgBlock.ClearCache` | .NET nulls cache dict; Go handles fss expiry, elastic pointer cleanup, buffer recycling |
| `mb.expireCache` | filestore.go:6176 | PARTIAL | `WriteCacheManager` background tick | .NET uses 500ms periodic timer; Go uses per-block AfterFunc timer |
| `mb.tryForceExpireCache` | filestore.go:6182 | MISSING | -- | Force expire by temporarily clearing timestamps |
| `mb.tryForceExpireCacheLocked` | filestore.go:6189 | MISSING | -- | Locked force-expire |
| `mb.tryExpireWriteCache` | filestore.go:6199 | MISSING | -- | Expire write cache on block rotation, return recyclable buffer |
| `mb.expireCacheLocked` | filestore.go:6220 | PARTIAL | `WriteCacheManager` eviction logic | .NET time-based eviction; Go handles elastic pointer strengthening, pending write detection, fss separate expiry |
| `fs.startAgeChk` | filestore.go:6277 | PARTIAL | `FileStore.RegisterTtl` + `HashWheel` | .NET uses HashWheel; Go uses AfterFunc timer |
| `fs.resetAgeChk` | filestore.go:6287 | PARTIAL | (implicit in ExpireFromWheel) | Go has sophisticated next-expire calculation with minimum 250ms floor |
| `fs.cancelAgeChk` | filestore.go:6355 | PARTIAL | (implicit in Dispose) | .NET disposes wheel; Go stops and nils timer |
| `fs.expireMsgs` | filestore.go:6364 | PARTIAL | `FileStore.ExpireFromWheel` | .NET does basic seq removal; Go handles: negative TTL (never-expire), SDM processing, THW-based TTL expiry, sorted removal for SDM, age delta recalculation |
| `fs.shouldProcessSdm` | filestore.go:6482 | MISSING | -- | Subject Delete Marker pending tracking |
| `fs.shouldProcessSdmLocked` | filestore.go:6489 | MISSING | -- | Locked SDM state check with rate limiting |
| `fs.handleRemovalOrSdm` | filestore.go:6522 | MISSING | -- | Either removes via callback or emits SDM header message |
| `fs.runMsgScheduling` | filestore.go:6543 | MISSING | -- | Background message scheduling with repeating schedule support |
| `fs.checkAndFlushLastBlock` | filestore.go:6581 | PARTIAL | `FileStore.FlushAllPending` | .NET flushes active block; Go also handles rebuild state on lost data |
| `fs.checkMsgs` | filestore.go:6598 | MISSING | -- | Full checksum verification scan of all blocks |
| `mb.enableForWriting` | filestore.go:6622 | NOT_APPLICABLE | -- | Go-specific: open file descriptor, spin up flusher; .NET MsgBlock always has FileStream open |
| `mb.writeTombstone` | filestore.go:6646 | PARTIAL | `MsgBlock.WriteSkip` | .NET writes skip/tombstone via WriteSkip; Go uses tbit flag on sequence |
| `mb.writeTombstoneNoFlush` | filestore.go:6652 | MISSING | -- | Tombstone write without flush (used in bulk operations) |
| `mb.writeMsgRecord` | filestore.go:6660 | PORTED | `MsgBlock.Write` / `MsgBlock.WriteAt` | .NET has both auto-seq and explicit-seq variants |
| `mb.writeMsgRecordLocked` | filestore.go:6669 | PARTIAL | `MsgBlock.WriteAt` (under lock) | .NET writes record + updates index/cache; Go handles: per-subject fss tracking, elastic pointer strengthening, buffer pool management, HighwayHash checksum, tombstone accounting, flush-in-place, flusher kick |
| `mb.pendingWriteSize` | filestore.go:6821 | MISSING | -- | Go tracks pending = cache.buf - cache.wp; .NET writes synchronously |
| `mb.pendingWriteSizeLocked` | filestore.go:6831 | MISSING | -- | Locked version |
| `mb.closeFDs` | filestore.go:6843 | PARTIAL | `MsgBlock.Dispose` | .NET disposes FileStream; Go checks pending data first |
| `mb.closeFDsLocked` | filestore.go:6849 | PARTIAL | `MsgBlock.Dispose` | .NET unconditional; Go returns error if pending data |
| `mb.closeFDsLockedNoCheck` | filestore.go:6857 | PARTIAL | `MsgBlock.Dispose` | .NET disposes; Go just closes mfd |
| `mb.bytesPending` | filestore.go:6867 | NOT_APPLICABLE | -- | Go write-cache pending slice; .NET writes synchronously (no pending buffer) |
| `mb.blkSize` | filestore.go:6885 | PORTED | `MsgBlock.BytesUsed` | Returns total bytes including deleted |
| `mb.updateAccounting` | filestore.go:6897 | PARTIAL | (inline in MsgBlock.Write/WriteAt) | .NET updates first/last/totalWritten; Go handles ebit, atomic first/last, rbytes/bytes split |
| `fs.checkLastBlock` | filestore.go:6920 | PARTIAL | `FileStore.EnsureActiveBlock` | .NET rotates on IsSealed; Go checks rbytes + rl > BlockSize |
| `fs.writeMsgRecord` | filestore.go:6933 | PARTIAL | `FileStore.StoreMsg` / `StoreRawMsg` | .NET writes via MsgBlock; Go checks msg too large, increments dirty counter |
| `fs.writeTombstone` | filestore.go:6956 | MISSING | -- | fs-level tombstone with block size enforcement |
| `fs.writeTombstoneNoFlush` | filestore.go:6968 | MISSING | -- | No-flush variant |
| `mb.recompressOnDiskIfNeeded` | filestore.go:6978 | MISSING | -- | On-disk block recompression (S2) after block seal |
<!-- ## Counts -->
| Status | Count |
| PORTED | 14 |
| PARTIAL | 27 |
| MISSING | 37 |
| NOT_APPLICABLE | 2 |
| **Total** | **80** |
<!-- ## Key Gaps by Priority -->
<!-- ### Critical (needed for correctness) -->
<!-- ### High (needed for performance at scale) -->
<!-- ### Medium (needed for feature parity) -->
<!-- JS-4c: filestore.go lines 7001-10000 -->
| `mb.recompressOnDiskIfNeeded` (tail) | filestore.go:7000-7027 | PARTIAL | `FileStore.TransformForPersist` | .NET applies compression at write time (S2Codec); no post-hoc re-compression of existing blocks |
| `mb.atomicOverwriteFile` | filestore.go:7030-7111 | MISSING | — | Go writes block to temp file then renames; .NET uses direct `RandomAccess.Write` with no atomic overwrite |
| `mb.decompressIfNeeded` | filestore.go:7114-7132 | PORTED | `FileStore.RestorePayload` / `S2Codec.Decompress` | .NET decompresses via S2Codec in RestorePayload; metadata parsing differs |
| `mb.encryptOrDecryptIfNeeded` | filestore.go:7135-7145 | PARTIAL | `AeadEncryptor.Encrypt/Decrypt` | .NET uses AEAD (ChaCha20/AES-GCM) not XOR stream cipher; no block-level BEK regeneration |
| `mb.ensureRawBytesLoaded` | filestore.go:7148-7163 | MISSING | — | .NET MsgBlock tracks `_writeOffset` directly; no lazy raw-bytes stat from disk |
| `fs.syncBlocks` | filestore.go:7166-7291 | MISSING | — | No periodic block sync timer; .NET uses `FileStream.Flush(flushToDisk: true)` on demand |
| `fs.selectMsgBlock` | filestore.go:7296-7299 | PORTED | `FileStore.FindBlockForSequence` | Binary search over `_blocks` by sequence range |
| `fs.selectMsgBlockWithIndex` | filestore.go:7302-7343 | PORTED | `FileStore.FindBlockForSequence` / `FindFirstBlockAtOrAfter` | Combined linear/binary search; .NET uses pure binary search |
| `fs.selectMsgBlockForStart` (by time) | filestore.go:7348-7372 | MISSING | — | No time-based block selection; `GetSeqFromTime` does linear scan over `_messages` |
| `mb.indexCacheBuf` | filestore.go:7376-7557 | MISSING | — | Go builds index+FSS from raw block buffer; .NET uses `Dictionary<ulong,(long,int)>` index from `RebuildIndex` (much simpler) |
| `mb.flushPendingMsgs` | filestore.go:7560-7572 | PARTIAL | `MsgBlock.Flush` | .NET calls `_file.Flush(flushToDisk: true)`; Go has pending write buffer with encryption and rebuild-on-error |
| `mb.writeAt` | filestore.go:7577-7588 | PORTED | `RandomAccess.Write` in `MsgBlock.Write/WriteAt` | .NET uses `RandomAccess.Write` for positional I/O; Go uses `dios` semaphore for I/O gating |
| `mb.flushPendingMsgsLocked` | filestore.go:7592-7694 | PARTIAL | `MsgBlock.Flush` | Go has complex pending-write logic with encryption, rebuild-on-error, sync-always mode; .NET is simpler flush |
| `mb.clearLoading` | filestore.go:7697-7699 | NOT_APPLICABLE | — | Go loading-guard flag; .NET has no equivalent concurrent loading concern |
| `mb.loadMsgs` / `mb.loadMsgsWithLock` | filestore.go:7703-7885 | PARTIAL | `MsgBlock.RebuildIndex` / `MsgBlock.Read` | Go loads full block into cache from disk with decrypt/decompress; .NET reads on-demand per-record |
| `mb.cacheAlreadyLoaded` / `mb.cacheNotLoaded` | filestore.go:7711-7724 | NOT_APPLICABLE | — | Go weak-reference cache management; .NET uses simple `Dictionary<ulong,MessageRecord>?` cache |
| `mb.fssNotLoaded` | filestore.go:7728-7730 | NOT_APPLICABLE | — | Go per-subject state tree tracking; .NET has no block-level FSS |
| `mb.openBlock` | filestore.go:7734-7740 | NOT_APPLICABLE | — | Go opens block file with dios semaphore; .NET holds `FileStream` open for block lifetime |
| `mb.loadBlock` | filestore.go:7745-7791 | PARTIAL | `MsgBlock.RebuildIndex` (disk read) | Go loads entire block file into pooled buffer; .NET reads record-by-record during recovery |
| `mb.fetchMsg` / `mb.fetchMsgNoCopy` / `mb.fetchMsgEx` | filestore.go:7890-7940 | PORTED | `MsgBlock.Read` | .NET does cache-then-disk lookup; Go has copy/no-copy variants and expiry tracking |
| Error sentinels (`errNoCache`, `errDeletedMsg`, etc.) | filestore.go:7943-7959 | PARTIAL | Exceptions in `MsgBlock.Read` / `FileStore.LoadMsg` | .NET uses `null` returns and `KeyNotFoundException` instead of sentinel errors |
| Bit constants (`cbit`, `dbit`, `hbit`, `ebit`, `tbit`) | filestore.go:7973-7983 | PARTIAL | `MessageRecord.Deleted` flag | Go uses bit-packed flags in seq/rl fields; .NET uses explicit `Deleted` bool in `MessageRecord` |
| `mb.cacheLookup` / `mb.cacheLookupNoCopy` / `mb.cacheLookupEx` | filestore.go:7988-8096 | PARTIAL | `MsgBlock.Read` (cache path) | .NET checks `_cache` dictionary then falls back to disk; Go has full slot-based index lookup with hash verification |
| `fs.sizeForSeq` | filestore.go:8101-8112 | MISSING | — | No per-sequence size calculation helper |
| `fs.msgForSeq` / `fs.msgForSeqLocked` | filestore.go:8117-8160 | PORTED | `FileStore.LoadMsg` | .NET does hash-map lookup then binary-search block fallback |
| `mb.msgFromBuf` / `mb.msgFromBufNoCopy` / `mb.msgFromBufEx` | filestore.go:8165-8300 | PARTIAL | `MessageRecord.Decode` | Go parses wire-format from raw buffer with header support; .NET uses `MessageRecord.Decode` with simpler format |
| `fs.LoadMsg` | filestore.go:8308-8350 | PORTED | `FileStore.LoadMsg` | Both implement sequence-based message loading with block selection |
| `fs.LoadLastMsg` | filestore.go:8352-8400 | PORTED | `FileStore.LoadLastMsg` | Both support subject-filtered last-message lookup |
| `mb.firstMatching` | filestore.go:8402-8470 | MISSING | — | Go's per-block first-matching scan with FSS optimization; .NET filters `_messages` dictionary |
| `mb.prevMatchingMulti` | filestore.go:~8470-8500 | MISSING | — | Go's per-block reverse multi-filter scan; .NET has no equivalent |
| `fs.LoadNextMsg` | filestore.go:8508-8577 | PORTED | `FileStore.LoadNextMsg` | Both support filtered forward scan; Go uses psim skip-ahead, .NET uses LINQ |
| `fs.LoadPrevMsg` | filestore.go:8579-8631 | PORTED | `FileStore.LoadPrevMsg` | Both walk backward from start; .NET iterates `_messages` dictionary |
| `fs.LoadPrevMsgMulti` | filestore.go:8633-8670 | MISSING | — | No multi-filter reverse load in .NET |
| `fs.Type` | filestore.go:8673-8675 | PORTED | `FileStore.Type()` | Returns `StorageType.File` |
| `fs.numSubjects` | filestore.go:8679-8681 | PARTIAL | `FileStore.State()` counts subjects | .NET counts in `State()` method; no dedicated `numSubjects` helper |
| `fs.numConsumers` | filestore.go:8684-8688 | MISSING | — | No consumer count tracking in FileStore; Go uses `cmu`+`cfs` |
| `fs.FastState` | filestore.go:8692-8711 | PORTED | `FileStore.FastState` | Both populate minimal state struct without allocating deleted arrays |
| `fs.State` | filestore.go:8714-8756 | PORTED | `FileStore.State` | Both return full state with deleted sequences and subject counts |
| `fs.Utilization` | filestore.go:8758-8768 | MISSING | — | No total/reported bytes utilization tracking across blocks |
| `fileStoreMsgSizeRaw` / `fileStoreMsgSize` / `fileStoreMsgSizeEstimate` | filestore.go:8770-8785 | MISSING | — | Go wire-format size calculation; .NET uses `MessageRecord.Encode` length |
| `fs.ResetState` | filestore.go:8788-8794 | PORTED | `FileStore.ResetState` | .NET is no-op (re-derives from blocks on construction); Go clears scheduling inflight |
| `mb.sinceLastActivity` | filestore.go:8797-8812 | MISSING | — | Go tracks lwts/lrts/llts/lsts for cache expiry timing; .NET uses `WriteCacheManager` TTL |
| `mb.sinceLastWriteActivity` | filestore.go:8816-8825 | MISSING | — | No separate write-activity tracking |
| `checkNewHeader` | filestore.go:8827-8833 | MISSING | — | Go index file magic/version check; .NET uses JSON for stream state |
| `mb.readIndexInfo` | filestore.go:8836-8935 | MISSING | — | Go reads binary index (.idx) files with varint encoding; .NET rebuilds from block scan |
| `fs.cacheLoads` | filestore.go:8938-8948 | MISSING | — | Cache load counter; no equivalent metric in .NET |
| `fs.cacheSize` | filestore.go:8951-8971 | MISSING | — | Cache size tracking; .NET `WriteCacheManager.TotalCachedBytes` is approximate |
| `fs.dmapEntries` | filestore.go:8974-8982 | MISSING | — | Total dmap entries across blocks; .NET `SequenceSet` per block but no aggregate |
| `subjectsEqual` / `subjectsAll` / `compareFn` | filestore.go:8986-9001 | PARTIAL | `FileStore.SubjectMatchesFilter` | .NET has unified `SubjectMatchesFilter`; Go has separate comparator functions |
| `fs.PurgeEx` | filestore.go:9005-9194 | PORTED | `FileStore.PurgeEx` | Both support subject-filtered purge with keep/seq limits; Go has tombstone writing, .NET is simpler |
| `fs.Purge` / `fs.purge` | filestore.go:9198-9313 | PORTED | `FileStore.Purge` | Both clear all messages; Go does atomic directory rename with tombstone, .NET does `_messages.Clear()` + block dispose |
| `fs.recoverPartialPurge` | filestore.go:9316-9337 | MISSING | — | Crash recovery for partial purge (ndir/pdir cleanup); .NET has no partial-purge recovery |
| `fs.Compact` / `fs.compact` | filestore.go:9342-9591 | PORTED | `FileStore.Compact` | Both remove messages below seq; Go has block-level compaction with encryption/compression, .NET is in-memory |
| `fs.reset` | filestore.go:9594-9641 | PARTIAL | `FileStore.Truncate(0)` | .NET `Truncate(0)` clears everything; Go `reset()` resets to zero state with psim/sdm cleanup |
| `mb.tombs` / `mb.tombsLocked` | filestore.go:9644-9682 | MISSING | — | Tombstone record scanning from raw block buffer; .NET has no tombstone concept |
| `mb.numPriorTombs` / `mb.numPriorTombsLocked` | filestore.go:9685-9738 | MISSING | — | Count tombstones for prior blocks; .NET has no tombstone tracking |
| `fs.Truncate` | filestore.go:9741-9925 | PORTED | `FileStore.Truncate` | Both remove messages above seq; Go has block-level truncation with tombstone writes, .NET is in-memory |
| `fs.lastSeq` | filestore.go:9927-9932 | PORTED | `FileStore._last` field | Direct field access vs Go lock-guarded helper |
| `fs.numMsgBlocks` | filestore.go:9935-9939 | PORTED | `FileStore.BlockCount` | Property returns `_blocks.Count` |
| `fs.addMsgBlock` | filestore.go:9943-9947 | PORTED | `FileStore.RotateBlock` (inline) | .NET adds to `_blocks` list and sets `_activeBlock` |
| `fs.removeMsgBlockFromList` | filestore.go:9951-9964 | PARTIAL | `FileStore.DisposeAllBlocks` | .NET only bulk-removes blocks; no individual block removal from list |
| `fs.removeMsgBlock` | filestore.go:9968-9988 | MISSING | — | Individual block removal with tombstone writes for last-seq preservation |
| `fs.forceRemoveMsgBlock` | filestore.go:9992-9995 | MISSING | — | Force removal (dirty close + remove from list) |
| `fs.purgeMsgBlock` | filestore.go:9999-10054 | MISSING | — | Per-block purge with per-subject tracking cleanup and scheduling metadata |
| `mb.dirtyClose` | filestore.go:10058-10062 | PARTIAL | `MsgBlock.Dispose` | .NET disposes file handle; Go clears cache, stops timers, closes channels |
| `mb.dirtyCloseWithRemove` | filestore.go:10065-10103 | PARTIAL | `MsgBlock.Dispose` + `FileStore.CleanBlockFiles` | Go closes and optionally removes files; .NET separates disposal from file cleanup |
| `mb.removeSeqPerSubject` | filestore.go:10107-10145 | MISSING | — | Per-block per-subject state tracking with lazy first/last recalculation |
| `mb.recalculateForSubj` | filestore.go:10149-10259 | MISSING | — | Scan cache buffer to recalculate first/last seq for a subject within a block |
| `fs.resetGlobalPerSubjectInfo` | filestore.go:10262-10271 | MISSING | — | Rebuild psim (global per-subject index map) from all blocks |
| `mb.resetPerSubjectInfo` / `mb.generatePerSubjectInfo` | filestore.go:10274-10339 | MISSING | — | Per-block subject tree (FSS) generation from block scan |
| `mb.ensurePerSubjectInfoLoaded` | filestore.go:10343-10356 | MISSING | — | Lazy FSS loading guard; .NET has no block-level subject tracking |
| `fs.populateGlobalPerSubjectInfo` | filestore.go:10360-10383 | MISSING | — | Populate psim from block FSS during recovery |
| `mb.close` | filestore.go:10386-10424 | PORTED | `MsgBlock.Dispose` | Both close file handles; Go also flushes pending, clears FSS, stops timers |
| `fs.closeAllMsgBlocks` | filestore.go:10426-10430 | PORTED | `FileStore.DisposeAllBlocks` | Both iterate and close/dispose all blocks |
| `fs.Delete` | filestore.go:10432-10511 | PORTED | `FileStore.Delete` | Both stop and remove all data; Go has async directory removal with `dios` gating |
| `fs.setSyncTimer` / `fs.cancelSyncTimer` | filestore.go:10514-10532 | MISSING | — | Periodic sync timer with jitter; .NET has no equivalent timer |
| Full state magic/version constants | filestore.go:10537-10541 | MISSING | — | Binary full-state format versioning; .NET uses JSON |
| `fs.flushStreamStateLoop` | filestore.go:10544-10571 | MISSING | — | Background goroutine for periodic state flush; .NET writes on `FlushAllPending` call |
| `timestampNormalized` | filestore.go:10574-10579 | PARTIAL | Inline in `FileStore` methods | .NET normalizes via `DateTimeOffset.ToUnixTimeMilliseconds` |
| `fs.writeFullState` / `fs._writeFullState` | filestore.go:10583-10831 | PARTIAL | `FileStore.WriteStreamStateAsync` | Go writes binary-encoded full state with psim, block metadata, checksums; .NET writes JSON snapshot |
| `fs.writeTTLState` | filestore.go:10833-10845 | MISSING | — | TTL state persistence; .NET uses in-memory `HashWheel` only |
| `fs.writeMsgSchedulingState` | filestore.go:10847-10859 | MISSING | — | Message scheduling state persistence |
| `fs.Stop` / `fs.stop` | filestore.go:10862-10940 | PORTED | `FileStore.Stop` | Both flush and close; Go also stops consumer stores, cancels timers, writes full state |
| `fs.streamSnapshot` | filestore.go:10945-10997+ | PARTIAL | `FileStore.CreateSnapshotAsync` | .NET creates JSON snapshot; Go creates S2-compressed tar archive with block files |
<!-- ## Summary Counts -->
| Status | Count |
| PORTED | 26 |
| PARTIAL | 19 |
| MISSING | 32 |
| NOT_APPLICABLE | 5 |
| DEFERRED | 0 |
| **Total** | **82** |
<!-- ## Key Gap Themes -->
<!-- JS-4d: filestore.go lines 10001-end + dirstore.go -->
| `mb.dirtyClose()` | filestore.go:10058 | PARTIAL | `MsgBlock.Dispose()` | .NET disposes file handle; Go also stops cache timer, closes fds without flush, optionally removes files |
| `mb.dirtyCloseWithRemove(remove)` | filestore.go:10065 | PARTIAL | `MsgBlock.Dispose()` | .NET has no remove-on-close path; Go removes .blk and key files when `remove=true` |
| `mb.removeSeqPerSubject(subj, seq)` | filestore.go:10107 | MISSING | — | Per-subject index tracking within a block; .NET MsgBlock has no per-subject FSS tree |
| `mb.recalculateForSubj(subj, ss)` | filestore.go:10149 | MISSING | — | Scans cache to recalculate first/last seq for a subject within a block |
| `mb.resetPerSubjectInfo()` | filestore.go:10274 | MISSING | — | Clears and regenerates per-subject info for a block |
| `mb.generatePerSubjectInfo()` | filestore.go:10281 | MISSING | — | Builds per-subject SimpleState tree (fss) from block cache |
| `mb.ensurePerSubjectInfoLoaded()` | filestore.go:10343 | MISSING | — | Lazy-loads per-subject info when first needed |
| `mb.close(sync)` | filestore.go:10386 | PARTIAL | `MsgBlock.Dispose()` | .NET disposes only; Go flushes pending, clears cache, syncs fd, marks closed |
<!-- ### fileStore methods — Per-Subject Info (lines 10262–10384) -->
| `fs.resetGlobalPerSubjectInfo()` | filestore.go:10262 | MISSING | — | Clears and rebuilds global PSIM (per-subject index map) from all blocks |
| `fs.populateGlobalPerSubjectInfo(mb)` | filestore.go:10360 | MISSING | — | Populates global PSIM from a single block's fss tree |
<!-- ### fileStore methods — Block Lifecycle (lines 10426–10532) -->
| `fs.closeAllMsgBlocks(sync)` | filestore.go:10426 | PORTED | `FileStore.DisposeAllBlocks()` | .NET iterates blocks and disposes; Go supports optional sync |
| `fs.Delete(inline)` | filestore.go:10432 | PORTED | `FileStore.Delete(bool)` | .NET calls Stop + Directory.Delete; Go has more elaborate rename-then-remove, storage callback, purge directory handling |
| `fs.setSyncTimer()` | filestore.go:10514 | MISSING | — | Periodic sync timer for dirty blocks; .NET has no equivalent background sync timer |
| `fs.cancelSyncTimer()` | filestore.go:10527 | MISSING | — | Cancels the periodic sync timer |
<!-- ### fileStore methods — Full State Persistence (lines 10537–10860) -->
| `fullStateMagic` / `fullStateVersion` | filestore.go:10537 | MISSING | — | Binary state file versioning constants |
| `fs.flushStreamStateLoop(qch, done)` | filestore.go:10544 | PARTIAL | `FileStore.WriteStreamStateAsync()` | .NET has on-demand JSON state write; Go has a goroutine loop that periodically writes binary index.db |
| `timestampNormalized(t)` | filestore.go:10574 | MISSING | — | Utility: returns UnixNano or 0 for zero time |
| `fs.writeFullState()` | filestore.go:10583 | PARTIAL | `FileStore.WriteStreamStateAsync()` | .NET writes JSON snapshot; Go writes binary format with PSIM, per-block dmaps, block checksums, encryption, highway hash verification |
| `fs.forceWriteFullState()` | filestore.go:10588 | PARTIAL | `FileStore.WriteStreamStateAsync()` | .NET has no force vs non-force distinction; Go skips if not dirty (non-forced) or too complex |
| `fs._writeFullState(force)` | filestore.go:10599 | PARTIAL | `FileStore.WriteStreamStateAsync()` | .NET is a simplified JSON version; Go has full binary encoding with PSIM, block iteration, encryption, consistency checking |
| `fs.writeTTLState()` | filestore.go:10833 | MISSING | — | Persists TTL tracking state to ttl.state file |
| `fs.writeMsgSchedulingState()` | filestore.go:10847 | MISSING | — | Persists message scheduling state to disk |
<!-- ### fileStore methods — Stop / Shutdown (lines 10862–10940) -->
| `fs.Stop()` | filestore.go:10862 | PORTED | `FileStore.Stop()` | .NET flushes active block and disposes; Go also writes full state, cancels timers, stops consumer stores, unregisters ATS |
| `fs.stop(delete, writeState)` | filestore.go:10867 | PARTIAL | `FileStore.Stop()` / `FileStore.Delete()` | .NET has separate Stop/Delete; Go combines into one method with flags for delete and writeState |
<!-- ### fileStore methods — Snapshot (lines 10945–11170) -->
| `fs.streamSnapshot(w, includeConsumers, errCh)` | filestore.go:10945 | MISSING | — | Streams tar+S2 snapshot through a pipe; .NET has `StreamSnapshotService` at a higher level but no block-level streaming snapshot |
| `fs.Snapshot(deadline, checkMsgs, includeConsumers)` | filestore.go:11129 | MISSING | — | Creates `SnapshotResult` with pipe reader; .NET `CreateSnapshotAsync` in IStreamStore returns byte[] |
<!-- ### fileStore methods — Config & Lock Helpers (lines 11171–11191) -->
| `fs.fileStoreConfig()` | filestore.go:11171 | NOT_APPLICABLE | — | Simple getter under lock; .NET options are immutable |
| `fs.readLockAllMsgBlocks()` | filestore.go:11179 | NOT_APPLICABLE | — | Go RWMutex pattern; .NET uses per-block RWLockSlim |
| `fs.readUnlockAllMsgBlocks()` | filestore.go:11187 | NOT_APPLICABLE | — | Go RWMutex pattern; .NET uses per-block RWLockSlim |
<!-- ### fileStore methods — Encoded State & Deletion Tracking (lines 11194–11378) -->
| `fs.EncodedStreamState(failed)` | filestore.go:11194 | PARTIAL | `FileStore.EncodedStreamState(ulong)` | .NET returns empty array (stub); Go returns full binary encoding with deleted blocks |
| `fs.deleteBlocks()` | filestore.go:11253 | MISSING | — | Returns DeleteBlocks (ranges + avl seqsets) representing interior deletes between and within blocks |
| `fs.deleteMap()` | filestore.go:11293 | MISSING | — | Unions all block dmaps into a single SequenceSet |
| `fs.SyncDeleted(dbs)` | filestore.go:11313 | MISSING | — | Synchronises deleted state from a peer's DeleteBlocks — used for NRG cluster replication |
<!-- ### fileStore + consumerFileStore — Consumer Factory (lines 11379–11509) -->
| `fs.ConsumerStore(name, created, cfg)` | filestore.go:11379 | PARTIAL | `FileStore.ConsumerStore(name, created, cfg)` | .NET creates ConsumerFileStore with state file; Go also handles encryption key setup, meta file writing, cipher conversion, memory-storage override |
<!-- ### consumerFileStore methods (lines 11511–12421) -->
| `o.convertCipher()` | filestore.go:11511 | MISSING | — | Converts consumer state between ChaCha and AES ciphers |
| `o.kickFlusher()` | filestore.go:11570 | PORTED | (implicit via `_dirty` flag + background flusher) | .NET uses periodic polling; Go uses channel-based kick |
| `o.setInFlusher()` | filestore.go:11581 | PORTED | `ConsumerFileStore.InFlusher` property | |
| `o.clearInFlusher()` | filestore.go:11588 | PORTED | `ConsumerFileStore.RunFlusherAsync()` finally block | |
| `o.inFlusher()` | filestore.go:11595 | PORTED | `ConsumerFileStore.InFlusher` | |
| `o.flushLoop(fch, qch)` | filestore.go:11602 | PORTED | `ConsumerFileStore.RunFlusherAsync()` | .NET uses Task.Delay polling; Go uses channel-based select with minTime throttle |
| `o.SetStarting(sseq)` | filestore.go:11658 | PARTIAL | `ConsumerFileStore.SetStarting(sseq)` | .NET sets AckFloor only; Go also sets Delivered.Stream, AckFloor.Stream, encodes + writes to disk immediately |
| `o.UpdateStarting(sseq)` | filestore.go:11671 | PARTIAL | `ConsumerFileStore.UpdateStarting(sseq)` | .NET sets AckFloor only; Go checks sseq > Delivered.Stream, handles AckNone policy, kicks flusher |
| `o.Reset(sseq)` | filestore.go:11687 | PORTED | `ConsumerFileStore.Reset(sseq)` | |
| `o.HasState()` | filestore.go:11695 | PARTIAL | `ConsumerFileStore.HasState()` | .NET checks in-memory only; Go also checks disk file existence |
| `o.UpdateDelivered(dseq, sseq, dc, ts)` | filestore.go:11708 | PARTIAL | `ConsumerFileStore.UpdateDelivered(...)` | .NET has basic pending/redelivery tracking; Go has more nuanced handling: skips if dseq <= AckFloor, updates existing pending entries, respects MaxDeliver, stores dc-1 in redelivered |
| `o.UpdateAcks(dseq, sseq)` | filestore.go:11777 | PARTIAL | `ConsumerFileStore.UpdateAcks(dseq, sseq)` | .NET has simplified ack floor advancement; Go handles AckAll policy separately, uses original dseq from pending, walks forward for floor advancement |
| `o.EncodedState()` | filestore.go:11857 | PORTED | `ConsumerFileStore.EncodedState()` | |
| `o.encodeState()` | filestore.go:11863 | PORTED | `ConsumerStateCodec.Encode()` | |
| `o.UpdateConfig(cfg)` | filestore.go:11872 | MISSING | — | Updates consumer config and rewrites meta file |
| `o.Update(state)` | filestore.go:11883 | PARTIAL | `ConsumerFileStore.Update(state)` | .NET does direct replacement; Go validates ranges, deep-copies pending/redelivered, checks for outdated updates |
| `o.encryptState(buf)` | filestore.go:11932 | MISSING | — | AEAD encryption of consumer state |
| `dios` semaphore + `init()` | filestore.go:11948 | NOT_APPLICABLE | — | Go disk I/O semaphore to limit OS thread blocking; .NET uses async I/O natively |
| `o.writeState(buf)` | filestore.go:11969 | PARTIAL | `ConsumerFileStore.FlushState()` | .NET writes synchronously with File.WriteAllBytes; Go has encryption, writing flag, dios semaphore, atomic write |
| `o.updateConfig(cfg)` | filestore.go:12005 | MISSING | — | Internal config update for ephemeral recovery |
| `o.writeConsumerMeta()` | filestore.go:12014 | MISSING | — | Writes meta.json + meta.sum with optional encryption and key file |
| `checkConsumerHeader(hdr)` | filestore.go:12070 | PORTED | `ConsumerStateCodec.Decode()` (inline check) | Magic byte and version check done inline in .NET decode |
| `o.copyPending()` | filestore.go:12082 | PORTED | `ConsumerFileStore.State()` (inline copy) | Done inline in State() method |
| `o.copyRedelivered()` | filestore.go:12090 | PORTED | `ConsumerFileStore.State()` (inline copy) | Done inline in State() method |
| `o.Type()` | filestore.go:12099 | PORTED | `ConsumerFileStore.Type()` | |
| `o.State()` | filestore.go:12103 | PORTED | `ConsumerFileStore.State()` | |
| `o.BorrowState()` | filestore.go:12109 | PORTED | `ConsumerFileStore.BorrowState()` | |
| `o.stateWithCopy(doCopy)` | filestore.go:12113 | PARTIAL | `ConsumerFileStore.State()` / `BorrowState()` | .NET always returns from memory; Go falls back to disk read with decryption |
| `o.stateWithCopyLocked(doCopy)` | filestore.go:12120 | PARTIAL | `ConsumerFileStore.State()` / `BorrowState()` | .NET has no encrypted disk fallback |
| `o.loadState()` | filestore.go:12203 | PORTED | `ConsumerFileStore` constructor | .NET loads state in constructor from disk |
| `decodeConsumerState(buf)` | filestore.go:12216 | PORTED | `ConsumerStateCodec.Decode()` | Full Go wire format compatibility including v1 and v2 |
| `o.Stop()` | filestore.go:12328 | PORTED | `ConsumerFileStore.Stop()` | |
| `o.waitOnFlusher()` | filestore.go:12368 | PARTIAL | `ConsumerFileStore.Stop()` (Task.Wait) | .NET uses Task.Wait with timeout; Go polls with sleep |
| `o.Delete()` | filestore.go:12383 | PORTED | `ConsumerFileStore.Delete()` | |
| `o.StreamDelete()` | filestore.go:12387 | PORTED | `ConsumerFileStore.StreamDelete()` | |
| `o.delete(streamDeleted)` | filestore.go:12391 | PARTIAL | `ConsumerFileStore.Delete()` | .NET always deletes file; Go skips dir removal if stream was already deleted |
<!-- ### fileStore methods — Consumer Registration (lines 12423–12440) -->
| `fs.AddConsumer(o)` | filestore.go:12423 | MISSING | — | Registers a ConsumerStore with the fileStore's consumer list |
| `fs.RemoveConsumer(o)` | filestore.go:12430 | MISSING | — | Removes a ConsumerStore from the fileStore's consumer list |
<!-- ### Compression Utilities (lines 12446–12544) -->
| `CompressionInfo.MarshalMetadata()` | filestore.go:12451 | MISSING | — | Encodes compression metadata header (algorithm + original size) |
| `CompressionInfo.UnmarshalMetadata(b)` | filestore.go:12459 | MISSING | — | Decodes compression metadata header |
| `StoreCompression.Compress(buf)` | filestore.go:12477 | PORTED | `S2Codec.CompressWithTrailingChecksum()` | .NET uses IronSnappy; Go uses S2 stream writer |
| `StoreCompression.Decompress(buf)` | filestore.go:12517 | PORTED | `S2Codec.DecompressWithTrailingChecksum()` | .NET uses IronSnappy; Go uses S2 stream reader |
<!-- ### Atomic File Write (lines 12549–12593) -->
| `fs.writeFileWithOptionalSync(name, data, perm)` | filestore.go:12549 | PORTED | `AtomicFileWriter.WriteAtomicallyAsync()` | .NET uses temp-file + rename pattern; Go identical pattern + optional O_SYNC + dios semaphore |
| `writeFileWithSync(name, data, perm)` | filestore.go:12553 | PARTIAL | `AtomicFileWriter.WriteAtomicallyAsync()` | .NET always async, no O_SYNC; Go forces sync |
| `writeAtomically(name, data, perm, sync)` | filestore.go:12557 | PORTED | `AtomicFileWriter.WriteAtomicallyAsync()` | Core implementation ported; .NET lacks O_SYNC flag and directory fsync |
<!-- ## DirStore — dirstore.go (full file, 725 lines) -->
| `validatePathExists(path, dir)` | dirstore.go:40 | MISSING | — | Path validation utility |
| `validateDirPath(path)` | dirstore.go:68 | MISSING | — | Directory path validation |
| `JWTChanged` callback type | dirstore.go:73 | MISSING | — | Change notification delegate |
| `DirJWTStore` struct | dirstore.go:77 | MISSING | — | JWT store with sharding, expiration, LRU |
| `newDir(dirPath, create)` | dirstore.go:89 | MISSING | — | Directory creation helper |
| `NewImmutableDirJWTStore(...)` | dirstore.go:110 | MISSING | — | Read-only JWT store factory |
| `NewDirJWTStore(...)` | dirstore.go:121 | MISSING | — | Writable JWT store factory |
| `deleteType` enum | dirstore.go:133 | MISSING | — | NoDelete / RenameDeleted / HardDelete |
| `NewExpiringDirJWTStore(...)` | dirstore.go:150 | MISSING | — | JWT store with TTL, LRU eviction, limits |
| `store.IsReadOnly()` | dirstore.go:193 | MISSING | — | |
| `store.LoadAcc(publicKey)` | dirstore.go:197 | MISSING | — | Load account JWT |
| `store.SaveAcc(publicKey, jwt)` | dirstore.go:201 | MISSING | — | Save account JWT |
| `store.LoadAct(hash)` | dirstore.go:205 | MISSING | — | Load activation JWT |
| `store.SaveAct(hash, jwt)` | dirstore.go:209 | MISSING | — | Save activation JWT |
| `store.Close()` | dirstore.go:213 | MISSING | — | Close expiration tracker |
| `store.Pack(maxJWTs)` | dirstore.go:223 | MISSING | — | Pack JWTs for replication |
| `store.PackWalk(maxJWTs, cb)` | dirstore.go:267 | MISSING | — | Streaming pack with callback |
| `store.Merge(pack)` | dirstore.go:318 | MISSING | — | Merge packed JWTs into store |
| `store.Reload()` | dirstore.go:339 | MISSING | — | Reload JWTs from disk |
| `store.pathForKey(publicKey)` | dirstore.go:377 | MISSING | — | Compute file path with optional sharding |
| `store.load(publicKey)` | dirstore.go:395 | MISSING | — | Load JWT from disk |
| `store.write(path, publicKey, jwt)` | dirstore.go:412 | MISSING | — | Write JWT with hash tracking, LRU eviction |
| `store.delete(publicKey)` | dirstore.go:449 | MISSING | — | Delete JWT (rename or hard delete) |
| `store.save(publicKey, jwt)` | dirstore.go:478 | MISSING | — | Save JWT with change callback |
| `store.saveIfNewer(publicKey, jwt)` | dirstore.go:506 | MISSING | — | Save only if JWT is newer |
| `xorAssign(lVal, rVal)` | dirstore.go:549 | MISSING | — | XOR hash helper |
| `store.Hash()` | dirstore.go:556 | MISSING | — | Return XOR hash of all indexed JWTs |
| `jwtItem` struct | dirstore.go:567 | MISSING | — | Priority queue item |
| `expirationTracker` struct | dirstore.go:575 | MISSING | — | Heap + LRU + hash tracker |
| `expirationTracker` heap methods | dirstore.go:587–619 | MISSING | — | Len, Less, Swap, Push, Pop |
| `pq.updateTrack(publicKey)` | dirstore.go:621 | MISSING | — | Update expiration + LRU position |
| `pq.unTrack(publicKey)` | dirstore.go:635 | MISSING | — | Remove from tracker |
| `pq.track(publicKey, hash, jwt)` | dirstore.go:643 | MISSING | — | Add/update tracking entry |
| `pq.close()` | dirstore.go:672 | MISSING | — | Stop expiration goroutine |
| `store.startExpiring(...)` | dirstore.go:680 | MISSING | — | Start background expiration ticker |
<!-- ## Summary Counts -->
<!-- ### FileStore (lines 10001–12593) -->
| Status | Count |
| PORTED | 22 |
| PARTIAL | 23 |
| MISSING | 24 |
| NOT_APPLICABLE | 4 |
| **Total** | **73** |
<!-- ### DirStore (dirstore.go) -->
| Status | Count |
| PORTED | 0 |
| PARTIAL | 0 |
| MISSING | 33 |
| NOT_APPLICABLE | 0 |
| **Total** | **33** |
<!-- ### Combined -->
| Status | Count |
| PORTED | 22 |
| PARTIAL | 23 |
| MISSING | 57 |
| NOT_APPLICABLE | 4 |
| **Total** | **106** |
<!-- ### Key Gap Areas -->


<!-- === JS-5: Cluster (jetstream_cluster.go) === -->
<!-- JS-5a: lines 1-4000 -->
| `jetStreamCluster` struct | :44-84 | PARTIAL | `JetStreamMetaGroup` in `Cluster/JetStreamMetaGroup.cs` | .NET has meta group tracking (streams map, inflight, peer management) but missing: `streamResults`/`consumerResults` subscriptions, `stepdown`/`peerRemove`/`peerStreamMove`/`peerStreamCancelMove` subscriptions, `qch`/`stopped` channels, `lastMetaSnapTime`/`lastMetaSnapDuration` monitoring fields |
| `inflightStreamInfo` struct | :87-91 | PORTED | `InflightInfo` record in `JetStreamMetaGroup.cs:1016` | Mapped to a record with `OpsCount`, `Deleted`, `Assignment` |
| `inflightConsumerInfo` struct | :94-98 | PORTED | `InflightInfo` record in `JetStreamMetaGroup.cs:1016` | Shared record type for both stream and consumer inflight |
| `peerRemoveInfo` struct | :101-106 | MISSING | — | Peer-remove reply tracking for responding after quorum |
| `Placement` struct | :109-113 | PORTED | `PlacementPolicy` in `Cluster/PlacementEngine.cs:229-243` | .NET adds `ExcludeTags` and `UniqueTag` beyond Go's Cluster+Tags |
| `entryOp` type + constants | :116-150 | PARTIAL | `MetaEntryType` enum in `JetStreamMetaGroup.cs:967-990` | .NET has 7 values (StreamCreate, StreamDelete, ConsumerCreate, ConsumerDelete, PeerAdd, PeerRemove, Unknown). Missing granular stream ops: `streamMsgOp`, `purgeStreamOp`, `deleteMsgOp`, `updateDeliveredOp`, `updateAcksOp`, `updateSkipOp`, `compressedStreamMsgOp`, `deleteRangeOp`, `batchMsgOp`, `batchCommitMsgOp`, `resetSeqOp`, `addPendingRequest`, `removePendingRequest` |
| `raftGroup` struct | :154-163 | PORTED | `RaftGroup` in `Cluster/ClusterAssignmentTypes.cs:9-105` | Full port with `Name`, `Peers`, `Storage`, `Cluster`, `Preferred`, plus .NET-specific `DesiredReplicas`, `QuorumSize`, `IsMember`, `SetPreferred`, `RemovePeer`, `AddPeer` helpers |
| `streamAssignment` struct | :166-184 | PORTED | `StreamAssignment` in `ClusterAssignmentTypes.cs:111-139` | Core fields ported. Missing: `Client`, `Restore`, `err`, `unsupported`, `resetting` fields |
| `unsupportedStreamAssignment` struct | :186-247 | MISSING | — | Entire unsupported stream assignment subsystem (info sub, handler) |
| `consumerAssignment` struct | :250-266 | PORTED | `ConsumerAssignment` in `ClusterAssignmentTypes.cs:145-164` | Core fields ported. Missing: `Client`, `State`, `err`, `unsupported` fields |
| `unsupportedConsumerAssignment` struct | :268-330 | MISSING | — | Entire unsupported consumer assignment subsystem |
| `writeableConsumerAssignment` struct | :332-340 | MISSING | — | Serialization DTO for consumer assignments in snapshots |
| `streamPurge` struct | :343-350 | MISSING | — | Replicated purge operation struct |
| `streamMsgDelete` struct | :353-360 | MISSING | — | Replicated message delete operation struct |
| Constants (`defaultStoreDirName`, etc.) | :362-367 | PARTIAL | — | No explicit constants in .NET; `defaultMetaGroupName` logic is embedded |
<!-- ## Section 2: Cluster State Queries (lines 370–577) -->
| `trackedJetStreamServers()` | :370-385 | MISSING | — | Mixed-mode node counting |
| `getJetStreamCluster()` | :387-399 | MISSING | — | Server-level accessor for JS cluster state |
| `JetStreamIsClustered()` | :401-403 | MISSING | — | Atomic boolean check |
| `JetStreamIsLeader()` | :405-407 | PARTIAL | `JetStreamMetaGroup.IsLeader()` | .NET has equivalent on meta group, not on Server |
| `JetStreamIsCurrent()` | :409-428 | MISSING | — | Meta RAFT currency check |
| `JetStreamSnapshotMeta()` | :430-451 | MISSING | — | Force meta snapshot |
| `JetStreamStepdownStream()` | :453-477 | PARTIAL | `ClusterControlApiHandlers.HandleStreamLeaderStepdown()` | .NET has API handler stub but missing actual RAFT node interaction |
| `JetStreamStepdownConsumer()` | :479-508 | PARTIAL | `ClusterControlApiHandlers.HandleConsumerLeaderStepdown()` | .NET has API handler stub but missing actual RAFT node interaction |
| `JetStreamSnapshotStream()` | :510-539 | MISSING | — | Force stream-level snapshot |
| `JetStreamClusterPeers()` | :541-568 | MISSING | — | Returns online JS peer names |
| `cc.isLeader()` | :571-577 | PORTED | `JetStreamMetaGroup.IsLeader()` | Equivalent logic |
<!-- ## Section 3: Stream/Consumer Currency & Health (lines 579–766) -->
| `cc.isStreamCurrent()` | :582-618 | MISSING | — | Checks if stream is up-to-date via RAFT node + catchup state |
| `isStreamHealthy()` | :622-679 | MISSING | — | Comprehensive stream health check (R1 check, node skew, monitor running, catching up, healthy) |
| `isConsumerHealthy()` | :683-746 | MISSING | — | Comprehensive consumer health check |
| `subjectsOverlap()` | :751-766 | MISSING | — | Cross-cluster subject overlap detection for stream assignments |
<!-- ## Section 4: Account/Stream Leader Queries (lines 768–1001) -->
| `getJetStreamFromAccount()` | :768-784 | MISSING | — | Account-level JS accessor |
| `JetStreamIsStreamLeader()` (Server) | :786-794 | MISSING | — | Server-level stream leader check |
| `JetStreamIsStreamLeader()` (Account) | :796-804 | MISSING | — | Account-level stream leader check |
| `JetStreamIsStreamCurrent()` | :806-814 | MISSING | — | Server-level stream currency check |
| `JetStreamIsConsumerLeader()` (Account) | :816-824 | MISSING | — | Account-level consumer leader check |
| `JetStreamIsConsumerLeader()` (Server) | :826-834 | MISSING | — | Server-level consumer leader check |
| `enableJetStreamClustering()` | :836-862 | MISSING | — | Startup: validates cluster name, routes, calls setupMetaGroup |
| `isClustered()` | :866-869 | MISSING | — | Lock-safe clustered check |
| `isClusteredNoLock()` | :875-877 | MISSING | — | Lock-free atomic clustered check |
| `setupMetaGroup()` | :879-993 | MISSING | — | Creates meta RAFT group: WAL, filestore, bootstrap, observer mode, starts monitorCluster |
| `getMetaGroup()` | :995-1002 | MISSING | — | Returns meta RaftNode |
| `server()` | :1004-1007 | NOT_APPLICABLE | — | Trivial getter |
<!-- ## Section 5: Leaderless/Current Checks (lines 1010–1121) -->
| `isLeaderless()` | :1010-1026 | MISSING | — | Checks if meta group has no leader beyond lost-quorum interval |
| `isGroupLeaderless()` | :1029-1071 | MISSING | — | Checks if a specific raft group is leaderless |
| `JetStreamIsStreamAssigned()` | :1073-1086 | MISSING | — | Server-level stream assignment check |
| `streamAssigned()` (jsAccount) | :1089-1101 | MISSING | — | Account-level stream assignment check |
| `cc.isStreamAssigned()` | :1104-1121 | MISSING | — | Core stream assignment membership check |
<!-- ## Section 6: Stream/Consumer Leader Determination (lines 1124–1280) -->
| `cc.isStreamLeader()` | :1124-1154 | MISSING | — | Checks if this node is leader for a stream's raft group |
| `cc.isConsumerLeader()` | :1157-1188 | MISSING | — | Checks if this node is leader for a consumer's raft group |
| `trackInflightStreamProposal()` | :1193-1209 | PORTED | `JetStreamMetaGroup.TrackInflightStreamProposal()` | Full equivalent with ops counting |
| `removeInflightStreamProposal()` | :1214-1229 | PORTED | `JetStreamMetaGroup.RemoveInflightStreamProposal()` | Full equivalent |
| `trackInflightConsumerProposal()` | :1234-1255 | PORTED | `JetStreamMetaGroup.TrackInflightConsumerProposal()` | Full equivalent |
| `removeInflightConsumerProposal()` | :1260-1280 | PORTED | `JetStreamMetaGroup.RemoveInflightConsumerProposal()` | Full equivalent |
<!-- ## Section 7: Quit/Stop Channels and Recovery State (lines 1282–1374) -->
| `clusterQuitC()` | :1283-1290 | MISSING | — | Returns cluster quit channel |
| `clusterStoppedC()` | :1293-1300 | MISSING | — | Returns cluster stopped channel |
| `setMetaRecovering()` | :1303-1310 | MISSING | — | Sets meta recovery state |
| `clearMetaRecovering()` | :1313-1317 | MISSING | — | Clears meta recovery state |
| `isMetaRecovering()` | :1320-1324 | MISSING | — | Checks meta recovery state |
| `recoveryUpdates` struct | :1327-1333 | MISSING | — | Recovery-time diff tracking for streams/consumers |
| `ru.removeStream()` | :1335-1342 | MISSING | — | Recovery diff: track stream removal |
| `ru.addStream()` | :1344-1347 | MISSING | — | Recovery diff: track stream addition |
| `ru.updateStream()` | :1349-1352 | MISSING | — | Recovery diff: track stream update |
| `ru.removeConsumer()` | :1354-1364 | MISSING | — | Recovery diff: track consumer removal |
| `ru.addOrUpdateConsumer()` | :1366-1373 | MISSING | — | Recovery diff: track consumer add/update |
<!-- ## Section 8: Orphan Detection (lines 1376–1454) -->
| `checkForOrphans()` | :1378-1428 | MISSING | — | Post-recovery orphan stream/consumer cleanup |
| `getOrphans()` | :1433-1453 | MISSING | — | Returns orphaned streams and consumers not in cluster assignments |
<!-- ## Section 9: monitorCluster (lines 1455–1825) -->
| `monitorCluster()` | :1455-1825 | PARTIAL | `JetStreamClusterMonitor.StartAsync()` in `Cluster/JetStreamClusterMonitor.cs` | .NET has basic channel-reading entry dispatch loop. Missing: compact/snapshot timer, leader check timer, health check timer, recovery state machine, snapshot scheduling (sync/async), leader change processing, cluster size checks, cold-boot mixed-mode adjustment |
<!-- ## Section 10: checkClusterSize and Snapshot Encoding (lines 1826–2238) -->
| `checkClusterSize()` | :1828-1869 | MISSING | — | Adjusts cluster size for mixed-mode deployments |
| `writeableStreamAssignment` struct | :1872-1879 | MISSING | — | Serialization DTO for snapshot encoding |
| `clusterStreamConfig()` | :1881-1888 | MISSING | — | Returns stream config from cluster assignment |
| `metaSnapshot()` | :1890-1895 | PARTIAL | `MetaSnapshotCodec.Encode()` | .NET has encoding but not the full `js.metaSnapshot()` wrapper |
| `applyMetaSnapshot()` | :1897-2028 | PARTIAL | `JetStreamClusterMonitor.ApplyMetaSnapshot()` + `JetStreamMetaGroup.ReplaceAllAssignments()` | .NET has basic snapshot apply via `ReplaceAllAssignments()`. Missing: the full diff algorithm (saAdd/saDel/saChk), consumer delta processing, recovery-aware branching, orphan cleanup post-snapshot |
| `decodeMetaSnapshot()` | :2031-2071 | PORTED | `MetaSnapshotCodec.Decode()` in `Cluster/MetaSnapshotCodec.cs` | S2-compressed JSON decode with version header |
| `encodeMetaSnapshot()` | :2075-2143 | PORTED | `MetaSnapshotCodec.Encode()` in `Cluster/MetaSnapshotCodec.cs` | S2-compressed JSON encode with version header, metrics tracking not ported |
| `collectStreamAndConsumerChanges()` | :2146-2237 | MISSING | — | Async snapshot pipeline: replay RAFT entries into assignment state machine |
<!-- ## Section 11: Recovery Helpers and Peer Management (lines 2240–2439) -->
| `setStreamAssignmentRecovering()` | :2241-2251 | MISSING | — | Marks stream assignment as recovering |
| `setConsumerAssignmentRecovering()` | :2254-2263 | MISSING | — | Marks consumer assignment as recovering |
| `sa.copyGroup()` | :2267-2272 | MISSING | — | Deep-copies stream assignment with group |
| `ca.copyGroup()` | :2276-2281 | MISSING | — | Deep-copies consumer assignment with group |
| `sa.missingPeers()` | :2284-2286 | PORTED | `RaftGroup.IsUnderReplicated` in `ClusterAssignmentTypes.cs:36` | Property-based equivalent |
| `processAddPeer()` | :2290-2339 | PORTED | `JetStreamMetaGroup.ProcessAddPeer()` in `JetStreamMetaGroup.cs:790-819` | .NET adds new peer to under-replicated streams. Missing: cluster affinity check, consumer re-assignment proposals |
| `processRemovePeer()` | :2342-2393 | PARTIAL | `JetStreamMetaGroup.ProcessRemovePeer()` in `JetStreamMetaGroup.cs:827-841` | .NET removes from known peers and calls RemovePeerFromStream. Missing: self-removal advisory, DisableJetStream, consumer ephemeral cleanup |
| `removePeerFromStream()` | :2396-2400 | PORTED | `JetStreamMetaGroup.RemovePeerFromStream()` in `JetStreamMetaGroup.cs:849-862` | Equivalent logic |
| `removePeerFromStreamLocked()` | :2403-2439 | PARTIAL | `JetStreamMetaGroup.RemovePeerFromStream()` | .NET has basic peer removal + remap. Missing: RAFT proposals for stream + consumer reassignment |
<!-- ## Section 12: Meta Entry Application (lines 2440–2621) -->
| `hasPeerEntries()` | :2442-2449 | MISSING | — | Checks if entries contain peer add/remove |
| `sa.recoveryKey()` | :2453-2458 | MISSING | — | Recovery key generation for stream assignments |
| `ca.streamRecoveryKey()` | :2460-2465 | MISSING | — | Recovery key for consumer's parent stream |
| `ca.recoveryKey()` | :2467-2472 | MISSING | — | Recovery key generation for consumer assignments |
| `applyMetaEntries()` | :2474-2608 | PARTIAL | `JetStreamClusterMonitor.ApplyMetaEntry()` in `JetStreamClusterMonitor.cs:108-146` | .NET dispatches on JSON "Op" field. Go dispatches on binary `entryOp` byte. Missing: catchup entry handling, snapshot entry handling, peer add/remove entry handling, recovery-aware branching, peer-remove reply delivery |
<!-- ## Section 13: raftGroup Helpers (lines 2611–2837) -->
| `rg.isMember()` | :2611-2621 | PORTED | `RaftGroup.IsMember()` in `ClusterAssignmentTypes.cs:48` | Equivalent |
| `rg.setPreferred()` | :2623-2656 | PARTIAL | `RaftGroup.SetPreferred()` in `ClusterAssignmentTypes.cs:55-61` | .NET requires explicit peer ID. Go version auto-selects from online peers |
| `createRaftGroup()` | :2659-2789 | MISSING | — | Full RAFT group creation: WAL setup, filestore/memstore, peer bootstrap, campaign, HA asset limits check |
| `mset.raftGroup()` | :2792-2802 | NOT_APPLICABLE | — | Trivial accessor |
| `mset.raftNode()` | :2804-2811 | NOT_APPLICABLE | — | Trivial accessor |
| `mset.removeNode()` | :2813-2820 | MISSING | — | Deletes RAFT node from stream |
| `genPeerInfo()` | :2824-2837 | MISSING | — | Helper for peer set splitting during migration |
<!-- ## Section 14: monitorStream (lines 2839–3506) -->
| `waitOnConsumerAssignments()` | :2842-2892 | MISSING | — | Waits for consumer assignments before proceeding (interest retention) |
| `monitorStream()` | :2895-3506 | PARTIAL | `StreamReplicaGroup` in `Cluster/StreamReplicaGroup.cs` | .NET has a simplified stream replica group model with election, propose, apply, snapshot. Missing: the full monitor loop (apply queue, leader change, compact timer, migration monitoring, direct access monitoring, interest state check, restore handling, scale-down logic, catchup error handling, `resetClusteredState` integration) |
<!-- ## Section 15: Stream Migration and Reset (lines 3508–3641) -->
| `isMigrating()` | :3509-3531 | MISSING | — | Detects stream migration (group peers != config replicas) |
| `resetClusteredState()` | :3534-3637 | MISSING | — | Full clustered state reset: stepdown, delete/stop node, re-create stream + consumers |
| `isControlHdr()` | :3639-3641 | NOT_APPLICABLE | — | Trivial header check |
<!-- ## Section 16: applyStreamEntries (lines 3643–4000) -->
| `applyStreamEntries()` | :3643-4000+ | PARTIAL | `StreamReplicaGroup.ApplyCommittedEntriesAsync()` in `StreamReplicaGroup.cs:228-269` | .NET dispatches on string-based command prefixes (smsg:, centry:, +peer:, -peer:). Go dispatches on binary `entryOp`. Missing: full `batchMsgOp`/`batchCommitMsgOp` batch apply logic, `deleteMsgOp` with erase/remove, `purgeStreamOp` with request replay, `EntrySnapshot` with `StreamReplicatedState`, `EntryRemovePeer` processing, recovering-aware error handling, `resetClusteredState` on error |
<!-- ## Section 17: Encoding/Decoding (referenced throughout, codec files) -->
| `encodeAddStreamAssignment()` | :8703+ (referenced) | PORTED | `AssignmentCodec.EncodeStreamAssignment()` in `Cluster/AssignmentCodec.cs:52-53` | JSON serialization |
| `decodeStreamAssignment()` | :8733+ (referenced) | PORTED | `AssignmentCodec.DecodeStreamAssignment()` in `Cluster/AssignmentCodec.cs:61-74` | JSON deserialization |
| `encodeAddConsumerAssignment()` | :9175+ (referenced) | PORTED | `AssignmentCodec.EncodeConsumerAssignment()` in `Cluster/AssignmentCodec.cs:87-88` | JSON serialization |
| `decodeConsumerAssignment()` | :9195+ (referenced) | PORTED | `AssignmentCodec.DecodeConsumerAssignment()` in `Cluster/AssignmentCodec.cs:96-109` | JSON deserialization |
| `encodeAddConsumerAssignmentCompressed()` | :9226+ (referenced) | PORTED | `AssignmentCodec.CompressIfLarge()` in `Cluster/AssignmentCodec.cs:131-141` | Snappy compression with sentinel byte |
| `decodeConsumerAssignmentCompressed()` | :9238+ (referenced) | PORTED | `AssignmentCodec.DecompressIfNeeded()` in `Cluster/AssignmentCodec.cs:151-157` | Snappy decompression |
<!-- ## Section 18: Placement and Selection (referenced, PlacementEngine) -->
| `selectPeerGroup()` | :7212+ (referenced) | PORTED | `PlacementEngine.SelectPeerGroup()` in `Cluster/PlacementEngine.cs:34-97` | Full pipeline: cluster affinity, tag filter, HA limit split, weighted score sort, unique-tag constraint |
| `remapStreamAssignment()` | :7077+ (referenced) | PORTED | `JetStreamMetaGroup.RemapStreamAssignment()` in `JetStreamMetaGroup.cs:872-904` | Replacement peer selection with fallback shrink |
<!-- ## Section 19: Clustered Request Processing (referenced) -->
| `jsClusteredStreamRequest()` | :7620+ (referenced) | PORTED | `ClusteredRequestProcessor` in `Api/ClusteredRequestProcessor.cs` | Channel-per-request pattern using TaskCompletionSource; register, wait, deliver, cancel-all |
<!-- ## Summary Counts -->
| Status | Count |
| PORTED | 25 |
| PARTIAL | 14 |
| MISSING | 48 |
| NOT_APPLICABLE | 4 |
| DEFERRED | 0 |
| **Total** | **91** |
<!-- ## Key Gaps by Severity -->
<!-- ### Critical (missing core cluster functionality) -->
<!-- ### High (missing health/status checks) -->
<!-- ### Medium (missing operational features) -->
<!-- JS-5b: lines 4001-8000 -->
| `stream.skipBatchIfRecovering` | 4034-4066 | MISSING | -- | Batch recovery skip logic for stream RAFT apply; requires real RAFT + stream integration |
| `jetStream.applyStreamMsgOp` | 4068-4242 | PARTIAL | `StreamReplicaGroup.ApplyStreamMsgOp` | .NET has simplified Store/Remove/Purge enum but lacks S2 decompression, flow-control reply, lastSeq mismatch retry, inflight/counter tracking, preAck clearing |
| `Server.replicas` | 4246-4259 | MISSING | -- | Converts RAFT node peers to external PeerInfo with lag/active time |
| `jetStream.processStreamLeaderChange` | 4262-4365 | PARTIAL | `JetStreamMetaGroup.ProcessLeaderChange` | .NET has leader change event + inflight clearing but no dedupe-ID clearing, inflight map clearing, advisory sending, stream setLeader, or API response to client |
| `stream.shouldSendLostQuorum` | 4371-4379 | MISSING | -- | Throttled quorum-loss advisory gate |
| `Server.sendStreamLostQuorumAdvisory` | 4381-4414 | MISSING | -- | Stream lost-quorum advisory publication |
| `Server.sendStreamLeaderElectAdvisory` | 4416-4444 | MISSING | -- | Stream leader-elected advisory publication |
| `jetStream.streamAssignment` (lookup) | 4448-4458 | PORTED | `JetStreamMetaGroup.GetStreamAssignment` | Direct lookup by account+stream |
| `jetStream.streamAssignmentOrInflight` | 4462-4481 | PARTIAL | `JetStreamMetaGroup.IsStreamInflight` + `GetStreamAssignment` | Separate methods exist but no single combined lookup that returns the SA from inflight |
| `jetStream.streamAssignmentsOrInflightSeq` | 4485-4507 | MISSING | -- | Iterator over all stream assignments (applied + inflight) for an account |
| `jetStream.streamAssignmentsOrInflightSeqAllAccounts` | 4511-4538 | MISSING | -- | Iterator over all stream assignments across all accounts |
| `jetStream.processStreamAssignment` | 4540-4647 | PARTIAL | `JetStreamMetaGroup.ProcessStreamAssignment` | .NET validates and adds assignment but lacks account lookup, stream creation dispatch, raft group node transfer, unsupported stream detection, syncSubject bug check |
| `jetStream.processUpdateStreamAssignment` | 4650-4763 | PARTIAL | `JetStreamMetaGroup.ProcessUpdateStreamAssignment` | .NET updates config but lacks member check, raft node transfer, stream removal on non-member, unsupported handling |
| `Server.removeStream` | 4767-4794 | MISSING | -- | Removes stream from non-member server (stepdown, node delete, monitor quit, stop) |
| `jetStream.processClusterUpdateStream` | 4798-4940 | MISSING | -- | Full cluster stream update with scaling up/down, raft group creation, monitor start/stop, advisory response |
| `jetStream.processClusterCreateStream` | 4944-5213 | MISSING | -- | Full cluster stream create with raft group, monitor goroutine, restore handling, single-replica path |
| `jetStream.processStreamRemoval` | 5216-5264 | PARTIAL | `JetStreamMetaGroup.ProcessStreamRemoval` / `JetStreamClusterMonitor.ProcessStreamRemoval` | .NET removes assignment from meta state; lacks account lookup, created-time check, file cleanup |
| `jetStream.processClusterDeleteStream` | 5266-5360 | MISSING | -- | Full stream deletion: raft node delete, monitor shutdown, file cleanup, response sending |
| `jetStream.processConsumerAssignment` | 5362-5541 | PARTIAL | `JetStreamMetaGroup.ProcessConsumerAssignment` | .NET validates and adds to meta state; lacks member check, raft group transfer, state capture, unsupported handling, non-member removal with stepdown |
| `jetStream.processConsumerRemoval` | 5543-5590 | PARTIAL | `JetStreamMetaGroup.ProcessConsumerRemoval` | .NET removes from meta state; lacks account lookup, created-time check, disk cleanup dispatch |
| `consumerAssignmentResult` (struct) | 5592-5597 | MISSING | -- | Result struct for consumer assignment error forwarding |
| `jetStream.processClusterCreateConsumer` | 5600-5854 | MISSING | -- | Full consumer create: raft group, add/update consumer, scale-down handling, monitor start, snapshot send, config update response |
| `jetStream.processClusterDeleteConsumer` | 5856-5922 | MISSING | -- | Full consumer deletion: stop consumer, node delete, file cleanup, response |
| `jetStream.consumerAssignment` (lookup) | 5926-5931 | PORTED | `JetStreamMetaGroup.GetConsumerAssignment` | Lookup by account/stream/consumer |
| `jetStream.consumerAssignmentOrInflight` | 5935-5955 | PARTIAL | `JetStreamMetaGroup.IsConsumerInflight` + `GetConsumerAssignment` | Separate methods but no combined lookup returning the CA from inflight |
| `jetStream.consumerAssignmentsOrInflightSeq` | 5959-5986 | MISSING | -- | Iterator over consumer assignments (applied + inflight) for account/stream |
| `jsAccount.consumerAssigned` | 5989-5999 | MISSING | -- | Checks if this server has a consumer assigned |
| `jetStreamCluster.isConsumerAssigned` | 6003-6025 | MISSING | -- | Checks consumer membership in cluster |
| `consumer.streamAndNode` | 6028-6035 | NOT_APPLICABLE | -- | Go-specific accessor for consumer's stream + raft node |
| `consumer.replica` | 6039-6049 | NOT_APPLICABLE | -- | Go-specific replica count getter |
| `consumer.raftGroup` | 6051-6061 | NOT_APPLICABLE | -- | Go-specific raft group accessor |
| `consumer.clearRaftNode` | 6063-6070 | NOT_APPLICABLE | -- | Go-specific raft node clear |
| `consumer.raftNode` | 6072-6079 | NOT_APPLICABLE | -- | Go-specific raft node accessor |
| `jetStream.monitorConsumer` | 6081-6349 | MISSING | -- | Consumer RAFT monitor loop: apply entries, leader change, snapshot, migration monitoring, compaction |
| `jetStream.applyConsumerEntries` | 6351-6531 | PARTIAL | `StreamReplicaGroup.ApplyConsumerEntry` | .NET has simplified Ack/Nak/Deliver/Term/Progress enum tracking but lacks full op decoding (updateDeliveredOp, updateAcksOp, updateSkipOp, resetSeqOp, addPendingRequest, removePendingRequest), snapshot application, peer removal |
| `consumer.processReplicatedAck` | 6535-6585 | MISSING | -- | Full replicated ack processing with AckAll gap handling, retention policy |
| `decodeAckUpdate` | 6590-6600 | MISSING | -- | Binary uvarint decoder for ack updates |
| `decodeDeliveredUpdate` | 6602-6620 | MISSING | -- | Binary uvarint/varint decoder for delivered updates |
| `jetStream.processConsumerLeaderChange` | 6622-6698 | MISSING | -- | Consumer leader change: advisory, setLeader, API response, pause advisory |
| `consumer.shouldSendLostQuorum` | 6701-6709 | MISSING | -- | Throttled consumer quorum-loss advisory gate |
| `Server.sendConsumerLostQuorumAdvisory` | 6711-6745 | MISSING | -- | Consumer lost-quorum advisory publication |
| `Server.sendConsumerLeaderElectAdvisory` | 6747-6777 | MISSING | -- | Consumer leader-elected advisory publication |
| `streamAssignmentResult` (struct) | 6779-6785 | MISSING | -- | Result struct for stream assignment error forwarding |
| `isInsufficientResourcesErr` | 6788-6790 | MISSING | -- | Error classification helper |
| `jetStream.processStreamAssignmentResults` | 6794-6870 | MISSING | -- | Stream assignment error result processing with retry across clusters |
| `jetStream.processConsumerAssignmentResults` | 6872-6906 | MISSING | -- | Consumer assignment error result processing |
| `streamAssignmentSubj` / `consumerAssignmentSubj` (consts) | 6908-6911 | MISSING | -- | System subject constants for assignment results |
| `jetStream.startUpdatesSub` | 6914-6937 | MISSING | -- | Subscribe to system subjects for assignment results, stepdown, peer remove, stream move |
| `jetStream.stopUpdatesSub` | 6940-6970 | MISSING | -- | Unsubscribe from system subjects |
| `Server.sendDomainLeaderElectAdvisory` | 6972-6999 | MISSING | -- | Domain leader-elected advisory publication |
| `jetStream.processLeaderChange` (meta) | 7001-7074 | PARTIAL | `JetStreamMetaGroup.ProcessLeaderChange` | .NET clears inflight and fires event; lacks server atomic update, startUpdatesSub/stopUpdatesSub, observer stepdown, streamsCheck fix |
| `jetStreamCluster.remapStreamAssignment` | 7077-7111 | PORTED | `JetStreamMetaGroup.RemapStreamAssignment` | .NET implements retain/replace/shrink logic |
| `selectPeerError` (struct + methods) | 7113-7207 | MISSING | -- | Detailed peer-selection error reporting with tag/storage/offline reasons |
| `jetStreamCluster.selectPeerGroup` | 7212-7507 | PARTIAL | `PlacementEngine.SelectPeerGroup` | .NET has tag filtering, cluster affinity, unique-tag, HA limit, weighted scoring; lacks per-peer storage availability check against MaxBytes, peerStreams/peerHA counting from live assignments, quorum check for online peers, detailed error reporting |
| `groupNameForStream` | 7509-7511 | MISSING | -- | Stream raft group name generator with hash |
| `groupNameForConsumer` | 7513-7515 | MISSING | -- | Consumer raft group name generator with hash |
| `groupName` | 7517-7520 | MISSING | -- | Group name helper: prefix-R{n}{storage}-{hash} |
| `jetStream.tieredStreamAndReservationCount` | 7524-7543 | MISSING | -- | Account-tier stream count + storage reservation tracking |
| `jetStream.createGroupForStream` | 7547-7576 | MISSING | -- | Creates raft group for new stream with multi-cluster fallback |
| `Account.selectLimits` | 7578-7596 | MISSING | -- | Selects account tier limits by replica count |
| `jetStream.jsClusteredStreamLimitsCheck` | 7599-7618 | MISSING | -- | Pre-proposal stream limits validation (max streams, account limits) |
| `Server.jsClusteredStreamRequest` | 7620-7701 | PARTIAL | `ClusteredRequestProcessor` | .NET has request-id correlation + timeout pattern; lacks full stream config validation, subject overlap check, placement + group creation, meta propose |
| `sysRequest[T]` | 7710-7755 | MISSING | -- | Generic blocking system-account request utility |
| `Server.jsClusteredStreamUpdateRequest` | 7757-8000+ | MISSING | -- | Full clustered stream update: config validation, move request handling, replica scaling, consumer remapping |
| `lostQuorumAdvInterval` (const) | 4368 | MISSING | -- | 10-second throttle interval for quorum-loss advisories |
<!-- ## Counts -->
| Status | Count |
| PORTED | 4 |
| PARTIAL | 14 |
| MISSING | 38 |
| NOT_APPLICABLE | 5 |
| **Total** | **61** |
<!-- ## Analysis -->
<!-- ### Well-covered areas (PORTED/PARTIAL): -->
<!-- ### Major gaps: -->
<!-- JS-5c: lines 8001-end -->
| `Cluster/JetStreamMetaGroup.cs` | Meta-group assignment tracking, proposals, inflight, peer mgmt |
| `Cluster/StreamReplicaGroup.cs` | Per-stream RAFT group model, propose/apply/stepdown, msg ops |
| `Cluster/PlacementEngine.cs` | Topology-aware peer selection (selectPeerGroup) |
| `Cluster/ClusterAssignmentTypes.cs` | RaftGroup, StreamAssignment, ConsumerAssignment types |
| `Cluster/AssignmentCodec.cs` | JSON encode/decode for assignments, S2 compression |
| `Cluster/MetaSnapshotCodec.cs` | Binary meta snapshot encode/decode |
| `Cluster/JetStreamClusterMonitor.cs` | Background loop consuming RAFT entries, dispatching |
| `Cluster/AssetPlacementPlanner.cs` | Simple replica count planning |
| `Api/Handlers/StreamApiHandlers.cs` | Clustered stream create/update/delete handlers |
| `Api/Handlers/ConsumerApiHandlers.cs` | Clustered consumer create/delete handlers |
| `Api/Handlers/ClusterControlApiHandlers.cs` | Meta stepdown, stream/consumer leader stepdown |
| `Api/ClusteredRequestProcessor.cs` | Pending request correlation (propose + wait) |
| `Snapshots/StreamSnapshotService.cs` | TAR-based snapshot create/restore |
<!-- ## Gap Table -->
| `jsClusteredStreamUpdateRequest` (tail) | jetstream_cluster.go:7757 (8001–8138) | PARTIAL | `StreamApiHandlers.HandleClusteredUpdateAsync` | Go version handles scale up/down, move requests, peer set reassignment, consumer migration. .NET version only does simple config update via `ProcessUpdateStreamAssignment`. Missing: scale up/down logic, move request handling, consumer peer reassignment. |
| `jsClusteredStreamDeleteRequest` | jetstream_cluster.go:8140–8165 | PORTED | `StreamApiHandlers.HandleClusteredDeleteAsync` | Proposes deletion to meta group, validates existence first. |
| `jsClusteredStreamPurgeRequest` | jetstream_cluster.go:8168–8212 | PARTIAL | `StreamApiHandlers.HandlePurge` (local) | Go proposes purge through RAFT when clustered (stream group node). .NET has local purge only, no RAFT proposal path for purge. |
| `jsClusteredStreamRestoreRequest` | jetstream_cluster.go:8214–8262 | PARTIAL | `StreamApiHandlers.HandleRestore` + `StreamSnapshotService` | Go creates a new RAFT group, sets preferred leader, proposes as stream assignment with Restore state. .NET has local restore path but no RAFT-based clustered restore proposal. |
| `allPeersOffline` | jetstream_cluster.go:8265–8278 | MISSING | — | Server-level helper to check if all peers in a RAFT group are offline. No nodeToInfo equivalent in .NET. |
| `jsClusteredStreamListRequest` | jetstream_cluster.go:8282–8444 | PARTIAL | `JetStreamApiRouter` routes to `StreamApiHandlers.HandleList` | Go does scatter-gather across cluster (sends internal msgs to each stream peer, collects responses, handles timeouts, offline peers, missing names). .NET has local-only list from StreamManager. |
| `jsClusteredConsumerListRequest` | jetstream_cluster.go:8448–8591 | PARTIAL | `JetStreamApiRouter` routes to `ConsumerApiHandlers.HandleList` | Same scatter-gather pattern as stream list. .NET has local-only consumer list. |
| `encodeStreamPurge` | jetstream_cluster.go:8593–8598 | MISSING | — | Binary encode of streamPurge for RAFT proposal. |
| `decodeStreamPurge` | jetstream_cluster.go:8600–8604 | MISSING | — | Binary decode of streamPurge from RAFT log. |
| `jsClusteredConsumerDeleteRequest` | jetstream_cluster.go:8606–8643 | PORTED | `ConsumerApiHandlers.HandleClusteredDeleteAsync` | Proposes consumer deletion via meta RAFT group. |
| `encodeMsgDelete` | jetstream_cluster.go:8645–8650 | MISSING | — | Binary encode of streamMsgDelete for RAFT proposal. |
| `decodeMsgDelete` | jetstream_cluster.go:8652–8656 | MISSING | — | Binary decode of streamMsgDelete from RAFT log. |
| `jsClusteredMsgDeleteRequest` | jetstream_cluster.go:8658–8701 | MISSING | — | Clustered message delete by sequence — proposes through stream RAFT node. No .NET equivalent. |
| `encodeAddStreamAssignment` | jetstream_cluster.go:8703–8711 | PORTED | `AssignmentCodec.EncodeStreamAssignment` | JSON serialization of stream assignment. |
| `encodeUpdateStreamAssignment` | jetstream_cluster.go:8713–8721 | PARTIAL | `AssignmentCodec.EncodeStreamAssignment` | Go uses a separate opcode byte (updateStreamOp). .NET uses same encoder without op-code prefix. |
| `encodeDeleteStreamAssignment` | jetstream_cluster.go:8723–8731 | PARTIAL | `AssignmentCodec.EncodeStreamAssignment` | Go uses removeStreamOp opcode byte. .NET lacks op-code prefix differentiation. |
| `decodeStreamAssignment` | jetstream_cluster.go:8733–8742 | PORTED | `AssignmentCodec.DecodeStreamAssignment` | JSON deserialization of stream assignment. |
| `decodeStreamAssignmentConfig` | jetstream_cluster.go:8744–8764 | PARTIAL | `AssignmentCodec.DecodeStreamAssignment` | Go does DisallowUnknownFields for forward-compat (marks as unsupported). .NET does basic decode without unsupported field detection. |
| `encodeDeleteRange` | jetstream_cluster.go:8766–8771 | MISSING | — | Binary encode of DeleteRange for catchup gap markers. |
| `decodeDeleteRange` | jetstream_cluster.go:8773–8780 | MISSING | — | Binary decode of DeleteRange. |
| `createGroupForConsumer` | jetstream_cluster.go:8783–8821 | PARTIAL | `PlacementEngine.SelectPeerGroup` | Go selects peers from stream's group, checks active/offline, enforces quorum, picks storage type. .NET PlacementEngine does general peer selection but not the specific consumer-from-stream-peers logic. |
| `jsClusteredConsumerRequest` | jetstream_cluster.go:8824–9173 | PARTIAL | `ConsumerApiHandlers.HandleClusteredCreateAsync` | Go has full validation: account limits, maxConsumers check, WQ policy checks, ephemeral naming, HA asset limits, scale up/down, consumer state transfer. .NET does basic leader + stream-exists validation only. |
| `encodeAddConsumerAssignment` | jetstream_cluster.go:9175–9183 | PORTED | `AssignmentCodec.EncodeConsumerAssignment` | JSON serialization of consumer assignment. |
| `encodeDeleteConsumerAssignment` | jetstream_cluster.go:9185–9193 | PARTIAL | `AssignmentCodec.EncodeConsumerAssignment` | Go uses removeConsumerOp opcode. .NET lacks op-code differentiation. |
| `decodeConsumerAssignment` | jetstream_cluster.go:9195–9204 | PORTED | `AssignmentCodec.DecodeConsumerAssignment` | JSON deserialization of consumer assignment. |
| `decodeConsumerAssignmentConfig` | jetstream_cluster.go:9206–9224 | PARTIAL | `AssignmentCodec.DecodeConsumerAssignment` | Go does unsupported-field detection + API level check. .NET does basic decode. |
| `encodeAddConsumerAssignmentCompressed` | jetstream_cluster.go:9226–9236 | PORTED | `AssignmentCodec.CompressIfLarge` | S2/Snappy compression for large consumer assignments. |
| `decodeConsumerAssignmentCompressed` | jetstream_cluster.go:9238–9250 | PORTED | `AssignmentCodec.DecompressIfNeeded` | Snappy decompression for compressed consumer assignments. |
| `decodeStreamMsg` | jetstream_cluster.go:9254–9309 | MISSING | — | Binary wire decode of replicated stream messages (seq, ts, subject, reply, hdr, msg, flags). Critical for RAFT replication. |
| `decodeBatchMsg` | jetstream_cluster.go:9311–9332 | MISSING | — | Binary decode of batch message wrapper (batchId, batchSeq, inner op). |
| `encodeStreamMsg` | jetstream_cluster.go:9339–9341 | MISSING | — | Binary wire encode of replicated stream messages. |
| `encodeStreamMsgAllowCompress` | jetstream_cluster.go:9343–9345 | MISSING | — | Wrapper calling encode with compression allowed. |
| `encodeStreamMsgAllowCompressAndBatch` | jetstream_cluster.go:9352–9421 | MISSING | — | Full binary encoder with S2 compression (threshold 8KB), batch support, source/mirror flags. Core of clustered message replication. |
| `compressThreshold` (const) | jetstream_cluster.go:9349 | MISSING | — | 8192-byte threshold for message compression. |
| `msgFlagFromSourceOrMirror` (const) | jetstream_cluster.go:9336 | MISSING | — | Flag constant for source/mirror tracking. |
| `supportsBinarySnapshot` | jetstream_cluster.go:9424–9428 | MISSING | — | Checks if all peers support binary snapshot format. |
| `supportsBinarySnapshotLocked` | jetstream_cluster.go:9432–9450 | MISSING | — | Lock-held version checking peer nodeInfo for binary snapshot support. |
| `streamSnapshot` (struct) | jetstream_cluster.go:9454–9461 | MISSING | — | Legacy JSON snapshot type (v1 format with deleted[]uint64). |
| `stateSnapshot` | jetstream_cluster.go:9464–9468 | MISSING | — | Creates binary/legacy snapshot for RAFT. .NET has `StreamSnapshotService` but it's TAR-based, not the RAFT binary snapshot format. |
| `stateSnapshotLocked` | jetstream_cluster.go:9472–9501 | MISSING | — | Lock-held snapshot with binary vs legacy fallback and EncodedStreamState. |
| `processClusteredInboundMsg` | jetstream_cluster.go:9507–9690 | MISSING | — | Proposes inbound published message to stream RAFT group. Core of clustered publish path: leader check, sealed check, limits, dedup, lag warning, message trace. |
| `streamLagWarnThreshold` (const) | jetstream_cluster.go:9504 | MISSING | — | 10,000 threshold for lag warning. |
| `getAndDeleteMsgTrace` | jetstream_cluster.go:9692–9703 | MISSING | — | Retrieves and deletes a message trace by sequence. |
| `streamSyncRequest` (struct) | jetstream_cluster.go:9707–9713 | MISSING | — | Request structure for stream catchup (Peer, FirstSeq, LastSeq, DeleteRangesOk, MinApplied). |
| `calculateSyncRequest` | jetstream_cluster.go:9717–9728 | MISSING | — | Calculates catchup request based on local state vs snapshot. |
| `processSnapshotDeletes` | jetstream_cluster.go:9732–9756 | MISSING | — | Processes deletes from snapshot (Compact, SyncDeleted). |
| `setCatchupPeer` | jetstream_cluster.go:9758–9768 | MISSING | — | Sets catchup peer with lag tracking. |
| `updateCatchupPeer` | jetstream_cluster.go:9771–9780 | MISSING | — | Decrements catchup lag by one. |
| `decrementCatchupPeer` | jetstream_cluster.go:9782–9796 | MISSING | — | Decrements catchup lag by N. |
| `clearCatchupPeer` | jetstream_cluster.go:9798–9804 | MISSING | — | Clears a single catchup peer. |
| `clearAllCatchupPeers` | jetstream_cluster.go:9807–9811 | MISSING | — | Clears all catchup peers. |
| `lagForCatchupPeer` | jetstream_cluster.go:9813–9820 | MISSING | — | Returns lag for a specific catchup peer. |
| `hasCatchupPeers` | jetstream_cluster.go:9822–9826 | MISSING | — | Returns true if any catchup peers exist. |
| `setCatchingUp` | jetstream_cluster.go:9828–9830 | MISSING | — | Atomic bool: set catching up. |
| `clearCatchingUp` | jetstream_cluster.go:9832–9834 | MISSING | — | Atomic bool: clear catching up. |
| `isCatchingUp` | jetstream_cluster.go:9836–9838 | MISSING | — | Atomic bool: is catching up. |
| `isCurrent` | jetstream_cluster.go:9842–9847 | MISSING | — | Check if non-leader stream is current (RAFT current + not catching up). |
| `maxConcurrentSyncRequests` (const) | jetstream_cluster.go:9850 | MISSING | — | 32 max concurrent sync requests. |
| Error sentinel vars (catchup errors) | jetstream_cluster.go:9852–9860 | MISSING | — | errCatchupCorruptSnapshot, errCatchupStalled, errCatchupStreamStopped, etc. |
| `processSnapshot` | jetstream_cluster.go:9863–10000+ | MISSING | — | Full stream snapshot processing: delete sync, calculate sync request, pause RAFT apply, semaphore-limited catchup with retries (max 3). ~140 lines. |
| `processCatchupMsg` | jetstream_cluster.go:10160–10251 | MISSING | — | Processes out-of-band catchup messages: deleteRange handling, S2 decompression, preAck clearing, store writes, dedup tracking. |
| `flushAllPending` | jetstream_cluster.go:10254–10256 | MISSING | — | Flush pending writes after snapshot install. |
| `handleClusterSyncRequest` | jetstream_cluster.go:10258–10265 | MISSING | — | Handler for inbound cluster sync requests; starts runCatchup goroutine. |
| `offlineClusterInfo` | jetstream_cluster.go:10268–10280 | MISSING | — | Returns ClusterInfo for offline RAFT group (all replicas marked offline). |
| `clusterInfo` | jetstream_cluster.go:10283–10355 | MISSING | — | Returns full ClusterInfo for RAFT group: leader, replicas, lag, current status, sorted by name. Central to /jsz and stream info responses. |
| `checkClusterInfo` | jetstream_cluster.go:10357–10365 | MISSING | — | Adjusts ClusterInfo replicas for catchup lag. |
| `streamAlternates` | jetstream_cluster.go:10370–10420 | MISSING | — | Returns ranked list of stream mirrors/alternates for client connection. |
| `handleClusterStreamInfoRequest` | jetstream_cluster.go:10423–10425 | MISSING | — | Inbound handler for cluster stream info (dispatches to processClusterStreamInfoRequest). |
| `processClusterStreamInfoRequest` | jetstream_cluster.go:10427–10460 | MISSING | — | Processes cluster stream info request: gathers state, config, cluster info, sources, mirror. |
| `defaultMaxTotalCatchupOutBytes` (const) | jetstream_cluster.go:10465 | MISSING | — | 64 MB max total server-wide catchup outbound bytes. |
| `gcbTotal` | jetstream_cluster.go:10468–10472 | MISSING | — | Returns total outstanding catchup bytes. |
| `gcbBelowMax` | jetstream_cluster.go:10476–10480 | MISSING | — | Returns true if catchup bytes below max. |
| `gcbAdd` | jetstream_cluster.go:10485–10493 | MISSING | — | Adds to server-wide and local catchup byte counters. |
| `gcbSubLocked` | jetstream_cluster.go:10499–10509 | MISSING | — | Subtracts from catchup byte counters (locked). |
| `gcbSub` | jetstream_cluster.go:10512–10516 | MISSING | — | Locking wrapper for gcbSubLocked. |
| `gcbSubLast` | jetstream_cluster.go:10522–10527 | MISSING | — | Final subtract + zero out local counter on catchup exit. |
| `cbKickChan` | jetstream_cluster.go:10530–10534 | MISSING | — | Returns kick channel for catchup bandwidth management. |
| `runCatchup` | jetstream_cluster.go:10536–10846 | MISSING | — | Leader-side catchup sender: flow control, batch sending, ack tracking, delete ranges, gap markers, activity timeout, global bandwidth cap. ~310 lines. |
| `syncSubjForStream` | jetstream_cluster.go:10850–10852 | MISSING | — | Returns sync subject for stream catchup. |
| `syncReplySubject` | jetstream_cluster.go:10854–10856 | MISSING | — | Returns reply subject for sync. |
| `infoReplySubject` | jetstream_cluster.go:10858–10860 | MISSING | — | Returns reply subject for info. |
| `syncAckSubject` | jetstream_cluster.go:10862–10864 | MISSING | — | Returns ack subject for sync flow control. |
| `syncSubject` | jetstream_cluster.go:10866–10880 | MISSING | — | Generates random sync subject with base encoding. |
| `clusterStreamInfoT` (const) | jetstream_cluster.go:10883 | MISSING | — | Subject template for cluster stream info requests. |
| `clusterConsumerInfoT` (const) | jetstream_cluster.go:10884 | MISSING | — | Subject template for cluster consumer info requests. |
| `jsaUpdatesSubT` / `jsaUpdatesPubT` (consts) | jetstream_cluster.go:10885–10886 | MISSING | — | Subject templates for JSC account-related update subscriptions. |
| `jscAllSubj` (const) | jetstream_cluster.go:10848 | MISSING | — | `$JSC.>` wildcard subscription for all cluster subjects. |
<!-- ## Status Counts -->
| Status | Count |
| PORTED | 8 |
| PARTIAL | 13 |
| MISSING | 56 |
| NOT_APPLICABLE | 0 |
| DEFERRED | 0 |
| **Total** | **77** |
<!-- ## Analysis -->
<!-- ### PORTED (8) -->
<!-- ### PARTIAL (13) -->
<!-- ### MISSING (56) -->

## Change Log

| Date | Change | By |
|------|--------|----|
| 2026-02-25 | JS-2a: store.go full + stream.go lines 1-4000. 172 symbols. PORTED:55 PARTIAL:30 MISSING:75 NA:12 | opus |
| 2026-02-25 | JS-2b: stream.go lines 4001-end. 116 symbols. PORTED:11 PARTIAL:24 MISSING:73 NA:8 | opus |
| 2026-02-25 | JS-3a: consumer.go lines 1-3500. 115 symbols. PORTED:23 PARTIAL:21 MISSING:70 NA:1 | opus |
| 2026-02-25 | JS-3b: consumer.go lines 3501-end. 105 symbols. PORTED:31 PARTIAL:28 MISSING:37 NA:9 | opus |
| 2026-02-25 | JS-4a: memstore + filestore 1-4000. 140 symbols. PORTED:37 PARTIAL:27 MISSING:71 NA:5 | opus |
| 2026-02-25 | JS-4b: filestore 4001-7000. 80 symbols. PORTED:14 PARTIAL:27 MISSING:37 NA:2 | opus |
| 2026-02-25 | JS-4c: filestore 7001-10000. 82 symbols. PORTED:26 PARTIAL:19 MISSING:32 NA:5 | opus |
| 2026-02-25 | JS-4d: filestore 10001-end + dirstore. 106 symbols. PORTED:22 PARTIAL:23 MISSING:57 NA:4 | opus |
| 2026-02-25 | JS-5a: jetstream_cluster.go lines 1-4000. 91 symbols. PORTED:25 PARTIAL:14 MISSING:48 NA:4 | opus |
| 2026-02-25 | JS-5b: jetstream_cluster.go lines 4001-8000. 61 symbols. PORTED:4 PARTIAL:14 MISSING:38 NA:5 | opus |
| 2026-02-25 | JS-5c: jetstream_cluster.go lines 8001-end. 77 symbols. PORTED:8 PARTIAL:13 MISSING:56 | opus |
| 2026-02-25 | File created with LLM analysis instructions | auto |
| 2026-02-25 | JS-1 Core sub-pass: analyzed jetstream.go, jetstream_api.go, jetstream_events.go, jetstream_errors.go, jetstream_versioning.go, jetstream_batching.go. 150+ symbols inventoried. | opus |
| 2026-02-25 | JS core config parity batch: added JetStream default constants (`JetStreamStoreDir`, max store/mem defaults) and server accessors (`JetStreamEnabled`, `JetStreamConfig`, `StoreDir`) with focused tests | codex |
| 2026-02-26 | JS config/model parity batch: extended `JetStreamOptions` (sync/compress/strict/unique-tag and account-limit fields), added parser support for extended jetstream config keys, and added core parity models (`JetStreamStats`, `JetStreamApiStats`, `JetStreamAccountLimits`, `JetStreamTier`) with focused tests | codex |
