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

505 lines
18 KiB
Markdown

# 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:
```bash
# 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:
```bash
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:
```bash
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:
```bash
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:
```bash
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:
```bash
dotnet run --project tools/NatsNet.PortTracker -- batch start 26 --db porting.db
```
Expected: start succeeds; no dependency error.
**Step 3: Materialize execution manifest**
Run:
```bash
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**
```bash
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:
```bash
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:
```bash
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:
```bash
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**
```bash
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:
```bash
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)**
```bash
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**
```bash
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:
```bash
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**
```bash
dotnet test dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ \
--filter "FullyQualifiedName~WebSocketHandlerTests" --verbosity normal
```
**Step 3: Stub scan + assertion quality check**
```bash
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**
```bash
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**
```bash
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**
```bash
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**
```bash
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**
```bash
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**
```bash
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**
```bash
./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.