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 +4 -4
- data/CHANGELOG.md +21 -0
- data/docs/RESTRICTED_PERMISSIONS.md +20 -18
- data/lib/jetstream_bridge/consumer/consumer.rb +8 -0
- data/lib/jetstream_bridge/consumer/subscription_manager.rb +33 -13
- data/lib/jetstream_bridge/errors.rb +1 -0
- data/lib/jetstream_bridge/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 45dac84dd11b563f4f5b0b3d52d85722fdb5b7900923d85f7d6cb7af9b0fad95
|
|
4
|
+
data.tar.gz: a53400f90050c039fa8ac72109a99e3778911200fab983efa6f18549bcf5620f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
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.>",
|
|
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: `
|
|
126
|
-
- subscribe allow: `
|
|
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: `
|
|
129
|
-
- subscribe allow: `
|
|
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
|
-
"
|
|
671
|
-
"$JS.API.CONSUMER.MSG.NEXT.
|
|
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
|
-
"
|
|
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
|
|
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!**
|
|
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
|
|
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
|
-
|
|
37
|
-
|
|
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
|
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.
|
|
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-
|
|
11
|
+
date: 2026-02-11 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activerecord
|