Skip to content

Commit a868dcd

Browse files
sonus21github-actions[bot]claude
authored
Nats v2 web (#295)
* ci: compile main sources in coverage_report job The coverage_report job was producing an effectively empty jacocoTestReport.xml (3.4KB vs ~1.1MB locally) because no .class files existed when coverageReportOnly ran — the job checked out source code and downloaded .exec artifacts, but never compiled. JaCoCo's report generator skips packages/classes it cannot resolve, so the merged XML ended up with only <sessioninfo> entries and no <package> elements. That made coverallsJacoco silently no-op via the "source file set empty, skipping" branch in CoverallsReporter, so "Push coverage to Coveralls" reported success without uploading. Verified by downloading the coverage-report artifact from a recent run and comparing its XML structure against a local build's report. Assisted-By: Claude Code * nats-web: implement pause / soft-delete admin ops and capability-aware Q-detail Replace the all-stub `NatsRqueueUtilityService` with real impls for the operations JetStream can model: `pauseUnpauseQueue` persists the `paused` flag on `QueueConfig` in the queue-config KV bucket and notifies the local listener container so the poller stops dispatching; `deleteMessage` is a soft delete via `MessageMetadataService` (stream message persists, dashboard hides via the metadata flag); `getDataType` reports `STREAM`. `moveMessage`, `enqueueMessage`, and `makeEmpty` deliberately remain "not supported" — there is no JetStream primitive for those. Update `RqueueQDetailServiceImpl.getRunningTasks` / `getScheduledTasks` to return header-only tables when the broker capabilities suppress those sections, instead of emitting zero rows or 501s on NATS. 20 new unit tests cover the pause/delete paths and lock in the still-unsupported operations. Updates `nats-task.md` / `nats-task-v2.md` to reflect what landed. Assisted-By: Claude Code * nats-web: capability-aware nav / charts and stream-based peek End-to-end browser-tested the NATS dashboard and shipped the templates + broker fixes uncovered by it: - `RqueueViewControllerServiceImpl.addBasicDetails` now propagates the active broker's `Capabilities` to every template via `hideRunningPanel`, `hideScheduledPanel`, `hideCronJobs`, and `hideCharts`. Templates default to "show" when these are absent so the legacy Redis path is unchanged. - `base.html` hides the Running tab when `hideRunningPanel` is set; Scheduled was already gated. - `index.html` and `queue_detail.html` skip the stats / latency chart panels (and their JS bootstrap) when `hideCharts` is set, replacing the home charts with a friendly backend-aware blurb. - `queues.html` swaps the hard-coded "backing Redis structures" copy for the broker-supplied `storageKicker`. - `JetStreamMessageBroker.peek` rewritten to read messages directly from the stream via `JetStreamManagement.getMessage(streamName, seq)` instead of creating an ephemeral pull consumer. NATS 2.12+ rejects `AckPolicy.None` on WorkQueue streams (10084) and rejects mixing filtered + non-filtered consumers (10100), so the consumer-based approach can't coexist with the durable poller. Sequence-based reads sidestep both. - `NatsRqueueMessageMetadataService.deleteMessage` now creates a tombstone metadata entry when no record exists (NATS skips the storeMessageMetadata path at enqueue time), so dashboard-driven deletes always succeed and the next peek renders the row as deleted. - `rqueue.js`'s `deleteMessage` / `enqueueMessage` button handlers now use `closest('tr')` instead of two `.parent()` hops. The recent `explorer-action-group` div wrapper added an extra level of nesting; the old walk landed on the action cell and read "Delete" as the message id. Assisted-By: Claude Code * nats-web: backend-aware data-type labels and Limits-aware queue size Replace the hard-coded "LIST" / "ZSET" tokens on the queue-detail page with a broker-supplied human label so NATS shows "Queue (Stream)", "Completed (KV)", and "Dead Letter (Stream)" instead of Redis-shaped data structure names. - New `MessageBroker.dataTypeLabel(NavTab, DataType)` SPI hook, default returns null (legacy Redis path keeps `DataType.name()`). - `JetStreamMessageBroker` overrides for the NATS-mapped tabs. - `RedisDataDetail` carries an optional `typeLabel` field; templates render via `{{ typeLabel | default(type) }}` so older callers stay correct. - `queue_detail.html` plus the `rqueue.js` modal title use the label and surface the broker-friendly token in the explorer header. Also fixes `JetStreamMessageBroker.size(QueueDetail)` for streams created with `RetentionPolicy.Limits`. WorkQueue retention drops messages on ack so `streamState.msgCount` already equals the pending count, but Limits keeps all messages and `msgCount` over-reports. The new path detects retention from `streamInfo.config` and walks the stream's durable consumers to surface the maximum `numPending` for Limits-mode queues, falling back to msgCount on consumer-enumeration errors. Assisted-By: Claude Code * nats-web: position-based pending estimate for Limits streams, render ~ prefix Replace the consumer-walking max(numPending) computation with stream position math so the dashboard surfaces the worst-case backlog using a single pass over consumers: pending ≈ lastSeq - min(consumer.delivered.streamSeq) Mathematically equivalent to the previous max(numPending), but expressed in terms of stream offsets (which is what the dashboard now signals as approximate to the operator). Also adds the user-facing approximation indicator: - New `MessageBroker.isSizeApproximate(QueueDetail)` SPI hook, default false (Redis returns exact list / sorted-set sizes). - `JetStreamMessageBroker.isSizeApproximate` returns true for streams with `RetentionPolicy.Limits` and false for WorkQueue (the standard rqueue queue mode where msgCount is exact after acks remove messages). - `RedisDataDetail.approximate` carries the flag through to the view. - `queue_detail.html` renders `~ N` when approximate, `N` otherwise; the `Queue-backed` short-circuit for size<0 stays. - `RqueueQDetailServiceImpl` sets the flag on the pending row when a broker is wired. Assisted-By: Claude Code * nats-web: per-consumer pending breakdown for Limits-retention streams A single aggregated "~ N" pending number hides the per-consumer lag that matters on a fan-out stream — Consumer A might be at seq 100 while Consumer B is at seq 1100, and the dashboard previously surfaced only the worst case. This commit replaces that aggregate with a per-row breakdown when the broker exposes one, leaving the WorkQueue / Redis path unchanged: - New `MessageBroker.consumerPendingSizes(QueueDetail)` SPI hook returning an ordered map of `consumerName -> pending`. Default returns null (no breakdown available). - `JetStreamMessageBroker` overrides for Limits-retention streams: walks the durable consumers, prefers `numPending` (server-computed), falls back to position math (`lastSeq - delivered.streamSeq`) when numPending is 0. Returns null on WorkQueue retention (single shared pool). - `RedisDataDetail` carries an optional `consumerName`. - `RqueueQDetailServiceImpl` emits one PENDING row per consumer when the broker provides a breakdown, with exact (non-approximate) counts. - `queue_detail.html` renders the consumer name as muted text next to the data-structure name. Result on a NATS Limits stream: PENDING | Stream | rqueue-js-feed / consumer-a | 100 PENDING | Stream | rqueue-js-feed / consumer-b | 500 PENDING | Stream | rqueue-js-feed / consumer-c | 0 Assisted-By: Claude Code * nats-web: consumer-level Subscribers + Terminal Storage redesign Replace the queue-detail page's data-structure-centric "Job Type / Data Type / Name / Size" table with a consumer-level table that works across all backends. The standalone "Queue Pollers" section is folded in via a worker-registry join on consumer name, so the queue-detail page now has a single integrated view. UI shape: Subscribers Consumer | Type | Storage | Pending | In-Flight | Status | Host/PID | Last Poll Terminal Storage (only when present) Bucket | Type | Storage | Size Backends: - Redis — every @RqueueListener registered for the queue is a row, with shared Pending (LIST size, marked "(shared)") and shared In-Flight (processing-ZSET size). EndpointRegistry.getAllForQueue() enumerates the handlers. - NATS WorkQueue — every durable consumer is a row; Pending = shared msgCount marked "(shared)", In-Flight = consumer's exclusive numAckPending. - NATS Limits — every durable consumer is a row; Pending = exact per-consumer numPending, In-Flight = numAckPending. Architecture: - New `MessageBroker.subscribers(QueueDetail)` SPI hook returning a list of `SubscriberView` records (consumerName, pending, inFlight, pendingShared). Default returns a single anonymous row backed by `size()` so brokers that don't track named consumers still render a working table. - `RedisMessageBroker` overrides via `EndpointRegistry.getAllForQueue()` + shared list/ZSET sizes. - `JetStreamMessageBroker` overrides via `jsm.getConsumerNames` + `getConsumerInfo` per consumer, branching on retention policy. - `RqueueQDetailService` exposes `getSubscriberRows` / `getTerminalRows`, joining broker SPI data with the worker registry for status / last-poll. - `RedisDataDetail` is unchanged (kept for existing API consumers). - `queue_detail.html` rewritten: "Subscribers" + "Terminal Storage" sections replace the Job Type / Data Type / Name / Size and Queue Pollers blocks. Side fix per user: charts (stats / latency) are no longer hidden when a broker reports `!supportsScheduledIntrospection`. NATS has the chart endpoints working; the panels just render empty until counters accumulate, which is the natural "no data yet" state. `index.html` and `queue_detail.html` always include the chart partials now; the friendly "not available" placeholder is removed. Assisted-By: Claude Code * nats-web: queue-detail redesign — hero, chip strip, subscriber & terminal cards Bring the queue-detail page in line with the modern card-based design system already used by /queues and /workers. The previous layout was Bootstrap table-bordered tables that felt like a different app. New shape: Hero Kicker "QUEUE" + queue name + paused/live badge inline + subtitle Stat chips: Subscribers / Pending / In-Flight (right-aligned panel) Configuration chip strip Concurrency · Retries · Visibility · Dead Letter (icons + values) Created / Updated meta below a dashed divider Subscribers section "LIVE" kicker + heading + supporting copy One subscriber-card per @RqueueListener consumer: - Consumer name (clickable into explorer modal) - Type pill (Queue (Stream) / Stream consumer / List) - Status badge (ACTIVE/STALE/PAUSED) using existing worker-status-* classes - Pending + In-Flight stat panels with "shared" hint where applicable - Storage / Host / Last Poll meta rows Empty state uses the queue-empty-state pattern from /queues. Terminal Storage section "SHARED" kicker + heading One terminal-card per shared bucket (COMPLETED / DEAD), color-coded via left border (green for completed, orange for dead letter). Big size value with "messages" label or "Queue-backed" placeholder. Stats & Latency Collapsed by default in a <details> disclosure ("TELEMETRY" kicker). Charts lazy-render on first open so the initial paint stays focused on the actionable data. The full chart partials are unchanged. CSS additions are scoped to new component classes (queue-detail-*, subscriber-*, terminal-*) and reuse the existing tokens — same colors, same border-radius scale, same shadow recipe — so the page sits flush with /queues and /workers in look and feel. Pebble fix: switched `if config` to `if config != null` because Pebble rejects domain objects in boolean context (PebbleException 10084 caught in browser test). Assisted-By: Claude Code * fix: reuse single consumer for workqueue streams For workqueue streams, NATS rejects multiple non-filtered consumers (error 10099). When multiple listeners were registered on the same workqueue queue without custom consumer names, each listener tried to create its own consumer, causing the provisioning to fail. Fix: For workqueue streams with no custom consumer name, use a consistent `queueName-consumer` name so all listeners share a single consumer. This matches the workqueue semantics where only one consumer can receive each message. - NatsStreamValidator: Resolve consumer name based on queue type, using `queueName-consumer` for workqueues without custom names - JetStreamMessageBroker: Use the same resolution logic in pop() to ensure validator and poller use the same consumer name Assisted-By: Claude Code * Revert "fix: reuse single consumer for workqueue streams" This reverts commit a6a9e69. * nats-web: tighten queue-detail layout, add play/pause action button User feedback was blunt: too much wasted whitespace and no clear pause/play control. This rewrite collapses the queue-detail page into a single header bar plus dense table sections so the operator can see and act on everything without scrolling. Layout changes: - Header: queue name + state pill (LIVE / PAUSED) + actionable pause/play toggle button + summary stats ("N subscribers · N pending · N in-flight") on the right, all on one row. Replaces the big hero block. - Configuration chip strip: 6 inline cells (Concurrency · Retries · Visibility · DLQ · Created · Updated) with dashed dividers — fits everything in ~60px of vertical space. - Subscribers and Terminal Storage as compact tables with light row dividers and pill-styled type labels. No more card-grid whitespace. - Stats & Latency stays behind a <details> disclosure so charts don't pin the actionable rows off-screen. Pause/play action: - The state pill is paired with a button that reuses the existing `pause-queue-btn` JS handler (POSTs to /pause-unpause-queue and reloads). Click toggles the queue state and the pill / button icon swap accordingly. Browser-tested: pausing the queue switches to "PAUSED" + play icon; clicking play unpauses, queue resumes processing pending messages. Bug caught while testing: - `QueueDetail.resolvedConsumerName()` previously returned different names for system-generated vs. primary queues (`-consumer-primary` vs `-consumer`). NATS WorkQueue streams reject multiple non-filtered consumers (10099) so the poller's runtime consumer name had to match whatever the bootstrap validator created. Unified to a single `{name}-consumer` suffix. Files touched: - queue_detail.html — full template rewrite (compact tables, header bar, inline config strip, charts disclosure) - rqueue.css — replaced the previous card-heavy queue-detail CSS block with a tighter `qd-*` namespaced ruleset (~250 lines, was ~430) - QueueDetail.java — consumer-name suffix fix - (assorted formatter cleanups across files touched in earlier commits) Assisted-By: Claude Code * fix: use single consumer-name suffix in resolvedConsumerName resolvedConsumerName() returned different suffixes (-consumer vs -consumer-primary) based on systemGenerated. The bootstrap validator and runtime poller therefore disagreed on the consumer name when systemGenerated was false, and the second creation attempt failed on NATS workqueue streams with error 10099 (multiple non-filtered consumers not allowed). Use {name}-consumer in both cases. The custom consumerName from @RqueueListener is still honoured when set; only the generated default loses the -primary distinction. Assisted-By: Claude Code * nats-web: consumer-aware peek for Limits-retention streams The queue-detail explorer was paginating from the stream's first sequence regardless of which subscriber the operator clicked on. For Limits-retention streams with competing consumers each consumer has its own delivered offset, so the explorer was showing messages the selected consumer had already acked instead of what is still pending for that subscriber. Wire consumerName end-to-end: - QueueExploreRequest: new optional consumerName field. - MessageBroker.peek: new consumer-aware overload, default delegates to the existing peek so non-NATS backends keep working. - JetStreamMessageBroker.peek: when consumerName is set on a Limits stream, base the start sequence on getConsumerInfo(stream, consumer).getDelivered().getStreamSequence()+1. WorkQueue and the no-consumer call site are unchanged. - RqueueQDetailService(.Impl): propagate consumerName into the broker peek call. - Rest controllers (blocking + reactive): forward consumerName from the request into the service. - queue_detail.html: subscribers table emits data-consumer on each consumer link. - rqueue.js: read data-consumer in exploreData() and include it in the queue-data POST body. Assisted-By: Claude Code * nats-web: peek from ackFloor, not delivered.streamSeq The consumer-aware peek was starting from delivered.streamSeq + 1, which skipped over the in-flight window (sequences delivered but not yet acked). Operators looking at a row with pending=0, in-flight=15 clicked through and got an empty explorer because all 15 in-flight sequences were <= delivered.streamSeq. Use ackFloor.streamSeq + 1 instead, so the explorer includes both in-flight and not-yet-delivered messages — i.e. everything this consumer still has work to do on. Matches the operator's mental model of "what is this consumer still chewing on". Assisted-By: Claude Code * fix: ack/nack target wrong NATS Message under multi-consumer fan-out The inFlight map was keyed only on RqueueMessage.id. For Limits- retention streams with two or more durable consumers (e.g. one @RqueueListener per handler with consumerName="linkedin-search" and consumerName="google-search" on the same queue), each consumer receives its own copy of every message and the second pop's inFlight.put silently overwrote the first's NATS Message handle. When the first worker later called broker.ack/nack, it picked up the wrong consumer's NATS Message and acknowledged that delivery instead of its own. The original delivery stayed in numAckPending forever, and Outstanding Acks grew without bound (e.g. 92 -> 101 -> 112 over 30s with no new produces). Key the inFlight map on "<consumerName>::<messageId>" so each consumer's delivery is tracked independently. Pop uses its consumerName arg; ack/nack/moveToDlq use q.resolvedConsumerName(), which matches what the poller passed at fetch time. Verified: with two @RqueueListener on a Limits stream, Outstanding Acks now drains to 0 and Ack Floor advances to lastSeq instead of sticking near the start. Assisted-By: Claude Code * nats: regression test for inFlight key-collision under fan-out Adds JetStreamMessageBrokerMultiConsumerAckIT, which catches the bug fixed in 524fc5c: under multi-consumer fan-out (Limits-retention stream + two durable consumers) the broker's inFlight map was keyed on RqueueMessage.id alone, so consumer-b's pop silently overwrote consumer-a's NATS Message handle and consumer-a's ack later targeted the wrong delivery. The trigger is timing-sensitive: pops on both consumers must occur before either acks. A sequential drain-then-drain pattern hides the bug because the inFlight key is removed before the second pop repopulates it. Verified that the test fails ("ack(consumer-b, m-0) must succeed ==> expected: <true> but was: <false>") against the reverted broker and passes once the fix is restored. Also updates four existing ITs to set q.resolvedConsumerName() to match the consumer name passed to pop, since the fix makes ack/nack key on (consumerName, messageId) and the contract is that callers keep the two in sync. mockQueue gains an overload that takes a consumerName for tests that need this explicitly. Assisted-By: Claude Code * nats-web: align Pending column with explorer + add Workers column Three Subscribers-table refinements after the consumer-aware peek landed: 1. Pending column = outstanding work, not just yet-to-deliver. Previously the column showed numPending (yet-to-deliver), but clicking the consumer link opens the explorer with peek starting at ackFloor + 1 — which includes in-flight messages. Operators saw "Pending: 0 / In-flight: 15" and got 15 rows in the explorer, confusing the column meaning. Switch the per-row pending count to numPending + numAckPending so the column matches what the explorer renders. WorkQueue retention is unaffected — its msgCount already represents outstanding work. Same shift applied to the queue-level approximateLimitsPending: base on min(ackFloor.streamSeq) instead of min(delivered.streamSeq) so the queue-wide "size" agrees with per-consumer pending semantics. 2. Workers column on Subscribers table. The registry stores one heartbeat per (JVM, consumer) pair, so multi-instance deployments show >1; thread-level fanout from concurrency = "10-20" lives inside a single instance and is not reflected. Javadoc spells this out so operators don't expect a thread count. 3. Example listener restored to mode = QueueType.STREAM on both job-queue listeners. Without it, two distinct consumerNames on the same queue would land on a WorkQueue stream and trip NATS error 10099. The mode change makes the multi-listener fan-out demonstrable end-to-end (and matches the scenario the new ITs exercise). Assisted-By: Claude Code * nats: tests for consumer-aware peek + adapt QueueModeIT to new contract Two test changes: 1. New JetStreamMessageBrokerConsumerAwarePeekIT covers the SPI overload peek(QueueDetail, consumerName, offset, count) on a Limits-retention stream with two durables at different ack positions. Asserts: - per-consumer peek for the fast consumer skips the acked range and returns only the un-acked tail - per-consumer peek for the untouched consumer returns the full stream (its ackFloor is 0) - the no-consumer overload bases on the stream's first sequence and is unaffected by per-consumer state 2. JetStreamQueueModeIT updated for the new (consumerName, id) inFlight keying contract. Three tests touched, all swap mockQueue(name, type) for mockQueue(name, type, consumerName) so q.resolvedConsumerName() matches what the test passes to broker.pop(...). Without the matching name, ack/nack lookups miss, the consumer's delivery-position assertions break, and consumerReuse falsely reports "5 messages remaining instead of 2." The fan-out test was also restructured to use one QueueDetail per listener — mirrors how production builds a separate QueueDetail per @RqueueListener with its own resolvedConsumerName. Assisted-By: Claude Code * nats-web: keep Pending column as numPending (yet-to-deliver only) Earlier ec99465 changed JetStreamMessageBroker.subscribers() to report pending = numPending + numAckPending so the Pending column would equal the explorer's row count. That collapsed the per-row pending vs in-flight split and made NATS disagree with Redis on what "Pending" means. The Subscribers table already has a separate In-Flight column, so the operator can see both numbers at a glance. Restoring pending to numPending keeps the columns disjoint (yet-to-deliver vs being- processed), aligns NATS Limits behaviour with the Redis backend's LIST-vs-processing-ZSET split, and makes the Pending column match NATS CLI's "Unprocessed" report. The columns and the explorer answer different questions on purpose: columns split outstanding work into pending + in-flight; clicking the consumer link browses everything outstanding (peek bases on ackFloor + 1, which spans both buckets). Operators see a 0-pending / 15-in-flight row and click through to see the 15 in-flight messages, which was the original UX motivation for consumer-aware peek. The queue-level approximateLimitsPending stays based on min(ackFloor.streamSeq) — that's the queue's total outstanding work, which is the right size for the queue listing. Assisted-By: Claude Code * core: expose message-converter exception to middleware via Job When the inbound message converter throws while deserializing the raw payload, RqueueExecutor.getUserMessage() catches it, logs at DEBUG, and falls back to the raw String so downstream middleware and the handler still run. The exception itself was lost — middleware had no clean way to tell "converter failed and we're staring at a raw String" apart from "queue legitimately carries Strings." Capture the exception in the executor and thread it into JobImpl via a new constructor overload (the old 10-arg constructor delegates with null to keep existing callers compiling). Surface it on the Job interface as Throwable getConversionException() plus a default hasConversionException() convenience. Middleware can now branch on conversion failure deliberately: if (job.hasConversionException()) { // route to DLQ, alert, attempt fallback decode, etc. return; } next.call(); Adds a unit test that swaps the handler's converter for one that throws on fromMessage, asserts middleware still runs, that job.getMessage() is the raw String fallback, and that job.getConversionException() exposes the original IllegalStateException. Assisted-By: Claude Code * web: fix tests for consumer-aware getExplorePageData + peek signatures The earlier consumer-aware peek work changed two SPI surfaces that the web tests still called with the old shape, so CI failed at compile time on rqueue-web:compileTestJava. RqueueQDetailService.getExplorePageData gained a String consumerName between DataType and pageNumber. Updated five call sites in RqueueQDetailServiceTest and three in RqueueQDetailServiceBrokerRoutingTest to pass null for consumerName. MessageBroker.peek gained a consumer-aware 4-arg overload, and the production explorer path now calls that overload. Two stubbings and one verify in RqueueQDetailServiceBrokerRoutingTest were still matching peek(QueueDetail, long, long) — strict Mockito reported a PotentialStubbingProblem. Updated to nullable(String.class) for the consumer arg so the stub matches both the explicit-null and any future non-null call sites. Assisted-By: Claude Code * build: bump version to 4.0.0-RC6 and publish nats/redis/web modules publish.sh was only pushing rqueue-core, rqueue-spring, and rqueue-spring-boot-starter to Maven Central; rqueue-nats, rqueue-redis, and rqueue-web were never published, so downstream consumers couldn't pull the new backends/explorer module. Added them to the publish script and bumped the subprojects version from 4.0.0-RC4 to 4.0.0-RC6 for the next release. * docs: changelog for 4.0.0 (date TBD) Captures the user-facing changes since RC2: the NATS JetStream backend and pluggable MessageBroker SPI, capability-aware dashboard, consumer- aware peek and queue-detail redesign for fan-out topologies, NATS pause/soft-delete admin ops, the new Job.getConversionException API for middleware, and the additional modules (rqueue-nats, rqueue-redis, rqueue-web) now published to Maven Central. Also notes the migration guidance: existing Redis users keep working, NATS users wire a JetStream MessageBroker bean, and /explore gained a nullable consumerName query parameter. * docs: backfill RC3/RC4 changelog entries and trim 4.0.0 scope The 4.0.0 entry I added in the previous commit claimed credit for the NATS JetStream backend, but that landed in RC4 (PR #292), not in 4.0.0. Backfill the missing RC entries based on git history and tags: RC3 (14-Apr-2026, never tagged) — housekeeping. Version bump + /docs dependabot bump for addressable. No user-facing code changes versus RC2. RC4 (14-Apr-2026, tag v4.0.rc4) — initial NATS JetStream backend drop, plus Coveralls CI fixes for GitHub Actions. RC5 — skipped entirely; build.gradle never had 4.0.0-RC5 and no tag exists. Trim the 4.0.0 entry to only cover post-RC4 work: the broker SPI extraction (PR #293), capability-aware dashboard, consumer-aware peek, queue-detail redesign, NATS pause/soft-delete admin ops, the fan-out ack/nack fix, the new Job.getConversionException middleware API, and the additional modules now published to Maven Central. Add a note about the RC5 skip so anyone tracing version numbers later isn't confused by the gap. * docs: split 4.0.0 entry into RC5 (web fixes + multi-consumer fix) and RC6 Per the release plan: RC5 is the broker SPI extraction, NATS-aware dashboard work, consumer-aware peek, and the multi-consumer fan-out ack/nack fix. RC6 is just the new Job.getConversionException middleware API plus the additional Maven Central publish targets. 4.0.0 GA promotes RC6 with no functional changes. Also fixed a forward reference in the RC4 entry that pointed to "4.0.0" for the SPI extraction; that work lands in RC5. * docs: correct RC3 changelog — Java 17 baseline (lowered from 21) RC3 wasn't housekeeping — it lowered the Java baseline from 21 (declared in RC1) back to 17 so the library can be consumed by applications still on Java 17. The current build is still on Java 17. Updated the RC3 entry to call this out as the headline change and amended the 4.0.0 preamble so the Java baseline is stated correctly (Java 17, not 21) instead of inheriting the now-superseded RC1 note. * Apply Palantir Java Format * nats: fix stale deleteMessage IT to assert tombstone behaviour NatsRqueueMessageMetadataService.deleteMessage was changed in 42eb61e to write a tombstone (a deleted MessageMetadata keyed by metaId) when called for an id that has no metadata yet. That's the intentional contract for stream-resident NATS messages: enqueue doesn't write metadata, so a dashboard delete request lands on a missing record but still has to mark the row as deleted in subsequent peeks. The Redis impl behaves the same way for parity. The IT was still asserting the pre-tombstone behaviour (false on missing) and started failing in CI. Update it to call out the intentional contract: deleteMessage on a missing id returns true and leaves a deleted MessageMetadata behind. Renamed to deleteMessageOnMissingCreatesTombstone so the file documents the behaviour rather than the old absence. * web: replace Pebble templates with plain Java HTML renderer Remove the pebble-spring7 dependency and all associated infrastructure (PebbleExtension, custom functions, ResourceLoader, view config, 10 HTML templates) in favour of a single RqueueHtmlRenderer @component that generates HTML using Java text blocks and StringBuilder. Also centralise the null/zero guard in fmtTime() so callers never need to inline the check. Assisted-By: Claude Code * nats-web: hide Explore Data and Move Messages on utility page Add supportsViewData and supportsMoveMessage to the Capabilities record (both false for NATS). The utility page now hides the two sections and shows an info notice when both are unsupported, avoiding 501 errors from clicking View on an arbitrary Redis key or hitting the move-message endpoint. Assisted-By: Claude Code * web: add @JsonProperty to request fields with camelCase names Host applications that configure Jackson with SNAKE_CASE PropertyNamingStrategy would fail to deserialize fields like aggregationType (→ aggregation_type), dateTypes (→ date_types), srcType/dstType, and consumerName. Adding explicit @JsonProperty annotations pins the JSON key to the camelCase name that the JavaScript dashboard already sends, making rqueue immune to the host app's global naming strategy. Assisted-By: Claude Code * web: guard getExplorePageData against null QueueConfig getQueueConfig returns null when the queue name is unknown. Dereferencing it immediately caused an NPE that surfaced as an HTTP 500 in the host app. Return a code=1 error response instead. Assisted-By: Claude Code * build: pin lettuce-core to explicit version to fix Netty 4.1/4.2 conflict spring-boot-data-redis 4.0.1 pulls spring-boot-netty which upgrades netty-common to 4.2.x while lettuce-core 6.8.x keeps other Netty artifacts at 4.1.x, causing AbstractMethodError at startup. Pinning lettuce-core to the project-defined lettuceVersion (7.2.1) aligns all Netty modules to 4.2.x as expected by Spring Boot 4.0. Also rename CLAUDE.md → AGENTS.md. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * build: bump version to 4.0.0-RC7 and update CHANGELOG RC7 includes a null-guard fix in getExplorePageData and removes the redundant explicit lettuce-core dependency from the example app (already provided transitively by spring-boot-starter-data-redis). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Apply Palantir Java Format * nats: remove duplicateWindow, implement moveMessage/enqueueMessage, add long-running job test - Remove duplicateWindow from StreamDefaults — a long-running job can exceed any finite window; let JetStream manage dedup server-side - Implement NatsRqueueUtilityService.moveMessage() via jsm.getMessage() + js.publish() + jsm.deleteMessage(); strips Nats-Msg-Id to avoid dedup collision at destination; skips error-code 10037 (already consumed) - Implement NatsRqueueUtilityService.enqueueMessage() — republishes from metadata store without Nats-Next-Deliver-Time for immediate delivery - Flip supportsMoveMessage=true and supportsViewData=true in JetStreamMessageBroker - Expose RqueueNatsConfig as a @bean in RqueueNatsAutoConfig so it can be injected into NatsRqueueUtilityService (avoids rqueue-nats → starter cycle) - Add LongRunningJobListener E2E test: loops 6×30 s calling job.updateVisibilityTimeout() to send NATS +WIP signals, preventing redelivery - Replace stale "not supported" stubs in NatsRqueueUtilityServiceTest with mock-based tests for moveMessage and enqueueMessage Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * build: bump version to 4.0.0-RC8 and update CHANGELOG RC8 highlights: NATS ADR-51 scheduling, dashboard move/re-enqueue for NATS, long-running job keep-alive via +WIP, duplicateWindow removed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Apply Palantir Java Format * docs: overhaul NATS configuration page for RC8 - Remove stale "scheduling not supported" warning — enqueueIn/enqueueAt/ enqueuePeriodic now work on NATS >= 2.12 (ADR-51) - Remove duplicate-window from stream property table (removed in RC8) - Add "How the NATS backend works" section: pull consumer model, stream/ subject naming, KV bucket roles, ADR-51 scheduling internals, dedup key shape, long-running job keep-alive (+WIP), dashboard operations - Add "Pitfalls" section: ack-wait vs handler duration, missing keep-alive, scheduling version requirement, periodic message dedup edge case, WORK_QUEUE retention, priority weighting, elastic concurrency, purge unsupported, replica count vs cluster size - Update feature comparison table to reflect current capabilities - Update index.md: fix requirements note and NATS section blurb Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs+fix: update RC7 changelog and fix RqueueViewControllerTest compilation Add RC7 changelog entry noting pebble-spring7 removal and the switch to RqueueHtmlRenderer. Update RqueueViewControllerTest to assert HTTP status, content-type, and rendered HTML content instead of the removed PebbleView. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: null-guard serverInfo in NatsProvisioner to fix unit tests connection.getServerInfo() can return null (e.g. on a Mockito mock or before the handshake completes). Guard both the version check and the log statement so schedulingSupported defaults to false instead of NPE-ing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Apply Palantir Java Format * test: skip scheduled-message E2E tests when NATS server < 2.12 The CI nats-server is older than 2.12.0 and does not support ADR-51 message scheduling. Add a @beforeeach assumeTrue guard that skips both scheduledMessageIsDeliveredAfterDelay and its reactive counterpart when NatsProvisioner reports scheduling is unavailable, matching the intent already documented in the class Javadoc. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test: add unit tests for NATS KV, lock, metrics, worker-registry, and exception New @NatsUnitTest-tagged test classes covering: - NatsKvBucketsTest: ALL_BUCKETS catalogue completeness, immutability, distinct names - NatsKvBucketValidatorTest: autoCreate skip, missing-bucket validation, IOException handling - NatsRqueueLockManagerTest: acquire/release happy paths, conflict, IOException, sanitize - NatsRqueueQueueMetricsProviderTest: pending/DLQ counts, unknown-queue, stream errors - NatsWorkerRegistryStoreTest: CRUD across worker-info and heartbeat buckets, key sanitisation, swallowed exceptions - RqueueNatsExceptionTest: constructors and cause propagation 137 unit tests now pass under :rqueue-nats:test -DincludeTags=unit. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test: add 100 unit tests across NATS DAO, repository, and metadata-service New @NatsUnitTest-tagged test classes: - NatsRqueueJobDaoTest (25 tests): save/createJob, findById, findJobsByIdIn, finByMessageId/In (scan + interrupt), delete, key sanitisation - NatsRqueueQStatsDaoTest (15 tests): findById (null, hit, miss, IO, deserialize failure, sanitize), findAll, save (null stat/id guards, IO, sanitize) - NatsRqueueSystemConfigDaoTest (20 tests): cache hit/miss/bypass, getQConfig (cache, scan, interrupt, IO), getConfigByNames, findAllQConfig, saveQConfig, saveAllQConfig, clearCacheByName - NatsMessageBrowsingRepositoryTest (14 tests): getDataSize routing (main queue, DLQ, processing/scheduled, null, stream-not-found 10059, other JsApiException, IOException), getDataSizes, viewData BackendCapabilityException - NatsRqueueMessageMetadataServiceTest (26 tests): get/delete/deleteAll/findAll, save, getByMessageId, deleteMessage (tombstone creation), getOrCreate, saveReactive, readMessageMetadataForQueue, deleteQueueMessages, saveMessageMetadataForQueue Total unit test count: 235 (up from 137). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test: add coverage-boosting tests for rqueue-spring, rqueue-web, rqueue-nats, rqueue-spring-boot-starter - rqueue-spring: NatsBackendConditionTest (7), RqueueBackendImportSelectorTest (7), RqueueRedisConfigImportSelectorTest (3), MetricsEnabledTest (2) - rqueue-web: BaseControllerTest (4), RqueueWebExceptionAdviceTest (7), RqueueRestControllerTest (28), RqueueViewControllerTest (23), RqueueJobServiceImplTest (11) - rqueue-nats: NatsProvisionerTest (15) — KV/stream/DLQ provisioning and scheduling detection - rqueue-spring-boot-starter: RqueueNatsPropertiesTest (28), RqueueNatsAutoConfigToBrokerConfigTest (22) All modules: 157 new unit tests; all pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Apply Palantir Java Format * fixed test * Apply Palantir Java Format --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 9757517 commit a868dcd

116 files changed

Lines changed: 10173 additions & 2274 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

AGENTS.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Project rules for AI tools
2+
3+
## Commits
4+
5+
**Never add `Co-Authored-By:` trailers crediting any AI tool to a git commit.**
6+
7+
This includes any AI assistant (e.g. GitHub Copilot, Cursor, Codeium, Gemini, and similar tools). Commits must list a human author only — AI tools are not co-authors.
8+
9+
**You may add an `Assisted-By:` trailer naming the tool.** Example:
10+
11+
```
12+
Assisted-By: <tool name>
13+
```
14+
15+
Use a short, plain tool name. No emails, no URLs, no marketing taglines. One trailer per commit is enough.
16+
17+
If your default commit template includes a `Co-Authored-By:` for an AI tool, strip it before committing and replace it with `Assisted-By: <tool>` instead. This rule applies to all branches and to rewrites.
18+
19+
## Versioning
20+
21+
**Never change the version declared in any project file** (`build.gradle`, `gradle.properties`, `pom.xml`, etc.) that is already stored locally.
22+
23+
If you need to publish a patched or experimental build, append a suffix to the existing version string instead of bumping the base version. Examples:
24+
25+
- `4.0.0-RC6``4.0.0-RC6-fix1`
26+
- `4.0.0-RC6``4.0.0-RC6-SNAPSHOT`
27+
28+
Never remove or increment the base version number. The human decides when the official version changes.
29+
30+
31+
<claude-mem-context>
32+
# Memory Context
33+
34+
# [rqueue] recent context, 2026-05-09 2:28pm GMT+5:30
35+
36+
No previous sessions found.
37+
</claude-mem-context>

CLAUDE.md

Lines changed: 0 additions & 17 deletions
This file was deleted.

build.gradle

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ ext {
6161

6262
// server
6363
jakartaServletVersion = "6.1.0"
64-
pebbleVersion = "4.1.0"
6564

6665
// database
6766
lettuceVersion = "7.2.1.RELEASE"
@@ -85,7 +84,7 @@ ext {
8584

8685
subprojects {
8786
group = "com.github.sonus21"
88-
version = "4.0.0-RC4"
87+
version = "4.0.0-RC8"
8988

9089
dependencies {
9190
// https://mvnrepository.com/artifact/org.springframework/spring-messaging

docs/CHANGELOG.md

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,160 @@ layout: default
88

99
All notable user-facing changes to this project are documented in this file.
1010

11+
## Release [4.0.0] TBD
12+
13+
{: .highlight}
14+
First stable 4.0.0 release. Targets Spring Boot 4.x and Spring Framework 7.x on
15+
Java 17 (lowered from the original Java 21 baseline in RC3). Promotes the RC6
16+
line to GA — no functional changes versus RC6. See RC1 / RC2 below for the
17+
foundational Spring Boot 4 and Jackson 3 migration notes; RC3 for the Java 17
18+
baseline change; RC4–RC6 below for the NATS backend, broker SPI, dashboard
19+
work, and middleware additions that build on top.
20+
21+
## Release [4.0.0.RC8] 2026-05-09
22+
23+
{: .highlight}
24+
Release candidate.
25+
26+
### Features
27+
* **NATS message scheduling (ADR-51)** — delayed and periodic message delivery
28+
is now fully supported on NATS servers ≥ 2.12 via the `Nats-Next-Deliver-Time`
29+
header (ADR-51). `RqueueMessageEnqueuer.enqueueIn()` and `enqueuePeriodic()`
30+
work transparently; the broker advertises `supportsDelayedEnqueue=true` when
31+
the connected server supports scheduling. Older servers continue to work with
32+
scheduling silently disabled.
33+
* **Dashboard move-message for NATS**`NatsRqueueUtilityService.moveMessage()`
34+
is now implemented: walks the source JetStream stream, republishes each message
35+
to the destination stream, and hard-deletes the source sequence. The dashboard
36+
"move messages" panel is now functional for NATS queues.
37+
* **Dashboard re-enqueue for NATS**`enqueueMessage()` looks up the message
38+
from the metadata store and republishes it immediately (no
39+
`Nats-Next-Deliver-Time` header) so the worker picks it up on the next poll.
40+
* **Long-running job keep-alive**`Job.updateVisibilityTimeout(Duration)` now
41+
issues a NATS `+WIP` (work-in-progress) signal that resets the consumer's
42+
`ackWait` timer, preventing redelivery while a long-running handler is still
43+
active.
44+
45+
### Changes
46+
* **`duplicateWindow` removed from stream config** — the per-stream
47+
`DuplicateWindow` setting has been removed. Any job running longer than a
48+
finite duplicate window could trigger unexpected dedup expiry on retry. JetStream
49+
now manages deduplication server-side with its own defaults. The dedup key shape
50+
(`id-at-processAt`) still guarantees correctness for periodic messages across
51+
periods.
52+
53+
## Release [4.0.0.RC7] 2026-05-08
54+
55+
{: .highlight}
56+
Release candidate.
57+
58+
### Fixes
59+
* **Null guard in queue explorer**`getExplorePageData` now returns a
60+
structured error response (`"Queue '…' does not exist"`) when the requested
61+
queue name is not registered, instead of throwing a `NullPointerException`.
62+
63+
### Changes
64+
* **Removed Pebble template engine** — the `pebble-spring7` dependency has been
65+
dropped. All dashboard HTML is now produced directly in Java using text blocks
66+
and `StringBuilder` via the new `RqueueHtmlRenderer` component, eliminating
67+
the external templating runtime entirely.
68+
69+
### Build
70+
* **Removed redundant `lettuce-core` dependency from example app** — the
71+
example app previously declared `io.lettuce:lettuce-core` without an explicit
72+
version. The dependency is now removed as it is already transitively provided
73+
by `spring-boot-starter-data-redis`.
74+
75+
## Release [4.0.0.RC6] 2026-05-07
76+
77+
{: .highlight}
78+
Release candidate.
79+
80+
### Features
81+
* **Message-converter exception exposed to middleware**`Job` now exposes
82+
`getConversionException()` (and a `hasConversionException()` default) so
83+
middleware can detect and react to inbound deserialization failures (route
84+
to DLQ, alert, attempt a fallback decode) instead of being unable to
85+
distinguish a converter error from a legitimately-String payload.
86+
87+
### Build
88+
* `rqueue-nats`, `rqueue-redis`, and `rqueue-web` now publish to Maven Central
89+
alongside `rqueue-core`, `rqueue-spring`, and `rqueue-spring-boot-starter`.
90+
91+
## Release [4.0.0.RC5] TBD
92+
93+
{: .highlight}
94+
Release candidate. The two themes are a multi-consumer correctness fix on the
95+
NATS backend and a NATS-aware dashboard built on a new pluggable broker SPI.
96+
97+
### Features
98+
* **Pluggable broker SPI** — the queueing layer was separated from Redis behind
99+
a `MessageBroker` SPI with a `Capabilities` model. The dashboard, explorer,
100+
and admin paths adapt to backend capabilities (nav tabs, charts, data-type
101+
labels, queue-size accounting) instead of assuming Redis primitives.
102+
* **Consumer-aware peek** — added a consumer-aware `peek` overload on the
103+
broker SPI. The dashboard explorer can browse a specific consumer's
104+
outstanding messages on Limits-retention streams, skipping already-acked
105+
ranges and reflecting per-consumer ack floors. Useful for fan-out topologies
106+
where each durable has a different delivery position.
107+
* **NATS-aware queue detail page** — redesigned queue detail with a hero panel,
108+
chip strip, per-consumer Subscribers table (separate Pending and In-Flight
109+
columns plus a Workers column), and a Terminal Storage card. Pending shows
110+
yet-to-deliver count; In-Flight shows messages currently being processed.
111+
Limits-retention queues render approximate sizes with a `~` prefix.
112+
* **Pause / soft-delete admin ops for NATS queues** — operators can pause and
113+
soft-delete NATS queues from the dashboard, with capability-gated controls
114+
so unsupported actions do not appear on backends that cannot honour them.
115+
116+
### Fixes
117+
* **NATS ack/nack under multi-consumer fan-out** — fixed an in-flight key
118+
collision that could cause ack/nack to target the wrong NATS message when
119+
multiple consumers were fanning out from the same stream.
120+
* **Consumer-name resolution**`resolvedConsumerName` now uses a single
121+
consumer-name suffix, preventing duplicated suffixing under repeated lookups.
122+
* **Peek base sequence** — NATS peek now bases on `ackFloor` rather than
123+
`delivered.streamSeq`, so the explorer shows the correct un-acked tail
124+
instead of skipping past acked-but-not-yet-deleted messages.
125+
126+
### Migration Notes
127+
* Backends are now selected via the `MessageBroker` SPI. Existing Redis
128+
applications continue to work without configuration changes — a Redis
129+
broker is wired by default. Applications wanting NATS should add
130+
`rqueue-nats` and configure a JetStream `MessageBroker` bean.
131+
* The dashboard `/explore` API gained a `consumerName` query parameter
132+
(nullable). Callers using the REST API directly should pass `null` to
133+
preserve existing behaviour or a specific consumer name to scope the peek.
134+
135+
## Release [4.0.0.RC4] 14-Apr-2026
136+
137+
{: .highlight}
138+
Release candidate. The headline change is the introduction of the NATS
139+
JetStream backend.
140+
141+
### Features
142+
* **NATS JetStream backend** — added a new `rqueue-nats` module that lets Rqueue
143+
run on NATS JetStream as the message broker. Supports Limits-retention and
144+
WorkQueue-retention streams, durable consumers, and ack/nack delivery
145+
semantics. This is the initial drop; the broker SPI extraction and the
146+
capability-aware dashboard land in RC5.
147+
148+
### CI / Build
149+
* Coveralls integration fixed for GitHub Actions (token wiring, build-number
150+
propagation, request-payload diagnostics).
151+
152+
## Release [4.0.0.RC3] 14-Apr-2026
153+
154+
{: .highlight}
155+
Lowers the Java baseline from 21 back to 17.
156+
157+
### Changes
158+
* **Java 17 baseline (was 21 in RC1/RC2)**`languageVersion`,
159+
`sourceCompatibility`, and `targetCompatibility` reverted from 21 to 17 so
160+
the library can be consumed by applications still on Java 17. RC1's
161+
"Java 21 baseline" note is superseded — 4.0.0 supports Java 17 and above.
162+
* Documentation tweaks (header refresh, dependabot bump for `addressable`
163+
in `/docs`).
164+
11165
## Release [4.0.0.RC2] 24-Mar-2026
12166

13167
{: .highlight}

0 commit comments

Comments
 (0)