jetstream_bridge 7.1.0 → 7.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 290f604d47d1605f6c2b2abcde100159327a7418b103c0c054b0b92f5e7fbf56
4
- data.tar.gz: 485e9cf4070d9671a1994ffbde32ab8ca6e41fbfb7d94d8cf571e2e8ec04cd3b
3
+ metadata.gz: 45dac84dd11b563f4f5b0b3d52d85722fdb5b7900923d85f7d6cb7af9b0fad95
4
+ data.tar.gz: a53400f90050c039fa8ac72109a99e3778911200fab983efa6f18549bcf5620f
5
5
  SHA512:
6
- metadata.gz: a1a17ed6a75d8f9b32de3efbe23da1797824c4e846c0d4d73ee1014d9ea20048367088cfaabd3f9a02179314090741237d21dffc0c618a429a47510597bb1ed8
7
- data.tar.gz: f3ae4a9e9b256f4dfa1d1e84a32d66140ae01c52d07f21b8fac7976c61bb76a81cb83a53aae6631af9c0affa81eb24d81b7167ebb18875489e398f7869ebd2d6
6
+ metadata.gz: 9e1790cb302107920d71736110db8bc19ae08ac111725614bb4221c73daddf4a992fea8c5c9913cc8dfeea7ebb4bcad359d189e691cd6cf45c1e7bb516b708c1
7
+ data.tar.gz: 60ee52e1c6943c8fb0c95cb7ea7f2b12d18d914fd2f03981134deaf6c5bef95680671d25ed66585914299216e70fc25f924dcf66925da7076687f95e6ee9b29d
data/CHANGELOG.md CHANGED
@@ -5,6 +5,27 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [7.1.2] - 2026-02-11
9
+
10
+ ### Fixed
11
+
12
+ - **Skip all JS.API calls when `auto_provision=false`** - `ensure_consumer!` and `auto_create_consumer_on_error` no longer issue `stream_info`, `consumer_info`, or `add_consumer` calls when `auto_provision=false`. This fixes failures in restricted NATS accounts where `$JS.API` permissions are not granted and the stream/consumer are pre-provisioned by an admin.
13
+
14
+ ### Changed
15
+
16
+ - **Restricted permissions docs** - Added missing `$JS.ACK.{stream}.{consumer}.>` publish permissions to all permission examples. Without this permission, consumers cannot acknowledge messages and they are redelivered until `max_deliver` is exhausted.
17
+
18
+ ## [7.1.1] - 2026-02-02
19
+
20
+ ### Changed
21
+
22
+ - Fail-fast consumer provisioning: when running in push mode with `auto_provision=false`, the bridge now raises `ConsumerProvisioningError` instead of silently skipping consumer setup, preventing idle workers when the durable is missing.
23
+ - Permission-aware creation: consumer creation now surfaces `ConsumerProvisioningError` on permissions violations (pull or push mode), guiding operators to grant the minimal `$JS.API` rights or pre-provision the durable.
24
+
25
+ ### Added
26
+
27
+ - New `ConsumerProvisioningError` topology error for clearer operator feedback when consumer setup cannot proceed.
28
+
8
29
  ## [7.1.0] - 2026-02-01
9
30
 
10
31
  ### Added
@@ -31,7 +31,7 @@ When you cannot modify NATS server permissions, you have **two options**:
31
31
 
32
32
  - **No JetStream API permissions required** at runtime
33
33
  - Messages delivered automatically to a subscription subject
34
- - Simpler permission model: only needs subscribe permission on delivery subject
34
+ - Simpler permission model: only needs subscribe on delivery subject + publish on `$JS.ACK` for acks
35
35
  - Best for restricted permission environments
36
36
 
37
37
  For both options, you need to:
@@ -82,7 +82,8 @@ end
82
82
  # NATS user permissions (e.g., pwas user)
83
83
  publish: {
84
84
  allow: [
85
- "pwas.>", # Your business subjects only
85
+ "pwas.>", # Your business subjects
86
+ "$JS.ACK.pwas-heavyworth-sync.pwas-workers.>", # Ack/nak messages (required)
86
87
  ]
87
88
  }
