Files
natsnet/docs/plans/2026-02-27-batch-26-websocket-implementation-plan.md
Joseph Doherty c05d93618e Add batch plans for batches 23-30 (rounds 12-15)
Generated design docs and implementation plans via Codex for:
- Batch 23: Routes
- Batch 24: Leaf Nodes
- Batch 25: Gateways
- Batch 26: WebSocket
- Batch 27: JetStream Core
- Batch 28: JetStream API
- Batch 29: JetStream Batching
- Batch 30: Raft Part 1

All plans include mandatory verification protocol and anti-stub guardrails.
Updated batches.md with file paths and planned status.
2026-02-27 16:33:10 -05:00

18 KiB

Batch 26 (WebSocket) Implementation Plan

For Codex: REQUIRED SUB-SKILL: Use executeplan to implement this plan task-by-task.

Goal: Port and verify Batch 26 WebSocket behavior end-to-end (37 features, 86 tests) from server/websocket.go and mapped Go tests without introducing stubs or fake test passes.

Architecture: Implement websocket protocol logic in focused websocket components, wire runtime behavior through ClientConnection and NatsServer partials, and execute ID-driven feature/test verification loops with hard build/test/status gates. Do low-level frame primitives first, then HTTP upgrade/options/listener integration, then mapped tests (including explicit benchmark classification handling).

Tech Stack: .NET 10, C# latest, xUnit 3, Shouldly, NSubstitute, PortTracker CLI, SQLite (porting.db)

Design doc: docs/plans/2026-02-27-batch-26-websocket-design.md


Batch Scope

  • Batch: 26 - WebSocket
  • Dependencies: Batches 16, 18 (must be complete before execution)
  • Features: 37
  • Tests: 86

Feature Groups (max ~20 each)

  1. Group A (20 features): 3506,3507,3509,3510,3511,3512,3513,3514,3515,3516,3517,3518,3519,3520,3521,3522,3523,3524,3525,3541
  2. Group B (17 features): 3526,3527,3528,3529,3530,3531,3532,3533,3534,3535,3536,3537,3538,3539,3540,3542,3543

MANDATORY VERIFICATION PROTOCOL

NON-NEGOTIABLE: Every feature and test in this batch must follow this protocol. Skipping any step is a plan violation.

What Counts as a Real Feature/Test

  1. Real feature:
    • Behavior implemented in runtime code (not placeholder return/no-op/throw stub).
    • Mapped to the Go method intent for that feature ID.
    • Covered by at least one related passing test.
  2. Real test:
    • Uses Arrange/Act/Assert.
    • Calls relevant production code.
    • Has meaningful Shouldly assertions.
    • Is not a placeholder pattern.

Per-Feature Verification Loop (REQUIRED for each feature ID)

  1. Read mapped Go method location and surrounding logic:
    • dotnet run --project tools/NatsNet.PortTracker -- feature show <feature-id> --db porting.db
    • sed -n '<start>,<end>p' golang/nats-server/server/websocket.go
  2. Write/adjust C# implementation for only that behavior slice.
  3. Build immediately:
    • dotnet build dotnet/
  4. Run related targeted tests (single method/class filter first).
  5. Record evidence (feature ID, Go line(s), test(s) run, pass summary) before status update.

Stub Detection Check (REQUIRED after each feature/test mini-batch)

Run both scans and resolve all hits before continuing:

# Production code stub scan
rg -n --pcre2 "(NotImplementedException|TODO: session 23|TODO: websocket|// TODO|// PLACEHOLDER|=>\\s*throw\\s+new\\s+NotImplementedException)" \
  dotnet/src/ZB.MOM.NatsNet.Server/WebSocket \
  dotnet/src/ZB.MOM.NatsNet.Server/ClientConnection.cs \
  dotnet/src/ZB.MOM.NatsNet.Server/NatsServer*.cs

# Empty/trivial method body scan in touched websocket-related files
rg -n --pcre2 "^\\s*(public|private|internal|protected).*\\)\\s*\\{\\s*\\}\\s*$" \
  dotnet/src/ZB.MOM.NatsNet.Server/WebSocket \
  dotnet/src/ZB.MOM.NatsNet.Server/ClientConnection.cs \
  dotnet/src/ZB.MOM.NatsNet.Server/NatsServer*.cs

# Test stub scan
rg -n --pcre2 "(Assert\\.True\\(true\\)|Assert\\.Pass\\(|NotImplementedException|// TODO|// PLACEHOLDER|ShouldBe\\(true\\);\\s*$)" \
  dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog

Build Gate (REQUIRED after each feature group)

After Group A and Group B, full build must pass:

dotnet build dotnet/ --verbosity minimal

If build fails, no status updates are allowed.

