Milestones
List view
Findings from the 2026-09-26 review of v0.4.2 (commit 1e52144): triage of open issues, alignment with the latest NATS/JetStream behaviour, test-suite quality, hot-path performance, and places where a message can be lost between JetStream and the browser without the client being able to recover via Last-Event-ID. Every finding was checked against the code; the behavioural claims were reproduced against real nats-server builds (embedded 2.14.2 plus 2.9-2.12 in Docker) where marked.
No due date•35/35 issues closed## Summary Followups from the post-v0.4.0 deep adversarial review run (`wf_fe75fb83-762`, 2026-06-19). The review used a 7-mapper / 15-dimension / 3-lens (code/spec/impact) adversarial verification workflow over 394 agents and 9.26M tokens; 124 raw findings collapsed to **49 confirmed** (60% refute rate, indicating a high adversarial bar) across 13 clusters. The shipped v0.4.0 feature surface is **substantially correct against NATS JetStream semantics** — no exploit-class bugs, no consumer-config violations, no replay correctness defects. This milestone addresses what the review did surface: - **One real production bug** (#REPLAY-FALLBACK-PLAN-STALENESS) where subscribe-time replay fallback mutates a local plan copy and leaves downstream logs, the client-side replay-window filter, and cap accounting all stale - **A cluster of vacuous / tautological tests** — including the test that ostensibly gates the M9 Batch A default-on contract, which manually sets the value it claims to verify - **Test coverage gaps for log keys and metric labels** that the M9 hardening commit `104299f` was specifically created to prevent (label drift would land silently for `stream_info_error`, `max_connections`, `subscription_failed`, `subscription_empty`) - **NATS API correctness refinements** — unbounded Provision-time `StreamInfo`, missing `NoCallbacksAfterClientClose`, `Cleanup` uses `Close` not `Drain`, `nats_idle_heartbeat=-1` silently loses consumer-reap detection - **README under-documentation** of operator-visible HTTP / auth / SSE-header / handshake surface (13 doc-drift findings batched into one PR) - **TLS / origin config validation gaps** — `nats_tls_ca` silently ignored with `InsecureSkipVerify`, `NatsURL` no scheme allowlist, `AllowedOrigins` no entry validation, `heartbeat_interval`/`reconnect_wait` skip parse-time negative rejection - **Metric Help-text enumeration drift** (3 CounterVecs break the file's own enumeration pattern) - **Trailing-slash probe path falls through to topic parsing** (`/livez/` → 503 `No topics` instead of liveness response) - **Pre-Batch-B alert gap** for `nuts_consumer_invalidated_total` / `nuts_nats_async_errors_total{kind=slow_consumer}` ## Suggested commit order 1. Replay fallback plan-staleness fix (`#M10-1`) — the only real production bug 2. Vacuous-test cluster (`#M10-2` through `#M10-8`) — rewrite the 7 tests that don't gate 3. Label/log-key assertion gap cluster (`#M10-9` through `#M10-13`) — `zaptest/observer` wiring 4. README documentation sweep (`#M10-14`) — 13 doc additions in one PR 5. Metric Help enumeration sweep (`#M10-15`) — 5 Help-text touchups in one PR 6. NATS API correctness cluster (`#M10-16` through `#M10-20`) 7. TLS / origin config validation cluster (`#M10-21` through `#M10-26`) 8. Edge case + error handling refinements (`#M10-27` through `#M10-29`) 9. Pre-Batch-B alert (`#M10-30`) — pairs with Batch B work in M9 10. JWT cookie negative test (`#M10-31`) ## Origin Deep-review run ID: `wf_fe75fb83-762` (commit `1ac59df`, the tagged v0.4.0 head). Tracked across 13 clusters with 3-lens (code/spec/impact) adversarial verification. Refute rate: 75/124 = 60% (every confirmed finding survived attempts to refute it from three independent angles).
No due date•31/31 issues closed## Summary NUTS' long-lived JetStream push consumers can be silently invalidated mid-stream — InactiveThreshold tripping during a network blip, leafnode route severed, replica failover losing the inbox. Today the SSE handler stays open: the SSE-layer heartbeat ticker (`serve.go:945`) proves the HTTP socket is open but says nothing about the JetStream push path, and the global async `ErrorHandler` (`provision.go:234-249`) routes `*nats.ErrConsumerSequenceMismatch` into the generic `"other"` metric bucket with no plumbing to the affected per-request goroutine. On a quiet stream the failure may never surface. This milestone wires NATS server-side `IdleHeartbeat` as the liveness primitive **and** the end-to-end termination path so an invalidated consumer disconnects its SSE client cleanly — the client reconnects with `Last-Event-ID` and gets a fresh consumer. ## Two batches, two PRs ### Batch A — Observability-only delivery (Phases 1-3, #M9-1 / #M9-2 / #M9-3) Surface the failure without changing the recovery behaviour. After Batch A, a reaped consumer produces a precise metric label and a structured Warn log — operators can SEE the failure, and we have on-the-wire confirmation that `IdleHeartbeat` is set. The bad consumer still stays attached to its SSE handler until the client reconnects for other reasons. - Phase 1 — Async-error classifier label `consumer_sequence_mismatch` - Phase 2 — Caddyfile / JSON `nats_idle_heartbeat <seconds>` config knob, **default 10s** (default-on), validated `0 < x < InactiveThreshold/2` - Phase 3 — Wire `nats.IdleHeartbeat()` into `subscriptionOptions`, heavy functional test that `ConsumerInfo.Config.Heartbeat` is non-zero across the matrix (nats:2.12, nats:2.14) ### Batch B — Termination plumbing (Phases 4-5, #M9-4 / #M9-5) Add the routing that turns the observed failure into a clean disconnect. - Phase 4 — `sync.Map` subscription registry on `Handler`; `provision.go`'s global ErrorHandler looks up the affected sub and non-blocking-sends the error onto the per-request `subError` channel - Phase 5 — New select arm in `serveStream` consumes `subError`, logs `disconnect_reason=consumer_invalidated`, bumps `nuts_consumer_invalidated_total{reason}`, writes a diagnostic SSE comment, returns nil. Client auto-reconnects with `Last-Event-ID`. ## Locked design decisions - **Default-on at 10s.** Protection is the default; per-connection NATS↔NUTS chatter is small (~6 RPS per active SSE) and operators get the safety without opt-in. - **`InactiveThreshold/2` upper bound.** With `defaultConsumerInactiveThreshold = 30s` (`provision.go:41`), valid range is `(0, 15)`. Two missed heartbeats must be detectable before the server reaps the consumer. - **Race H1 — uuid double-map.** Subscribe returns the `*Subscription` synchronously, but the ErrorHandler dispatches on a separate goroutine — an error can fire after Subscribe returns but before `subRegistry.Store(sub, ch)` is reached. Mitigation: pre-allocate a UUID, register `uuid → chan` BEFORE Subscribe, register `sub → uuid` immediately AFTER; ErrorHandler resolves `sub → uuid → chan`. Worth the ~20 LOC because the alternative is silently dropped invalidations during the very moments operators care about most. - **Two classifier values terminate, others log-only.** `*nats.ErrConsumerSequenceMismatch` (heartbeat-miss / gap) and `nats.ErrSlowConsumer` (nats.go-internal buffer overflow upstream of the NUTS msgChan). Everything else stays log+metric only. ## Acceptance criteria (whole milestone) - [ ] Default-on `nats_idle_heartbeat=10s` ships with no operator config required - [ ] `Validate` rejects `nats_idle_heartbeat >= InactiveThreshold/2` with a clear error - [ ] `ConsumerInfo.Config.Heartbeat` asserted non-zero in functional matrix (Batch A) - [ ] `nuts_nats_async_errors_total{kind="consumer_sequence_mismatch"}` documented in README + docs/OPERATIONS.md (Batch A) - [ ] `nuts_consumer_invalidated_total{reason}` ships with `reason=heartbeat_missed` and `reason=slow_consumer` (Batch B) - [ ] E2E test: synthetic consumer deletion via `$JS.API.CONSUMER.DELETE.*` triggers SSE disconnect within ~2× IdleHeartbeat (Batch B) - [ ] Race-detector clean on the full unit + functional suite - [ ] Mutation score on `helpers.go` + `serve.go` + `provision.go` does not regress - [ ] CHANGELOG entries for both batches
No due date•5/5 issues closedIdiomatic Go cleanups that a strict golangci-lint config (errorlint, nilness) would catch: %v→%w in error wrapping, interface{}→any in Go 1.26 module, drop redundant explicit string typing, move misplaced typed nats.ErrorCode constant, add missing doc comments on exported helpers. Exit criteria: golangci-lint config covers these via lint rules so they cannot regress; no remaining occurrences in the tree.
No due date•3/3 issues closedFix the first-impression docs so a new adopter can get NUTS running end-to-end without troubleshooting: docker-compose healthcheck port, Go toolchain version alignment, Caddyfile in the headline Docker run example, minimum NATS server version, SECURITY link, breaking-change policy, plus the small declared-vs-implemented gaps (replay_fallbacks_total scope, header-vs-query precedence, drop-reason split, log field name). Exit criteria: README quick-start works by copy-paste; every documented metric / directive / behaviour is accurate.
No due date•5/5 issues closedAddress concrete hot-path allocations and back-pressure pathologies surfaced by benchmarks and code reading: wasted RFC3339 alloc in formatMessageEvent, two SetWriteDeadline syscalls per frame, signalSlowClient time.Timer alloc under back-pressure, dispatch select-default disconnecting on transient delays at small buffer sizes, CapSequence/replay-cap race with live messages. Add a channel-pump benchmark that covers the actual hot path, not just pure functions. Exit criteria: BenchmarkFormatMessageEvent allocs/op down by at least one, new pump benchmark establishes a steady-state baseline.
No due date•4/4 issues closedStrengthen the assertion model on security-critical predicates and quantitative limits: property-based / fuzz coverage on topic/filter/cookie validation and subject matching, exact boundary tests for auth.go limits, kill the two 'accepted' mutants at serve.go:351 and :883, fill the small but high-leverage gaps surfaced by the mutation work (TopicPrefix variants, subjectMatchesFilter token-count edge case, auth-before-subscribe ordering). Exit criteria: mutation report stays at 100% MSI with zero accepted survivors AND every security-critical predicate has a fuzz target.
No due date•5/5 issues closedAdd behavioural tests for the failure-recovery branches the file headers call out as load-bearing but no test exercises today: replay-fallback retry, replay cursor boundaries, write-error disconnects, multi-topic subscribe failure, NATS auth modes against a real auth-requiring server, Cleanup idempotency, JS-context creation failure. Exit criteria: every 'safety net' comment in serve.go and provision.go has at least one test that exercises it.
No due date•5/5 issues closedMake the existing unit suite trustworthy: kill data races between handler goroutines and test assertions, drop bare time.Sleep synchronisation, isolate subtests from shared mutable fixtures, decouple metrics tests from global counter values, and run -race across the full suite. Exit criteria: 'make test-unit' is green under -race and survives shuffled subtest order.
No due date•4/4 issues closedTighten the request and lifecycle plumbing: probe-path matching that does not collide with topic shorthand, CORS headers emitted for all responses (not just streaming), Provision/Cleanup ordering, probe timeouts, defensive nil-checks. Exit criteria: every control-path branch returns the correct status + headers under at least one integration test.
No due date•5/5 issues closedClose the operator-visibility gaps in NUTS' hot path: NATS async errors, slow-consumer drops, write timeouts, wildcard-fallback drops, ephemeral-consumer hygiene. Exit criteria: every realistic failure mode has a Warn-or-higher log AND a Prometheus metric; no silent drops anywhere on the SSE message path.
No due date•5/5 issues closed