88
89
  subscribe: {
@@ -92,7 +93,7 @@ subscribe: {
92
93
  }
93
94
  ```
94
95
 
95
- **No `$JS.API.*` or `_INBOX.>` permissions needed!**
96
+ **No `$JS.API.*` or `_INBOX.>` permissions needed!** However, `$JS.ACK` publish permission is required for the consumer to acknowledge messages. Without it, messages will be redelivered until `max_deliver` is exhausted.
96
97
 
97
98
  ### Provisioning Push Consumers
98
99
 
@@ -122,11 +123,11 @@ nats consumer add pwas-heavyworth-sync pwas-workers \
122
123
  - Topology: **one shared stream per app pair**, with one durable consumer per app (each filters the opposite direction). Pre-provision via `bundle exec rake jetstream_bridge:provision` or NATS CLI.
123
124
  - NATS permissions for runtime creds:
124
125
  - **Pull consumers** (default):
125
- - publish allow: `">"` (or narrowed to your business subjects) and `$JS.API.CONSUMER.MSG.NEXT.{stream_name}.{app_name}-workers`
126
- - subscribe allow: `">"` (or narrowed) and `_INBOX.>` (responses for pull consumers)
126
+ - publish allow: `{app_name}.>`, `$JS.API.CONSUMER.MSG.NEXT.{stream_name}.{app_name}-workers`, and `$JS.ACK.{stream_name}.{app_name}-workers.>`
127
+ - subscribe allow: `{destination_app}.>` and `_INBOX.>` (responses for pull consumers)
127
128
  - **Push consumers** (recommended for restricted environments):
128
- - publish allow: `">"` (or narrowed to your business subjects only - e.g., `pwas.>`)
129
- - subscribe allow: `">"` (or narrowed to your business subjects only - e.g., `heavyworth.>`)
129
+ - publish allow: `{app_name}.>` and `$JS.ACK.{stream_name}.{app_name}-workers.>` (ack/nak)
130
+ - subscribe allow: `{destination_app}.>` (includes delivery subject)
130
131
  - No `$JS.API.*` or `_INBOX.>` permissions needed
131
132
  - Health check will only report connectivity (stream info skipped).
132
133
 
@@ -643,15 +644,15 @@ end
643
644
 
644
645
  ```conf
645
646
  # pwas user
646
- publish: { allow: ["pwas.>"] }
647
+ publish: { allow: ["pwas.>", "$JS.ACK.pwas-heavyworth-sync.pwas-workers.>"] }
647
648
  subscribe: { allow: ["heavyworth.>"] }
648
649
 
649
650
  # heavyworth user
650
- publish: { allow: ["heavyworth.>"] }
651
+ publish: { allow: ["heavyworth.>", "$JS.ACK.pwas-heavyworth-sync.heavyworth-workers.>"] }
651
652
  subscribe: { allow: ["pwas.>"] }
652
653
  ```
653
654
 
654
- **Note:** With push consumers, no `$JS.API.*` or `_INBOX.>` permissions are required.
655
+ **Note:** With push consumers, no `$JS.API.*` or `_INBOX.>` permissions are required. The `$JS.ACK` permission is needed so the consumer can acknowledge (ack/nak) delivered messages.
655
656
 
656
657
  ---
657
658
 
@@ -664,16 +665,17 @@ If you have any influence over NATS permissions, you have two options:
664
665
  Request minimal JetStream API permissions:
665
666
 
666
667
  ```conf
667
- # Minimal permissions needed for pull consumer
668
+ # Minimal permissions needed for pull consumer (e.g., pwas user)
668
669
  publish: {
669
670
  allow: [
670
- ">", # Your app subjects (narrow if desired)
671
- "$JS.API.CONSUMER.MSG.NEXT.{stream_name}.{app_name}-workers", # Fetch messages (required)
671
+ "pwas.>", # Your app subjects
672
+ "$JS.API.CONSUMER.MSG.NEXT.pwas-heavyworth-sync.pwas-workers", # Fetch messages (required)
673
+ "$JS.ACK.pwas-heavyworth-sync.pwas-workers.>", # Ack/nak messages (required)
672
674
  ]
673
675
  }
674
676
  subscribe: {
675
677
  allow: [
676
- ">", # Your app subjects (narrow if desired)
678
+ "heavyworth.>", # Destination app subjects
677
679
  "_INBOX.>", # Request-reply responses (required)
678
680
  ]
679
681
  }
@@ -683,16 +685,16 @@ These are read-only operations and don't allow creating/modifying streams or con
683
685
 
684
686
  ### Option 2: Push Consumers (Recommended)
685
687
 
686
- Use push consumers with business subject permissions only:
688
+ Use push consumers with business subject permissions plus ack:
687
689
 
688
690
  ```conf
689
691
  # For pwas app
690
- publish: { allow: ["pwas.>"] }
692
+ publish: { allow: ["pwas.>", "$JS.ACK.pwas-heavyworth-sync.pwas-workers.>"] }
691
693
  subscribe: { allow: ["heavyworth.>"] }
692
694
 
693
695
  # For heavyworth app
694
- publish: { allow: ["heavyworth.>"] }
696
+ publish: { allow: ["heavyworth.>", "$JS.ACK.pwas-heavyworth-sync.heavyworth-workers.>"] }
695
697
  subscribe: { allow: ["pwas.>"] }
696
698
  ```
697
699
 
698
- **No `$JS.API.*` or `_INBOX.>` permissions required!** This is the simplest and most restrictive permission model.
700
+ **No `$JS.API.*` or `_INBOX.>` permissions required!** The only extra permission beyond business subjects is `$JS.ACK` for acknowledging messages.
@@ -450,6 +450,14 @@ module JetstreamBridge
450
450
  end
451
451
 
452
452
  def auto_create_consumer_on_error(_error)
453
+ unless JetstreamBridge.config.auto_provision
454
+ Logging.info(
455
+ "Skipping consumer auto-creation (auto_provision=false) for #{@durable}",
456
+ tag: 'JetstreamBridge::Consumer'
457
+ )
458
+ return
459
+ end
460
+
453
461
  Logging.info(
454
462
  "Consumer not found error detected, attempting auto-creation for #{@durable}...",
455
463
  tag: 'JetstreamBridge::Consumer'
@@ -28,13 +28,13 @@ module JetstreamBridge
28
28
  @desired_cfg
29
29
  end
30
30
 
31
- # Ensure consumer exists, auto-creating if missing.
32
- #
33
- # @param force [Boolean] Kept for backward compatibility but no longer used.
34
- # Consumers are always auto-created regardless of this parameter.
31
+ # Ensure consumer exists; skips all JS.API calls when auto_provision is false.
35
32
  def ensure_consumer!(**_options)
36
- # Always auto-create consumer if it doesn't exist, regardless of auto_provision setting.
37
- # auto_provision only controls stream topology creation, not consumer creation.
33
+ unless @cfg.auto_provision
34
+ Logging.info("Skipping consumer validation (auto_provision=false); assuming '#{@durable}' exists.",
35
+ tag: 'JetstreamBridge::Consumer')
36
+ return
37
+ end
38
38
  create_consumer_if_missing!
39
39
  end
40
40
 
@@ -70,15 +70,18 @@ module JetstreamBridge
70
70
  false
71
71
  end
72
72
 
73
- # Create consumer only if it doesn't already exist.
74
- #
75
- # Fails if the stream doesn't exist - streams must be provisioned separately.
76
- #
77
- # This is a safer alternative to create_consumer! that won't fail
78
- # if the consumer was already created by another process.
79
- #
73
+ # Create consumer only if it doesn't already exist. Fails if stream is missing.
80
74
  # @raise [StreamNotFoundError] if the stream doesn't exist
81
75
  def create_consumer_if_missing!
76
+ # In restricted environments (push + auto_provision=false), we still want fail-fast semantics.
77
+ # Attempt creation and raise a clear error so operators know the consumer must be pre-provisioned
78
+ # or the account must allow the minimal $JS.API consumer permissions.
79
+ if skip_consumer_management?
80
+ raise JetstreamBridge::ConsumerProvisioningError,
81
+ "Consumer '#{@durable}' not ensured because auto_provision=false and push mode is enabled. " \
82
+ 'Provision the consumer with admin credentials or grant minimal consumer create/info permissions.'
83
+ end
84
+
82
85
  # First, verify stream exists - fail fast with clear error if not
83
86
  unless stream_exists?
84
87
  raise StreamNotFoundError,
@@ -103,6 +106,8 @@ module JetstreamBridge
103
106
  rescue StreamNotFoundError
104
107
  raise
105
108
  rescue StandardError => e
109
+ raise ConsumerProvisioningError, permission_error_message(e) if permission_denied?(e)
110
+
106
111
  # If creation fails due to consumer already existing (race condition), that's OK
107
112
  msg = e.message.to_s.downcase
108
113
  if msg.include?('already') || msg.include?('exists')
@@ -210,6 +215,21 @@ module JetstreamBridge
210
215
  config
211
216
  end
212
217
 
218
+ def skip_consumer_management?
219
+ @cfg.push_consumer? && !@cfg.auto_provision
220
+ end
221
+
222
+ def permission_denied?(error)
223
+ msg = error.message.to_s.downcase
224
+ msg.include?('permission') || msg.include?('permissions violation')
225
+ end
226
+
227
+ def permission_error_message(error)
228
+ "Consumer '#{@durable}' could not be ensured due to permissions: #{error.message}. " \
229
+ 'Grant $JS.API.STREAM.INFO/$JS.API.CONSUMER.INFO/$JS.API.CONSUMER.CREATE for the stream, ' \
230
+ 'or pre-provision the consumer with an admin account.'
231
+ end
232
+
213
233
  def create_consumer!
214
234
  @jts.add_consumer(stream_name, **desired_consumer_cfg)
215
235
  Logging.info(
@@ -67,6 +67,7 @@ module JetstreamBridge
67
67
 
68
68
  # Topology errors
69
69
  class TopologyError < Error; end
70
+ class ConsumerProvisioningError < TopologyError; end
70
71
  class StreamNotFoundError < TopologyError; end
71
72
  class SubjectOverlapError < TopologyError; end
72
73
  class StreamCreationFailedError < TopologyError; end
@@ -4,5 +4,5 @@
4
4
  #
5
5
  # Version constant for the gem.
6
6
  module JetstreamBridge
7
- VERSION = '7.1.0'
7
+ VERSION = '7.1.2'
8
8
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jetstream_bridge
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.1.0
4
+ version: 7.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Attara
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-02-01 00:00:00.000000000 Z
11
+ date: 2026-02-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord