bug_bunny 4.6.1 → 4.8.0

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 (71) hide show
  1. checksums.yaml +4 -4
  2. data/.agents/skills/rabbitmq-expert/SKILL.md +1555 -0
  3. data/.claude/commands/gem-ai-setup.md +174 -0
  4. data/.claude/commands/pr.md +53 -0
  5. data/.claude/commands/release.md +52 -0
  6. data/.claude/commands/rubocop.md +22 -0
  7. data/.claude/commands/service-ai-setup.md +168 -0
  8. data/.claude/commands/test.md +28 -0
  9. data/.claude/commands/yard.md +46 -0
  10. data/CHANGELOG.md +50 -15
  11. data/CLAUDE.md +240 -0
  12. data/README.md +154 -221
  13. data/Rakefile +19 -3
  14. data/docs/_index.md +50 -0
  15. data/docs/ai/_index.md +56 -0
  16. data/docs/ai/antipatterns.md +166 -0
  17. data/docs/ai/api.md +251 -0
  18. data/docs/ai/architecture.md +92 -0
  19. data/docs/ai/errors.md +158 -0
  20. data/docs/ai/faq_external.md +133 -0
  21. data/docs/ai/faq_internal.md +86 -0
  22. data/docs/ai/glossary.md +45 -0
  23. data/docs/concepts.md +140 -0
  24. data/docs/howto/controller.md +194 -0
  25. data/docs/howto/middleware_client.md +119 -0
  26. data/docs/howto/middleware_consumer.md +127 -0
  27. data/docs/howto/rails.md +214 -0
  28. data/docs/howto/resource.md +200 -0
  29. data/docs/howto/routing.md +133 -0
  30. data/docs/howto/testing.md +259 -0
  31. data/docs/howto/tracing.md +119 -0
  32. data/lib/bug_bunny/client.rb +45 -21
  33. data/lib/bug_bunny/configuration.rb +63 -0
  34. data/lib/bug_bunny/consumer.rb +51 -37
  35. data/lib/bug_bunny/consumer_middleware.rb +14 -5
  36. data/lib/bug_bunny/controller.rb +39 -18
  37. data/lib/bug_bunny/exception.rb +5 -1
  38. data/lib/bug_bunny/middleware/raise_error.rb +3 -3
  39. data/lib/bug_bunny/observability.rb +28 -6
  40. data/lib/bug_bunny/producer.rb +11 -13
  41. data/lib/bug_bunny/railtie.rb +8 -7
  42. data/lib/bug_bunny/request.rb +3 -11
  43. data/lib/bug_bunny/resource.rb +81 -41
  44. data/lib/bug_bunny/routing/route.rb +6 -1
  45. data/lib/bug_bunny/routing/route_set.rb +60 -22
  46. data/lib/bug_bunny/session.rb +18 -11
  47. data/lib/bug_bunny/version.rb +1 -1
  48. data/lib/bug_bunny.rb +4 -2
  49. data/lib/generators/bug_bunny/install/install_generator.rb +45 -5
  50. data/lib/tasks/bug_bunny.rake +50 -0
  51. data/plan_test.txt +63 -0
  52. data/skills-lock.json +10 -0
  53. data/spec/integration/client_spec.rb +117 -0
  54. data/spec/integration/consumer_middleware_spec.rb +86 -0
  55. data/spec/integration/controller_spec.rb +140 -0
  56. data/spec/integration/error_handling_spec.rb +57 -0
  57. data/spec/integration/infrastructure_spec.rb +52 -0
  58. data/spec/integration/resource_spec.rb +113 -0
  59. data/spec/spec_helper.rb +70 -0
  60. data/spec/support/bunny_mocks.rb +18 -0
  61. data/spec/support/integration_helper.rb +87 -0
  62. data/spec/unit/client_session_pool_spec.rb +159 -0
  63. data/spec/unit/configuration_spec.rb +164 -0
  64. data/spec/unit/consumer_middleware_spec.rb +129 -0
  65. data/spec/unit/consumer_spec.rb +90 -0
  66. data/spec/unit/controller_after_action_spec.rb +155 -0
  67. data/spec/unit/observability_spec.rb +167 -0
  68. data/spec/unit/resource_attributes_spec.rb +69 -0
  69. data/spec/unit/session_spec.rb +98 -0
  70. metadata +50 -3
  71. data/sig/bug_bunny.rbs +0 -4
