natsmsg: filter capture headers by namespace instead of by name

main

Commit

nodo3w ago

Third review pass, third missing directive, so this changes the design rather than the list. Nats-Incr, Nats-Counter-Sources and Nats-Batch-{Id,Sequence,Commit} were still copied onto the DLQ, and Nats-Schedule-Rollup with them — none of the six is exported by the pinned client, which is exactly why enumerating names kept failing.

Measured on the pinned nats-server 2.14.3, an ordinary DLQ refuses each of them: per-message TTL 10166, counter increment 10168, atomic batch 10174, and an expectation about another stream 10060. The refusals are permanent — retrying does not change the stream's configuration — so a capture carrying one fails on every delivery of the ladder and the message ends stranded with the floor pinned. The breaker terminates through the same capture, so it is defeated identically. That is the same failure this branch has now fixed three times.

The filter is now a namespace boundary. Nats- is NATS's reserved header space: everything the broker interprets lives there and an application has no business writing there. A captured copy keeps every header OUTSIDE it verbatim — tracing, application metadata — and nothing inside it except this package's own Nats-Dlq- provenance, plus a Nats-Msg-Id it authors itself. A directive NATS ships in a future release is dropped before anyone here has heard of it, which is the property both earlier designs lacked. Filtering happens on the way in, so a copy never holds a directive that a missed name could leak.

Matching is case-insensitive although the broker's lookup is a case-sensitive byte compare (server.getHeaderKeyIndex), which makes an oddly-cased directive inert today. Being stricter than necessary costs nothing here and holds if that changes.

Deliberately given up: the server-set republish and direct-get provenance (Nats-Stream, Nats-Sequence, Nats-Time-Stamp, Nats-Subject) on a message that was itself republished — reversing what the previous commit said about them. Keeping them under an allow-list means asserting they are safe for a client to publish, which nats.go explicitly says they are not. The facts that matter are re-stated in this package's own namespace, where the authorship is known.

Broker-backed regressions, in internal/brokersemantics per this module's rule that a JetStream claim gets measured rather than asserted:

  • the four rejections above, on a DLQ declared the way adopters declare one;
  • an end-to-end capture of a message carrying every directive the pinned server knows, asserting the capture succeeds and the stored copy carries none.

The directive list references natsserver's own constants, so a rename on the next server bump breaks compilation — the signal a header-filtering rule wants. Against the previous denylist that end-to-end test fails with err_code=10174.

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

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

Checkpoints

Reconcile jsconsumer Retry Schedule After Broker Semantics

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