Test Gate (REQUIRED before marking features verified)

  1. Run all related tests for the feature group.
  2. Confirm Failed: 0.
  3. Only after test gate passes can feature IDs move to verified.

Example:

dotnet test dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ \
  --filter "FullyQualifiedName~WebSocketHandlerTests" --verbosity normal

Status Update Protocol (REQUIRED)

  1. Use max 15 IDs per batch-update command.
  2. Never update status without captured evidence for those exact IDs.
  3. Keep feature and test updates separate.
  4. If audit rejects update and behavior is truly implemented, use --override "<reason>" with explicit evidence note.

Examples:

dotnet run --project tools/NatsNet.PortTracker -- \
  feature batch-update --ids "3506-3517,3518" --set-status complete --db porting.db --execute

dotnet run --project tools/NatsNet.PortTracker -- \
  test batch-update --ids "3075,3076,3077,3078,3079,3080,3082,3083,3084,3085,3086,3087,3088,3089,3093" \
  --set-status verified --db porting.db --execute

Checkpoint Protocol Between Tasks (REQUIRED)

Between every task below:

  1. Run full build:
    • dotnet build dotnet/ --verbosity minimal
  2. Run full unit tests:
    • dotnet test dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ --verbosity normal
  3. If regressions exist, fix before next task.
  4. Commit checkpoint:
    • git add <changed-files>
    • git commit -m "<task checkpoint message>"

ANTI-STUB GUARDRAILS (NON-NEGOTIABLE)

Forbidden Patterns

Any appearance below in touched Batch 26 feature/test methods is disallowed:

  1. throw new NotImplementedException(...)
  2. Empty method body { } for mapped feature methods
  3. Trivial constant-return placeholder (return true;, return null;, return default;) where real behavior is required
  4. Assert.True(true) or equivalent always-pass assertions
  5. // TODO, // PLACEHOLDER, TODO: session 23 in mapped code paths
  6. Test methods that only assert non-null on unrelated constants without exercising websocket behavior

Hard Limits

  1. Max ~20 feature IDs per implementation group (already enforced by Group A/B).
  2. Max 15 IDs per status batch update.
  3. Max 1 feature group status finalization cycle at a time (finish Group A fully before Group B verification updates).
  4. No marking verified for features until related tests pass.
  5. No marking tests verified without direct pass evidence.

If You Get Stuck (REQUIRED)

  1. Do not leave stubs.
  2. Mark blocked feature/test IDs as deferred with explicit reason.
  3. Add a code comment near deferred code/test:
    • // DEFERRED(batch26): <specific dependency/runtime blocker>
  4. Continue with next unblocked ID.
  5. Revisit deferred IDs only after blocker is resolved.

Task 1: Dependency and Batch Readiness Gate

Files:

  • Modify: none
  • Validate context: porting.db, batch metadata

Step 1: Verify dependencies are complete

Run:

dotnet run --project tools/NatsNet.PortTracker -- batch show 16 --db porting.db
dotnet run --project tools/NatsNet.PortTracker -- batch show 18 --db porting.db
dotnet run --project tools/NatsNet.PortTracker -- batch ready --db porting.db

Expected: batch 26 is ready only after 16/18 are complete.

Step 2: Start batch 26

Run:

dotnet run --project tools/NatsNet.PortTracker -- batch start 26 --db porting.db

Expected: start succeeds; no dependency error.

Step 3: Materialize execution manifest

Run:

dotnet run --project tools/NatsNet.PortTracker -- batch show 26 --db porting.db

Expected: explicit list of 37 features + 86 tests used as task checklist.

Step 4: Commit checkpoint

git add porting.db
git commit -m "chore(batch26): start websocket batch after dependency gate"

Task 2: Implement Feature Group A (20 features, frame/core path)

Feature IDs:

  • 3506,3507,3509,3510,3511,3512,3513,3514,3515,3516,3517,3518,3519,3520,3521,3522,3523,3524,3525,3541

Files:

  • Modify: dotnet/src/ZB.MOM.NatsNet.Server/WebSocket/WebSocketTypes.cs
  • Modify: dotnet/src/ZB.MOM.NatsNet.Server/WebSocket/WebSocketConstants.cs
  • Modify: dotnet/src/ZB.MOM.NatsNet.Server/ClientConnection.cs
  • Add or modify focused websocket helper files under: dotnet/src/ZB.MOM.NatsNet.Server/WebSocket/
  • Test: dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/WebSocketHandlerTests.Impltests.cs

Step 1: Write/port failing tests for Group A behaviors

Start with tests covering: control-frame detection, masking/unmasking, close message creation, frame header generation, read/decompression paths, close enqueue, outbound collapse.

Run:

