jetstream_bridge 4.5.0 → 4.5.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.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +338 -87
  3. data/README.md +3 -13
  4. data/docs/GETTING_STARTED.md +8 -12
  5. data/docs/PRODUCTION.md +13 -35
  6. data/docs/RESTRICTED_PERMISSIONS.md +525 -0
  7. data/docs/TESTING.md +33 -22
  8. data/lib/generators/jetstream_bridge/health_check/health_check_generator.rb +3 -3
  9. data/lib/generators/jetstream_bridge/initializer/templates/jetstream_bridge.rb +3 -0
  10. data/lib/jetstream_bridge/consumer/consumer.rb +100 -39
  11. data/lib/jetstream_bridge/consumer/message_processor.rb +1 -1
  12. data/lib/jetstream_bridge/consumer/subscription_manager.rb +97 -121
  13. data/lib/jetstream_bridge/core/bridge_helpers.rb +127 -0
  14. data/lib/jetstream_bridge/core/config.rb +32 -161
  15. data/lib/jetstream_bridge/core/connection.rb +508 -0
  16. data/lib/jetstream_bridge/core/connection_factory.rb +95 -0
  17. data/lib/jetstream_bridge/core/debug_helper.rb +2 -9
  18. data/lib/jetstream_bridge/core.rb +2 -0
  19. data/lib/jetstream_bridge/models/subject.rb +15 -23
  20. data/lib/jetstream_bridge/provisioner.rb +67 -0
  21. data/lib/jetstream_bridge/publisher/publisher.rb +121 -92
  22. data/lib/jetstream_bridge/rails/integration.rb +5 -8
  23. data/lib/jetstream_bridge/rails/railtie.rb +3 -4
  24. data/lib/jetstream_bridge/tasks/install.rake +59 -12
  25. data/lib/jetstream_bridge/topology/topology.rb +1 -6
  26. data/lib/jetstream_bridge/version.rb +1 -1
  27. data/lib/jetstream_bridge.rb +345 -202
  28. metadata +8 -8
  29. data/lib/jetstream_bridge/consumer/health_monitor.rb +0 -107
  30. data/lib/jetstream_bridge/core/connection_manager.rb +0 -513
  31. data/lib/jetstream_bridge/core/health_checker.rb +0 -184
  32. data/lib/jetstream_bridge/facade.rb +0 -212
  33. data/lib/jetstream_bridge/publisher/event_envelope_builder.rb +0 -110
data/docs/PRODUCTION.md CHANGED
@@ -78,9 +78,9 @@ JetstreamBridge.configure do |config|
78
78
  config.connect_retry_delay = 3 # Default: 2 seconds
79
79
 
80
80
  # Required configuration
81
+ config.stream_name = ENV.fetch("JETSTREAM_STREAM_NAME", "jetstream-bridge-stream")
81
82
  config.app_name = ENV.fetch("APP_NAME", "myapp")
82
83
  config.destination_app = ENV.fetch("DESTINATION_APP")
83
- config.stream_name = ENV.fetch("STREAM_NAME", "myapp-stream")
84
84
 
85
85
  # Enable reliability features
86
86
  config.use_outbox = true
@@ -100,31 +100,6 @@ JetstreamBridge.configure do |config|
100
100
  end
