jsconsumer: correct beliefs the broker-semantics suite measured differently

Commit

nodo3w ago

COR-1257's suite contradicted three things this package asserted. Verified the load-bearing one here rather than taking it on report, and it holds.

TERM SETTLES. Measured on nats-server 2.14.3 single-node across limits, workqueue and interest retention: after a Term the ack floor advances past the message and NumAckPending returns to zero, and on workqueue/interest the message also leaves the stream. ENT-1492's write-up implies otherwise and this package repeated it. The objection to a bare Term is the missing RECORD, not the settlement, and that is the only part the capture-first design ever depended on — so no behaviour changes, but the reasoning no longer teaches something false to whoever reads it next. (The production wedge ENT-1492 recorded is better explained by the COR-1224 ack-permission gap: acks are fire-and-forget publishes to $JS.ACK.>, so without publish rights msg.Ack() returns nil while the floor stays pinned. That is precisely why the terminal path here uses DoubleAck.)

NAKWITHDELAY CANNOT EXPRESS ITS OWN ENVELOPE. The server backdates the pending timestamp by AckWait and measures it against the CURRENT rung, so the effective wait is d + (BackOff[rung] - BackOff[0]). Measured on [200ms, 1200ms], NakWithDelay(50ms) redelivered at 50ms, 1050ms, 1050ms. My own 3s/3s/3s measurement could not see this because a flat ladder's stretch is zero. This makes the case for doing nothing stronger than the one previously documented: not merely that a client would have to know the delay, but that on a growing ladder its timing is not predictable at all. Also the likeliest explanation for ENT-1535's unverified "~78s against a 5m first rung".

FLOOR+1 FLIPS ON AN UNRELATED ACK. The failure is narrower and stranger than "after a drain": with unmatched sequences below the blocker and nothing acked beneath them the floor sits under those, so floor+1 names a message the consumer never receives — and one ack of any matching message below the blocker makes the floor skip the unmatched run so floor+1 starts naming the blocker correctly. Stream-side removal drags the floor to the delivered high-water mark with no ack at all. Rewritten to describe the mechanism rather than a figure another session could not reproduce.

Two confirmations folded in where they matter: an exhausted message pins the floor with NumAckPending at ZERO, which is direct evidence for the delivered-versus-floor stall gate over the num-ack-pending one the monitor text suggests; and a stream's ConsumerLimits silently fill an unset InactiveThreshold, so a consumer can inherit a deletion timer it never asked for — noted on the field.

Refs ENT-1601, ENT-1535, ENT-1492, COR-1257, COR-1224.

Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com Claude-Session: https://claude.ai/code/session_01AJNdqGnT92NC2TRnpf8MNr Entire-Checkpoint: f2748f00e41d

Checkpoints

Fix NATS Consumer Retry Ladder Implementation

Claude CodeOpus 5
View session
Checkpoint 1

Reconcile jsconsumer Retry Schedule After Broker Semantics

Claude CodeOpus 5.[1m]
View session
Checkpoint 1