data/README.md CHANGED
@@ -1,312 +1,245 @@
1
- # 🐰 BugBunny
1
+ # BugBunny
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/bug_bunny.svg)](https://badge.fury.io/rb/bug_bunny)
4
4
 
5
- **Active Record over AMQP.**
5
+ RESTful messaging over RabbitMQ for Ruby microservices.
6
6
 
7
- BugBunny transforma la complejidad de la mensajería asíncrona (RabbitMQ) en una arquitectura familiar RESTful. Permite que tus microservicios se comuniquen como si fueran APIs locales, utilizando controladores, rutas y modelos con una interfaz idéntica a Active Record.
8
-
9
- ## ✨ Características
10
-
11
- * **RESTful Routing:** Define tus endpoints AMQP con un DSL estilo Rails (`get`, `post`, `resources`).
12
- * **Active Record Pattern:** Modela tus recursos remotos con validaciones, callbacks y tracking de cambios.
13
- * **Middleware Stack:** Arquitectura de cebolla (Onion) para interceptar peticiones, manejar errores y transformar payloads.
14
- * **RPC & Pub/Sub:** Soporta nativamente tanto peticiones síncronas (Request-Response) como publicaciones asíncronas.
15
- * **Observabilidad de Clase Mundial:** Integración nativa con **ExisRay** (Tracing distribuido y Logs estructurados KV).
16
- * **Resiliencia Enterprise:** Reconexión automática con Backoff Exponencial y Health Checks automáticos.
7
+ BugBunny maps AMQP messages to controllers, routes, and models using the same patterns as Rails. Services communicate through RabbitMQ without HTTP coupling, with full support for synchronous RPC and fire-and-forget publishing.
17
8
 
18
9
  ---
19
10
 
20
- ## 🚀 Instalación
21
-
22
- Añade esta línea al `Gemfile` de tu aplicación:
11
+ ## Installation
23
12
 
24
13
  ```ruby
25
14
  gem 'bug_bunny'
26
15
  ```
27
16
 
28
- Y luego ejecuta:
29
17
  ```bash
30
- $ bundle install
31
- ```
32
-
33
- O instálalo manualmente:
34
- ```bash
35
- $ gem install bug_bunny
36
- ```
37
-
38
- Luego, genera el inicializador (en Rails):
39
- ```bash
40
- $ rails generate bug_bunny:install
18
+ bundle install
19
+ rails generate bug_bunny:install # Rails only
41
20
  ```
42
21
 
43
22
  ---
44
23
 
45
- ## 🛠️ Configuración
24
+ ## Quickstart
25
+
26
+ BugBunny connects two services through RabbitMQ. One service hosts the consumer (server side); the other uses a Resource or Client to call it (client side).
46
27
 
47
- Configura la conexión a RabbitMQ y las opciones globales:
28
+ ### Service B Consumer
48
29
 
49
30
  ```ruby
50
31
  # config/initializers/bug_bunny.rb
51
32
  BugBunny.configure do |config|
52
- config.host = ENV['RABBITMQ_HOST'] || '127.0.0.1'
53
- config.port = 5672
54
- config.username = 'guest'
55
- config.password = 'guest'
56
-
57
- # Resiliencia
58
- config.max_reconnect_attempts = 10 # Falla tras 10 intentos (útil en K8s)
59
- config.max_reconnect_interval = 60 # Máximo 60s entre reintentos
60
- config.network_recovery_interval = 5 # Intervalo base para backoff
61
-
62
- # Infraestructura por defecto (Nivel 2)
63
- config.exchange_options = { durable: true }
33
+ config.host = ENV.fetch('RABBITMQ_HOST', 'localhost')
34
+ config.port = 5672
35
+ config.username = ENV.fetch('RABBITMQ_USER', 'guest')
36
+ config.password = ENV.fetch('RABBITMQ_PASS', 'guest')
64
37
  end
65
- ```
66
38
 
67
- ---
39
+ # config/initializers/bug_bunny_routes.rb
40
+ BugBunny.routes.draw do
41
+ resources :nodes
42
+ end
43
+
44
+ # app/controllers/bug_bunny/controllers/nodes_controller.rb
45
+ module BugBunny
46
+ module Controllers
47
+ class NodesController < BugBunny::Controller
48
+ def show
49
+ node = Node.find(params[:id])
50
+ render status: :ok, json: node.as_json
51
+ end
52
+
53
+ def index
54
+ render status: :ok, json: Node.all.map(&:as_json)
55
+ end
56
+ end
57
+ end
58
+ end
68
59
 
69
- ## 📦 Uso como Modelo (Consumer + Producer)
60
+ # Worker entrypoint (dedicated thread or process)
61
+ consumer = BugBunny::Consumer.new
62
+ consumer.subscribe(
63
+ queue_name: 'inventory_queue',
64
+ exchange_name: 'inventory',
65
+ routing_key: 'nodes'
66
+ )
67
+ ```
70
68
 
71
- BugBunny permite definir modelos que representan recursos en otros microservicios.
69
+ ### Service A Producer
72
70
 
73
71
  ```ruby
72
+ # config/initializers/bug_bunny.rb — same connection config as above
73
+
74
+ # Pool shared across threads (Puma / Sidekiq)
75
+ BUG_BUNNY_POOL = ConnectionPool.new(size: 5, timeout: 5) do
76
+ BugBunny.create_connection
77
+ end
78
+
74
79
  class RemoteNode < BugBunny::Resource
75
- # Configuración del canal
76
- self.exchange = 'inventory_exchange'
77
- self.resource_name = 'nodes' # Equivale al path de la URL
80
+ self.exchange = 'inventory'
81
+ self.resource_name = 'nodes'
78
82
 
79
- # Atributos (ActiveRecord style)
80
- attribute :name, :string
83
+ attribute :name, :string
81
84
  attribute :status, :string
82
- attribute :cpu_cores, :integer
83
-
84
- # Validaciones
85
- validates :name, presence: true
86
85
  end
87
86
 
88
- # Uso:
89
- node = RemoteNode.find('node-123')
87
+ RemoteNode.connection_pool = BUG_BUNNY_POOL
88
+
89
+ # Use it like ActiveRecord
90
+ node = RemoteNode.find('node-123') # GET nodes/node-123 via RabbitMQ
90
91
  node.status = 'active'
91
- node.save # Realiza un PUT a inventory_exchange con routing_key 'nodes.node-123'
92
-
93
- # Búsqueda con filtros (query params)
94
- # Los filtros se serializan como query string en el header 'type' del mensaje.
95
- # El consumer los recibe en params[] igual que en Rails.
96
- RemoteNode.all # GET nodes
97
- RemoteNode.where(status: 'active') # GET nodes?status=active
98
- RemoteNode.where(q: { cpu_cores: 4 }) # GET nodes?q[cpu_cores]=4
92
+ node.save # PUT nodes/node-123
93
+
94
+ RemoteNode.where(status: 'active') # GET nodes?status=active
95
+ RemoteNode.create(name: 'web-01', status: 'pending')
99
96
  ```
100
97
 
101
98
  ---
102
99
 
103
- ## 🛣️ Enrutamiento y Controladores (Server side)
104
-
105
- Define cómo debe responder tu aplicación a los mensajes entrantes:
100
+ ## Modes of Use
106
101
 
107
- ```ruby
108
- # config/rabbit_routes.rb
109
- BugBunny.routes.draw do
110
- resources :nodes do
111
- member do
112
- put :drain
113
- end
114
- end
115
- end
102
+ **Resource ORM** — ActiveRecord-like model for a remote service. Handles CRUD, validations, change tracking, and typed or dynamic attributes. Best when you own both sides of the communication.
116
103
 
117
- # app/controllers/bug_bunny/nodes_controller.rb
118
- module BugBunny
119
- module Controllers
120
- class NodesController < BugBunny::Controller
121
- def index
122
- nodes = Node.all # Lógica local de tu app
123
- render status: :ok, json: nodes
124
- end
104
+ **Direct Client** — `BugBunny::Client` for explicit RPC or fire-and-forget calls with full middleware control. Best when calling external services or when you need precise control over the request.
125
105
 
126
- def drain
127
- # El ID viene automáticamente en params[:id]
128
- Node.find(params[:id]).start_drain_process!
129
- render status: :accepted, json: { message: "Draining started" }
130
- end
131
- end
132
- end
133
- end
134
- ```
135
-
136
- > **Namespace de Controladores:** Por defecto BugBunny busca los controladores bajo `BugBunny::Controllers`. Podés cambiarlo en la configuración:
137
- > ```ruby
138
- > BugBunny.configure do |config|
139
- > config.controller_namespace = 'MyApp::RabbitControllers'
140
- > end
141
- > ```
106
+ **Consumer** — Subscribe loop that routes incoming messages to controllers, with a middleware stack for cross-cutting concerns (tracing, auth, auditing).
142
107
 
143
108
  ---
144
109
 
145
- ## 🔌 Middlewares
146
-
147
- Puedes extender el comportamiento del cliente globalmente o por recurso:
110
+ ## Configuration
148
111
 
149
112
  ```ruby
150
- # Globalmente en el inicializador
151
113
  BugBunny.configure do |config|
152
- # ...
153
- end
114
+ # Connection — required
115
+ config.host = 'localhost'
116
+ config.port = 5672
117
+ config.username = 'guest'
118
+ config.password = 'guest'
119
+ config.vhost = '/'
154
120
 
155
- # Uso con el cliente manual
156
- client = BugBunny::Client.new(pool: BUG_BUNNY_POOL) do |stack|
157
- stack.use BugBunny::Middleware::RaiseError
158
- stack.use BugBunny::Middleware::JsonResponse
159
- end
121
+ # Resilience
122
+ config.max_reconnect_attempts = 10 # nil = infinite
123
+ config.max_reconnect_interval = 60 # seconds, ceiling for backoff
124
+ config.network_recovery_interval = 5 # seconds, base for exponential backoff
160
125
 
161
- # 1. Método genérico 'send' (Estilo Faraday)
162
- # El comportamiento (RPC o Fire-and-forget) depende de 'delivery_mode'
163
- client.delivery_mode = :rpc # Default
164
- client.send('users/1', method: :get)
126
+ # Timeouts
127
+ config.rpc_timeout = 30 # seconds, for synchronous RPC calls
128
+ config.connection_timeout = 10
129
+ config.read_timeout = 10
130
+ config.write_timeout = 10
165
131
 
166
- # 2. Configuración flexible del modo de entrega
167
- # Por cada petición
168
- client.send('logs', method: :post, body: { msg: 'system_up' }, delivery_mode: :publish)
132
+ # AMQP defaults applied to all exchanges and queues
133
+ config.exchange_options = { durable: true }
134
+ config.queue_options = { durable: true }
169
135
 
170
- # O mediante un bloque para configuración avanzada
171
- client.send('users/1') do |req|
172
- req.method = :get
173
- req.delivery_mode = :rpc
174
- req.timeout = 5
175
- end
136
+ # Controller namespace (default: 'BugBunny::Controllers')
137
+ config.controller_namespace = 'MyApp::RabbitHandlers'
176
138
 
177
- # 3. Query params (estilo Faraday)
178
- # Usá req.params para enviar filtros. La gema los serializa como query string
179
- # en el header 'type' del mensaje (que el consumer usa para rutear).
180
- # La routing_key del exchange NO se ve afectada.
181
- client.request('users') do |req|
182
- req.method = :get
183
- req.params = { q: { active: true }, page: 2 }
139
+ # Logger any object responding to debug/info/warn/error
140
+ config.logger = Rails.logger
141
+
142
+ # Health check file for Kubernetes / Docker Swarm liveness probes
143
+ config.health_check_file = '/tmp/bug_bunny_health'
184
144
  end
185
- # Equivalente usando args:
186
- client.request('users', method: :get, params: { q: { active: true }, page: 2 })
145
+ ```
187
146
 
188
- # 4. Métodos de conveniencia (Atajos)
189
- client.request('users/1') # Siempre :rpc
190
- client.publish('events', body: { type: 'click' }) # Siempre :publish
147
+ `BugBunny.configure` validates all required fields on exit. A missing or invalid value raises `BugBunny::ConfigurationError` immediately, before any connection attempt.
191
148
 
192
- # Ahora el cliente devolverá Hashes y lanzará errores si el worker falla
193
- response = client.request('users/1', method: :get)
194
- ```
149
+ ---
195
150
 
196
- **Middlewares Personalizados**
151
+ ## Routing DSL
197
152
 
198
153
  ```ruby
199
- class MyCustomMiddleware < BugBunny::Middleware::Base
200
- def call(request)
201
- puts "Enviando mensaje a: #{request.path}"
202
- app.call(request)
154
+ BugBunny.routes.draw do
155
+ resources :users # GET/POST users, GET/PUT/DELETE users/:id
156
+ resources :orders, only: [:index, :show, :create]
157
+
158
+ resources :nodes do
159
+ member { put :drain } # PUT nodes/:id/drain
160
+ collection { post :rebalance } # POST nodes/rebalance
161
+ end
162
+
163
+ namespace :api do
164
+ namespace :v1 do
165
+ resources :metrics # Routes to Api::V1::MetricsController
166
+ end
203
167
  end
168
+
169
+ get 'status', to: 'health#show'
170
+ post 'events/:id', to: 'events#track'
204
171
  end
205
172
  ```
206
173
 
207
174
  ---
208
175
 
209
- ## 🔗 Consumer Middleware Stack
210
-
211
- BugBunny expone un middleware stack que se ejecuta **antes** de que la gema procese cada mensaje entrante (antes del primer log `consumer.message_received`). Es el punto ideal para hidratar contexto de tracing distribuido, autenticación, auditoría, etc.
212
-
213
- ### Implementar un middleware
176
+ ## Direct Client
214
177
 
215
178
  ```ruby
216
- class MyMiddleware < BugBunny::ConsumerMiddleware::Base
217
- def call(delivery_info, properties, body)
218
- # lógica pre-procesamiento
219
- # properties.headers contiene todos los headers AMQP custom
220
- @app.call(delivery_info, properties, body)
221
- # lógica post-procesamiento (opcional)
222
- end
179
+ pool = ConnectionPool.new(size: 5, timeout: 5) { BugBunny.create_connection }
180
+ client = BugBunny::Client.new(pool: pool) do |stack|
181
+ stack.use BugBunny::Middleware::RaiseError
182
+ stack.use BugBunny::Middleware::JsonResponse
223
183
  end
224
- ```
225
184
 
226
- ### Registrar un middleware
185
+ # Synchronous RPC
186
+ response = client.request('users/42', method: :get)
187
+ response['body'] # => { 'id' => 42, 'name' => 'Alice' }
227
188
 
228
- ```ruby
229
- BugBunny.configure do |config|
230
- # ...
231
- end
189
+ # Fire-and-forget
190
+ client.publish('events', body: { type: 'user.signed_in', user_id: 42 })
232
191
 
233
- BugBunny.consumer_middlewares.use MyMiddleware
192
+ # With params
193
+ client.request('users', method: :get, params: { role: 'admin', page: 2 })
234
194
  ```
235
195
 
236
- ### Auto-registro desde una gema externa
196
+ ---
197
+
198
+ ## Consumer Middleware
237
199
 
238
- Las gemas de integración pueden registrarse automáticamente al ser requeridas, sin que el usuario tenga que tocar el bloque `configure`:
200
+ Middlewares run before every message reaches the router. Use them for distributed tracing, authentication, or audit logging.
239
201
 
240
202
  ```ruby
241
- # lib/exis_ray/bug_bunny/consumer_tracing.rb
242
- require 'exis_ray/bug_bunny/consumer_tracing_middleware'
243
- BugBunny.consumer_middlewares.use ExisRay::BugBunny::ConsumerTracingMiddleware
203
+ class TracingMiddleware < BugBunny::ConsumerMiddleware::Base
204
+ def call(delivery_info, properties, body)
205
+ trace_id = properties.headers&.dig('X-Trace-Id')
206
+ MyTracer.with_trace(trace_id) { @app.call(delivery_info, properties, body) }
207
+ end
208
+ end
244
209
 
245
- # El usuario solo necesita:
246
- # require 'exis_ray/bug_bunny/consumer_tracing'
210
+ BugBunny.consumer_middlewares.use TracingMiddleware
247
211
  ```
248
212
 
249
- ### Datos disponibles en el middleware
250
-
251
- | Argumento | Tipo | Contenido |
252
- |---|---|---|
253
- | `delivery_info` | `Bunny::DeliveryInfo` | `routing_key`, `exchange`, `delivery_tag` |
254
- | `properties` | `Bunny::MessageProperties` | `headers` (headers AMQP custom), `correlation_id`, `reply_to`, `content_type` |
255
- | `body` | `String` | Payload crudo del mensaje |
256
-
257
- > **Orden de ejecución:** FIFO — el primero en registrarse es el primero en ejecutarse.
258
- > `Middleware A → Middleware B → process_message`
259
-
260
213
  ---
261
214
 
262
- ## 🔎 Observabilidad y Tracing
215
+ ## Observability
263
216
 
264
- BugBunny implementa Distributed Tracing nativo y sigue los estándares de observabilidad de **ExisRay** para logs estructurados.
217
+ All internal events are emitted as structured key=value logs compatible with Datadog, CloudWatch, and ELK.
265
218
 
266
- ### 1. Logs Estructurados (Key-Value)
267
- A partir de la v4.3.0, todos los logs internos de la gema utilizan un formato `key=value` optimizado para motores de logs (CloudWatch, Datadog, ELK).
268
-
269
- * **Data First:** Las unidades están en la llave (`_s`, `_ms`, `_count`), permitiendo que los valores sean números puros para agregaciones automáticas.
270
- * **Reloj Monotónico:** Las duraciones (`duration_s`) se calculan con precisión de microsegundos usando el reloj monotónico del sistema.
271
- * **Campos de Identidad:** Todos los logs incluyen `component=bug_bunny` y un `event` semántico.
272
-
273
- **Ejemplos de Logs:**
274
- ```text
275
- # Mensaje procesado con éxito (incluye duración y status numérico)
276
- component=bug_bunny event=consumer.message_processed status=200 duration_s=0.015432 controller=UsersController action=show
277
-
278
- # Error de ejecución (campos estandarizados)
279
- component=bug_bunny event=consumer.execution_error error_class=NoMethodError error_message="undefined method..." duration_s=0.008123
280
-
281
- # Reintento de conexión con backoff (sufijos de unidad)
282
- component=bug_bunny event=consumer.connection_error error_message="..." attempt_count=3 retry_in_s=20
219
+ ```
220
+ component=bug_bunny event=consumer.message_processed status=200 duration_s=0.012 controller=NodesController action=show
221
+ component=bug_bunny event=consumer.execution_error error_class=RuntimeError error_message="..." duration_s=0.003
222
+ component=bug_bunny event=consumer.connection_error attempt_count=2 retry_in_s=10 error_message="..."
283
223
  ```
284
224
 
285
- **Ventaja en Cloud:**
286
- Al usar `duration_s` como un float puro, puedes realizar consultas analíticas directamente en tu motor de logs sin parsear strings:
287
- `stats avg(duration_s), max(duration_s) by controller, action`
288
-
289
- ### 2. Distributed Tracing
290
- El `correlation_id` se mantiene intacto a través de toda la cadena: `Producer -> RabbitMQ -> Consumer -> Controller`.
225
+ Sensitive keys (`password`, `token`, `secret`, `api_key`, `authorization`, etc.) are automatically filtered to `[FILTERED]` in all log output.
291
226
 
292
- ### 3. Manejo de Errores Declarativo
293
- Captura excepciones y devuélvelas como códigos de estado AMQP/HTTP.
227
+ ---
294
228
 
295
- ```ruby
296
- class ApplicationController < BugBunny::Controller
297
- rescue_from ActiveRecord::RecordNotFound do |e|
298
- render status: :not_found, json: { error: "Resource missing" }
299
- end
229
+ ## Documentation
300
230
 
301
- rescue_from StandardError do |e|
302
- safe_log(:error, "application.crash", **exception_metadata(e))
303
- render status: :internal_server_error, json: { error: "Crash" }
304
- end
305
- end
306
- ```
231
+ - [Concepts](docs/concepts.md) What BugBunny is, AMQP in 5 minutes, RPC vs fire-and-forget
232
+ - [Routing](docs/howto/routing.md) — Full routing DSL reference
233
+ - [Controllers](docs/howto/controller.md) — Filters, `rescue_from`, `render`, `after_action`
234
+ - [Resource ORM](docs/howto/resource.md) — CRUD, typed and dynamic attributes, `.with` scoping
235
+ - [Client Middleware](docs/howto/middleware_client.md) — Request/response middleware stack
236
+ - [Consumer Middleware](docs/howto/middleware_consumer.md) — Message processing middleware stack
237
+ - [Distributed Tracing](docs/howto/tracing.md) — Propagating trace context through RPC cycles
238
+ - [Rails Setup](docs/howto/rails.md) — Full integration: Puma, Sidekiq, Zeitwerk, health checks
239
+ - [Testing](docs/howto/testing.md) — Unit and integration testing with Bunny mocks
307
240
 
308
241
  ---
309
242
 
310
- ## 📄 Licencia
243
+ ## License
311
244
 
312
- Código abierto bajo [MIT License](https://opensource.org/licenses/MIT).
245
+ [MIT](https://opensource.org/licenses/MIT)
data/Rakefile CHANGED
@@ -1,12 +1,28 @@
1
1
  require 'bundler/gem_tasks'
2
2
  require 'rake/testtask'
3
+ require 'rspec/core/rake_task'
3
4
 
5
+ RSpec::Core::RakeTask.new(:spec) do |t|
6
+ t.rspec_opts = '--require spec_helper'
7
+ end
8
+
9
+ RSpec::Core::RakeTask.new('spec:unit') do |t|
10
+ t.pattern = 'spec/unit/**/*_spec.rb'
11
+ t.rspec_opts = '--require spec_helper'
12
+ end
13
+
14
+ RSpec::Core::RakeTask.new('spec:integration') do |t|
15
+ t.pattern = 'spec/integration/**/*_spec.rb'
16
+ t.rspec_opts = '--require spec_helper'
17
+ end
18
+
19
+ # Mantiene la tarea :test apuntando a los tests de integración legacy de Minitest
4
20
  Rake::TestTask.new(:test) do |t|
5
21
  t.libs << 'test'
6
22
  t.libs << 'lib'
7
23
  t.test_files = FileList['test/**/*_test.rb']
8
- t.verbose = true
9
- t.warning = false
24
+ t.verbose = true
25
+ t.warning = false
10
26
  end
11
27
 
12
- task default: :test
28
+ task default: :spec
data/docs/_index.md ADDED
@@ -0,0 +1,50 @@
1
+ # docs/_index.md — Documentation Manifest
2
+
3
+ This file is the source of truth for the `docs/` directory structure.
4
+ It is read by the `/release` command to know which files to generate or update.
5
+
6
+ ---
7
+
8
+ ## Human documentation (developers integrating BugBunny)
9
+
10
+ | File | Purpose |
11
+ |---|---|
12
+ | `concepts.md` | AMQP in 5 min, architecture diagram, RPC vs fire-and-forget, connection pool |
13
+ | `howto/routing.md` | Routes DSL: `resources`, `namespace`, `member`, `collection`, `recognize` |
14
+ | `howto/controller.md` | `params`, `before_action`, `after_action`, `around_action`, `rescue_from`, `render` |
15
+ | `howto/resource.md` | CRUD methods, typed vs dynamic attributes, dirty tracking, validations, `.with` |
16
+ | `howto/middleware_client.md` | Client-side middlewares: built-ins, custom, usage in Client and Resource |
17
+ | `howto/middleware_consumer.md` | Consumer-side middlewares: execution order, writing, registering |
18
+ | `howto/tracing.md` | Trace context propagation: `rpc_reply_headers`, `on_rpc_reply`, consumer middleware |
19
+ | `howto/rails.md` | Full Rails setup: initializer, connection pool, Zeitwerk, Puma, Sidekiq, K8s health checks |
20
+ | `howto/testing.md` | Bunny doubles, unit tests for controllers/middleware, integration helper |
21
+
22
+ These files are referenced by `README.md`. Update them before updating the README.
23
+
24
+ ---
25
+
26
+ ## AI documentation (agents consuming or maintaining BugBunny)
27
+
28
+ Managed by `docs/ai/_index.md`. See that file for the full manifest and audience breakdown.
29
+
30
+ | File | Audience | Purpose |
31
+ |---|---|---|
32
+ | `ai/_index.md` | internal + external | Manifest: version, profile, file index |
33
+ | `ai/glossary.md` | internal + external | Domain terms with precise definitions |
34
+ | `ai/architecture.md` | internal | Internal patterns, component map, data flows |
35
+ | `ai/api.md` | external | Public API contracts |
36
+ | `ai/faq_internal.md` | internal | Q&A for gem maintainers |
37
+ | `ai/faq_external.md` | external | Q&A for gem integrators |
38
+ | `ai/antipatterns.md` | internal + external | What NOT to do and why |
39
+ | `ai/errors.md` | external | All exceptions with cause and resolution |
40
+
41
+ ---
42
+
43
+ ## Update rules for `/release`
44
+
45
+ 1. Run tests first. If they fail, stop.
46
+ 2. Read this file to discover all files to update.
47
+ 3. For each file in **Human documentation**: update only sections affected by the changes in this release.
48
+ 4. For each file in **AI documentation**: update only sections affected by the changes. Update `version` in `ai/_index.md`.
49
+ 5. Update `README.md` last — it depends on `docs/howto/` being up to date.
50
+ 6. Show the full diff to the developer and wait for approval before touching version or CHANGELOG.
data/docs/ai/_index.md ADDED
@@ -0,0 +1,56 @@
1
+ ---
2
+ type: knowledge_base
3
+ kind: gem
4
+ name: bug_bunny
5
+ version: 4.8.0
6
+ profile: full
7
+ language: ruby
8
+ generated_by: gem-ai-setup@1.0.0
9
+ audiences:
10
+ - internal
11
+ - external
12
+ files:
13
+ - path: glossary.md
14
+ audience: [internal, external]
15
+ - path: architecture.md
16
+ audience: [internal]
17
+ - path: api.md
18
+ audience: [external]
19
+ - path: faq_internal.md
20
+ audience: [internal]
21
+ - path: faq_external.md
22
+ audience: [external]
23
+ - path: antipatterns.md
24
+ audience: [internal, external]
25
+ - path: errors.md
26
+ audience: [external]
27
+ ---
28
+
29
+ ## What is BugBunny?
30
+
31
+ BugBunny is a Ruby gem that implements a RESTful routing layer over AMQP (RabbitMQ). It lets microservices communicate via RabbitMQ using familiar HTTP patterns: verbs (GET, POST, PUT, DELETE), controllers, declarative routes, synchronous RPC, and fire-and-forget.
32
+
33
+ **Problem solved:** Eliminates direct HTTP coupling between microservices. RabbitMQ acts as the message bus with the same ergonomics as a web framework.
34
+
35
+ ## Version
36
+
37
+ 4.8.0 — April 2026
38
+
39
+ ## Key features in this version
40
+
41
+ - Namespace routing (`namespace :admin { resources :users }`)
42
+ - `after_action` filter (runs after action, not after `before_action` halts)
43
+ - `render(headers:)` — inject custom headers into RPC replies
44
+ - `Consumer#shutdown` — explicit graceful shutdown with health check cleanup
45
+ - `Configuration#validate!` — invoked automatically at end of `BugBunny.configure`
46
+ - Producer/Session caching per connection slot (prevents double-consumer AMQP error)
47
+ - Expanded `SENSITIVE_KEYS` filter in `safe_log`
48
+ - `ConsumerMiddleware::Stack` mutex for thread-safe registration
49
+
50
+ ## How to use this knowledge base
51
+
52
+ - **Building an integration** → start with `api.md`, then `faq_external.md`
53
+ - **Debugging errors** → `errors.md`
54
+ - **Avoiding mistakes** → `antipatterns.md`
55
+ - **Understanding internals** → `architecture.md`, then `faq_internal.md`
56
+ - **Domain vocabulary** → `glossary.md`