101
101
  ```
102
102
 
103
- ### Permissions and Inbox Prefix
104
-
105
- If your NATS account restricts `_INBOX.>` subscriptions, set an allowed prefix:
106
-
107
- ```ruby
108
- JetstreamBridge.configure do |config|
109
- config.inbox_prefix = "$RPC"
110
- end
111
- ```
112
-
113
- For pre-provisioned streams and consumers:
114
-
115
- ```ruby
116
- JetstreamBridge.configure do |config|
117
- config.stream_name = "my-stream" # required
118
- config.durable_name = "my-durable" # optional
119
- config.disable_js_api = true # skip JetStream management APIs
120
- end
121
- ```
122
-
123
- Minimum NATS permissions:
124
-
125
- - **Publish**: `$JS.API.>`, `$JS.ACK.>`, source/destination subjects, DLQ subject
126
- - **Subscribe**: `_INBOX.>` (or custom inbox_prefix), destination subject
127
-
128
103
  ---
129
104
 
130
105
  ## Consumer Tuning
@@ -167,7 +142,7 @@ Long-running consumers automatically:
167
142
 
168
143
  - Log health checks every 10 minutes (iterations, memory, uptime)
169
144
  - Warn when memory exceeds 1GB
170
- - Warn once when heap object counts grow large so you can profile/trigger GC in the host app
145
+ - Suggest garbage collection when heap grows large
171
146
 
172
147
  Monitor these logs to detect memory leaks early.
173
148
 
@@ -178,7 +153,7 @@ Monitor these logs to detect memory leaks early.
178
153
  ### Key Metrics to Track
179
154
 
180
155
  | Metric | Description | Alert Threshold |
181
- | -------- | ------------- | ----------------- |
156
+ | --- | --- | --- |
182
157
  | Consumer Lag | Pending messages in stream | > 1000 messages |
183
158
  | DLQ Size | Messages in dead letter queue | > 100 messages |
184
159
  | Connection Status | Health check failures | 2 consecutive failures |
@@ -194,7 +169,7 @@ Use the built-in health check for monitoring:
194
169
  # config/routes.rb
195
170
  Rails.application.routes.draw do
196
171
  get '/health/jetstream', to: proc { |env|
197
- health = JetstreamBridge.health
172
+ health = JetstreamBridge.health_check
198
173
  status = health[:healthy] ? 200 : 503
199
174
  [status, { 'Content-Type' => 'application/json' }, [health.to_json]]
200
175
  }
@@ -213,8 +188,8 @@ end
213
188
  },
214
189
  "stream": {
215
190
  "exists": true,
216
- "name": "production-jetstream-bridge-stream",
217
- "subjects": ["production.app.sync.worker"],
191
+ "name": "jetstream-bridge-stream",
192
+ "subjects": ["app.sync.worker"],
218
193
  "messages": 1523
219
194
  },
220
195
  "performance": {
@@ -222,15 +197,14 @@ end
222
197
  "health_check_duration_ms": 45.2
223
198
  },
224
199
  "config": {
225
- "env": "production",
200
+ "stream_name": "jetstream-bridge-stream",
226
201
  "app_name": "app",
227
202
  "destination_app": "worker",
228
203
  "use_outbox": true,
229
204
  "use_inbox": true,
230
- "use_dlq": true,
231
- "disable_js_api": true
205
+ "use_dlq": true
232
206
  },
233
- "version": "4.4.0"
207
+ "version": "4.0.3"
234
208
  }
235
209
  ```
236
210
 