dotnet test dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ \
  --filter "FullyQualifiedName~WebSocketHandlerTests.WSIsControlFrame_ShouldSucceed|FullyQualifiedName~WebSocketHandlerTests.WSUnmask_ShouldSucceed"

Expected: initially fail until implementation lands.

Step 2: Implement minimal code per feature using per-feature loop

For each feature ID in Group A, execute the mandatory loop (Go source -> C# -> build -> related tests).

Step 3: Run Group A build and focused tests

Run:

dotnet build dotnet/ --verbosity minimal
dotnet test dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ \
  --filter "FullyQualifiedName~WebSocketHandlerTests" --verbosity normal

Expected: no failures in mapped Group A coverage.

Step 4: Run mandatory stub detection checks

Run all scans from the MANDATORY VERIFICATION PROTOCOL section. Expected: zero matches in touched methods/tests.

Step 5: Update feature statuses (chunked <=15 IDs)

Example chunks:

dotnet run --project tools/NatsNet.PortTracker -- \
  feature batch-update --ids "3506,3507,3509,3510,3511,3512,3513,3514,3515,3516,3517,3518,3519,3520,3521" \
  --set-status complete --db porting.db --execute

dotnet run --project tools/NatsNet.PortTracker -- \
  feature batch-update --ids "3522,3523,3524,3525,3541" --set-status complete --db porting.db --execute

Step 6: Commit checkpoint

git add dotnet/src/ZB.MOM.NatsNet.Server/WebSocket dotnet/src/ZB.MOM.NatsNet.Server/ClientConnection.cs porting.db
git commit -m "feat(batch26): implement websocket frame/core feature group A"

Task 3: Implement Feature Group B (17 features, upgrade/options/server integration)

Feature IDs:

  • 3526,3527,3528,3529,3530,3531,3532,3533,3534,3535,3536,3537,3538,3539,3540,3542,3543

Files:

  • Modify: dotnet/src/ZB.MOM.NatsNet.Server/NatsServer.cs
  • Modify: dotnet/src/ZB.MOM.NatsNet.Server/NatsServer.Init.cs
  • Modify: dotnet/src/ZB.MOM.NatsNet.Server/NatsServer.Listeners.cs
  • Modify: dotnet/src/ZB.MOM.NatsNet.Server/NatsServer.Lifecycle.cs
  • Modify: websocket source files in dotnet/src/ZB.MOM.NatsNet.Server/WebSocket/
  • Test: websocket/backlog test files listed in later tasks

Step 1: Write failing tests for upgrade/origin/options behavior

Focus on IDs mapped to:

  • TestWSUpgradeConnDeadline, TestWSCompressNegotiation, TestWSSetHeader, TestWSSetOriginOptions.

Step 2: Implement feature-by-feature with mandatory loop

For each Group B feature:

  • Read Go source section from websocket.go.
  • Implement in .NET with lock/lifecycle parity.
  • Build and run targeted tests before next feature.

Step 3: Remove related integration stubs

Eliminate websocket placeholder behavior in:

  • readiness checks
  • listener lists
  • websocket server shutdown path
  • websocket connect URL propagation
  • websocket client creation path

Step 4: Build + focused tests + stub scans

Run:

dotnet build dotnet/ --verbosity minimal
dotnet test dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ \
  --filter "FullyQualifiedName~WebSocketHandlerTests" --verbosity normal

Step 5: Update feature statuses (chunked <=15 IDs)

dotnet run --project tools/NatsNet.PortTracker -- \
  feature batch-update --ids "3526,3527,3528,3529,3530,3531,3532,3533,3534,3535,3536,3537,3538,3539,3540" \
  --set-status complete --db porting.db --execute

dotnet run --project tools/NatsNet.PortTracker -- \
  feature batch-update --ids "3542,3543" --set-status complete --db porting.db --execute

Step 6: Commit checkpoint

git add dotnet/src/ZB.MOM.NatsNet.Server/NatsServer*.cs dotnet/src/ZB.MOM.NatsNet.Server/WebSocket porting.db
git commit -m "feat(batch26): implement websocket upgrade/options/server feature group B"

Task 4: Port WebSocket Functional Tests (22 tests in websocket_test.go)

Test IDs:

  • 3075,3076,3077,3078,3079,3080,3082,3083,3084,3085,3086,3087,3088,3089,3093,3097,3098,3099,3102,3113,3117,3132

Files:

  • Modify: dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/WebSocketHandlerTests.Impltests.cs

Step 1: Port test-by-test from Go source

For each test ID:

  1. test show <id>
  2. Read Go body in websocket_test.go
  3. Port behavior to Shouldly assertions
  4. Run single test

Single-test command template:

dotnet test dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ \
  --filter "FullyQualifiedName~ZB.MOM.NatsNet.Server.Tests.ImplBacklog.WebSocketHandlerTests.<MethodName>" \
  --verbosity normal

Step 2: Run class-level gate

dotnet test dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ \
  --filter "FullyQualifiedName~WebSocketHandlerTests" --verbosity normal

Step 3: Stub scan + assertion quality check

rg -n "(Assert\\.True\\(true\\)|NotImplementedException|// TODO|// PLACEHOLDER)" \
  dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/WebSocketHandlerTests.Impltests.cs

Step 4: Update test statuses in chunks <=15

Use chunks with pass evidence recorded for each chunk.

Step 5: Commit checkpoint

git add dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/WebSocketHandlerTests.Impltests.cs porting.db
git commit -m "test(batch26): port websocket functional tests"

Task 5: Port Cross-Module Functional Tests Dependent on WebSocket (11 tests)

Test IDs:

  • 409,465,466,1353,1902,1903,1975,1986,2371,2384,2488

Files:

  • Modify: dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/JetStreamFileStoreTests.Impltests.cs
  • Modify: dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/NatsConsumerTests.Impltests.cs
  • Modify: dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/LeafNodeHandlerTests.Impltests.cs
  • Modify: dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/ConcurrencyTests1.Impltests.cs
  • Modify: dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/ConcurrencyTests2.Impltests.cs
  • Create if missing: dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/LeafNodeProxyTests.Impltests.cs

Step 1: Ensure mapped class files exist

Create missing files/classes before test-porting if absent (especially LeafNodeProxyTests).

Step 2: Port tests ID-by-ID with single-test loop

Use the same per-test loop and evidence policy as Task 4.

Step 3: Run focused class gates

Run each touched class with dotnet test --filter "FullyQualifiedName~<ClassName>".

Step 4: Update statuses in <=15 chunks

Mark verified only with evidence. If blocked by missing infra, mark deferred and add // DEFERRED(batch26): <reason>.

Step 5: Commit checkpoint

git add dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog porting.db
git commit -m "test(batch26): port cross-module websocket-dependent tests"

Task 6: Resolve Benchmark-Mapped Test IDs (53 tests)

Benchmark test IDs:

  • 154,274,275,276,277,3134,3135,3136,3137,3138,3139,3140,3141,3142,3143,3144,3145,3146,3147,3148,3149,3150,3151,3152,3153,3154,3155,3156,3157,3158,3159,3160,3161,3162,3163,3164,3165,3166,3167,3168,3169,3170,3171,3172,3173,3174,3175,3176,3177,3179,3180,3181,3182

Files:

  • Modify/create: dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/WebSocketHandlerTests.Impltests.cs
  • Create if missing: dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/CoreBenchmarks.Impltests.cs
  • Create if missing: dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/PublishBenchmarks.Impltests.cs

Step 1: Benchmark decision gate per ID

For each benchmark ID choose exactly one:

  1. Convert to deterministic functional test with real assertions and mark verified.
  2. Mark n_a (benchmark-only/perf harness requirement) with explicit reason.
  3. Mark deferred if runtime infra blocker exists.

Step 2: Run tests for converted benchmark IDs

Use class-level and method-level gates as in previous tasks.

Step 3: Apply status updates in <=15 chunks

Separate commands by status (verified, n_a, deferred).

Step 4: Commit checkpoint

git add dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog porting.db
git commit -m "test(batch26): resolve benchmark-mapped websocket test IDs"

Task 7: Final Verification, Status Closure, and Batch Completion

Files:

  • Modify: porting.db
  • Optional report artifacts: reports/current.md (if generator updates it)

Step 1: Full verification run

dotnet build dotnet/ --verbosity minimal
dotnet test dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ --verbosity normal
dotnet test dotnet/tests/ZB.MOM.NatsNet.Server.IntegrationTests/ --verbosity normal

Step 2: Promote complete feature IDs to verified

Use chunked updates with evidence references.

Step 3: Audit and reconcile

dotnet run --project tools/NatsNet.PortTracker -- audit --type features --db porting.db
dotnet run --project tools/NatsNet.PortTracker -- audit --type tests --db porting.db

Step 4: Complete batch

dotnet run --project tools/NatsNet.PortTracker -- batch complete 26 --db porting.db

Expected: completion succeeds only when all batch items are in valid terminal statuses.

Step 5: Generate report and commit

./reports/generate-report.sh
git add porting.db reports/
git commit -m "feat(batch26): complete websocket batch with verified implementation and tests"

Execution Notes

  1. If dotnet is not on PATH, use absolute binary (for this machine): /usr/local/share/dotnet/dotnet.
  2. Always keep evidence for each status update chunk (test output snippet + feature/test IDs).
  3. Never move a feature to verified before its related tests are passing.
  4. Never convert a blocked item into a stub just to make progress.