@@ -267,6 +241,10 @@ jetstream_dlq = prometheus.gauge(
267
241
 
268
242
  ## Security Hardening
269
243
 
244
+ ### Permissions & Topology
245
+
246
+ Keep runtime credentials least-privileged: set `config.auto_provision = false` and provision the stream/consumer during deploy (`bundle exec rake jetstream_bridge:provision` or NATS CLI). The exact durable/subject layout and minimal publish/subscribe permissions live in [RESTRICTED_PERMISSIONS.md](RESTRICTED_PERMISSIONS.md). Health checks skip stream info when `auto_provision=false`; rely on your provisioning pipeline for validation.
247
+
270
248
  ### Rate Limiting
271
249
 
272
250
  The health check endpoint has built-in rate limiting (1 uncached request per 5 seconds). For HTTP endpoints, add additional protection:
@@ -0,0 +1,525 @@
1
+ # Working with Restricted NATS Permissions
2
+
3
+ This guide explains how to use JetStream Bridge when your NATS user lacks JetStream API permissions (`$JS.API.*` subjects).
4
+
5
+ ## Problem
6
+
7
+ When the NATS user has restricted permissions and cannot access JetStream API subjects, you'll see errors like:
8
+
9
+ ```markdown
10
+ ERROR -- : [JetstreamBridge::ConnectionManager] NATS error: 'Permissions Violation for Publish to "$JS.API.CONSUMER.INFO.{stream}.{consumer}"'
11
+ NATS::IO::Timeout: nats: timeout
12
+ ```
13
+
14
+ This happens because:
15
+
16
+ 1. JetStream Bridge tries to verify consumer configuration using `$JS.API.CONSUMER.INFO.*`
17
+ 2. The NATS user doesn't have permission to publish to these API subjects
18
+ 3. The connection is terminated, causing timeout errors
19
+
20
+ ## Solution Overview
21
+
22
+ When you cannot modify NATS server permissions, you need to:
23
+
24
+ 0. **Turn off runtime provisioning** so the app never calls `$JS.API.*`:
25
+ - Set `config.auto_provision = false`
26
+ - Provision stream + consumer once with admin credentials (CLI below or `bundle exec rake jetstream_bridge:provision`)
27
+ 1. **Pre-create the consumer** using a privileged NATS account
28
+ 2. **Ensure the consumer configuration matches** what your app expects
29
+
30
+ > Tip: When `auto_provision=false`, the app still connects/publishes/consumes but skips JetStream management APIs (account_info, stream_info, consumer_info). Health checks will report basic connectivity only.
31
+
32
+ ---
33
+
34
+ ## Runtime requirements (least privilege)
35
+
36
+ - Config: `config.auto_provision = false`, `config.stream_name` set explicitly.
37
+ - 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.
38
+ - NATS permissions for runtime creds:
39
+ - publish allow: `">"` (or narrowed to your business subjects) and `$JS.API.CONSUMER.MSG.NEXT.{stream_name}.{app_name}-workers`
40
+ - subscribe allow: `">"` (or narrowed) and `_INBOX.>` (responses for pull consumers)
41
+ - Health check will only report connectivity (stream info skipped).
42
+
43
+ ### Topology required
44
+
45
+ - Stream: `config.stream_name` (retention: workqueue, storage: file).
46
+ - Subjects:
47
+ - Publish: `{app_name}.sync.{destination_app}`
48
+ - Consume: `{destination_app}.sync.{app_name}`
49
+ - DLQ (if enabled): `{app_name}.sync.dlq`
50
+ - Durable consumer: `{app_name}-workers` filtering on `{destination_app}.sync.{app_name}`.
51
+
52
+ ---
53
+
54
+ ### Connectivity check with restricted creds
55
+
56
+ After setting `config.auto_provision = false`, you can still confirm basic connectivity without touching `$JS.API.*` by running:
57
+
58
+ ```bash
59
+ bundle exec rake jetstream_bridge:test_connection
60
+ ```
61
+
62
+ In this mode the task performs a NATS ping and JetStream client setup only; it assumes the stream + consumer are already provisioned with admin credentials. If you see a permissions violation for `$JS.API.*`, re-run with a privileged user to provision or set `auto_provision=false` and pre-create the stream/consumer first.
63
+
64
+ ---
65
+
66
+ ## Option A: Provision with the built-in task (creates stream + consumer)
67
+
68
+ Run this from CI/deploy with admin NATS credentials:
69
+
70
+ ```bash
71
+ # Uses your configured stream/app/destination to create the stream + durable consumer
72
+ NATS_URLS="nats://admin:pass@nats.example.com:4222" \
73
+ bundle exec rake jetstream_bridge:provision
74
+ ```
75
+
76
+ This is the easiest way to keep `auto_provision=false` in runtime while still reusing the bridge’s topology logic (subjects, DLQ, overlap guard).
77
+
78
+ ## Option B: Pre-create the Consumer using NATS CLI
79
+
80
+ ### Install NATS CLI
81
+
82
+ ```bash
83
+ # Download from https://github.com/nats-io/natscli/releases
84
+ curl -sf https://binaries.nats.dev/nats-io/natscli/nats@latest | sh
85
+
86
+ # Or using Homebrew (macOS)
87
+ brew install nats-io/nats-tools/nats
88
+ ```
89
+
90
+ ### Create the Consumer
91
+
92
+ You need to create a durable pull consumer with the exact configuration your app expects. Both apps share a single stream; create one consumer per app (each filters the opposite direction).
93
+
94
+ **Required values from your JetStream Bridge config:**
95
+
96
+ - **Stream name**: `JETSTREAM_STREAM_NAME` (e.g., `pwas-heavyworth-sync`)
97
+ - **Consumer name**: `{app_name}-workers` (e.g., `pwas-workers`)
98
+ - **Filter subject**: `{destination_app}.sync.{app_name}` (e.g., `heavyworth.sync.pwas`)
99
+
100
+ **Create consumer command:**
101
+
102
+ ```bash
103
+ # Connect using a privileged NATS account
104
+ nats context save admin \
105
+ --server=nats://admin-user:admin-pass@nats.example.com:4222 \
106
+ --description="Admin account for consumer creation"
107
+
108
+ # Select the context
109
+ nats context select admin
110
+
111
+ # Create the consumer (replace stream/app/destination with yours)
112
+ nats consumer add pwas-heavyworth-sync pwas-workers \
113
+ --filter "heavyworth.sync.pwas" \
114
+ --ack explicit \
115
+ --pull \
116
+ --deliver all \
117
+ --max-deliver 5 \
118
+ --ack-wait 30s \
119
+ --replay instant \
120
+ --max-pending 25000
121
+ ```
122
+
123
+ **With backoff (recommended for production):**
124
+
125
+ ```bash
126
+ nats consumer add pwas-heavyworth-sync pwas-workers \
127
+ --filter "heavyworth.sync.pwas" \
128
+ --ack explicit \
129
+ --pull \
130
+ --deliver all \
131
+ --max-deliver 5 \
132
+ --ack-wait 30s \
133
+ --backoff 1s,5s,15s,30s,60s \
134
+ --replay instant \
135
+ --max-pending 25000
136
+ ```
137
+
138
+ **Example using your observed stream (`pwas-heavyworth-sync`) and defaults (shared stream, two consumers):**
139
+
140
+ ```bash
141
+ # Admin context
142
+ nats context save admin \
143
+ --server=nats://admin-user:admin-pass@nats.example.com:4222 \
144
+ --description="Admin account for jetstream_bridge provisioning"
145
+ nats context select admin
146
+
147
+ # Create stream with bridge subjects + DLQ
148
+ nats stream add pwas-heavyworth-sync \
149
+ --subjects "pwas.sync.heavyworth" "heavyworth.sync.pwas" "pwas.sync.dlq" \
150
+ --retention workqueue \
151
+ --storage file
152
+
153
+ # Create durable pull consumer (must match JetstreamBridge config)
154
+ nats consumer add pwas-heavyworth-sync pwas-workers \
155
+ --filter "heavyworth.sync.pwas" \
156
+ --ack explicit \
157
+ --pull \
158
+ --deliver all \
159
+ --max-deliver 5 \
160
+ --ack-wait 30s \
161
+ --backoff 1s,5s,15s,30s,60s \
162
+ --replay instant \
163
+ --max-pending 25000
164
+
165
+ # Consumer for heavyworth (receives pwas -> heavyworth)
166
+ nats consumer add pwas-heavyworth-sync heavyworth-workers \
167
+ --filter "pwas.sync.heavyworth" \
168
+ --ack explicit \
169
+ --pull \
170
+ --deliver all \
171
+ --max-deliver 5 \
172
+ --ack-wait 30s \
173
+ --backoff 1s,5s,15s,30s,60s \
174
+ --replay instant \
175
+ --max-pending 25000
176
+
177
+ # Verify
178
+ nats stream info pwas-heavyworth-sync
179
+ nats consumer info pwas-heavyworth-sync heavyworth-workers
180
+ nats consumer info pwas-heavyworth-sync pwas-workers
181
+ ```
182
+
183
+ ### Provision both apps (shared stream + two consumers)
184
+
185
+ If both apps share one stream, create it once and add a durable consumer for each side:
186
+
187
+ ```bash
188
+ STREAM="appA-appB-sync"
189
+ APP_A="appA" # first app name
190
+ APP_B="appB" # second app name
191
+
192
+ # Admin context (update server/creds as needed)
193
+ nats context save admin \
194
+ --server=nats://admin-user:admin-pass@nats.example.com:4222 \
195
+ --description="Admin account for jetstream_bridge provisioning"
196
+ nats context select admin
197
+
198
+ # Stream covers both publish directions + both DLQs
199
+ nats stream add "$STREAM" \
200
+ --subjects "$APP_A.sync.$APP_B" "$APP_B.sync.$APP_A" "$APP_A.sync.dlq" "$APP_B.sync.dlq" \
201
+ --retention workqueue \
202
+ --storage file
203
+
204
+ # Consumer for APP_A (receives messages destined to APP_A)
205
+ nats consumer add "$STREAM" "$APP_A-workers" \
206
+ --filter "$APP_B.sync.$APP_A" \
207
+ --ack explicit \
208
+ --pull \
209
+ --deliver all \
210
+ --max-deliver 5 \
211
+ --ack-wait 30s \
212
+ --backoff 1s,5s,15s,30s,60s \
213
+ --replay instant \
214
+ --max-pending 25000
215
+
216
+ # Consumer for APP_B (receives messages destined to APP_B)
217
+ nats consumer add "$STREAM" "$APP_B-workers" \
218
+ --filter "$APP_A.sync.$APP_B" \
219
+ --ack explicit \
220
+ --pull \
221
+ --deliver all \
222
+ --max-deliver 5 \
223
+ --ack-wait 30s \
224
+ --backoff 1s,5s,15s,30s,60s \
225
+ --replay instant \
226
+ --max-pending 25000
227
+
228
+ # Verify both
229
+ nats consumer info "$STREAM" "$APP_A-workers"
230
+ nats consumer info "$STREAM" "$APP_B-workers"
231
+ ```
232
+
233
+ ### Verify Consumer Creation
234
+
235
+ ```bash
236
+ nats consumer info pwas-heavyworth-sync pwas-workers
237
+ ```
238
+
239
+ Expected output:
240
+
241
+ ```bash
242
+ Information for Consumer pwas-heavyworth-sync > pwas-workers
243
+
244
+ Configuration:
245
+
246
+ Durable Name: pwas-workers
247
+ Filter Subject: heavyworth.sync.pwas
248
+ Ack Policy: explicit
249
+ Ack Wait: 30s
250
+ Replay Policy: instant
251
+ Maximum Deliveries: 5
252
+ Backoff: [1s 5s 15s 30s 60s]
253
+
254
+ State:
255
+
256
+ Last Delivered Message: Consumer sequence: 0 Stream sequence: 0
257
+ Acknowledgment Floor: Consumer sequence: 0 Stream sequence: 0
258
+ Pending Messages: 0
259
+ Redelivered Messages: 0
260
+ ```
261
+
262
+ ---
263
+
264
+ ## Step 2: Configure JetStream Bridge
265
+
266
+ Configure JetStream Bridge to avoid JetStream management APIs at runtime (pre-provisioning handles them instead):
267
+
268
+ ```ruby
269
+ # config/initializers/jetstream_bridge.rb
270
+ JetstreamBridge.configure do |config|
271
+ config.nats_urls = ENV.fetch("NATS_URLS")
272
+ config.stream_name = "pwas-heavyworth-sync"
273
+ config.app_name = "pwas"
274
+ config.destination_app = "heavyworth"
275
+ config.auto_provision = false
276
+
277
+ config.use_outbox = true
278
+ config.use_inbox = true
279
+ config.use_dlq = true
280
+
281
+ # These settings MUST match the pre-created consumer
282
+ config.max_deliver = 5
283
+ config.ack_wait = "30s"
284
+ config.backoff = %w[1s 5s 15s 30s 60s]
285
+ end
286
+ ```
287
+
288
+ **Critical:** The `max_deliver`, `ack_wait`, and `backoff` values in your config **must exactly match** the pre-created consumer configuration. If they don't match, messages may be redelivered incorrectly.
289
+
290
+ ---
291
+
292
+ ## Step 3: Deploy and Verify
293
+
294
+ ### Deploy the Updated Gem
295
+
296
+ If you're working on the jetstream_bridge gem itself:
297
+
298
+ ```bash
299
+ # Build the gem
300
+ gem build jetstream_bridge.gemspec
301
+
302
+ # Install locally for testing
303
+ gem install ./jetstream_bridge-4.5.0.gem
304
+
305
+ # Or update in your application's Gemfile.lock
306
+ bundle update jetstream_bridge
307
+ ```
308
+
309
+ If this is a local modification, you can point your Gemfile to the local path:
310
+
311
+ ```ruby
312
+ # Gemfile (temporary for testing)
313
+ gem 'jetstream_bridge', path: '/path/to/local/jetstream_bridge'
314
+ ```
315
+
316
+ ### Restart the Service
317
+
318
+ ```bash
319
+ sudo systemctl restart pwas_production_sync
320
+ ```
321
+
322
+ ### Monitor the Logs
323
+
324
+ ```bash
325
+ sudo journalctl -u pwas_production_sync -f
326
+ ```
327
+
328
+ **Expected success output:**
329
+
330
+ ```bash
331
+ INFO -- : [JetstreamBridge::ConnectionManager] Connected to NATS (1 server): nats://pwas:***@10.199.12.34:4222
332
+ INFO -- : [DataSync] Consumer starting (durable=pwas-workers, batch=25, dest="heavyworth")
333
+ INFO -- : [DataSync] run! started successfully
334
+ ```
335
+
336
+ Health checks will report connectivity only when `auto_provision=false` (stream info is skipped to avoid `$JS.API.STREAM.INFO`).
337
+
338
+ **If you still see errors:**
339
+
340
+ 1. **Timeout during subscribe** - The pre-created consumer name/filter might not match. Verify:
341
+
342
+ ```bash
343
+ nats consumer ls pwas-heavyworth-sync
344
+ ```
345
+
346
+ 2. **Permission violations on fetch** - The NATS user also needs permission to fetch messages. Minimum required:
347
+
348
+ ```conf
349
+ subscribe: {
350
+ allow: ["production.>", "_INBOX.>"]
351
+ }
352
+ ```
353
+
354
+ ## Maintenance
355
+
356
+ ### Updating Consumer Configuration
357
+
358
+ If you need to change consumer settings (e.g., increase `max_deliver`):
359
+
360
+ 1. **Stop your application** to prevent message processing
361
+
362
+ ```bash
363
+ sudo systemctl stop pwas_production_sync
364
+ ```
365
+
366
+ 2. **Delete the old consumer** (using privileged account)
367
+
368
+ ```bash
369
+ nats consumer rm pwas-heavyworth-sync pwas-workers -f
370
+ ```
371
+
372
+ 3. **Create the new consumer** with updated settings
373
+
374
+ ```bash
375
+ nats consumer add pwas-heavyworth-sync pwas-workers \
376
+ --filter "heavyworth.sync.pwas" \
377
+ --ack explicit \
378
+ --pull \
379
+ --deliver all \
380
+ --max-deliver 10 \
381
+ --ack-wait 60s \
382
+ --backoff 2s,10s,30s,60s,120s \
383
+ --replay instant \
384
+ --max-pending 25000
385
+ ```
386
+
387
+ 4. **Update your application config** to match
388
+
389
+ ```ruby
390
+ config.max_deliver = 10
391
+ config.ack_wait = "60s"
392
+ config.backoff = %w[2s 10s 30s 60s 120s]
393
+ ```
394
+
395
+ 5. **Restart your application**
396
+
397
+ ```bash
398
+ sudo systemctl start pwas_production_sync
399
+ ```
400
+
401
+ ### Monitoring
402
+
403
+ Add monitoring to detect configuration drift:
404
+
405
+ ```ruby
406
+ # Check consumer exists and is healthy
407
+ health = JetstreamBridge.health_check
408
+ unless health[:healthy]
409
+ alert("JetStream consumer unhealthy: #{health}")
410
+ end
411
+ ```
412
+
413
+ ---
414
+
415
+ ## Troubleshooting
416
+
417
+ ### Consumer doesn't receive messages
418
+
419
+ **Check stream has messages:**
420
+
421
+ ```bash
422
+ nats stream info pwas-heavyworth-sync
423
+ ```
424
+
425
+ **Verify filter subject matches your topology:**
426
+
427
+ ```bash
428
+ # App publishes to: {app_name}.sync.{destination_app}
429
+ # Consumer filters on: {destination_app}.sync.{app_name}
430
+ ```
431
+
432
+ ### Service keeps restarting
433
+
434
+ Check if the issue is:
435
+
436
+ 1. **Consumer doesn't exist** - Pre-create it with NATS CLI
437
+ 2. **Filter subject mismatch** - Verify with `nats consumer info`
438
+ 3. **Permissions still insufficient** - User needs subscribe permissions on the filtered subject
439
+ 4. **Wrong stream name** - Ensure stream name matches your configured `config.stream_name`
440
+
441
+ ---
442
+
443
+ ## Security Considerations
444
+
445
+ 1. **Consumer pre-creation requires privileged access** - Keep admin credentials secure
446
+ 2. **Configuration drift risk** - If app config doesn't match consumer config, message delivery may fail silently
447
+ 3. **No automatic recovery** - If consumer is deleted, it won't be recreated automatically
448
+ 4. **Consider automation** - Use infrastructure-as-code (Terraform, Ansible) to manage consumer creation
449
+
450
+ ---
451
+
452
+ ## Example: Production Setup for pwas-api
453
+
454
+ Based on your logs, here's the exact setup:
455
+
456
+ ```bash
457
+ # 1. Provision stream and both consumers (as admin)
458
+ nats stream add pwas-heavyworth-sync \
459
+ --subjects "pwas.sync.heavyworth" "heavyworth.sync.pwas" "pwas.sync.dlq" "heavyworth.sync.dlq" \
460
+ --retention workqueue \
461
+ --storage file
462
+
463
+ # Consumer for pwas (receives heavyworth -> pwas)
464
+ nats consumer add pwas-heavyworth-sync pwas-workers \
465
+ --filter "heavyworth.sync.pwas" \
466
+ --ack explicit \
467
+ --pull \
468
+ --deliver all \
469
+ --max-deliver 5 \
470
+ --ack-wait 30s \
471
+ --backoff 1s,5s,15s,30s,60s \
472
+ --replay instant \
473
+ --max-pending 25000
474
+
475
+ # Consumer for heavyworth (receives pwas -> heavyworth)
476
+ nats consumer add pwas-heavyworth-sync heavyworth-workers \
477
+ --filter "pwas.sync.heavyworth" \
478
+ --ack explicit \
479
+ --pull \
480
+ --deliver all \
481
+ --max-deliver 5 \
482
+ --ack-wait 30s \
483
+ --backoff 1s,5s,15s,30s,60s \
484
+ --replay instant \
485
+ --max-pending 25000
486
+ ```
487
+
488
+ ```ruby
489
+ # 2. Update config/initializers/jetstream_bridge.rb
490
+ JetstreamBridge.configure do |config|
491
+ config.nats_urls = ENV.fetch("NATS_URLS") # e.g., nats://pwas:***@10.199.12.34:4222
492
+ config.stream_name = "pwas-heavyworth-sync"
493
+ config.app_name = "pwas"
494
+ config.destination_app = "heavyworth"
495
+ config.max_deliver = 5
496
+ config.ack_wait = "30s"
497
+ config.backoff = %w[1s 5s 15s 30s 60s]
498
+ end
499
+ ```
500
+
501
+ **Note:** Verify your exact stream name and consumer durable (`app_name-workers`) match what was provisioned.
502
+
503
+ ---
504
+
505
+ ## Alternative: Request Minimal Permissions
506
+
507
+ If you have any influence over NATS permissions, request only these minimal subjects:
508
+
509
+ ```conf
510
+ # Minimal permissions needed for JetStream Bridge consumer
511
+ publish: {
512
+ allow: [
513
+ ">", # Your app subjects (narrow if desired)
514
+ "$JS.API.CONSUMER.MSG.NEXT.{stream_name}.{app_name}-workers", # Fetch messages (required)
515
+ ]
516
+ }
517
+ subscribe: {
518
+ allow: [
519
+ ">", # Your app subjects (narrow if desired)
520
+ "_INBOX.>", # Request-reply responses (required)
521
+ ]
522
+ }
523
+ ```
524
+
525
+ These are read-only operations and don't allow creating/modifying streams or consumers.